diff --git a/proxy/proxy.go b/proxy/proxy.go index 3df4caa..e661a71 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -314,10 +314,14 @@ func (p *SNIProxy) buildProxyProtocolHeaderFromInfo(proxyInfo *ProxyProtocolInfo if srcIP == nil { return "PROXY UNKNOWN\r\n" } + srcIPStr := proxyInfo.SrcIP - if srcIP.To4() != nil { - // Source is IPv4, use TCP4 protocol + if srcIP4 := srcIP.To4(); srcIP4 != nil { + // Source is IPv4, use TCP4 protocol. Write it in dotted form: an + // IPv4-mapped IPv6 source ("::ffff:a.b.c.d", as sent in a TCP6 line + // by a dual-stack upstream) is not a valid TCP4 address. protocol = "TCP4" + srcIPStr = srcIP4.String() if targetTCP.IP.To4() != nil { // Target is also IPv4, use as-is targetIP = targetTCP.IP.String() @@ -343,7 +347,7 @@ func (p *SNIProxy) buildProxyProtocolHeaderFromInfo(proxyInfo *ProxyProtocolInfo return fmt.Sprintf("PROXY %s %s %s %d %d\r\n", protocol, - proxyInfo.SrcIP, + srcIPStr, targetIP, proxyInfo.SrcPort, targetTCP.Port) diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 7e3e6e6..99fb097 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -185,3 +185,31 @@ func TestParseProxyProtocolHeaderUnknownPreservesPayload(t *testing.T) { t.Fatalf("Expected payload %q, got %q", payload, got) } } + +// TestBuildProxyProtocolHeaderFromInfoIPv4MappedSource checks that an IPv4 +// client that a dual-stack upstream (e.g. HAProxy bound to "::") reports as +// "PROXY TCP6 ::ffff:a.b.c.d ..." is forwarded with a valid TCP4 line. +// PROXY v1 requires TCP4 addresses in dotted-quad form, and parsers such as +// go-proxyproto (used by Traefik) reject "TCP4 ::ffff:a.b.c.d". +func TestBuildProxyProtocolHeaderFromInfoIPv4MappedSource(t *testing.T) { + proxy, err := NewSNIProxy(8443, "", "", "127.0.0.1", 443, nil, true, nil) + if err != nil { + t.Fatalf("Failed to create SNI proxy: %v", err) + } + + proxyInfo := &ProxyProtocolInfo{ + Protocol: "TCP6", + SrcIP: "::ffff:203.0.113.7", + DestIP: "::ffff:10.0.0.1", + SrcPort: 51000, + DestPort: 443, + } + + targetAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") + header := proxy.buildProxyProtocolHeaderFromInfo(proxyInfo, targetAddr) + + expected := "PROXY TCP4 203.0.113.7 127.0.0.1 51000 8080\r\n" + if header != expected { + t.Errorf("Expected header %q, got %q", expected, header) + } +}