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 20:30:27 +00:00
committed by James Tucker
parent 94af1b00fb
commit 25b8ed8d9e
13 changed files with 212 additions and 30 deletions

View File

@@ -114,6 +114,22 @@ type Knobs struct {
// EmitRuntimeMetrics is whether the node should poll and emit [runtime/metrics]
// as [tailscale.com/util/clientmetric]'s.
EmitRuntimeMetrics atomic.Bool
// DisableUDPGRO disables UDP GRO on the magicsock UDP socket. See
// [tailcfg.NodeAttrDisableUDPGRO].
DisableUDPGRO atomic.Bool
// DisableUDPGSO disables UDP GSO on the magicsock UDP socket. See
// [tailcfg.NodeAttrDisableUDPGSO].
DisableUDPGSO atomic.Bool
// DisableTUNUDPGRO disables UDP GRO on the Tailscale TUN device. See
// [tailcfg.NodeAttrDisableTUNUDPGRO].
DisableTUNUDPGRO atomic.Bool
// DisableTUNTCPGRO disables TCP GRO on the Tailscale TUN device. See
// [tailcfg.NodeAttrDisableTUNTCPGRO].
DisableTUNTCPGRO atomic.Bool
}
// UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
@@ -144,6 +160,10 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
disableHostsFileUpdates = has(tailcfg.NodeAttrDisableHostsFileUpdates)
forceRegisterMagicDNSIPv4Only = has(tailcfg.NodeAttrForceRegisterMagicDNSIPv4Only)
emitRuntimeMetrics = has(tailcfg.NodeAttrEmitRuntimeMetrics)
disableUDPGRO = has(tailcfg.NodeAttrDisableUDPGRO)
disableUDPGSO = has(tailcfg.NodeAttrDisableUDPGSO)
disableTUNUDPGRO = has(tailcfg.NodeAttrDisableTUNUDPGRO)
disableTUNTCPGRO = has(tailcfg.NodeAttrDisableTUNTCPGRO)
)
if has(tailcfg.NodeAttrOneCGNATEnable) {
@@ -172,6 +192,10 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
k.DisableHostsFileUpdates.Store(disableHostsFileUpdates)
k.ForceRegisterMagicDNSIPv4Only.Store(forceRegisterMagicDNSIPv4Only)
k.EmitRuntimeMetrics.Store(emitRuntimeMetrics)
k.DisableUDPGRO.Store(disableUDPGRO)
k.DisableUDPGSO.Store(disableUDPGSO)
k.DisableTUNUDPGRO.Store(disableTUNUDPGRO)
k.DisableTUNTCPGRO.Store(disableTUNTCPGRO)
}
// AsDebugJSON returns k as something that can be marshalled with json.Marshal