// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause // Package controlknobs contains client options configurable from control which can be turned on // or off. The ability to turn options on and off is for incrementally adding features in. package controlknobs import ( "slices" "sync/atomic" "tailscale.com/syncs" "tailscale.com/tailcfg" "tailscale.com/types/opt" ) // Knobs is the set of knobs that the control plane's coordination server can // adjust at runtime. type Knobs struct { // DisableUPnP indicates whether to attempt UPnP mapping. DisableUPnP atomic.Bool // DisableDRPO is whether control says to disable the // DERP route optimization (Issue 150). DisableDRPO atomic.Bool // KeepFullWGConfig is whether we should disable the lazy wireguard // programming and instead give WireGuard the full netmap always, even for // idle peers. KeepFullWGConfig atomic.Bool // RandomizeClientPort is whether control says we should randomize // the client port. RandomizeClientPort atomic.Bool // OneCGNAT is whether the the node should make one big CGNAT route // in the OS rather than one /32 per peer. OneCGNAT syncs.AtomicValue[opt.Bool] // ForceBackgroundSTUN forces netcheck STUN queries to keep // running in magicsock, even when idle. ForceBackgroundSTUN atomic.Bool // DisableDeltaUpdates is whether the node should not process // incremental (delta) netmap updates and should treat all netmap // changes as "full" ones as tailscaled did in 1.48.x and earlier. DisableDeltaUpdates atomic.Bool } // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self // node attributes (Node.Capabilities). func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability, capMap tailcfg.NodeCapMap) { if k == nil { return } has := func(attr tailcfg.NodeCapability) bool { _, ok := capMap[attr] return ok || slices.Contains(selfNodeAttrs, attr) } var ( keepFullWG = has(tailcfg.NodeAttrDebugDisableWGTrim) disableDRPO = has(tailcfg.NodeAttrDebugDisableDRPO) disableUPnP = has(tailcfg.NodeAttrDisableUPnP) randomizeClientPort = has(tailcfg.NodeAttrRandomizeClientPort) disableDeltaUpdates = has(tailcfg.NodeAttrDisableDeltaUpdates) oneCGNAT opt.Bool forceBackgroundSTUN = has(tailcfg.NodeAttrDebugForceBackgroundSTUN) ) if has(tailcfg.NodeAttrOneCGNATEnable) { oneCGNAT.Set(true) } else if has(tailcfg.NodeAttrOneCGNATDisable) { oneCGNAT.Set(false) } k.KeepFullWGConfig.Store(keepFullWG) k.DisableDRPO.Store(disableDRPO) k.DisableUPnP.Store(disableUPnP) k.RandomizeClientPort.Store(randomizeClientPort) k.OneCGNAT.Store(oneCGNAT) k.ForceBackgroundSTUN.Store(forceBackgroundSTUN) k.DisableDeltaUpdates.Store(disableDeltaUpdates) } // AsDebugJSON returns k as something that can be marshalled with json.Marshal // for debug. func (k *Knobs) AsDebugJSON() map[string]any { if k == nil { return nil } return map[string]any{ "DisableUPnP": k.DisableUPnP.Load(), "DisableDRPO": k.DisableDRPO.Load(), "KeepFullWGConfig": k.KeepFullWGConfig.Load(), "RandomizeClientPort": k.RandomizeClientPort.Load(), "OneCGNAT": k.OneCGNAT.Load(), "ForceBackgroundSTUN": k.ForceBackgroundSTUN.Load(), "DisableDeltaUpdates": k.DisableDeltaUpdates.Load(), } }