controlknobs.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package controlknobs contains client options configurable from control which can be turned on
  4. // or off. The ability to turn options on and off is for incrementally adding features in.
  5. package controlknobs
  6. import (
  7. "fmt"
  8. "reflect"
  9. "sync/atomic"
  10. "tailscale.com/syncs"
  11. "tailscale.com/tailcfg"
  12. "tailscale.com/types/opt"
  13. )
  14. // Knobs is the set of knobs that the control plane's coordination server can
  15. // adjust at runtime.
  16. type Knobs struct {
  17. // DisableUPnP indicates whether to attempt UPnP mapping.
  18. DisableUPnP atomic.Bool
  19. // KeepFullWGConfig is whether we should disable the lazy wireguard
  20. // programming and instead give WireGuard the full netmap always, even for
  21. // idle peers.
  22. KeepFullWGConfig atomic.Bool
  23. // RandomizeClientPort is whether control says we should randomize
  24. // the client port.
  25. RandomizeClientPort atomic.Bool
  26. // OneCGNAT is whether the the node should make one big CGNAT route
  27. // in the OS rather than one /32 per peer.
  28. OneCGNAT syncs.AtomicValue[opt.Bool]
  29. // ForceBackgroundSTUN forces netcheck STUN queries to keep
  30. // running in magicsock, even when idle.
  31. ForceBackgroundSTUN atomic.Bool
  32. // DisableDeltaUpdates is whether the node should not process
  33. // incremental (delta) netmap updates and should treat all netmap
  34. // changes as "full" ones as tailscaled did in 1.48.x and earlier.
  35. DisableDeltaUpdates atomic.Bool
  36. // PeerMTUEnable is whether the node should do peer path MTU discovery.
  37. PeerMTUEnable atomic.Bool
  38. // DisableDNSForwarderTCPRetries is whether the DNS forwarder should
  39. // skip retrying truncated queries over TCP.
  40. DisableDNSForwarderTCPRetries atomic.Bool
  41. // SilentDisco is whether the node should suppress disco heartbeats to its
  42. // peers.
  43. SilentDisco atomic.Bool
  44. // LinuxForceIPTables is whether the node should use iptables for Linux
  45. // netfiltering, unless overridden by the user.
  46. LinuxForceIPTables atomic.Bool
  47. // LinuxForceNfTables is whether the node should use nftables for Linux
  48. // netfiltering, unless overridden by the user.
  49. LinuxForceNfTables atomic.Bool
  50. // SeamlessKeyRenewal is whether to renew node keys without breaking connections.
  51. // This is enabled by default in 1.90 and later, but we but we can remotely disable
  52. // it from the control plane if there's a problem.
  53. // http://go/seamless-key-renewal
  54. SeamlessKeyRenewal atomic.Bool
  55. // ProbeUDPLifetime is whether the node should probe UDP path lifetime on
  56. // the tail end of an active direct connection in magicsock.
  57. ProbeUDPLifetime atomic.Bool
  58. // AppCStoreRoutes is whether the node should store RouteInfo to StateStore
  59. // if it's an app connector.
  60. AppCStoreRoutes atomic.Bool
  61. // UserDialUseRoutes is whether tsdial.Dialer.UserDial should use routes to determine
  62. // how to dial the destination address. When true, it also makes the DNS forwarder
  63. // use UserDial instead of SystemDial when dialing resolvers.
  64. UserDialUseRoutes atomic.Bool
  65. // DisableSplitDNSWhenNoCustomResolvers indicates that the node's DNS manager
  66. // should not adopt a split DNS configuration even though the Config of the
  67. // resolver only contains routes that do not specify custom resolver(s), hence
  68. // all DNS queries can be safely sent to the upstream DNS resolver and the
  69. // node's DNS forwarder doesn't need to handle all DNS traffic.
  70. // This is for now (2024-06-06) an iOS-specific battery life optimization,
  71. // and this knob allows us to disable the optimization remotely if needed.
  72. DisableSplitDNSWhenNoCustomResolvers atomic.Bool
  73. // DisableLocalDNSOverrideViaNRPT indicates that the node's DNS manager should not
  74. // create a default (catch-all) Windows NRPT rule when "Override local DNS" is enabled.
  75. // Without this rule, Windows 8.1 and newer devices issue parallel DNS requests to DNS servers
  76. // associated with all network adapters, even when "Override local DNS" is enabled and/or
  77. // a Mullvad exit node is being used, resulting in DNS leaks.
  78. // We began creating this rule on 2024-06-14, and this knob
  79. // allows us to disable the new behavior remotely if needed.
  80. DisableLocalDNSOverrideViaNRPT atomic.Bool
  81. // DisableCaptivePortalDetection is whether the node should not perform captive portal detection
  82. // automatically when the network state changes.
  83. DisableCaptivePortalDetection atomic.Bool
  84. // DisableSkipStatusQueue is whether the node should disable skipping
  85. // of queued netmap.NetworkMap between the controlclient and LocalBackend.
  86. // See tailscale/tailscale#14768.
  87. DisableSkipStatusQueue atomic.Bool
  88. }
  89. // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
  90. // node attributes (Node.Capabilities).
  91. func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
  92. if k == nil {
  93. return
  94. }
  95. has := capMap.Contains
  96. var (
  97. keepFullWG = has(tailcfg.NodeAttrDebugDisableWGTrim)
  98. disableUPnP = has(tailcfg.NodeAttrDisableUPnP)
  99. randomizeClientPort = has(tailcfg.NodeAttrRandomizeClientPort)
  100. disableDeltaUpdates = has(tailcfg.NodeAttrDisableDeltaUpdates)
  101. oneCGNAT opt.Bool
  102. forceBackgroundSTUN = has(tailcfg.NodeAttrDebugForceBackgroundSTUN)
  103. peerMTUEnable = has(tailcfg.NodeAttrPeerMTUEnable)
  104. dnsForwarderDisableTCPRetries = has(tailcfg.NodeAttrDNSForwarderDisableTCPRetries)
  105. silentDisco = has(tailcfg.NodeAttrSilentDisco)
  106. forceIPTables = has(tailcfg.NodeAttrLinuxMustUseIPTables)
  107. forceNfTables = has(tailcfg.NodeAttrLinuxMustUseNfTables)
  108. seamlessKeyRenewal = has(tailcfg.NodeAttrSeamlessKeyRenewal)
  109. disableSeamlessKeyRenewal = has(tailcfg.NodeAttrDisableSeamlessKeyRenewal)
  110. probeUDPLifetime = has(tailcfg.NodeAttrProbeUDPLifetime)
  111. appCStoreRoutes = has(tailcfg.NodeAttrStoreAppCRoutes)
  112. userDialUseRoutes = has(tailcfg.NodeAttrUserDialUseRoutes)
  113. disableSplitDNSWhenNoCustomResolvers = has(tailcfg.NodeAttrDisableSplitDNSWhenNoCustomResolvers)
  114. disableLocalDNSOverrideViaNRPT = has(tailcfg.NodeAttrDisableLocalDNSOverrideViaNRPT)
  115. disableCaptivePortalDetection = has(tailcfg.NodeAttrDisableCaptivePortalDetection)
  116. disableSkipStatusQueue = has(tailcfg.NodeAttrDisableSkipStatusQueue)
  117. )
  118. if has(tailcfg.NodeAttrOneCGNATEnable) {
  119. oneCGNAT.Set(true)
  120. } else if has(tailcfg.NodeAttrOneCGNATDisable) {
  121. oneCGNAT.Set(false)
  122. }
  123. k.KeepFullWGConfig.Store(keepFullWG)
  124. k.DisableUPnP.Store(disableUPnP)
  125. k.RandomizeClientPort.Store(randomizeClientPort)
  126. k.OneCGNAT.Store(oneCGNAT)
  127. k.ForceBackgroundSTUN.Store(forceBackgroundSTUN)
  128. k.DisableDeltaUpdates.Store(disableDeltaUpdates)
  129. k.PeerMTUEnable.Store(peerMTUEnable)
  130. k.DisableDNSForwarderTCPRetries.Store(dnsForwarderDisableTCPRetries)
  131. k.SilentDisco.Store(silentDisco)
  132. k.LinuxForceIPTables.Store(forceIPTables)
  133. k.LinuxForceNfTables.Store(forceNfTables)
  134. k.ProbeUDPLifetime.Store(probeUDPLifetime)
  135. k.AppCStoreRoutes.Store(appCStoreRoutes)
  136. k.UserDialUseRoutes.Store(userDialUseRoutes)
  137. k.DisableSplitDNSWhenNoCustomResolvers.Store(disableSplitDNSWhenNoCustomResolvers)
  138. k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT)
  139. k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection)
  140. k.DisableSkipStatusQueue.Store(disableSkipStatusQueue)
  141. // If both attributes are present, then "enable" should win. This reflects
  142. // the history of seamless key renewal.
  143. //
  144. // Before 1.90, seamless was a private alpha, opt-in feature. Devices would
  145. // only seamless do if customers opted in using the seamless renewal attr.
  146. //
  147. // In 1.90 and later, seamless is the default behaviour, and devices will use
  148. // seamless unless explicitly told not to by control (e.g. if we discover
  149. // a bug and want clients to use the prior behaviour).
  150. //
  151. // If a customer has opted in to the pre-1.90 seamless implementation, we
  152. // don't want to switch it off for them -- we only want to switch it off for
  153. // devices that haven't opted in.
  154. k.SeamlessKeyRenewal.Store(seamlessKeyRenewal || !disableSeamlessKeyRenewal)
  155. }
  156. // AsDebugJSON returns k as something that can be marshalled with json.Marshal
  157. // for debug.
  158. func (k *Knobs) AsDebugJSON() map[string]any {
  159. if k == nil {
  160. return nil
  161. }
  162. ret := map[string]any{}
  163. rt := reflect.TypeFor[Knobs]()
  164. rv := reflect.ValueOf(k).Elem() // of *k
  165. for i := 0; i < rt.NumField(); i++ {
  166. name := rt.Field(i).Name
  167. switch v := rv.Field(i).Addr().Interface().(type) {
  168. case *atomic.Bool:
  169. ret[name] = v.Load()
  170. case *syncs.AtomicValue[opt.Bool]:
  171. ret[name] = v.Load()
  172. default:
  173. panic(fmt.Sprintf("unknown field type %T for %v", v, name))
  174. }
  175. }
  176. return ret
  177. }