Precheck of different IP stack
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)
Can you, please, implement tests that show the behavior you describe? Thanks.
Btw for refence https://github.com/golang/go/issues/37921.
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(((
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.
This will make both of examples send unexpected data(not compliant with spec) I mean both of them should fail((
Can you point to me what in the spec says IPv4-in-IPv6 to IPv6 shouldn't be possible?
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.
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.
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
Thank you!