control/controlknobs,net/{batching,tstun},wgengine: add nodecaps to disable UDP & TUN GRO/GSO

Add four control-plane node attributes that let us disable UDP GSO/GRO
on the magicsock UDP socket and UDP/TCP GRO on the Tailscale TUN
device.

These complement the pre-existing TS_DEBUG_DISABLE_UDP_{GRO,GSO} and
TS_TUN_DISABLE_{UDP,TCP}_GRO envknobs. They exist so we can mitigate
upstream Linux kernel regressions on a deployed fleet without
requiring a client release, after two incidents (#13041, #19777) where
buggy kernel patches landed upstream and the fix took an excessively
long time to reach downstream distros.

Knob changes are reacted to in setNetworkMapInternal / SetNetworkMap via
a comparison against a cached "last applied" value and only an actual
transition triggers work: magicsock Rebind()+ReSTUN for UDP,
ApplyGROKnobs for TUN. The TUN side is gated by buildfeatures.HasGRO and
is one-way (wireguard-go GRO disablement is sticky); re-enabling
requires a client restart.

Updates #13041
Updates #19777

Change-Id: I802993070afa659cc06809bb0bfbb7f8a0cdb273
Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker
2026-05-27 17:10:14 -07:00
committed by James Tucker
parent 94af1b00fb
commit 25b8ed8d9e
13 changed files with 212 additions and 30 deletions
+6 -3
View File
@@ -25,6 +25,7 @@ import (
"go4.org/mem"
"golang.org/x/crypto/blake2s"
"golang.org/x/net/ipv6"
"tailscale.com/control/controlknobs"
"tailscale.com/disco"
"tailscale.com/net/batching"
"tailscale.com/net/netaddr"
@@ -83,6 +84,7 @@ type Server struct {
metrics *metrics
netMon *netmon.Monitor
cloudInfo *cloudinfo.CloudInfo // used to query cloud metadata services
controlKnobs *controlknobs.Knobs // or nil
mu sync.Mutex // guards the following fields
macSecrets views.Slice[[blake2s.Size]byte] // [0] is most recent, max 2 elements
@@ -376,8 +378,8 @@ const (
// port selection is left up to the host networking stack. If
// onlyStaticAddrPorts is true, then dynamic addr:port discovery will be
// disabled, and only addr:port's set via [Server.SetStaticAddrPorts] will be
// used. Metrics must be non-nil.
func NewServer(logf logger.Logf, port uint16, onlyStaticAddrPorts bool, metrics *usermetric.Registry) (s *Server, err error) {
// used. Metrics must be non-nil. knobs may be nil.
func NewServer(logf logger.Logf, port uint16, onlyStaticAddrPorts bool, metrics *usermetric.Registry, knobs *controlknobs.Knobs) (s *Server, err error) {
s = &Server{
logf: logf,
disco: key.NewDisco(),
@@ -388,6 +390,7 @@ func NewServer(logf logger.Logf, port uint16, onlyStaticAddrPorts bool, metrics
serverEndpointByDisco: make(map[key.SortedPairOfDiscoPublic]*serverEndpoint),
nextVNI: minVNI,
cloudInfo: cloudinfo.New(logf),
controlKnobs: knobs,
}
s.discoPublic = s.disco.Public()
s.metrics = registerMetrics(metrics)
@@ -689,7 +692,7 @@ func (s *Server) bindSockets(desiredPort uint16) error {
break SocketsLoop
}
}
pc := batching.TryUpgradeToConn(uc, network, batching.IdealBatchSize, "udprelay_rxq_overflows")
pc := batching.TryUpgradeToConn(uc, network, batching.IdealBatchSize, "udprelay_rxq_overflows", s.controlKnobs)
bc, ok := pc.(batching.Conn)
if !ok {
bc = &singlePacketConn{uc}