go-proxyproto icon indicating copy to clipboard operation
go-proxyproto copied to clipboard

Precheck of different IP stack

Open Fangliding opened this issue 3 months ago • 9 comments

proxy protocol does not allow forward between ipv4 and ipv6, but go-proxyproto may send this in proxyproto v1 this only happen in client v6 → dest v4, but client v4 → dest v6 returned err as expected because on client v4, it tried to convert dest v6 to v4 and encountered err but on client v6, it tried to convert dest v4 to v6 and can this "conversion" can work (https://cs.opensource.google/go/go/+/refs/tags/go1.25.1:src/net/ip.go;l=228)

Fangliding avatar Sep 16 '25 09:09 Fangliding

Can you, please, implement tests that show the behavior you describe? Thanks.

Btw for refence https://github.com/golang/go/issues/37921.

pires avatar Sep 16 '25 16:09 pires

Can you, please, implement tests that show the behavior you describe? Thanks.

Btw for refence golang/go#37921.

here is the test file

import (
	"bytes"
	"net"
	"testing"

	"github.com/pires/go-proxyproto"
)

func TestErrorProxyProtoHeader(t *testing.T) {
	// From v4 to v6
	From := net.TCPAddr{
		IP:   net.ParseIP("127.0.0.1"),
		Port: 12345,
	}
	To := net.TCPAddr{
		IP:   net.ParseIP("::1"),
		Port: 54321,
	}
	header := proxyproto.HeaderProxyFromAddrs(1, &From, &To)
	var buffer bytes.Buffer
	_, err := header.WriteTo(&buffer)
	if err != nil {
		t.Log("Failed to write header to buffer(as expected):", err)
	} else {
		t.Log("writed(unexpected):", buffer.String())
	}

	// From v6 to v4
	From2 := net.TCPAddr{
		IP:   net.ParseIP("::1"),
		Port: 12345,
	}
	To2 := net.TCPAddr{
		IP:   net.ParseIP("127.0.0.1"),
		Port: 54321,
	}
	header2 := proxyproto.HeaderProxyFromAddrs(1, &From2, &To2)
	var buffer2 bytes.Buffer
	_, err2 := header2.WriteTo(&buffer2)
	if err2 != nil {
		t.Log("Failed to write header to buffer(as expected):", err2)
	} else {
		t.Log("writed(unexpected):", buffer2.String())
	}
}

To fix this is simple, just check len(sourceIP) == len(destIP) But I don't know how to deal with mapped ipv4 correctly(((

Fangliding avatar Sep 17 '25 12:09 Fangliding

Thank you for this.

The HeaderProxyFromAddrs helper function is optimistic and never errors out. In this particular case the only option I can come up with is to set the transport protocol to be TCPv6 instead of TCPv4 which is causing the issue you are seeing.

The changes I'm considering are something like this:

diff --git a/header.go b/header.go
index 209c2cc..c95de63 100644
--- a/header.go
+++ b/header.go
@@ -61,31 +61,40 @@ func HeaderProxyFromAddrs(version byte, sourceAddr, destAddr net.Addr) *Header {
 	}
 	switch sourceAddr := sourceAddr.(type) {
 	case *net.TCPAddr:
-		if _, ok := destAddr.(*net.TCPAddr); !ok {
+		destAddr, ok := destAddr.(*net.TCPAddr)
+		if !ok {
 			break
 		}
-		if len(sourceAddr.IP.To4()) == net.IPv4len {
+		if len(sourceAddr.IP.To4()) == net.IPv4len && len(destAddr.IP.To4()) == net.IPv4len {
 			h.TransportProtocol = TCPv4
-		} else if len(sourceAddr.IP) == net.IPv6len {
+		} else if len(sourceAddr.IP) == net.IPv6len && len(destAddr.IP) == net.IPv6len {
 			h.TransportProtocol = TCPv6
 		}
 	case *net.UDPAddr:
-		if _, ok := destAddr.(*net.UDPAddr); !ok {
+		destAddr, ok := destAddr.(*net.UDPAddr)
+		if !ok {
 			break
 		}
-		if len(sourceAddr.IP.To4()) == net.IPv4len {
+		if len(sourceAddr.IP.To4()) == net.IPv4len && len(destAddr.IP.To4()) == net.IPv4len {
 			h.TransportProtocol = UDPv4
-		} else if len(sourceAddr.IP) == net.IPv6len {
+		} else if len(sourceAddr.IP) == net.IPv6len && len(destAddr.IP) == net.IPv6len {
 			h.TransportProtocol = UDPv6
 		}
 	case *net.UnixAddr:
-		if _, ok := destAddr.(*net.UnixAddr); !ok {
+		destAddr, ok := destAddr.(*net.UnixAddr)
+		if !ok {
 			break
 		}
 		switch sourceAddr.Net {
 		case "unix":
+			if destAddr.Net != "unix" {
+				break
+			}
 			h.TransportProtocol = UnixStream
 		case "unixgram":
+			if destAddr.Net != "unixgram" {
+				break
+			}
 			h.TransportProtocol = UnixDatagram
 		}
 	}

With these changes, the following just works:

package proxyproto

import (
	"bytes"
	"net"
	"testing"
)

func TestErrorProxyProtoHeader(t *testing.T) {
	// v4 to v6
	sourceAddr := net.TCPAddr{
		IP:   net.ParseIP("127.0.0.1"),
		Port: 12345,
	}
	destAddr := net.TCPAddr{
		IP:   net.ParseIP("::1"),
		Port: 54321,
	}

	header := HeaderProxyFromAddrs(1, &sourceAddr, &destAddr)
	if header.TransportProtocol != TCPv6 {
		t.Fatalf("expected transport protocol to be TCPv6, got %v", header.TransportProtocol)
	}

	var buffer bytes.Buffer
	if _, err := header.WriteTo(&buffer); err != nil {
		t.Fatalf("failed to write header to buffer: %s", err.Error())
	}

	// v6 to v4
	sourceAddr = net.TCPAddr{
		IP:   net.ParseIP("::1"),
		Port: 12345,
	}
	destAddr = net.TCPAddr{
		IP:   net.ParseIP("127.0.0.1"),
		Port: 54321,
	}

	header = HeaderProxyFromAddrs(1, &sourceAddr, &destAddr)
	if header.TransportProtocol != TCPv6 {
		t.Fatalf("expected transport protocol to be TCPv6, got %v", header.TransportProtocol)
	}

	buffer.Reset()
	if _, err := header.WriteTo(&buffer); err != nil {
		t.Fatalf("failed to write header to buffer: %s", err.Error())
	}
}

Please, test and let me know if it works for you.

pires avatar Sep 17 '25 19:09 pires

This will make both of examples send unexpected data(not compliant with spec) I mean both of them should fail((

Fangliding avatar Sep 18 '25 09:09 Fangliding

Can you point to me what in the spec says IPv4-in-IPv6 to IPv6 shouldn't be possible?

pires avatar Sep 18 '25 11:09 pires

Can you point to me what in the spec says IPv4-in-IPv6 to IPv6 shouldn't be possible?

In https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

  • the layer 3 destination address in its canonical format. It is the same format as the layer 3 source address and matches the same family.

Fangliding avatar Sep 19 '25 14:09 Fangliding

Well, then you can't use this helper function because it doesn't error out. I am not sure if I want to break the API for such an helper function tbh.

pires avatar Sep 19 '25 20:09 pires

I understand, I also chose to keep this behavior in my repo‘s implement. I can have a pre check in my code, just open this to inform

Fangliding avatar Sep 20 '25 05:09 Fangliding

Thank you!

pires avatar Sep 22 '25 09:09 pires