nats-io/nats-server · error

: 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.)

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.

Appendix: 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)