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
This commit is contained in:
ginuerzh
2026-06-20 18:20:44 +08:00
parent 684a0ddf65
commit 34549e0e98
2 changed files with 12 additions and 4 deletions
+9 -4
View File
@@ -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)),
}
}
})
+3
View File
@@ -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)
}
})
}