From 34549e0e98ee357d243ac01e4a80396c91ddec6e Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sat, 20 Jun 2026 18:20:44 +0800 Subject: [PATCH] fix(redirect): add IPv6 TPROXY support for TCP redirect listener Add missing IPV6_TRANSPARENT socket option to the TCP redirect listener Control callback, matching what the UDP redirect listener already sets. Without this, the kernel silently drops IPv6 TPROXY-redirected connections. Also clean up handler_linux.go getOriginalDstAddr: - Remove encoding/binary import and heap allocation for port conversion - Replace with zero-allocation ntohs (native-to-network byte order swap) - Document the struct layout hack where GetsockoptIPv6Mreq and GetsockoptIPv6MTUInfo are used to read sockaddr_in/sockaddr_in6 from SO_ORIGINAL_DST (kernel returns smaller structs than the getter functions expect, but only the first N bytes are populated) Fixes go-gost/gost#126 --- handler/redirect/tcp/handler_linux.go | 13 +++++++++---- listener/redirect/tcp/listener_linux.go | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/handler/redirect/tcp/handler_linux.go b/handler/redirect/tcp/handler_linux.go index 6c15c327..610c77b4 100644 --- a/handler/redirect/tcp/handler_linux.go +++ b/handler/redirect/tcp/handler_linux.go @@ -1,7 +1,6 @@ package redirect import ( - "encoding/binary" "errors" "net" "syscall" @@ -30,6 +29,10 @@ func (h *redirectHandler) getOriginalDstAddr(conn net.Conn) (addr net.Addr, err var cerr error err = rc.Control(func(fd uintptr) { if tcpAddr.IP.To4() != nil { + // SO_ORIGINAL_DST returns a struct sockaddr_in (16 bytes). + // GetsockoptIPv6Mreq reads into a 20-byte ipv6_mreq; only the + // first 16 bytes (the Multiaddr field) are populated. + // Layout: [0:2]=sin_family, [2:4]=sin_port (BE), [4:8]=sin_addr. mreq, err := unix.GetsockoptIPv6Mreq(int(fd), unix.IPPROTO_IP, unix.SO_ORIGINAL_DST) if err != nil { cerr = err @@ -41,17 +44,19 @@ func (h *redirectHandler) getOriginalDstAddr(conn net.Conn) (addr net.Addr, err Port: int(mreq.Multiaddr[2])<<8 + int(mreq.Multiaddr[3]), } } else { + // SO_ORIGINAL_DST returns a struct sockaddr_in6 (28 bytes). + // GetsockoptIPv6MTUInfo reads into a 32-byte ipv6_mtuinfo; only + // the first 28 bytes (the Addr field) are populated. info, err := unix.GetsockoptIPv6MTUInfo(int(fd), unix.IPPROTO_IPV6, unix.SO_ORIGINAL_DST) if err != nil { cerr = err return } - var buf = make([]byte, 2) - binary.BigEndian.PutUint16(buf, info.Addr.Port) + // info.Addr.Port is in network byte order (big-endian). addr = &net.TCPAddr{ IP: net.IP(info.Addr.Addr[:]), - Port: int(binary.NativeEndian.Uint16(buf)), + Port: int(uint16(info.Addr.Port>>8 | info.Addr.Port<<8)), } } }) diff --git a/listener/redirect/tcp/listener_linux.go b/listener/redirect/tcp/listener_linux.go index 6e33b26f..581f7381 100644 --- a/listener/redirect/tcp/listener_linux.go +++ b/listener/redirect/tcp/listener_linux.go @@ -11,5 +11,8 @@ func (l *redirectListener) control(network, address string, c syscall.RawConn) e if err := unix.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_TRANSPARENT, 1); err != nil { l.logger.Errorf("SetsockoptInt(SOL_IP, IP_TRANSPARENT, 1): %v", err) } + if err := unix.SetsockoptInt(int(fd), unix.SOL_IPV6, unix.IPV6_TRANSPARENT, 1); err != nil { + l.logger.Errorf("SetsockoptInt(SOL_IPV6, IPV6_TRANSPARENT, 1): %v", err) + } }) }