mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-14 14:59:57 -04:00
This commit bumps the wireguard-go dependency to incorporate changes to the packet memory model and the tun.Device.Read and conn.ReceiveFunc I/O interfaces. It updates their implementations accordingly. These changes improve throughput in all measured benchmarks and reduce peak RSS in six of eight cases. The two regressions will be addressed in a follow-up commit that reduces peak RSS below the baseline measured at1e69418. That work is kept separate to simplify review. The following throughput and peak RSS benchmarks were performed with iperf3 between two Intel i5-12400 nodes running Ubuntu 24.04 (Linux 6.8). The UDP benchmarks did not use UDP GSO on the sender, so they were roughly equivalent to single packet I/O through wireguard-go. TCP/1 signifies one TCP stream; TCP/128 signifies 128 parallel TCP streams. Throughput (Mb/s) Test1e69418After Change TCP/1 10,371 11,354 +9.5% TCP/128 7,886 8,404 +6.6% UDP/1 2,111 2,853 +35.1% UDP/128 1,747 2,235 +28.0% Peak memory (VmHWM, kB) Test Side1e69418After Change TCP/1 TX 98,240 52,596 -46.5% RX 287,748 73,384 -74.5% TCP/128 TX 101,196 52,812 -47.8% RX 290,420 63,620 -78.1% UDP/1 TX 58,864 160,840 +173.2% RX 137,516 49,900 -63.7% UDP/128 TX 66,148 116,096 +75.5% RX 154,384 56,556 -63.4% Updates tailscale/corp#46716 Updates tailscale/corp#22467 Updates tailscale/corp#36989 Updates tailscale/corp#37878 Signed-off-by: Jordan Whited <jordan@tailscale.com>
114 lines
3.7 KiB
Go
114 lines
3.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux && !ts_omit_gro
|
|
|
|
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/control/controlknobs"
|
|
"tailscale.com/envknob"
|
|
"tailscale.com/net/tsaddr"
|
|
)
|
|
|
|
// SetLinkFeaturesPostUp configures link features on t based on select TS_TUN_
|
|
// environment variables, control-plane node attributes (via knobs, which may be
|
|
// nil), and OS feature tests. Callers should ensure t is up prior to calling,
|
|
// otherwise OS feature tests may be inconclusive.
|
|
func (t *Wrapper) SetLinkFeaturesPostUp(knobs *controlknobs.Knobs) {
|
|
if t.isTAP || runtime.GOOS == "android" {
|
|
return
|
|
}
|
|
if groDev, ok := t.tdev.(tun.GRODevice); ok {
|
|
if envknob.Bool("TS_TUN_DISABLE_UDP_GRO") ||
|
|
(knobs != nil && knobs.DisableTUNUDPGRO.Load()) {
|
|
groDev.DisableUDPGRO()
|
|
}
|
|
if envknob.Bool("TS_TUN_DISABLE_TCP_GRO") ||
|
|
(knobs != nil && knobs.DisableTUNTCPGRO.Load()) {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ApplyGROKnobs applies the [tailcfg.NodeAttrDisableTUNUDPGRO] and
|
|
// [tailcfg.NodeAttrDisableTUNTCPGRO] knob values (via knobs, which must be
|
|
// non-nil) to t's underlying device. It is intended to be called when a
|
|
// control-plane node attribute change is detected after [SetLinkFeaturesPostUp]
|
|
// has already run.
|
|
//
|
|
// Note: wireguard-go's GRO disablement is one-way (sticky); ApplyGROKnobs can
|
|
// move TUN UDP/TCP GRO from enabled to disabled, but the reverse requires a
|
|
// client restart.
|
|
func (t *Wrapper) ApplyGROKnobs(knobs *controlknobs.Knobs) {
|
|
if t.isTAP || runtime.GOOS == "android" || knobs == nil {
|
|
return
|
|
}
|
|
groDev, ok := t.tdev.(tun.GRODevice)
|
|
if !ok {
|
|
return
|
|
}
|
|
if knobs.DisableTUNUDPGRO.Load() {
|
|
groDev.DisableUDPGRO()
|
|
}
|
|
if knobs.DisableTUNTCPGRO.Load() {
|
|
groDev.DisableTCPGRO()
|
|
}
|
|
}
|
|
|
|
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, WritePacketStartOffset+totalLen, WritePacketStartOffset+(totalLen*2))
|
|
ipv4H := header.IPv4(bufs[i][WritePacketStartOffset:])
|
|
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][WritePacketStartOffset+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][WritePacketStartOffset+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][WritePacketStartOffset+iphLen+tcphLen:], pseudoCsum)
|
|
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
|
}
|
|
_, err := dev.Write(bufs, WritePacketStartOffset)
|
|
return err
|
|
}
|