netbirdio/netbird · error

invalid IPv6 port forward specification: %s

Error message

invalid IPv6 port forward specification: %s

What it means

Returned by parseIPv6ForwardSpec when a spec that starts with '[' does not contain the closing ']:'. The parser needs that exact marker to split the bracketed IPv6 literal from the rest; without it there is no safe place to cut, so the whole spec is rejected before any port validation. This is the bare form of the message — no format hint is attached.

Source

Thrown at client/cmd/ssh.go:769

	}
	localAddr := "localhost:" + parts[0]
	remoteAddr := parts[1] + ":" + parts[2]
	return localAddr, remoteAddr, nil
}

// parseFourPartForwardSpec handles "host:port:host:hostport" format.
func parseFourPartForwardSpec(parts []string) (string, string, error) {
	localHost := normalizeLocalHost(parts[0])
	localAddr := localHost + ":" + parts[1]
	remoteAddr := parts[2] + ":" + parts[3]
	return localAddr, remoteAddr, nil
}

// parseIPv6ForwardSpec handles "[host]:port:host:hostport" format.
func parseIPv6ForwardSpec(spec string) (string, string, error) {
	idx := strings.Index(spec, "]:")
	if idx == -1 {
		return "", "", fmt.Errorf("invalid IPv6 port forward specification: %s", spec)
	}

	ipv6Host := spec[:idx+1]
	remaining := spec[idx+2:]

	parts := strings.Split(remaining, ":")
	if len(parts) != 3 {
		return "", "", fmt.Errorf("invalid IPv6 port forward specification: %s (expected [ipv6]:port:host:hostport)", spec)
	}

	localAddr := ipv6Host + ":" + parts[0]
	remoteAddr := parts[1] + ":" + parts[2]
	return localAddr, remoteAddr, nil
}

// isUnixSocket checks if a path is a Unix socket path.
func isUnixSocket(path string) bool {
	return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "./")

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Close the bracket and follow it with a colon: [2001:db8::1]:8080:host:80.
  2. When building specs programmatically, use fmt.Sprintf("[%s]:%s:%s:%s", v6host, lport, rhost, rport) rather than manual concatenation.
  3. Sanity-check the rendered spec with a regexp like ^\[[0-9a-fA-F:]+\]: before passing it to the CLI.

Example fix

# before
netbird ssh -L '[::1:8080:host:80' peer1
# -> invalid IPv6 port forward specification: [::1:8080:host:80

# after
netbird ssh -L '[::1]:8080:host:80' peer1
Defensive patterns

Strategy: validation

Validate before calling

if strings.HasPrefix(spec, "[") && !strings.Contains(spec, "]:") {
	return fmt.Errorf("IPv6 spec %q missing ']:" + "' after the bracketed host", spec)
}
// build programmatically instead:
spec := fmt.Sprintf("[%s]:%d:%s:%d", v6Host, localPort, rHost, rPort)

Type guard

func isBracketedV6Spec(s string) bool {
	return strings.HasPrefix(s, "[") && strings.Contains(s, "]:")
}

Try / catch

if idx := strings.Index(spec, "]:"); idx == -1 {
	// unbalanced bracket: reject with a hint showing the canonical form;
	// do not attempt to auto-close brackets — that hides the real typo
}

Prevention

When it happens

Trigger: `-L [::1:8080:host:80 peer` (missing closing bracket), `-L [fe80::1` (bracket opened, never closed, no colon-bracket sequence), or a spec where the ']' exists but is not immediately followed by ':' such as `[::1]8080:...`. The earlier router in parsePortForwardSpec only sends specs here when they start with '[' AND contain ']:' — so in practice this branch is reached via direct calls or malformed mixes; the more common user-visible path for a broken bracket is error 376 instead.

Common situations: Hand-typing IPv6 specs and dropping the closing bracket; find/replace operations that strip ']' but leave '['; specs assembled by concatenation where the ']:' separator variable is empty.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/094ea5f649d3dde7. Report an issue: GitHub.