Browse Source

net/tstun: probe TCP GRO (#13376)

Disable TCP & UDP GRO if the probe fails.

torvalds/linux@e269d79c7d35aa3808b1f3c1737d63dab504ddc8 broke virtio_net
TCP & UDP GRO causing GRO writes to return EINVAL. The bug was then
resolved later in
torvalds/linux@89add40066f9ed9abe5f7f886fe5789ff7e0c50e. The offending
commit was pulled into various LTS releases.

Updates #13041

Signed-off-by: Jordan Whited <[email protected]>
Jordan Whited 1 year ago
parent
commit
7aa766ee65
7 changed files with 87 additions and 32 deletions
  1. 1 1
      go.mod
  2. 2 2
      go.sum
  3. 0 3
      net/tstun/tun.go
  4. 0 19
      net/tstun/tun_features_linux.go
  5. 82 0
      net/tstun/wrap_linux.go
  6. 1 7
      net/tstun/wrap_noop.go
  7. 1 0
      wgengine/userspace.go

+ 1 - 1
go.mod

@@ -85,7 +85,7 @@ require (
 	github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4
 	github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1
 	github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6
-	github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98
+	github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc
 	github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e
 	github.com/tc-hib/winres v0.2.1
 	github.com/tcnksm/go-httpstat v0.2.0

+ 2 - 2
go.sum

@@ -946,8 +946,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:t
 github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
 github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
 github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
-github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso=
-github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
+github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc=
+github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
 github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
 github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
 github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=

+ 0 - 3
net/tstun/tun.go

@@ -53,9 +53,6 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
 		dev.Close()
 		return nil, "", err
 	}
-	if err := setLinkFeatures(dev); err != nil {
-		logf("setting link features: %v", err)
-	}
 	if err := setLinkAttrs(dev); err != nil {
 		logf("setting link attributes: %v", err)
 	}

+ 0 - 19
net/tstun/tun_features_linux.go

@@ -1,19 +0,0 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package tstun
-
-import (
-	"github.com/tailscale/wireguard-go/tun"
-	"tailscale.com/envknob"
-)
-
-func setLinkFeatures(dev tun.Device) error {
-	if envknob.Bool("TS_TUN_DISABLE_UDP_GRO") {
-		linuxDev, ok := dev.(tun.LinuxDevice)
-		if ok {
-			linuxDev.DisableUDPGRO()
-		}
-	}
-	return nil
-}

+ 82 - 0
net/tstun/wrap_linux.go

@@ -0,0 +1,82 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package tstun
+
+import (
+	"errors"
+	"net/netip"
+	"runtime"
+
+	"github.com/tailscale/wireguard-go/tun"
+	"golang.org/x/sys/unix"
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/checksum"
+	"gvisor.dev/gvisor/pkg/tcpip/header"
+	"tailscale.com/envknob"
+	"tailscale.com/net/tsaddr"
+)
+
+// SetLinkFeaturesPostUp configures link features on t based on select TS_TUN_
+// environment variables and OS feature tests. Callers should ensure t is
+// up prior to calling, otherwise OS feature tests may be inconclusive.
+func (t *Wrapper) SetLinkFeaturesPostUp() {
+	if t.isTAP || runtime.GOOS == "android" {
+		return
+	}
+	if groDev, ok := t.tdev.(tun.GRODevice); ok {
+		if envknob.Bool("TS_TUN_DISABLE_UDP_GRO") {
+			groDev.DisableUDPGRO()
+		}
+		if envknob.Bool("TS_TUN_DISABLE_TCP_GRO") {
+			groDev.DisableTCPGRO()
+		}
+		err := probeTCPGRO(groDev)
+		if errors.Is(err, unix.EINVAL) {
+			groDev.DisableTCPGRO()
+			groDev.DisableUDPGRO()
+			t.logf("disabled TUN TCP & UDP GRO due to GRO probe error: %v", err)
+		}
+	}
+}
+
+func probeTCPGRO(dev tun.GRODevice) error {
+	ipPort := netip.MustParseAddrPort(tsaddr.TailscaleServiceIPString + ":0")
+	fingerprint := []byte("tailscale-probe-tun-gro")
+	segmentSize := len(fingerprint)
+	iphLen := 20
+	tcphLen := 20
+	totalLen := iphLen + tcphLen + segmentSize
+	ipAs4 := ipPort.Addr().As4()
+	bufs := make([][]byte, 2)
+	for i := range bufs {
+		bufs[i] = make([]byte, PacketStartOffset+totalLen, PacketStartOffset+(totalLen*2))
+		ipv4H := header.IPv4(bufs[i][PacketStartOffset:])
+		ipv4H.Encode(&header.IPv4Fields{
+			SrcAddr:  tcpip.AddrFromSlice(ipAs4[:]),
+			DstAddr:  tcpip.AddrFromSlice(ipAs4[:]),
+			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][PacketStartOffset+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][PacketStartOffset+iphLen+tcphLen:], fingerprint)
+		ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
+		pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddress(), ipv4H.DestinationAddress(), uint16(tcphLen+segmentSize))
+		pseudoCsum = checksum.Checksum(bufs[i][PacketStartOffset+iphLen+tcphLen:], pseudoCsum)
+		tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
+	}
+	_, err := dev.Write(bufs, PacketStartOffset)
+	return err
+}

+ 1 - 7
net/tstun/tun_features_notlinux.go → net/tstun/wrap_noop.go

@@ -5,10 +5,4 @@
 
 package tstun
 
-import (
-	"github.com/tailscale/wireguard-go/tun"
-)
-
-func setLinkFeatures(dev tun.Device) error {
-	return nil
-}
+func (t *Wrapper) SetLinkFeaturesPostUp() {}

+ 1 - 0
wgengine/userspace.go

@@ -492,6 +492,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
 	if err := e.router.Up(); err != nil {
 		return nil, fmt.Errorf("router.Up: %w", err)
 	}
+	tsTUNDev.SetLinkFeaturesPostUp()
 
 	// It's a little pointless to apply no-op settings here (they
 	// should already be empty?), but it at least exercises the