summaryrefslogtreecommitdiffhomepage
path: root/control/controlknobs/controlknobs.go
blob: 7810c53337d8c7601bd6ecb64b2cace425e86401 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// 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"
	"strconv"
	"sync/atomic"
	"time"

	"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

	// PeerMTUEnable is whether the node should do peer path MTU discovery.
	PeerMTUEnable atomic.Bool

	// DisableDNSForwarderTCPRetries is whether the DNS forwarder should
	// skip retrying truncated queries over TCP.
	DisableDNSForwarderTCPRetries atomic.Bool

	// MagicsockSessionActiveTimeout is an alternate magicsock session timeout
	// duration to use. If zero or unset, the default is used.
	MagicsockSessionActiveTimeout syncs.AtomicValue[time.Duration]
}

// 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)
		peerMTUEnable                 = has(tailcfg.NodeAttrPeerMTUEnable)
		dnsForwarderDisableTCPRetries = has(tailcfg.NodeAttrDNSForwarderDisableTCPRetries)
	)

	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)
	k.PeerMTUEnable.Store(peerMTUEnable)
	k.DisableDNSForwarderTCPRetries.Store(dnsForwarderDisableTCPRetries)

	var timeout time.Duration
	if vv := capMap[tailcfg.NodeAttrMagicsockSessionTimeout]; len(vv) > 0 {
		if v, _ := strconv.Unquote(string(vv[0])); v != "" {
			timeout, _ = time.ParseDuration(v)
			timeout = max(timeout, 0)
		}
	}
	if was := k.MagicsockSessionActiveTimeout.Load(); was != timeout {
		k.MagicsockSessionActiveTimeout.Store(timeout)
	}
}

// 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(),
		"PeerMTUEnable":                 k.PeerMTUEnable.Load(),
		"DisableDNSForwarderTCPRetries": k.DisableDNSForwarderTCPRetries.Load(),
		"MagicsockSessionActiveTimeout": k.MagicsockSessionActiveTimeout.Load().String(),
	}
}