From c177abb523c33c03ff0bd93d475443fd072dad59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 23 Nov 2024 13:13:48 +0800 Subject: [PATCH] Copy TCP GRO probe from tailscale --- tun.go | 2 -- tun_linux.go | 63 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/tun.go b/tun.go index 3fe654f..4664cef 100644 --- a/tun.go +++ b/tun.go @@ -40,8 +40,6 @@ type LinuxTUN interface { BatchSize() int BatchRead(buffers [][]byte, offset int, readN []int) (n int, err error) BatchWrite(buffers [][]byte, offset int) (n int, err error) - DisableUDPGRO() - DisableTCPGRO() TXChecksumOffload() bool } diff --git a/tun_linux.go b/tun_linux.go index 5c7df19..9336ecd 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -14,6 +14,8 @@ import ( "unsafe" "github.com/sagernet/netlink" + "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/internal/gtcpip/header" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -194,20 +196,44 @@ func (t *NativeTun) BatchSize() int { return idealBatchSize } -// DisableUDPGRO disables UDP GRO if it is enabled. See the GRODevice interface -// for cases where it should be called. -func (t *NativeTun) DisableUDPGRO() { - t.writeAccess.Lock() - t.gro.disableUDPGRO() - t.writeAccess.Unlock() -} - -// DisableTCPGRO disables TCP GRO if it is enabled. See the GRODevice interface -// for cases where it should be called. -func (t *NativeTun) DisableTCPGRO() { - t.writeAccess.Lock() - t.gro.disableTCPGRO() - t.writeAccess.Unlock() +func (t *NativeTun) probeTCPGRO() error { + ipPort := netip.AddrPortFrom(t.options.Inet4Address[0].Addr(), 0) + fingerprint := []byte("sing-tun-probe-tun-gro") + segmentSize := len(fingerprint) + iphLen := 20 + tcphLen := 20 + totalLen := iphLen + tcphLen + segmentSize + bufs := make([][]byte, 2) + for i := range bufs { + bufs[i] = make([]byte, virtioNetHdrLen+totalLen, virtioNetHdrLen+(totalLen*2)) + ipv4H := header.IPv4(bufs[i][virtioNetHdrLen:]) + ipv4H.Encode(&header.IPv4Fields{ + SrcAddr: ipPort.Addr(), + DstAddr: ipPort.Addr(), + Protocol: unix.IPPROTO_TCP, + // Use a zero value TTL as best effort means to reduce chance of + // probe packet leaking further than it needs to. + TTL: 0, + TotalLength: uint16(totalLen), + }) + tcpH := header.TCP(bufs[i][virtioNetHdrLen+iphLen:]) + tcpH.Encode(&header.TCPFields{ + SrcPort: ipPort.Port(), + DstPort: ipPort.Port(), + SeqNum: 1 + uint32(i*segmentSize), + AckNum: 1, + DataOffset: 20, + Flags: header.TCPFlagAck, + WindowSize: 3000, + }) + copy(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], fingerprint) + ipv4H.SetChecksum(^ipv4H.CalculateChecksum()) + pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddressSlice(), ipv4H.DestinationAddressSlice(), uint16(tcphLen+segmentSize)) + pseudoCsum = checksum.Checksum(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], pseudoCsum) + tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum)) + } + _, err := t.BatchWrite(bufs, virtioNetHdrLen) + return err } func (t *NativeTun) BatchRead(buffers [][]byte, offset int, readN []int) (n int, err error) { @@ -397,6 +423,15 @@ func (t *NativeTun) Start() error { return err } + if t.vnetHdr && len(t.options.Inet4Address) > 0 { + err = t.probeTCPGRO() + if err != nil { + t.gro.disableTCPGRO() + t.gro.disableUDPGRO() + t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error")) + } + } + if t.options.IPRoute2TableIndex == 0 { for { t.options.IPRoute2TableIndex = int(rand.Uint32())