nats-io/nats-server · error

%w: TCP6 with IPv4 address

Error message

%w: TCP6 with IPv4 address

What it means

The header declared protocol TCP6 but at least one address field lacks a colon, i.e. it is an IPv4 dotted-quad literal. TCP6 requires IPv6 literals for both source and destination, so the server rejects the header with errProxyProtoInvalid. (IPv4-mapped forms like '::ffff:192.0.2.1' do count as IPv6 for TCP6.)

Source

Thrown at server/client_proxyproto.go:219

	}

	dstPort, err := strconv.ParseUint(parts[4], 10, 16)
	if err != nil {
		return nil, nil, fmt.Errorf("invalid dest port: %w", err)
	}

	// Validate protocol matches IP version. The textual form determines the
	// family: TCP4 requires dotted-quad addresses, TCP6 requires IPv6
	// addresses. IPv4-mapped IPv6 addresses (e.g. "::ffff:192.0.2.1") are
	// valid for TCP6 since dual-stack proxies can emit those for IPv4
	// clients on IPv6 sockets, matching the v2 parser behavior.
	srcIsV6 := strings.Contains(parts[1], ":")
	dstIsV6 := strings.Contains(parts[2], ":")
	if protocol == proxyProtoV1TCP4 && (srcIsV6 || dstIsV6) {
		return nil, nil, fmt.Errorf("%w: TCP4 with IPv6 address", errProxyProtoInvalid)
	}
	if protocol == proxyProtoV1TCP6 && (!srcIsV6 || !dstIsV6) {
		return nil, nil, fmt.Errorf("%w: TCP6 with IPv4 address", errProxyProtoInvalid)
	}
	if protocol != proxyProtoV1TCP4 && protocol != proxyProtoV1TCP6 {
		return nil, nil, fmt.Errorf("%w: invalid protocol %s", errProxyProtoInvalid, protocol)
	}

	return &proxyProtoAddr{
		srcIP:   srcIP,
		srcPort: uint16(srcPort),
		dstIP:   dstIP,
		dstPort: uint16(dstPort),
	}, remaining, nil
}

// readProxyProtoHeader reads and parses PROXY protocol (v1 or v2) from the connection.
// Automatically detects version and routes to appropriate parser.
// If the command is LOCAL/UNKNOWN (health check), it returns nil for addr and no error.
// If the command is PROXY, it returns the parsed address information.
// It also returns any bytes that were read past the v1 header terminator so the

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Make the proxy emit TCP4 when addresses are dotted-quad IPv4
  2. Fix the proxy's protocol keyword to match the actual address family of the socket
  3. If IPv4 clients arrive on IPv6 sockets, use TCP6 with IPv4-mapped literals ('::ffff:192.0.2.1') or switch to TCP4 with plain IPv4
  4. Audit load-balancer proxy-protocol templates for hardcoded TCP6

Example fix

// before
"PROXY TCP6 192.0.2.1 198.51.100.7 35646 4222\r\n"
// after
"PROXY TCP4 192.0.2.1 198.51.100.7 35646 4222\r\n"
Defensive patterns

Strategy: validation

Validate before calling

proto := "TCP4"
if strings.Contains(srcIPStr, ":") && strings.Contains(dstIPStr, ":") {
    proto = "TCP6"
}
header := fmt.Sprintf("PROXY %s %s %s %d %d\r\n", proto, srcIPStr, dstIPStr, sport, dport)

Type guard

func protoMatchesFamily(proto, src, dst string) bool {
    srcV6, dstV6 := strings.Contains(src, ":"), strings.Contains(dst, ":")
    if proto == "TCP6" { return srcV6 && dstV6 }
    if proto == "TCP4" { return !srcV6 && !dstV6 }
    return false
}

Try / catch

_, _, err := readProxyProtoHeader(conn)
if err != nil {
    if errors.Is(err, errProxyProtoInvalid) && strings.Contains(err.Error(), "TCP6 with IPv4") {
        log.Printf("peer sent TCP6 with IPv4 address: %v", err)
        return
    }
    return err
}

Prevention

When it happens

Trigger: Header like 'PROXY TCP6 192.0.2.1 198.51.100.7 35646 4222\r\n', or mixed 'PROXY TCP6 ::1 192.0.2.1 35646 4222\r\n'; triggered when either parts[1] or parts[2] contains no ':'.

Common situations: Proxy hardcoding TCP6 while connected over IPv4; dual-stack misconfiguration; templates copied from an IPv6 deployment but running on IPv4 sockets.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/27c501eef60a3bb6. Report an issue: GitHub.