envknob: support changing envknobs post-init

Updates #5114

Change-Id: Ia423fc7486e1b3f3180a26308278be0086fae49b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/5654/head
Brad Fitzpatrick 2 years ago committed by Brad Fitzpatrick
parent 33ee2c058e
commit 74674b110d

@ -132,6 +132,7 @@ var subCommands = map[string]*func([]string) error{
var beCLI func() // non-nil if CLI is linked in var beCLI func() // non-nil if CLI is linked in
func main() { func main() {
envknob.PanicIfAnyEnvCheckedInInit()
printVersion := false printVersion := false
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose") flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit") flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
@ -376,7 +377,7 @@ func run() error {
return fmt.Errorf("newNetstack: %w", err) return fmt.Errorf("newNetstack: %w", err)
} }
ns.ProcessLocalIPs = useNetstack ns.ProcessLocalIPs = useNetstack
ns.ProcessSubnets = useNetstack || wrapNetstack ns.ProcessSubnets = useNetstack || shouldWrapNetstack()
if useNetstack { if useNetstack {
dialer.UseNetstackForIP = func(ip netip.Addr) bool { dialer.UseNetstackForIP = func(ip netip.Addr) bool {
@ -477,8 +478,6 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer)
return nil, false, multierr.New(errs...) return nil, false, multierr.New(errs...)
} }
var wrapNetstack = shouldWrapNetstack()
func shouldWrapNetstack() bool { func shouldWrapNetstack() bool {
if v, ok := envknob.LookupBool("TS_DEBUG_WRAP_NETSTACK"); ok { if v, ok := envknob.LookupBool("TS_DEBUG_WRAP_NETSTACK"); ok {
return v return v
@ -549,7 +548,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, na
} }
conf.DNS = d conf.DNS = d
conf.Router = r conf.Router = r
if wrapNetstack { if shouldWrapNetstack() {
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router) conf.Router = netstack.NewSubnetRouterWrapper(conf.Router)
} }
} }

@ -274,7 +274,7 @@ func startIPNServer(ctx context.Context, logid string) error {
dev.Close() dev.Close()
return nil, nil, fmt.Errorf("router: %w", err) return nil, nil, fmt.Errorf("router: %w", err)
} }
if wrapNetstack { if shouldWrapNetstack() {
r = netstack.NewSubnetRouterWrapper(r) r = netstack.NewSubnetRouterWrapper(r)
} }
d, err := dns.NewOSConfigurator(logf, devName) d, err := dns.NewOSConfigurator(logf, devName)
@ -301,7 +301,7 @@ func startIPNServer(ctx context.Context, logid string) error {
return nil, nil, fmt.Errorf("newNetstack: %w", err) return nil, nil, fmt.Errorf("newNetstack: %w", err)
} }
ns.ProcessLocalIPs = false ns.ProcessLocalIPs = false
ns.ProcessSubnets = wrapNetstack ns.ProcessSubnets = shouldWrapNetstack()
if err := ns.Start(); err != nil { if err := ns.Start(); err != nil {
return nil, nil, fmt.Errorf("failed to start netstack: %w", err) return nil, nil, fmt.Errorf("failed to start netstack: %w", err)
} }

@ -490,7 +490,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("RegisterReq sign error: %v", err) c.logf("RegisterReq sign error: %v", err)
} }
} }
if debugRegister { if debugRegister() {
j, _ := json.MarshalIndent(request, "", "\t") j, _ := json.MarshalIndent(request, "", "\t")
c.logf("RegisterRequest: %s", j) c.logf("RegisterRequest: %s", j)
} }
@ -533,7 +533,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err) c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
return regen, opt.URL, fmt.Errorf("register request: %v", err) return regen, opt.URL, fmt.Errorf("register request: %v", err)
} }
if debugRegister { if debugRegister() {
j, _ := json.MarshalIndent(resp, "", "\t") j, _ := json.MarshalIndent(resp, "", "\t")
c.logf("RegisterResponse: %s", j) c.logf("RegisterResponse: %s", j)
} }
@ -715,7 +715,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
c.logf("[v1] PollNetMap: stream=%v ep=%v", allowStream, epStrs) c.logf("[v1] PollNetMap: stream=%v ep=%v", allowStream, epStrs)
vlogf := logger.Discard vlogf := logger.Discard
if DevKnob.DumpNetMaps { if DevKnob.DumpNetMaps() {
// TODO(bradfitz): update this to use "[v2]" prefix perhaps? but we don't // TODO(bradfitz): update this to use "[v2]" prefix perhaps? but we don't
// want to upload it always. // want to upload it always.
vlogf = c.logf vlogf = c.logf
@ -963,12 +963,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
controlTrimWGConfig.Store(d.TrimWGConfig) controlTrimWGConfig.Store(d.TrimWGConfig)
} }
if DevKnob.StripEndpoints { if DevKnob.StripEndpoints() {
for _, p := range resp.Peers { for _, p := range resp.Peers {
p.Endpoints = nil p.Endpoints = nil
} }
} }
if DevKnob.StripCaps { if DevKnob.StripCaps() {
nm.SelfNode.Capabilities = nil nm.SelfNode.Capabilities = nil
} }
@ -1012,8 +1012,8 @@ func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePubl
} }
var ( var (
debugMap = envknob.Bool("TS_DEBUG_MAP") debugMap = envknob.RegisterBool("TS_DEBUG_MAP")
debugRegister = envknob.Bool("TS_DEBUG_REGISTER") debugRegister = envknob.RegisterBool("TS_DEBUG_REGISTER")
) )
var jsonEscapedZero = []byte(`\u0000`) var jsonEscapedZero = []byte(`\u0000`)
@ -1051,7 +1051,7 @@ func (c *Direct) decodeMsg(msg []byte, v any, mkey key.MachinePrivate) error {
return err return err
} }
} }
if debugMap { if debugMap() {
var buf bytes.Buffer var buf bytes.Buffer
json.Indent(&buf, b, "", " ") json.Indent(&buf, b, "", " ")
log.Printf("MapResponse: %s", buf.Bytes()) log.Printf("MapResponse: %s", buf.Bytes())
@ -1088,7 +1088,7 @@ func encode(v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.Machine
if err != nil { if err != nil {
return nil, err return nil, err
} }
if debugMap { if debugMap() {
if _, ok := v.(*tailcfg.MapRequest); ok { if _, ok := v.(*tailcfg.MapRequest); ok {
log.Printf("MapRequest: %s", b) log.Printf("MapRequest: %s", b)
} }
@ -1139,18 +1139,18 @@ func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string
var DevKnob = initDevKnob() var DevKnob = initDevKnob()
type devKnobs struct { type devKnobs struct {
DumpNetMaps bool DumpNetMaps func() bool
ForceProxyDNS bool ForceProxyDNS func() bool
StripEndpoints bool // strip endpoints from control (only use disco messages) StripEndpoints func() bool // strip endpoints from control (only use disco messages)
StripCaps bool // strip all local node's control-provided capabilities StripCaps func() bool // strip all local node's control-provided capabilities
} }
func initDevKnob() devKnobs { func initDevKnob() devKnobs {
return devKnobs{ return devKnobs{
DumpNetMaps: envknob.Bool("TS_DEBUG_NETMAP"), DumpNetMaps: envknob.RegisterBool("TS_DEBUG_NETMAP"),
ForceProxyDNS: envknob.Bool("TS_DEBUG_PROXY_DNS"), ForceProxyDNS: envknob.RegisterBool("TS_DEBUG_PROXY_DNS"),
StripEndpoints: envknob.Bool("TS_DEBUG_STRIP_ENDPOINTS"), StripEndpoints: envknob.RegisterBool("TS_DEBUG_STRIP_ENDPOINTS"),
StripCaps: envknob.Bool("TS_DEBUG_STRIP_CAPS"), StripCaps: envknob.RegisterBool("TS_DEBUG_STRIP_CAPS"),
} }
} }

@ -190,7 +190,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
} }
ms.addUserProfile(peer.User) ms.addUserProfile(peer.User)
} }
if DevKnob.ForceProxyDNS { if DevKnob.ForceProxyDNS() {
nm.DNS.Proxied = true nm.DNS.Proxied = true
} }
ms.netMapBuilding = nil ms.netMapBuilding = nil
@ -356,13 +356,13 @@ func cloneNodes(v1 []*tailcfg.Node) []*tailcfg.Node {
return v2 return v2
} }
var debugSelfIPv6Only = envknob.Bool("TS_DEBUG_SELF_V6_ONLY") var debugSelfIPv6Only = envknob.RegisterBool("TS_DEBUG_SELF_V6_ONLY")
func filterSelfAddresses(in []netip.Prefix) (ret []netip.Prefix) { func filterSelfAddresses(in []netip.Prefix) (ret []netip.Prefix) {
switch { switch {
default: default:
return in return in
case debugSelfIPv6Only: case debugSelfIPv6Only():
for _, a := range in { for _, a := range in {
if a.Addr().Is6() { if a.Addr().Is6() {
ret = append(ret, a) ret = append(ret, a)

@ -13,20 +13,18 @@ import (
) )
// disableUPnP indicates whether to attempt UPnP mapping. // disableUPnP indicates whether to attempt UPnP mapping.
var disableUPnP atomic.Bool var disableUPnPControl atomic.Bool
func init() { var disableUPnpEnv = envknob.RegisterBool("TS_DISABLE_UPNP")
SetDisableUPnP(envknob.Bool("TS_DISABLE_UPNP"))
}
// DisableUPnP reports the last reported value from control // DisableUPnP reports the last reported value from control
// whether UPnP portmapping should be disabled. // whether UPnP portmapping should be disabled.
func DisableUPnP() bool { func DisableUPnP() bool {
return disableUPnP.Load() return disableUPnPControl.Load() || disableUPnpEnv()
} }
// SetDisableUPnP sets whether control says that UPnP should be // SetDisableUPnP sets whether control says that UPnP should be
// disabled. // disabled.
func SetDisableUPnP(v bool) { func SetDisableUPnP(v bool) {
disableUPnP.Store(v) disableUPnPControl.Store(v)
} }

@ -47,8 +47,6 @@ import (
"tailscale.com/version" "tailscale.com/version"
) )
var debug = envknob.Bool("DERP_DEBUG_LOGS")
// verboseDropKeys is the set of destination public keys that should // verboseDropKeys is the set of destination public keys that should
// verbosely log whenever DERP drops a packet. // verbosely log whenever DERP drops a packet.
var verboseDropKeys = map[key.NodePublic]bool{} var verboseDropKeys = map[key.NodePublic]bool{}
@ -106,6 +104,7 @@ type Server struct {
limitedLogf logger.Logf limitedLogf logger.Logf
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
dupPolicy dupPolicy dupPolicy dupPolicy
debug bool
// Counters: // Counters:
packetsSent, bytesSent expvar.Int packetsSent, bytesSent expvar.Int
@ -299,6 +298,7 @@ func NewServer(privateKey key.NodePrivate, logf logger.Logf) *Server {
runtime.ReadMemStats(&ms) runtime.ReadMemStats(&ms)
s := &Server{ s := &Server{
debug: envknob.Bool("DERP_DEBUG_LOGS"),
privateKey: privateKey, privateKey: privateKey,
publicKey: privateKey.Public(), publicKey: privateKey.Public(),
logf: logf, logf: logf,
@ -980,7 +980,7 @@ func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, r
msg := fmt.Sprintf("drop (%s) %s -> %s", srcKey.ShortString(), reason, dstKey.ShortString()) msg := fmt.Sprintf("drop (%s) %s -> %s", srcKey.ShortString(), reason, dstKey.ShortString())
s.limitedLogf(msg) s.limitedLogf(msg)
} }
if debug { if s.debug {
s.logf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, disco.LooksLikeDiscoWrapper(packetBytes)) s.logf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, disco.LooksLikeDiscoWrapper(packetBytes))
} }
} }

@ -19,28 +19,36 @@ package envknob
import ( import (
"log" "log"
"os" "os"
"runtime"
"sort"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic"
"tailscale.com/types/opt" "tailscale.com/types/opt"
) )
var ( var (
mu sync.Mutex mu sync.Mutex
set = map[string]string{} set = map[string]string{}
list []string regStr = map[string]*string{}
regBool = map[string]*bool{}
regOptBool = map[string]*opt.Bool{}
) )
func noteEnv(k, v string) { func noteEnv(k, v string) {
if v == "" {
return
}
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
if _, ok := set[k]; !ok { noteEnvLocked(k, v)
list = append(list, k) }
func noteEnvLocked(k, v string) {
if v != "" {
set[k] = v
} else {
delete(set, k)
} }
set[k] = v
} }
// logf is logger.Logf, but logger depends on envknob, so for circular // logf is logger.Logf, but logger depends on envknob, so for circular
@ -52,11 +60,39 @@ type logf = func(format string, args ...any)
func LogCurrent(logf logf) { func LogCurrent(logf logf) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
list := make([]string, 0, len(set))
for k := range set {
list = append(list, k)
}
sort.Strings(list)
for _, k := range list { for _, k := range list {
logf("envknob: %s=%q", k, set[k]) logf("envknob: %s=%q", k, set[k])
} }
} }
// Setenv changes an environment variable.
//
// It is not safe for concurrent reading of environment variables via the
// Register functions. All Setenv calls are meant to happen early in main before
// any goroutines are started.
func Setenv(envVar, val string) {
mu.Lock()
defer mu.Unlock()
os.Setenv(envVar, val)
noteEnvLocked(envVar, val)
if p := regStr[envVar]; p != nil {
*p = val
}
if p := regBool[envVar]; p != nil {
setBoolLocked(p, envVar, val)
}
if p := regOptBool[envVar]; p != nil {
setOptBoolLocked(p, envVar, val)
}
}
// String returns the named environment variable, using os.Getenv. // String returns the named environment variable, using os.Getenv.
// //
// If the variable is non-empty, it's also tracked & logged as being // If the variable is non-empty, it's also tracked & logged as being
@ -67,6 +103,82 @@ func String(envVar string) string {
return v return v
} }
// RegisterString returns a func that gets the named environment variable,
// without a map lookup per call. It assumes that mutations happen via
// envknob.Setenv.
func RegisterString(envVar string) func() string {
mu.Lock()
defer mu.Unlock()
p, ok := regStr[envVar]
if !ok {
val := os.Getenv(envVar)
if val != "" {
noteEnvLocked(envVar, val)
}
p = &val
regStr[envVar] = p
}
return func() string { return *p }
}
// RegisterBool returns a func that gets the named environment variable,
// without a map lookup per call. It assumes that mutations happen via
// envknob.Setenv.
func RegisterBool(envVar string) func() bool {
mu.Lock()
defer mu.Unlock()
p, ok := regBool[envVar]
if !ok {
var b bool
p = &b
setBoolLocked(p, envVar, os.Getenv(envVar))
regBool[envVar] = p
}
return func() bool { return *p }
}
// RegisterOptBool returns a func that gets the named environment variable,
// without a map lookup per call. It assumes that mutations happen via
// envknob.Setenv.
func RegisterOptBool(envVar string) func() opt.Bool {
mu.Lock()
defer mu.Unlock()
p, ok := regOptBool[envVar]
if !ok {
var b opt.Bool
p = &b
setOptBoolLocked(p, envVar, os.Getenv(envVar))
regOptBool[envVar] = p
}
return func() opt.Bool { return *p }
}
func setBoolLocked(p *bool, envVar, val string) {
noteEnvLocked(envVar, val)
if val == "" {
*p = false
return
}
var err error
*p, err = strconv.ParseBool(val)
if err != nil {
log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
}
}
func setOptBoolLocked(p *opt.Bool, envVar, val string) {
noteEnvLocked(envVar, val)
if val == "" {
*p = ""
return
}
b, err := strconv.ParseBool(val)
if err != nil {
log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
}
p.Set(b)
}
// Bool returns the boolean value of the named environment variable. // Bool returns the boolean value of the named environment variable.
// If the variable is not set, it returns false. // If the variable is not set, it returns false.
// An invalid value exits the binary with a failure. // An invalid value exits the binary with a failure.
@ -81,6 +193,7 @@ func BoolDefaultTrue(envVar string) bool {
} }
func boolOr(envVar string, implicitValue bool) bool { func boolOr(envVar string, implicitValue bool) bool {
assertNotInInit()
val := os.Getenv(envVar) val := os.Getenv(envVar)
if val == "" { if val == "" {
return implicitValue return implicitValue
@ -98,6 +211,7 @@ func boolOr(envVar string, implicitValue bool) bool {
// The ok result is whether a value was set. // The ok result is whether a value was set.
// If the value isn't a valid int, it exits the program with a failure. // If the value isn't a valid int, it exits the program with a failure.
func LookupBool(envVar string) (v bool, ok bool) { func LookupBool(envVar string) (v bool, ok bool) {
assertNotInInit()
val := os.Getenv(envVar) val := os.Getenv(envVar)
if val == "" { if val == "" {
return false, false return false, false
@ -113,6 +227,7 @@ func LookupBool(envVar string) (v bool, ok bool) {
// OptBool is like Bool, but returns an opt.Bool, so the caller can // OptBool is like Bool, but returns an opt.Bool, so the caller can
// distinguish between implicitly and explicitly false. // distinguish between implicitly and explicitly false.
func OptBool(envVar string) opt.Bool { func OptBool(envVar string) opt.Bool {
assertNotInInit()
b, ok := LookupBool(envVar) b, ok := LookupBool(envVar)
if !ok { if !ok {
return "" return ""
@ -126,6 +241,7 @@ func OptBool(envVar string) opt.Bool {
// The ok result is whether a value was set. // The ok result is whether a value was set.
// If the value isn't a valid int, it exits the program with a failure. // If the value isn't a valid int, it exits the program with a failure.
func LookupInt(envVar string) (v int, ok bool) { func LookupInt(envVar string) (v int, ok bool) {
assertNotInInit()
val := os.Getenv(envVar) val := os.Getenv(envVar)
if val == "" { if val == "" {
return 0, false return 0, false
@ -164,5 +280,44 @@ func NoLogsNoSupport() bool {
// SetNoLogsNoSupport enables no-logs-no-support mode. // SetNoLogsNoSupport enables no-logs-no-support mode.
func SetNoLogsNoSupport() { func SetNoLogsNoSupport() {
os.Setenv("TS_NO_LOGS_NO_SUPPORT", "true") Setenv("TS_NO_LOGS_NO_SUPPORT", "true")
}
// notInInit is set true the first time we've seen a non-init stack trace.
var notInInit atomic.Bool
func assertNotInInit() {
if notInInit.Load() {
return
}
skip := 0
for {
pc, _, _, ok := runtime.Caller(skip)
if !ok {
notInInit.Store(true)
return
}
fu := runtime.FuncForPC(pc)
if fu == nil {
return
}
name := fu.Name()
name = strings.TrimRightFunc(name, func(r rune) bool { return r >= '0' && r <= '9' })
if strings.HasSuffix(name, ".init") || strings.HasSuffix(name, ".init.") {
stack := make([]byte, 1<<10)
stack = stack[:runtime.Stack(stack, false)]
envCheckedInInitStack = stack
}
skip++
}
}
var envCheckedInInitStack []byte
// PanicIfAnyEnvCheckedInInit panics if environment variables were read during
// init.
func PanicIfAnyEnvCheckedInInit() {
if envCheckedInInitStack != nil {
panic("envknob check of called from init function: " + string(envCheckedInInitStack))
}
} }

@ -325,7 +325,7 @@ func OverallError() error {
return overallErrorLocked() return overallErrorLocked()
} }
var fakeErrForTesting = envknob.String("TS_DEBUG_FAKE_HEALTH_ERROR") var fakeErrForTesting = envknob.RegisterString("TS_DEBUG_FAKE_HEALTH_ERROR")
func overallErrorLocked() error { func overallErrorLocked() error {
if !anyInterfaceUp { if !anyInterfaceUp {
@ -383,7 +383,7 @@ func overallErrorLocked() error {
for _, s := range controlHealth { for _, s := range controlHealth {
errs = append(errs, errors.New(s)) errs = append(errs, errors.New(s))
} }
if e := fakeErrForTesting; len(errs) == 0 && e != "" { if e := fakeErrForTesting(); len(errs) == 0 && e != "" {
return errors.New(e) return errors.New(e)
} }
sort.Slice(errs, func(i, j int) bool { sort.Slice(errs, func(i, j int) bool {

@ -68,7 +68,6 @@ import (
) )
var controlDebugFlags = getControlDebugFlags() var controlDebugFlags = getControlDebugFlags()
var canSSH = envknob.CanSSHD()
func getControlDebugFlags() []string { func getControlDebugFlags() []string {
if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" { if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" {
@ -1510,12 +1509,12 @@ func (b *LocalBackend) tellClientToBrowseToURL(url string) {
} }
// For testing lazy machine key generation. // For testing lazy machine key generation.
var panicOnMachineKeyGeneration = envknob.Bool("TS_DEBUG_PANIC_MACHINE_KEY") var panicOnMachineKeyGeneration = envknob.RegisterBool("TS_DEBUG_PANIC_MACHINE_KEY")
func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePrivate, error) { func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePrivate, error) {
var cache syncs.AtomicValue[key.MachinePrivate] var cache syncs.AtomicValue[key.MachinePrivate]
return func() (key.MachinePrivate, error) { return func() (key.MachinePrivate, error) {
if panicOnMachineKeyGeneration { if panicOnMachineKeyGeneration() {
panic("machine key generated") panic("machine key generated")
} }
if v, ok := cache.LoadOk(); ok { if v, ok := cache.LoadOk(); ok {
@ -1752,7 +1751,7 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
// setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic // setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic
// from the prefs p, which may be nil. // from the prefs p, which may be nil.
func (b *LocalBackend) setAtomicValuesFromPrefs(p *ipn.Prefs) { func (b *LocalBackend) setAtomicValuesFromPrefs(p *ipn.Prefs) {
b.sshAtomicBool.Store(p != nil && p.RunSSH && canSSH) b.sshAtomicBool.Store(p != nil && p.RunSSH && envknob.CanSSHD())
if p == nil { if p == nil {
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil)) b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil))
@ -1967,7 +1966,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
default: default:
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS) return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
} }
if !canSSH { if !envknob.CanSSHD() {
return errors.New("The Tailscale SSH server has been administratively disabled.") return errors.New("The Tailscale SSH server has been administratively disabled.")
} }
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" { if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
@ -2032,7 +2031,7 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
b.logf("EditPrefs check error: %v", err) b.logf("EditPrefs check error: %v", err)
return nil, err return nil, err
} }
if p1.RunSSH && !canSSH { if p1.RunSSH && !envknob.CanSSHD() {
b.mu.Unlock() b.mu.Unlock()
b.logf("EditPrefs requests SSH, but disabled by envknob; returning error") b.logf("EditPrefs requests SSH, but disabled by envknob; returning error")
return nil, errors.New("Tailscale SSH server administratively disabled.") return nil, errors.New("Tailscale SSH server administratively disabled.")
@ -2854,7 +2853,7 @@ func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Pre
hi.ShieldsUp = prefs.ShieldsUp hi.ShieldsUp = prefs.ShieldsUp
var sshHostKeys []string var sshHostKeys []string
if prefs.RunSSH && canSSH { if prefs.RunSSH && envknob.CanSSHD() {
// TODO(bradfitz): this is called with b.mu held. Not ideal. // TODO(bradfitz): this is called with b.mu held. Not ideal.
// If the filesystem gets wedged or something we could block for // If the filesystem gets wedged or something we could block for
// a long time. But probably fine. // a long time. But probably fine.
@ -3073,7 +3072,7 @@ func (b *LocalBackend) ResetForClientDisconnect() {
b.setAtomicValuesFromPrefs(nil) b.setAtomicValuesFromPrefs(nil)
} }
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && canSSH } func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() }
// ShouldHandleViaIP reports whether whether ip is an IPv6 address in the // ShouldHandleViaIP reports whether whether ip is an IPv6 address in the
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to // Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to

@ -478,8 +478,8 @@ func (panicOnUseTransport) RoundTrip(*http.Request) (*http.Response, error) {
// Issue 1573: don't generate a machine key if we don't want to be running. // Issue 1573: don't generate a machine key if we don't want to be running.
func TestLazyMachineKeyGeneration(t *testing.T) { func TestLazyMachineKeyGeneration(t *testing.T) {
defer func(old bool) { panicOnMachineKeyGeneration = old }(panicOnMachineKeyGeneration) defer func(old func() bool) { panicOnMachineKeyGeneration = old }(panicOnMachineKeyGeneration)
panicOnMachineKeyGeneration = true panicOnMachineKeyGeneration = func() bool { return true }
var logf logger.Logf = logger.Discard var logf logger.Logf = logger.Discard
store := new(mem.Store) store := new(mem.Store)

@ -24,7 +24,7 @@ import (
"tailscale.com/types/tkatype" "tailscale.com/types/tkatype"
) )
var networkLockAvailable = envknob.Bool("TS_EXPERIMENTAL_NETWORK_LOCK") var networkLockAvailable = envknob.RegisterBool("TS_EXPERIMENTAL_NETWORK_LOCK")
type tkaState struct { type tkaState struct {
authority *tka.Authority authority *tka.Authority
@ -82,7 +82,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
if b.tka != nil { if b.tka != nil {
return errors.New("network-lock is already initialized") return errors.New("network-lock is already initialized")
} }
if !networkLockAvailable { if !networkLockAvailable() {
return errors.New("this is an experimental feature in your version of tailscale - Please upgrade to the latest to use this.") return errors.New("this is an experimental feature in your version of tailscale - Please upgrade to the latest to use this.")
} }
if !b.CanSupportNetworkLock() { if !b.CanSupportNetworkLock() {

@ -73,7 +73,7 @@ func (h *Handler) certDir() (string, error) {
return full, nil return full, nil
} }
var acmeDebug = envknob.Bool("TS_DEBUG_ACME") var acmeDebug = envknob.RegisterBool("TS_DEBUG_ACME")
func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) { func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite && !h.PermitCert { if !h.PermitWrite && !h.PermitCert {
@ -96,7 +96,7 @@ func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
now := time.Now() now := time.Now()
logf := logger.WithPrefix(h.logf, fmt.Sprintf("cert(%q): ", domain)) logf := logger.WithPrefix(h.logf, fmt.Sprintf("cert(%q): ", domain))
traceACME := func(v any) { traceACME := func(v any) {
if !acmeDebug { if !acmeDebug() {
return return
} }
j, _ := json.MarshalIndent(v, "", "\t") j, _ := json.MarshalIndent(v, "", "\t")

@ -32,7 +32,7 @@ const (
versionKey = `SOFTWARE\Microsoft\Windows NT\CurrentVersion` versionKey = `SOFTWARE\Microsoft\Windows NT\CurrentVersion`
) )
var configureWSL = envknob.Bool("TS_DEBUG_CONFIGURE_WSL") var configureWSL = envknob.RegisterBool("TS_DEBUG_CONFIGURE_WSL")
type windowsManager struct { type windowsManager struct {
logf logger.Logf logf logger.Logf
@ -359,7 +359,7 @@ func (m windowsManager) SetDNS(cfg OSConfig) error {
// On initial setup of WSL, the restart caused by --shutdown is slow, // On initial setup of WSL, the restart caused by --shutdown is slow,
// so we do it out-of-line. // so we do it out-of-line.
if configureWSL { if configureWSL() {
go func() { go func() {
if err := m.wslManager.SetDNS(cfg); err != nil { if err := m.wslManager.SetDNS(cfg); err != nil {
m.logf("WSL SetDNS: %v", err) // continue m.logf("WSL SetDNS: %v", err) // continue

@ -484,13 +484,13 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client,
return res, err return res, err
} }
var verboseDNSForward = envknob.Bool("TS_DEBUG_DNS_FORWARD_SEND") var verboseDNSForward = envknob.RegisterBool("TS_DEBUG_DNS_FORWARD_SEND")
// send sends packet to dst. It is best effort. // send sends packet to dst. It is best effort.
// //
// send expects the reply to have the same txid as txidOut. // send expects the reply to have the same txid as txidOut.
func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) { func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
if verboseDNSForward { if verboseDNSForward() {
f.logf("forwarder.send(%q) ...", rr.name.Addr) f.logf("forwarder.send(%q) ...", rr.name.Addr)
defer func() { defer func() {
f.logf("forwarder.send(%q) = %v, %v", rr.name.Addr, len(ret), err) f.logf("forwarder.send(%q) = %v, %v", rr.name.Addr, len(ret), err)

@ -141,7 +141,7 @@ func (r *Resolver) ttl() time.Duration {
return 10 * time.Minute return 10 * time.Minute
} }
var debug = envknob.Bool("TS_DEBUG_DNS_CACHE") var debug = envknob.RegisterBool("TS_DEBUG_DNS_CACHE")
// LookupIP returns the host's primary IP address (either IPv4 or // LookupIP returns the host's primary IP address (either IPv4 or
// IPv6, but preferring IPv4) and optionally its IPv6 address, if // IPv6, but preferring IPv4) and optionally its IPv6 address, if
@ -167,14 +167,14 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 netip.Addr
} }
if ip, err := netip.ParseAddr(host); err == nil { if ip, err := netip.ParseAddr(host); err == nil {
ip = ip.Unmap() ip = ip.Unmap()
if debug { if debug() {
log.Printf("dnscache: %q is an IP", host) log.Printf("dnscache: %q is an IP", host)
} }
return ip, zaddr, []netip.Addr{ip}, nil return ip, zaddr, []netip.Addr{ip}, nil
} }
if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok { if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok {
if debug { if debug() {
log.Printf("dnscache: %q = %v (cached)", host, ip) log.Printf("dnscache: %q = %v (cached)", host, ip)
} }
return ip, ip6, allIPs, nil return ip, ip6, allIPs, nil
@ -192,13 +192,13 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 netip.Addr
if res.Err != nil { if res.Err != nil {
if r.UseLastGood { if r.UseLastGood {
if ip, ip6, allIPs, ok := r.lookupIPCacheExpired(host); ok { if ip, ip6, allIPs, ok := r.lookupIPCacheExpired(host); ok {
if debug { if debug() {
log.Printf("dnscache: %q using %v after error", host, ip) log.Printf("dnscache: %q using %v after error", host, ip)
} }
return ip, ip6, allIPs, nil return ip, ip6, allIPs, nil
} }
} }
if debug { if debug() {
log.Printf("dnscache: error resolving %q: %v", host, res.Err) log.Printf("dnscache: error resolving %q: %v", host, res.Err)
} }
return zaddr, zaddr, nil, res.Err return zaddr, zaddr, nil, res.Err
@ -206,7 +206,7 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 netip.Addr
r := res.Val r := res.Val
return r.ip, r.ip6, r.allIPs, nil return r.ip, r.ip6, r.allIPs, nil
case <-ctx.Done(): case <-ctx.Done():
if debug { if debug() {
log.Printf("dnscache: context done while resolving %q: %v", host, ctx.Err()) log.Printf("dnscache: context done while resolving %q: %v", host, ctx.Err())
} }
return zaddr, zaddr, nil, ctx.Err() return zaddr, zaddr, nil, ctx.Err()
@ -250,7 +250,7 @@ func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
func (r *Resolver) lookupIP(host string) (ip, ip6 netip.Addr, allIPs []netip.Addr, err error) { func (r *Resolver) lookupIP(host string) (ip, ip6 netip.Addr, allIPs []netip.Addr, err error) {
if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok { if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok {
if debug { if debug() {
log.Printf("dnscache: %q found in cache as %v", host, ip) log.Printf("dnscache: %q found in cache as %v", host, ip)
} }
return ip, ip6, allIPs, nil return ip, ip6, allIPs, nil
@ -300,13 +300,13 @@ func (r *Resolver) addIPCache(host string, ip, ip6 netip.Addr, allIPs []netip.Ad
if ip.IsPrivate() { if ip.IsPrivate() {
// Don't cache obviously wrong entries from captive portals. // Don't cache obviously wrong entries from captive portals.
// TODO: use DoH or DoT for the forwarding resolver? // TODO: use DoH or DoT for the forwarding resolver?
if debug { if debug() {
log.Printf("dnscache: %q resolved to private IP %v; using but not caching", host, ip) log.Printf("dnscache: %q resolved to private IP %v; using but not caching", host, ip)
} }
return return
} }
if debug { if debug() {
log.Printf("dnscache: %q resolved to IP %v; caching", host, ip) log.Printf("dnscache: %q resolved to IP %v; caching", host, ip)
} }
@ -382,7 +382,7 @@ func (d *dialer) DialContext(ctx context.Context, network, address string) (retC
} }
i4s := v4addrs(allIPs) i4s := v4addrs(allIPs)
if len(i4s) < 2 { if len(i4s) < 2 {
if debug { if debug() {
log.Printf("dnscache: dialing %s, %s for %s", network, ip, address) log.Printf("dnscache: dialing %s, %s for %s", network, ip, address)
} }
c, err := dc.dialOne(ctx, ip.Unmap()) c, err := dc.dialOne(ctx, ip.Unmap())
@ -406,7 +406,7 @@ func (d *dialer) shouldTryBootstrap(ctx context.Context, err error, dc *dialCall
// Can't try bootstrap DNS if we don't have a fallback function // Can't try bootstrap DNS if we don't have a fallback function
if d.dnsCache.LookupIPFallback == nil { if d.dnsCache.LookupIPFallback == nil {
if debug { if debug() {
log.Printf("dnscache: not using bootstrap DNS: no fallback") log.Printf("dnscache: not using bootstrap DNS: no fallback")
} }
return false return false
@ -415,7 +415,7 @@ func (d *dialer) shouldTryBootstrap(ctx context.Context, err error, dc *dialCall
// We can't retry if the context is canceled, since any further // We can't retry if the context is canceled, since any further
// operations with this context will fail. // operations with this context will fail.
if ctxErr := ctx.Err(); ctxErr != nil { if ctxErr := ctx.Err(); ctxErr != nil {
if debug { if debug() {
log.Printf("dnscache: not using bootstrap DNS: context error: %v", ctxErr) log.Printf("dnscache: not using bootstrap DNS: context error: %v", ctxErr)
} }
return false return false
@ -423,7 +423,7 @@ func (d *dialer) shouldTryBootstrap(ctx context.Context, err error, dc *dialCall
wasTrustworthy := dc.dnsWasTrustworthy() wasTrustworthy := dc.dnsWasTrustworthy()
if wasTrustworthy { if wasTrustworthy {
if debug { if debug() {
log.Printf("dnscache: not using bootstrap DNS: DNS was trustworthy") log.Printf("dnscache: not using bootstrap DNS: DNS was trustworthy")
} }
return false return false

@ -167,10 +167,8 @@ func TestInterleaveSlices(t *testing.T) {
func TestShouldTryBootstrap(t *testing.T) { func TestShouldTryBootstrap(t *testing.T) {
oldDebug := debug oldDebug := debug
t.Cleanup(func() { t.Cleanup(func() { debug = oldDebug })
debug = oldDebug debug = func() bool { return true }
})
debug = true
type step struct { type step struct {
ip netip.Addr // IP we pretended to dial ip netip.Addr // IP we pretended to dial

@ -43,7 +43,7 @@ import (
// Debugging and experimentation tweakables. // Debugging and experimentation tweakables.
var ( var (
debugNetcheck = envknob.Bool("TS_DEBUG_NETCHECK") debugNetcheck = envknob.RegisterBool("TS_DEBUG_NETCHECK")
) )
// The various default timeouts for things. // The various default timeouts for things.
@ -210,7 +210,7 @@ func (c *Client) logf(format string, a ...any) {
} }
func (c *Client) vlogf(format string, a ...any) { func (c *Client) vlogf(format string, a ...any) {
if c.Verbose || debugNetcheck { if c.Verbose || debugNetcheck() {
c.logf(format, a...) c.logf(format, a...)
} }
} }

@ -63,12 +63,12 @@ func socketMarkWorks() bool {
return true return true
} }
var forceBindToDevice = envknob.Bool("TS_FORCE_LINUX_BIND_TO_DEVICE") var forceBindToDevice = envknob.RegisterBool("TS_FORCE_LINUX_BIND_TO_DEVICE")
// UseSocketMark reports whether SO_MARK is in use. // UseSocketMark reports whether SO_MARK is in use.
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead. // If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
func UseSocketMark() bool { func UseSocketMark() bool {
if forceBindToDevice { if forceBindToDevice() {
return false return false
} }
socketMarkWorksOnce.Do(func() { socketMarkWorksOnce.Do(func() {

@ -32,7 +32,7 @@ var counterFallbackOK int32 // atomic
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format // See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
var sslKeyLogFile = os.Getenv("SSLKEYLOGFILE") var sslKeyLogFile = os.Getenv("SSLKEYLOGFILE")
var debug = envknob.Bool("TS_DEBUG_TLS_DIAL") var debug = envknob.RegisterBool("TS_DEBUG_TLS_DIAL")
// Config returns a tls.Config for connecting to a server. // Config returns a tls.Config for connecting to a server.
// If base is non-nil, it's cloned as the base config before // If base is non-nil, it's cloned as the base config before
@ -77,7 +77,7 @@ func Config(host string, base *tls.Config) *tls.Config {
opts.Intermediates.AddCert(cert) opts.Intermediates.AddCert(cert)
} }
_, errSys := cs.PeerCertificates[0].Verify(opts) _, errSys := cs.PeerCertificates[0].Verify(opts)
if debug { if debug() {
log.Printf("tlsdial(sys %q): %v", host, errSys) log.Printf("tlsdial(sys %q): %v", host, errSys)
} }
if errSys == nil { if errSys == nil {
@ -88,7 +88,7 @@ func Config(host string, base *tls.Config) *tls.Config {
// or broken, fall back to trying LetsEncrypt at least. // or broken, fall back to trying LetsEncrypt at least.
opts.Roots = bakedInRoots() opts.Roots = bakedInRoots()
_, err := cs.PeerCertificates[0].Verify(opts) _, err := cs.PeerCertificates[0].Verify(opts)
if debug { if debug() {
log.Printf("tlsdial(bake %q): %v", host, err) log.Printf("tlsdial(bake %q): %v", host, err)
} }
if err == nil { if err == nil {
@ -142,7 +142,7 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
opts.Intermediates.AddCert(cert) opts.Intermediates.AddCert(cert)
} }
_, errSys := certs[0].Verify(opts) _, errSys := certs[0].Verify(opts)
if debug { if debug() {
log.Printf("tlsdial(sys %q/%q): %v", c.ServerName, certDNSName, errSys) log.Printf("tlsdial(sys %q/%q): %v", c.ServerName, certDNSName, errSys)
} }
if errSys == nil { if errSys == nil {
@ -150,7 +150,7 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
} }
opts.Roots = bakedInRoots() opts.Roots = bakedInRoots()
_, err := certs[0].Verify(opts) _, err := certs[0].Verify(opts)
if debug { if debug() {
log.Printf("tlsdial(bake %q/%q): %v", c.ServerName, certDNSName, err) log.Printf("tlsdial(bake %q/%q): %v", c.ServerName, certDNSName, err)
} }
if err == nil { if err == nil {

@ -20,14 +20,6 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
var tunMTU = DefaultMTU
func init() {
if mtu, ok := envknob.LookupInt("TS_DEBUG_MTU"); ok {
tunMTU = mtu
}
}
// createTAP is non-nil on Linux. // createTAP is non-nil on Linux.
var createTAP func(tapName, bridgeName string) (tun.Device, error) var createTAP func(tapName, bridgeName string) (tun.Device, error)
@ -52,6 +44,10 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
} }
dev, err = createTAP(tapName, bridgeName) dev, err = createTAP(tapName, bridgeName)
} else { } else {
tunMTU := DefaultMTU
if mtu, ok := envknob.LookupInt("TS_DEBUG_MTU"); ok {
tunMTU = mtu
}
dev, err = tun.CreateTUN(tunName, tunMTU) dev, err = tun.CreateTUN(tunName, tunMTU)
} }
if err != nil { if err != nil {

@ -74,10 +74,10 @@ func (pl List) String() string {
return strings.TrimRight(sb.String(), "\n") return strings.TrimRight(sb.String(), "\n")
} }
var debugDisablePortlist = envknob.Bool("TS_DEBUG_DISABLE_PORTLIST") var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
func GetList(prev List) (List, error) { func GetList(prev List) (List, error) {
if debugDisablePortlist { if debugDisablePortlist() {
return nil, nil return nil, nil
} }
pl, err := listPorts() pl, err := listPorts()

@ -46,9 +46,7 @@ import (
) )
var ( var (
debugPolicyFile = envknob.SSHPolicyFile() sshVerboseLogging = envknob.RegisterBool("TS_DEBUG_SSH_VLOG")
debugIgnoreTailnetSSHPolicy = envknob.SSHIgnoreTailnetPolicy()
sshVerboseLogging = envknob.Bool("TS_DEBUG_SSH_VLOG")
) )
type server struct { type server struct {
@ -384,9 +382,10 @@ func (c *conn) sshPolicy() (_ *tailcfg.SSHPolicy, ok bool) {
if nm == nil { if nm == nil {
return nil, false return nil, false
} }
if pol := nm.SSHPolicy; pol != nil && !debugIgnoreTailnetSSHPolicy { if pol := nm.SSHPolicy; pol != nil && !envknob.SSHIgnoreTailnetPolicy() {
return pol, true return pol, true
} }
debugPolicyFile := envknob.SSHPolicyFile()
if debugPolicyFile != "" { if debugPolicyFile != "" {
c.logf("reading debug SSH policy file: %v", debugPolicyFile) c.logf("reading debug SSH policy file: %v", debugPolicyFile)
f, err := os.ReadFile(debugPolicyFile) f, err := os.ReadFile(debugPolicyFile)
@ -769,7 +768,7 @@ type sshSession struct {
} }
func (ss *sshSession) vlogf(format string, args ...interface{}) { func (ss *sshSession) vlogf(format string, args ...interface{}) {
if sshVerboseLogging { if sshVerboseLogging() {
ss.logf(format, args...) ss.logf(format, args...)
} }
} }
@ -952,7 +951,7 @@ func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *user.User) err
// functionality and support off-node streaming. // functionality and support off-node streaming.
// //
// TODO(bradfitz,maisem): move this to SSHPolicy. // TODO(bradfitz,maisem): move this to SSHPolicy.
var recordSSH = envknob.Bool("TS_DEBUG_LOG_SSH") var recordSSH = envknob.RegisterBool("TS_DEBUG_LOG_SSH")
// run is the entrypoint for a newly accepted SSH session. // run is the entrypoint for a newly accepted SSH session.
// //
@ -1092,7 +1091,7 @@ func (ss *sshSession) shouldRecord() bool {
// TODO(bradfitz,maisem): make configurable on SSHPolicy and // TODO(bradfitz,maisem): make configurable on SSHPolicy and
// support recording non-pty stuff too. // support recording non-pty stuff too.
_, _, isPtyReq := ss.Pty() _, _, isPtyReq := ss.Pty()
return recordSSH && isPtyReq return recordSSH() && isPtyReq
} }
type sshConnInfo struct { type sshConnInfo struct {

@ -129,8 +129,6 @@ type limitData struct {
ele *list.Element // list element used to access this string in the cache ele *list.Element // list element used to access this string in the cache
} }
var disableRateLimit = envknob.String("TS_DEBUG_LOG_RATE") == "all"
// rateFree are format string substrings that are exempt from rate limiting. // rateFree are format string substrings that are exempt from rate limiting.
// Things should not be added to this unless they're already limited otherwise // Things should not be added to this unless they're already limited otherwise
// or are critical for generating important stats from the logs. // or are critical for generating important stats from the logs.
@ -156,7 +154,7 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
// timeNow is a function that returns the current time, used for calculating // timeNow is a function that returns the current time, used for calculating
// rate limits. // rate limits.
func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int, timeNow func() time.Time) Logf { func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int, timeNow func() time.Time) Logf {
if disableRateLimit { if envknob.String("TS_DEBUG_LOG_RATE") == "all" {
return logf return logf
} }
var ( var (

@ -11,28 +11,30 @@ import (
"tailscale.com/envknob" "tailscale.com/envknob"
) )
const linkDebug = true
// Various debugging and experimental tweakables, set by environment // Various debugging and experimental tweakables, set by environment
// variable. // variable.
var ( var (
// debugDisco prints verbose logs of active discovery events as // debugDisco prints verbose logs of active discovery events as
// they happen. // they happen.
debugDisco = envknob.Bool("TS_DEBUG_DISCO") debugDisco = envknob.RegisterBool("TS_DEBUG_DISCO")
// debugOmitLocalAddresses removes all local interface addresses // debugOmitLocalAddresses removes all local interface addresses
// from magicsock's discovered local endpoints. Used in some tests. // from magicsock's discovered local endpoints. Used in some tests.
debugOmitLocalAddresses = envknob.Bool("TS_DEBUG_OMIT_LOCAL_ADDRS") debugOmitLocalAddresses = envknob.RegisterBool("TS_DEBUG_OMIT_LOCAL_ADDRS")
// debugUseDerpRoute temporarily (2020-03-22) controls whether DERP // debugUseDerpRoute temporarily (2020-03-22) controls whether DERP
// reverse routing is enabled (Issue 150). // reverse routing is enabled (Issue 150).
debugUseDerpRoute = envknob.OptBool("TS_DEBUG_ENABLE_DERP_ROUTE") debugUseDerpRoute = envknob.RegisterOptBool("TS_DEBUG_ENABLE_DERP_ROUTE")
// logDerpVerbose logs all received DERP packets, including their // logDerpVerbose logs all received DERP packets, including their
// full payload. // full payload.
logDerpVerbose = envknob.Bool("TS_DEBUG_DERP") logDerpVerbose = envknob.RegisterBool("TS_DEBUG_DERP")
// debugReSTUNStopOnIdle unconditionally enables the "shut down // debugReSTUNStopOnIdle unconditionally enables the "shut down
// STUN if magicsock is idle" behavior that normally only triggers // STUN if magicsock is idle" behavior that normally only triggers
// on mobile devices, lowers the shutdown interval, and logs more // on mobile devices, lowers the shutdown interval, and logs more
// verbosely about idle measurements. // verbosely about idle measurements.
debugReSTUNStopOnIdle = envknob.Bool("TS_DEBUG_RESTUN_STOP_ON_IDLE") debugReSTUNStopOnIdle = envknob.RegisterBool("TS_DEBUG_RESTUN_STOP_ON_IDLE")
// debugAlwaysDERP disables the use of UDP, forcing all peer communication over DERP. // debugAlwaysDERP disables the use of UDP, forcing all peer communication over DERP.
debugAlwaysDERP = envknob.Bool("TS_DEBUG_ALWAYS_USE_DERP") debugAlwaysDERP = envknob.RegisterBool("TS_DEBUG_ALWAYS_USE_DERP")
) )
// inTest reports whether the running program is a test that set the // inTest reports whether the running program is a test that set the

@ -10,15 +10,15 @@ package magicsock
import "tailscale.com/types/opt" import "tailscale.com/types/opt"
// All knobs are disabled on iOS and Wasm. // All knobs are disabled on iOS and Wasm.
// Further, they're const, so the toolchain can produce smaller binaries. //
const ( // They're inlinable and the linker can deadcode that's guarded by them to make
debugDisco = false // smaller binaries.
debugOmitLocalAddresses = false func debugDisco() bool { return false }
debugUseDerpRouteEnv = "" func debugOmitLocalAddresses() bool { return false }
debugUseDerpRoute opt.Bool = "" func logDerpVerbose() bool { return false }
logDerpVerbose = false func debugReSTUNStopOnIdle() bool { return false }
debugReSTUNStopOnIdle = false func debugAlwaysDERP() bool { return false }
debugAlwaysDERP = false func debugUseDerpRouteEnv() string { return "" }
) func debugUseDerpRoute() opt.Bool { return "" }
func inTest() bool { return false } func inTest() bool { return false }

@ -74,7 +74,7 @@ const (
// useDerpRoute reports whether magicsock should enable the DERP // useDerpRoute reports whether magicsock should enable the DERP
// return path optimization (Issue 150). // return path optimization (Issue 150).
func useDerpRoute() bool { func useDerpRoute() bool {
if b, ok := debugUseDerpRoute.Get(); ok { if b, ok := debugUseDerpRoute().Get(); ok {
return b return b
} }
ob := controlclient.DERPRouteFlag() ob := controlclient.DERPRouteFlag()
@ -638,18 +638,18 @@ func (c *Conn) updateEndpoints(why string) {
// etc) // etc)
d := tstime.RandomDurationBetween(20*time.Second, 26*time.Second) d := tstime.RandomDurationBetween(20*time.Second, 26*time.Second)
if t := c.periodicReSTUNTimer; t != nil { if t := c.periodicReSTUNTimer; t != nil {
if debugReSTUNStopOnIdle { if debugReSTUNStopOnIdle() {
c.logf("resetting existing periodicSTUN to run in %v", d) c.logf("resetting existing periodicSTUN to run in %v", d)
} }
t.Reset(d) t.Reset(d)
} else { } else {
if debugReSTUNStopOnIdle { if debugReSTUNStopOnIdle() {
c.logf("scheduling periodicSTUN to run in %v", d) c.logf("scheduling periodicSTUN to run in %v", d)
} }
c.periodicReSTUNTimer = time.AfterFunc(d, c.doPeriodicSTUN) c.periodicReSTUNTimer = time.AfterFunc(d, c.doPeriodicSTUN)
} }
} else { } else {
if debugReSTUNStopOnIdle { if debugReSTUNStopOnIdle() {
c.logf("periodic STUN idle") c.logf("periodic STUN idle")
} }
c.stopPeriodicReSTUNTimerLocked() c.stopPeriodicReSTUNTimerLocked()
@ -1074,7 +1074,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, erro
return return
} }
addAddr := func(ipp netip.AddrPort, et tailcfg.EndpointType) { addAddr := func(ipp netip.AddrPort, et tailcfg.EndpointType) {
if !ipp.IsValid() || (debugOmitLocalAddresses && et == tailcfg.EndpointLocal) { if !ipp.IsValid() || (debugOmitLocalAddresses() && et == tailcfg.EndpointLocal) {
return return
} }
if _, ok := already[ipp]; !ok { if _, ok := already[ipp]; !ok {
@ -1575,7 +1575,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netip.AddrPort, d
pkt = m pkt = m
res.n = len(m.Data) res.n = len(m.Data)
res.src = m.Source res.src = m.Source
if logDerpVerbose { if logDerpVerbose() {
c.logf("magicsock: got derp-%v packet: %q", regionID, m.Data) c.logf("magicsock: got derp-%v packet: %q", regionID, m.Data)
} }
// If this is a new sender we hadn't seen before, remember it and // If this is a new sender we hadn't seen before, remember it and
@ -1826,7 +1826,7 @@ func (c *Conn) sendDiscoMessage(dst netip.AddrPort, dstKey key.NodePublic, dstDi
pkt = append(pkt, box...) pkt = append(pkt, box...)
sent, err = c.sendAddr(dst, dstKey, pkt) sent, err = c.sendAddr(dst, dstKey, pkt)
if sent { if sent {
if logLevel == discoLog || (logLevel == discoVerboseLog && debugDisco) { if logLevel == discoLog || (logLevel == discoVerboseLog && debugDisco()) {
node := "?" node := "?"
if !dstKey.IsZero() { if !dstKey.IsZero() {
node = dstKey.ShortString() node = dstKey.ShortString()
@ -1890,7 +1890,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
if c.closed { if c.closed {
return return
} }
if debugDisco { if debugDisco() {
c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString()) c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString())
} }
if c.privateKey.IsZero() { if c.privateKey.IsZero() {
@ -1899,7 +1899,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
return return
} }
if c.discoPrivate.IsZero() { if c.discoPrivate.IsZero() {
if debugDisco { if debugDisco() {
c.logf("magicsock: disco: ignoring disco-looking frame, no local key") c.logf("magicsock: disco: ignoring disco-looking frame, no local key")
} }
return return
@ -1907,7 +1907,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
if !c.peerMap.anyEndpointForDiscoKey(sender) { if !c.peerMap.anyEndpointForDiscoKey(sender) {
metricRecvDiscoBadPeer.Add(1) metricRecvDiscoBadPeer.Add(1)
if debugDisco { if debugDisco() {
c.logf("magicsock: disco: ignoring disco-looking frame, don't know endpoint for %v", sender.ShortString()) c.logf("magicsock: disco: ignoring disco-looking frame, don't know endpoint for %v", sender.ShortString())
} }
return return
@ -1931,7 +1931,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
// Don't log in normal case. Pass on to wireguard, in case // Don't log in normal case. Pass on to wireguard, in case
// it's actually a wireguard packet (super unlikely, // it's actually a wireguard packet (super unlikely,
// but). // but).
if debugDisco { if debugDisco() {
c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender) c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender)
} }
metricRecvDiscoBadKey.Add(1) metricRecvDiscoBadKey.Add(1)
@ -1939,7 +1939,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
} }
dm, err := disco.Parse(payload) dm, err := disco.Parse(payload)
if debugDisco { if debugDisco() {
c.logf("magicsock: disco: disco.Parse = %T, %v", dm, err) c.logf("magicsock: disco: disco.Parse = %T, %v", dm, err)
} }
if err != nil { if err != nil {
@ -2094,7 +2094,7 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, src netip.AddrPort, di *discoInf
return return
} }
if !likelyHeartBeat || debugDisco { if !likelyHeartBeat || debugDisco() {
pingNodeSrcStr := dstKey.ShortString() pingNodeSrcStr := dstKey.ShortString()
if numNodes > 1 { if numNodes > 1 {
pingNodeSrcStr = "[one-of-multi]" pingNodeSrcStr = "[one-of-multi]"
@ -2381,7 +2381,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
} }
ep.wgEndpoint = n.Key.UntypedHexString() ep.wgEndpoint = n.Key.UntypedHexString()
ep.initFakeUDPAddr() ep.initFakeUDPAddr()
if debugDisco { // rather than making a new knob if debugDisco() { // rather than making a new knob
c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key.ShortString(), n.DiscoKey.ShortString(), logger.ArgWriter(func(w *bufio.Writer) { c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key.ShortString(), n.DiscoKey.ShortString(), logger.ArgWriter(func(w *bufio.Writer) {
const derpPrefix = "127.3.3.40:" const derpPrefix = "127.3.3.40:"
if strings.HasPrefix(n.DERP, derpPrefix) { if strings.HasPrefix(n.DERP, derpPrefix) {
@ -2736,7 +2736,7 @@ func (c *Conn) goroutinesRunningLocked() bool {
} }
func maxIdleBeforeSTUNShutdown() time.Duration { func maxIdleBeforeSTUNShutdown() time.Duration {
if debugReSTUNStopOnIdle { if debugReSTUNStopOnIdle() {
return 45 * time.Second return 45 * time.Second
} }
return sessionActiveTimeout return sessionActiveTimeout
@ -2753,7 +2753,7 @@ func (c *Conn) shouldDoPeriodicReSTUNLocked() bool {
} }
if f := c.idleFunc; f != nil { if f := c.idleFunc; f != nil {
idleFor := f() idleFor := f()
if debugReSTUNStopOnIdle { if debugReSTUNStopOnIdle() {
c.logf("magicsock: periodicReSTUN: idle for %v", idleFor.Round(time.Second)) c.logf("magicsock: periodicReSTUN: idle for %v", idleFor.Round(time.Second))
} }
if idleFor > maxIdleBeforeSTUNShutdown() { if idleFor > maxIdleBeforeSTUNShutdown() {
@ -2834,7 +2834,7 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur
return nil return nil
} }
if debugAlwaysDERP { if debugAlwaysDERP() {
c.logf("disabled %v per TS_DEBUG_ALWAYS_USE_DERP", network) c.logf("disabled %v per TS_DEBUG_ALWAYS_USE_DERP", network)
ruc.setConnLocked(newBlockForeverConn()) ruc.setConnLocked(newBlockForeverConn())
return nil return nil
@ -3626,7 +3626,7 @@ func (de *endpoint) pingTimeout(txid stun.TxID) {
if !ok { if !ok {
return return
} }
if debugDisco || !de.bestAddr.IsValid() || mono.Now().After(de.trustBestAddrUntil) { if debugDisco() || !de.bestAddr.IsValid() || mono.Now().After(de.trustBestAddrUntil) {
de.c.logf("[v1] magicsock: disco: timeout waiting for pong %x from %v (%v, %v)", txid[:6], sp.to, de.publicKey.ShortString(), de.discoShort) de.c.logf("[v1] magicsock: disco: timeout waiting for pong %x from %v (%v, %v)", txid[:6], sp.to, de.publicKey.ShortString(), de.discoShort)
} }
de.removeSentPingLocked(txid, sp) de.removeSentPingLocked(txid, sp)

@ -28,7 +28,7 @@ const (
) )
// Enable/disable using raw sockets to receive disco traffic. // Enable/disable using raw sockets to receive disco traffic.
var debugDisableRawDisco = envknob.Bool("TS_DEBUG_DISABLE_RAW_DISCO") var debugDisableRawDisco = envknob.RegisterBool("TS_DEBUG_DISABLE_RAW_DISCO")
// These are our BPF filters that we use for testing packets. // These are our BPF filters that we use for testing packets.
var ( var (
@ -125,7 +125,7 @@ var (
// and BPF filter. // and BPF filter.
// https://github.com/tailscale/tailscale/issues/3824 // https://github.com/tailscale/tailscale/issues/3824
func (c *Conn) listenRawDisco(family string) (io.Closer, error) { func (c *Conn) listenRawDisco(family string) (io.Closer, error) {
if debugDisableRawDisco { if debugDisableRawDisco() {
return nil, errors.New("raw disco listening disabled by debug flag") return nil, errors.New("raw disco listening disabled by debug flag")
} }

@ -20,7 +20,7 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
var debugNetlinkMessages = envknob.Bool("TS_DEBUG_NETLINK") var debugNetlinkMessages = envknob.RegisterBool("TS_DEBUG_NETLINK")
// unspecifiedMessage is a minimal message implementation that should not // unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better // be ignored. In general, OS-specific implementations should use better
@ -96,7 +96,7 @@ func (c *nlConn) Receive() (message, error) {
nip := netaddrIP(rmsg.Attributes.Address) nip := netaddrIP(rmsg.Attributes.Address)
if debugNetlinkMessages { if debugNetlinkMessages() {
typ := "RTM_NEWADDR" typ := "RTM_NEWADDR"
if msg.Header.Type == unix.RTM_DELADDR { if msg.Header.Type == unix.RTM_DELADDR {
typ = "RTM_DELADDR" typ = "RTM_DELADDR"
@ -125,7 +125,7 @@ func (c *nlConn) Receive() (message, error) {
} }
if addrs[nip] { if addrs[nip] {
if debugNetlinkMessages { if debugNetlinkMessages() {
c.logf("ignored duplicate RTM_NEWADDR for %s", nip) c.logf("ignored duplicate RTM_NEWADDR for %s", nip)
} }
return ignoreMessage{}, nil return ignoreMessage{}, nil
@ -147,7 +147,7 @@ func (c *nlConn) Receive() (message, error) {
Addr: nip, Addr: nip,
Delete: msg.Header.Type == unix.RTM_DELADDR, Delete: msg.Header.Type == unix.RTM_DELADDR,
} }
if debugNetlinkMessages { if debugNetlinkMessages() {
c.logf("%+v", nam) c.logf("%+v", nam)
} }
return nam, nil return nam, nil
@ -169,7 +169,7 @@ func (c *nlConn) Receive() (message, error) {
(rmsg.Attributes.Table == 255 || rmsg.Attributes.Table == 254) && (rmsg.Attributes.Table == 255 || rmsg.Attributes.Table == 254) &&
(dst.Addr().IsMulticast() || dst.Addr().IsLinkLocalUnicast()) { (dst.Addr().IsMulticast() || dst.Addr().IsLinkLocalUnicast()) {
if debugNetlinkMessages { if debugNetlinkMessages() {
c.logf("%s ignored", typeStr) c.logf("%s ignored", typeStr)
} }
@ -202,7 +202,7 @@ func (c *nlConn) Receive() (message, error) {
Dst: dst, Dst: dst,
Gateway: gw, Gateway: gw,
} }
if debugNetlinkMessages { if debugNetlinkMessages() {
c.logf("%+v", nrm) c.logf("%+v", nrm)
} }
return nrm, nil return nrm, nil
@ -225,7 +225,7 @@ func (c *nlConn) Receive() (message, error) {
table: rmsg.Table, table: rmsg.Table,
priority: rmsg.Attributes.Priority, priority: rmsg.Attributes.Priority,
} }
if debugNetlinkMessages { if debugNetlinkMessages() {
c.logf("%+v", rdm) c.logf("%+v", rdm)
} }
return rdm, nil return rdm, nil

@ -55,7 +55,7 @@ import (
const debugPackets = false const debugPackets = false
var debugNetstack = envknob.Bool("TS_DEBUG_NETSTACK") var debugNetstack = envknob.RegisterBool("TS_DEBUG_NETSTACK")
var ( var (
magicDNSIP = tsaddr.TailscaleServiceIP() magicDNSIP = tsaddr.TailscaleServiceIP()
@ -638,7 +638,7 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte) {
} }
return return
} }
if debugNetstack { if debugNetstack() {
ns.logf("exec pinged %v in %v", dstIP, time.Since(t0)) ns.logf("exec pinged %v in %v", dstIP, time.Since(t0))
} }
if err := ns.tundev.InjectOutbound(pingResPkt); err != nil { if err := ns.tundev.InjectOutbound(pingResPkt); err != nil {
@ -718,7 +718,7 @@ func netaddrIPFromNetstackIP(s tcpip.Address) netip.Addr {
func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) { func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
reqDetails := r.ID() reqDetails := r.ID()
if debugNetstack { if debugNetstack() {
ns.logf("[v2] TCP ForwarderRequest: %s", stringifyTEI(reqDetails)) ns.logf("[v2] TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
} }
clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress) clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress)
@ -849,7 +849,7 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip.Addr, wq *waiter.Queue, dialAddr netip.AddrPort) (handled bool) { func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip.Addr, wq *waiter.Queue, dialAddr netip.AddrPort) (handled bool) {
dialAddrStr := dialAddr.String() dialAddrStr := dialAddr.String()
if debugNetstack { if debugNetstack() {
ns.logf("[v2] netstack: forwarding incoming connection to %s", dialAddrStr) ns.logf("[v2] netstack: forwarding incoming connection to %s", dialAddrStr)
} }
@ -866,7 +866,7 @@ func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip
go func() { go func() {
select { select {
case <-notifyCh: case <-notifyCh:
if debugNetstack { if debugNetstack() {
ns.logf("[v2] netstack: forwardTCP notifyCh fired; canceling context for %s", dialAddrStr) ns.logf("[v2] netstack: forwardTCP notifyCh fired; canceling context for %s", dialAddrStr)
} }
case <-done: case <-done:
@ -919,7 +919,7 @@ func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip
func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) { func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {
sess := r.ID() sess := r.ID()
if debugNetstack { if debugNetstack() {
ns.logf("[v2] UDP ForwarderRequest: %v", stringifyTEI(sess)) ns.logf("[v2] UDP ForwarderRequest: %v", stringifyTEI(sess))
} }
var wq waiter.Queue var wq waiter.Queue
@ -995,7 +995,7 @@ func (ns *Impl) handleMagicDNSUDP(srcAddr netip.AddrPort, c *gonet.UDPConn) {
// proxy to it directly. // proxy to it directly.
func (ns *Impl) forwardUDP(client *gonet.UDPConn, wq *waiter.Queue, clientAddr, dstAddr netip.AddrPort) { func (ns *Impl) forwardUDP(client *gonet.UDPConn, wq *waiter.Queue, clientAddr, dstAddr netip.AddrPort) {
port, srcPort := dstAddr.Port(), clientAddr.Port() port, srcPort := dstAddr.Port(), clientAddr.Port()
if debugNetstack { if debugNetstack() {
ns.logf("[v2] netstack: forwarding incoming UDP connection on port %v", port) ns.logf("[v2] netstack: forwarding incoming UDP connection on port %v", port)
} }
@ -1071,7 +1071,7 @@ func (ns *Impl) forwardUDP(client *gonet.UDPConn, wq *waiter.Queue, clientAddr,
} }
func startPacketCopy(ctx context.Context, cancel context.CancelFunc, dst net.PacketConn, dstAddr net.Addr, src net.PacketConn, logf logger.Logf, extend func()) { func startPacketCopy(ctx context.Context, cancel context.CancelFunc, dst net.PacketConn, dstAddr net.Addr, src net.PacketConn, logf logger.Logf, extend func()) {
if debugNetstack { if debugNetstack() {
logf("[v2] netstack: startPacketCopy to %v (%T) from %T", dstAddr, dst, src) logf("[v2] netstack: startPacketCopy to %v (%T) from %T", dstAddr, dst, src)
} }
go func() { go func() {
@ -1096,7 +1096,7 @@ func startPacketCopy(ctx context.Context, cancel context.CancelFunc, dst net.Pac
} }
return return
} }
if debugNetstack { if debugNetstack() {
logf("[v2] wrote UDP packet %s -> %s", srcAddr, dstAddr) logf("[v2] wrote UDP packet %s -> %s", srcAddr, dstAddr)
} }
extend() extend()

@ -316,7 +316,7 @@ func useAmbientCaps() bool {
return distro.DSMVersion() >= 7 return distro.DSMVersion() >= 7
} }
var forceIPCommand = envknob.Bool("TS_DEBUG_USE_IP_COMMAND") var forceIPCommand = envknob.RegisterBool("TS_DEBUG_USE_IP_COMMAND")
// useIPCommand reports whether r should use the "ip" command (or its // useIPCommand reports whether r should use the "ip" command (or its
// fake commandRunner for tests) instead of netlink. // fake commandRunner for tests) instead of netlink.
@ -324,7 +324,7 @@ func (r *linuxRouter) useIPCommand() bool {
if r.cmd == nil { if r.cmd == nil {
panic("invalid init") panic("invalid init")
} }
if forceIPCommand { if forceIPCommand() {
return true return true
} }
// In the future we might need to fall back to using the "ip" // In the future we might need to fall back to using the "ip"

@ -534,7 +534,7 @@ func (e *userspaceEngine) pollResolver() {
} }
} }
var debugTrimWireguard = envknob.OptBool("TS_DEBUG_TRIM_WIREGUARD") var debugTrimWireguard = envknob.RegisterOptBool("TS_DEBUG_TRIM_WIREGUARD")
// forceFullWireguardConfig reports whether we should give wireguard our full // forceFullWireguardConfig reports whether we should give wireguard our full
// network map, even for inactive peers. // network map, even for inactive peers.
@ -550,7 +550,7 @@ var debugTrimWireguard = envknob.OptBool("TS_DEBUG_TRIM_WIREGUARD")
// with these knobs in place. // with these knobs in place.
func forceFullWireguardConfig(numPeers int) bool { func forceFullWireguardConfig(numPeers int) bool {
// Did the user explicitly enable trimmming via the environment variable knob? // Did the user explicitly enable trimmming via the environment variable knob?
if b, ok := debugTrimWireguard.Get(); ok { if b, ok := debugTrimWireguard().Get(); ok {
return !b return !b
} }
if opt := controlclient.TrimWGConfig(); opt != "" { if opt := controlclient.TrimWGConfig(); opt != "" {

Loading…
Cancel
Save