netbirdio/netbird · error

parse address %s: %w

Error message

parse address %s: %w

What it means

Produced inside validateDestinationPort when net.SplitHostPort cannot split the destination address into host and port. SplitHostPort errors on a missing port (`address host: missing port in address`), on too many colons for an unbracketed IPv6 literal (`too many colons in address`), and on malformed bracket forms. Addresses starting with / or ./ are exempted (unix sockets) before this check.

Source

Thrown at client/cmd/ssh.go:684

		if err := c.RemotePortForward(ctx, remoteAddr, localAddr); err != nil && !errors.Is(err, context.Canceled) {
			cmd.Printf("Remote port forward error: %v\n", err)
		}
	}()

	return nil
}

// validateDestinationPort checks that the destination address has a valid port.
// Port 0 is only valid for bind addresses (where the OS picks an available port),
// not for destination addresses where we need to connect.
func validateDestinationPort(addr string) error {
	if strings.HasPrefix(addr, "/") || strings.HasPrefix(addr, "./") {
		return nil
	}

	_, portStr, err := net.SplitHostPort(addr)
	if err != nil {
		return fmt.Errorf("parse address %s: %w", addr, err)
	}

	port, err := strconv.Atoi(portStr)
	if err != nil {
		return fmt.Errorf("invalid port %s: %w", portStr, err)
	}

	if port == 0 {
		return fmt.Errorf("port 0 is not valid for destination address")
	}

	if port < 0 || port > 65535 {
		return fmt.Errorf("port %d out of range (1-65535)", port)
	}

	return nil
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Add the :port suffix so the destination is host:port, e.g., dbhost:5432.
  2. Bracket IPv6 hosts before appending the port: [2001:db8::1]:443.
  3. If the destination is a unix socket, give it a / or ./ prefix so it bypasses this check.
  4. For empty ports from variables, default them explicitly: ${PORT:-80}.

Example fix

# before
netbird ssh -L 8080:2001:db8::1 peer1
# -> invalid remote address: parse address 2001:db8::1: address 2001:db8::1: too many colons in address

# after
netbird ssh -L 8080:'[2001:db8::1]:443' peer1
Defensive patterns

Strategy: validation

Validate before calling

// pre-split before composing/running the CLI
host, portStr, err := net.SplitHostPort(candidate)
if err != nil {
	if strings.Contains(err.Error(), "too many colons") {
		// unbracketed IPv6: re-emit as [v6]:port
		candidate = fmt.Sprintf("[%s]:%s", host, portStr) // adjust per your data shape
	} else {
		return fmt.Errorf("add :port to destination %q", candidate)
	}
}

Type guard

// true when the string is a parseable host:port or a unix socket path
func isHostPortOrSocket(s string) bool {
	if strings.HasPrefix(s, "/") || strings.HasPrefix(s, "./") {
		return true
	}
	_, _, err := net.SplitHostPort(s)
	return err == nil
}

Try / catch

if _, _, err := net.SplitHostPort(addr); err != nil {
	// err is *net.AddrError with Err in {"missing port in address", "too many colons in address"};
	// branch on it: prompt for a port vs demand brackets
}

Prevention

When it happens

Trigger: Destination like `dbhost` (no port at all), `::1:8080` (unbracketed IPv6 plus port — SplitHostPort cannot disambiguate), `[fe80::1]` without a port, or a stray bracket form like `[host:80`. Reached from -L via `invalid remote address: ...` and from -R via `invalid local address: ...`.

Common situations: Omitting the port when shortening specs; pasting IPv6 addresses without brackets; specs assembled by string concatenation where the port segment came out empty; targets meant to be sockets but missing the leading slash so they parse as hostnames.

Related errors


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