nats-io/nats-server · warning

failed to read IPv4 address data: %w

Error message

failed to read IPv4 address data: %w

What it means

The v2 header declared a valid (>=12 byte) IPv4 address length, but reading that many bytes from the connection with io.ReadFull failed (connection closed, timeout, or reset) before the full address payload arrived. The underlying I/O error is wrapped in the %w so the cause is preserved.

Source

Thrown at server/client_proxyproto.go:389

				return nil, fmt.Errorf("failed to discard UNSPEC address address data: %w", err)
			}
		}
		return nil, nil
	default:
		return nil, fmt.Errorf("%w: unsupported address family 0x%02x", errProxyProtoUnsupported, family)
	}
	return addr, err
}

// parseIPv4Addr parses IPv4 address data from PROXY protocol header
func parseIPv4Addr(conn net.Conn, addrLen uint16) (*proxyProtoAddr, error) {
	// IPv4: 4 (src IP) + 4 (dst IP) + 2 (src port) + 2 (dst port) = 12 bytes minimum
	if addrLen < proxyProtoAddrSizeIPv4 {
		return nil, fmt.Errorf("IPv4 address data too short: %d bytes", addrLen)
	}
	addrData := make([]byte, addrLen)
	if _, err := io.ReadFull(conn, addrData); err != nil {
		return nil, fmt.Errorf("failed to read IPv4 address data: %w", err)
	}
	return &proxyProtoAddr{
		srcIP:   net.IP(addrData[0:4]),
		dstIP:   net.IP(addrData[4:8]),
		srcPort: binary.BigEndian.Uint16(addrData[8:10]),
		dstPort: binary.BigEndian.Uint16(addrData[10:12]),
	}, nil
}

// parseIPv6Addr parses IPv6 address data from PROXY protocol header
func parseIPv6Addr(conn net.Conn, addrLen uint16) (*proxyProtoAddr, error) {
	// IPv6: 16 (src IP) + 16 (dst IP) + 2 (src port) + 2 (dst port) = 36 bytes minimum
	if addrLen < proxyProtoAddrSizeIPv6 {
		return nil, fmt.Errorf("IPv6 address data too short: %d bytes", addrLen)
	}
	addrData := make([]byte, addrLen)
	if _, err := io.ReadFull(conn, addrData); err != nil {
		return nil, fmt.Errorf("failed to read IPv6 address data: %w", err)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Ensure the upstream proxy writes the complete PROXY header atomically (single write) so it is never split by disconnects.
  2. Check whether clients/probes are connecting to a PROXY-enabled listener without speaking PROXY protocol and move them to a non-PROXY listener.
  3. Handle this as a transient connection error: retry the connection; use errors.Is/As on the wrapped cause to distinguish io.EOF/io.ErrUnexpectedEOF from timeouts.

Example fix

// before: partial writes from proxy
tcpConn.Write(headerSig)
// peer may disconnect here
// after
buf := buildFullV2Header(addr)
tcpConn.Write(buf) // one atomic write
Defensive patterns

Strategy: retry

Type guard

func isTransientHeaderRead(err error) bool {
    return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, os.ErrDeadlineExceeded)
}

Try / catch

_, err := connectWithProxy()
if err != nil && strings.Contains(err.Error(), "failed to read IPv4 address data") {
    if isTransientHeaderRead(errors.Unwrap(err)) {
        time.Sleep(backoff)
        return connectWithProxy() // retry transient disconnect
    }
}

Prevention

When it happens

Trigger: Client disconnects immediately after sending the PROXY header signature and length but before the 12-byte address data; network timeout mid-header; LB health-check probes that send a partial header then close.

Common situations: Health checks / port scanners hitting a PROXY-enabled port with half-formed headers; flaky mobile clients; LB connection draining between header write and body write.

Related errors


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