mirror of
https://github.com/tailscale/tailscale.git
synced 2026-02-08 06:51:42 -05:00
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
205 lines
8.7 KiB
Go
205 lines
8.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// 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 (
|
|
"fmt"
|
|
"reflect"
|
|
"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
|
|
|
|
// 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
|
|
|
|
// SilentDisco is whether the node should suppress disco heartbeats to its
|
|
// peers.
|
|
SilentDisco atomic.Bool
|
|
|
|
// LinuxForceIPTables is whether the node should use iptables for Linux
|
|
// netfiltering, unless overridden by the user.
|
|
LinuxForceIPTables atomic.Bool
|
|
|
|
// LinuxForceNfTables is whether the node should use nftables for Linux
|
|
// netfiltering, unless overridden by the user.
|
|
LinuxForceNfTables atomic.Bool
|
|
|
|
// SeamlessKeyRenewal is whether to renew node keys without breaking connections.
|
|
// This is enabled by default in 1.90 and later, but we but we can remotely disable
|
|
// it from the control plane if there's a problem.
|
|
// http://go/seamless-key-renewal
|
|
SeamlessKeyRenewal atomic.Bool
|
|
|
|
// ProbeUDPLifetime is whether the node should probe UDP path lifetime on
|
|
// the tail end of an active direct connection in magicsock.
|
|
ProbeUDPLifetime atomic.Bool
|
|
|
|
// AppCStoreRoutes is whether the node should store RouteInfo to StateStore
|
|
// if it's an app connector.
|
|
AppCStoreRoutes atomic.Bool
|
|
|
|
// UserDialUseRoutes is whether tsdial.Dialer.UserDial should use routes to determine
|
|
// how to dial the destination address. When true, it also makes the DNS forwarder
|
|
// use UserDial instead of SystemDial when dialing resolvers.
|
|
UserDialUseRoutes atomic.Bool
|
|
|
|
// DisableSplitDNSWhenNoCustomResolvers indicates that the node's DNS manager
|
|
// should not adopt a split DNS configuration even though the Config of the
|
|
// resolver only contains routes that do not specify custom resolver(s), hence
|
|
// all DNS queries can be safely sent to the upstream DNS resolver and the
|
|
// node's DNS forwarder doesn't need to handle all DNS traffic.
|
|
// This is for now (2024-06-06) an iOS-specific battery life optimization,
|
|
// and this knob allows us to disable the optimization remotely if needed.
|
|
DisableSplitDNSWhenNoCustomResolvers atomic.Bool
|
|
|
|
// DisableLocalDNSOverrideViaNRPT indicates that the node's DNS manager should not
|
|
// create a default (catch-all) Windows NRPT rule when "Override local DNS" is enabled.
|
|
// Without this rule, Windows 8.1 and newer devices issue parallel DNS requests to DNS servers
|
|
// associated with all network adapters, even when "Override local DNS" is enabled and/or
|
|
// a Mullvad exit node is being used, resulting in DNS leaks.
|
|
// We began creating this rule on 2024-06-14, and this knob
|
|
// allows us to disable the new behavior remotely if needed.
|
|
DisableLocalDNSOverrideViaNRPT atomic.Bool
|
|
|
|
// DisableCaptivePortalDetection is whether the node should not perform captive portal detection
|
|
// automatically when the network state changes.
|
|
DisableCaptivePortalDetection atomic.Bool
|
|
|
|
// DisableSkipStatusQueue is whether the node should disable skipping
|
|
// of queued netmap.NetworkMap between the controlclient and LocalBackend.
|
|
// See tailscale/tailscale#14768.
|
|
DisableSkipStatusQueue atomic.Bool
|
|
}
|
|
|
|
// UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
|
|
// node attributes (Node.Capabilities).
|
|
func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
|
|
if k == nil {
|
|
return
|
|
}
|
|
has := capMap.Contains
|
|
var (
|
|
keepFullWG = has(tailcfg.NodeAttrDebugDisableWGTrim)
|
|
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)
|
|
silentDisco = has(tailcfg.NodeAttrSilentDisco)
|
|
forceIPTables = has(tailcfg.NodeAttrLinuxMustUseIPTables)
|
|
forceNfTables = has(tailcfg.NodeAttrLinuxMustUseNfTables)
|
|
seamlessKeyRenewal = has(tailcfg.NodeAttrSeamlessKeyRenewal)
|
|
disableSeamlessKeyRenewal = has(tailcfg.NodeAttrDisableSeamlessKeyRenewal)
|
|
probeUDPLifetime = has(tailcfg.NodeAttrProbeUDPLifetime)
|
|
appCStoreRoutes = has(tailcfg.NodeAttrStoreAppCRoutes)
|
|
userDialUseRoutes = has(tailcfg.NodeAttrUserDialUseRoutes)
|
|
disableSplitDNSWhenNoCustomResolvers = has(tailcfg.NodeAttrDisableSplitDNSWhenNoCustomResolvers)
|
|
disableLocalDNSOverrideViaNRPT = has(tailcfg.NodeAttrDisableLocalDNSOverrideViaNRPT)
|
|
disableCaptivePortalDetection = has(tailcfg.NodeAttrDisableCaptivePortalDetection)
|
|
disableSkipStatusQueue = has(tailcfg.NodeAttrDisableSkipStatusQueue)
|
|
)
|
|
|
|
if has(tailcfg.NodeAttrOneCGNATEnable) {
|
|
oneCGNAT.Set(true)
|
|
} else if has(tailcfg.NodeAttrOneCGNATDisable) {
|
|
oneCGNAT.Set(false)
|
|
}
|
|
|
|
k.KeepFullWGConfig.Store(keepFullWG)
|
|
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)
|
|
k.SilentDisco.Store(silentDisco)
|
|
k.LinuxForceIPTables.Store(forceIPTables)
|
|
k.LinuxForceNfTables.Store(forceNfTables)
|
|
k.ProbeUDPLifetime.Store(probeUDPLifetime)
|
|
k.AppCStoreRoutes.Store(appCStoreRoutes)
|
|
k.UserDialUseRoutes.Store(userDialUseRoutes)
|
|
k.DisableSplitDNSWhenNoCustomResolvers.Store(disableSplitDNSWhenNoCustomResolvers)
|
|
k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT)
|
|
k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection)
|
|
k.DisableSkipStatusQueue.Store(disableSkipStatusQueue)
|
|
|
|
// If both attributes are present, then "enable" should win. This reflects
|
|
// the history of seamless key renewal.
|
|
//
|
|
// Before 1.90, seamless was a private alpha, opt-in feature. Devices would
|
|
// only seamless do if customers opted in using the seamless renewal attr.
|
|
//
|
|
// In 1.90 and later, seamless is the default behaviour, and devices will use
|
|
// seamless unless explicitly told not to by control (e.g. if we discover
|
|
// a bug and want clients to use the prior behaviour).
|
|
//
|
|
// If a customer has opted in to the pre-1.90 seamless implementation, we
|
|
// don't want to switch it off for them -- we only want to switch it off for
|
|
// devices that haven't opted in.
|
|
k.SeamlessKeyRenewal.Store(seamlessKeyRenewal || !disableSeamlessKeyRenewal)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
ret := map[string]any{}
|
|
rt := reflect.TypeFor[Knobs]()
|
|
rv := reflect.ValueOf(k).Elem() // of *k
|
|
for i := 0; i < rt.NumField(); i++ {
|
|
name := rt.Field(i).Name
|
|
switch v := rv.Field(i).Addr().Interface().(type) {
|
|
case *atomic.Bool:
|
|
ret[name] = v.Load()
|
|
case *syncs.AtomicValue[opt.Bool]:
|
|
ret[name] = v.Load()
|
|
default:
|
|
panic(fmt.Sprintf("unknown field type %T for %v", v, name))
|
|
}
|
|
}
|
|
return ret
|
|
}
|