net/tstun: probe TCP GRO (#13376)

Disable TCP & UDP GRO if the probe fails.

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

Updates #13041

Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
Jordan Whited 2024-09-05 09:59:31 -07:00 committed by GitHub
parent 7dcf65a10a
commit 7aa766ee65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 87 additions and 32 deletions

2
go.mod
View File

@ -85,7 +85,7 @@ require (
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 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/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e
github.com/tc-hib/winres v0.2.1 github.com/tc-hib/winres v0.2.1
github.com/tcnksm/go-httpstat v0.2.0 github.com/tcnksm/go-httpstat v0.2.0

4
go.sum
View File

@ -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/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 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= 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-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc=
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/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 h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=

View File

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

View File

@ -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
net/tstun/wrap_linux.go Normal file
View File

@ -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
}

View File

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

View File

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