From 1a79abf5fb0358242e77e8dedfd699a4d7e4e6c5 Mon Sep 17 00:00:00 2001 From: Nick O'Neill Date: Wed, 14 Jan 2026 14:19:17 -0800 Subject: [PATCH 001/202] VERSION.txt: this is v1.95.0 (#18414) Signed-off-by: Nick O'Neill --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 95784efddbc41..55f6ae93382d1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.93.0 +1.95.0 From 54d77898da17c0051384c63ebcab9831586dbd48 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Wed, 14 Jan 2026 14:33:33 -0700 Subject: [PATCH 002/202] tool/gocross: update gocross-wrapper.ps1 to use absolute path for resolving tar gocross-wrapper.ps1 is written to use the version of tar that ships with Windows; we want to avoid conflicts with any other tar on the PATH, such ones installed by MSYS and/or Cygwin. Updates https://github.com/tailscale/corp/issues/29940 Signed-off-by: Aaron Klotz --- tool/gocross/gocross-wrapper.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tool/gocross/gocross-wrapper.ps1 b/tool/gocross/gocross-wrapper.ps1 index fe0b46996204d..324b220c8319d 100644 --- a/tool/gocross/gocross-wrapper.ps1 +++ b/tool/gocross/gocross-wrapper.ps1 @@ -114,7 +114,12 @@ $bootstrapScriptBlock = { New-Item -Force -Path $toolchain -ItemType Directory | Out-Null Start-ChildScope -ScriptBlock { Set-Location -LiteralPath $toolchain - tar --strip-components=1 -xf "$toolchain.tar.gz" + + # Using an absolute path to the tar that ships with Windows + # to avoid conflicts with others (eg msys2). + $system32 = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::System) + $tar = Join-Path $system32 'tar.exe' -Resolve + & $tar --strip-components=1 -xf "$toolchain.tar.gz" if ($LASTEXITCODE -ne 0) { throw "tar failed with exit code $LASTEXITCODE" } From 1cc6f3282e547fd38d77bf90e61d3ac5ebd62420 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Fri, 16 Jan 2026 13:29:12 +0000 Subject: [PATCH 003/202] k8s-operator,kube: allowing k8s api request events to be enabled via grants (#18393) Updates #35796 Signed-off-by: chaosinthecrd --- cmd/vet/jsontags_allowlist | 2 + k8s-operator/api-proxy/proxy.go | 126 +++++++++++++------- k8s-operator/api-proxy/proxy_events_test.go | 18 ++- k8s-operator/api-proxy/proxy_test.go | 10 +- k8s-operator/sessionrecording/hijacker.go | 2 + kube/kubetypes/grants.go | 10 +- 6 files changed, 118 insertions(+), 50 deletions(-) diff --git a/cmd/vet/jsontags_allowlist b/cmd/vet/jsontags_allowlist index 9526f44ef9d9a..b9f91d562cb46 100644 --- a/cmd/vet/jsontags_allowlist +++ b/cmd/vet/jsontags_allowlist @@ -221,6 +221,8 @@ OmitEmptyUnsupportedInV2 tailscale.com/kube/kubeapi.Event.Count OmitEmptyUnsupportedInV2 tailscale.com/kube/kubeapi.ObjectMeta.Generation OmitEmptyUnsupportedInV2 tailscale.com/kube/kubeapi.Status.Code OmitEmptyUnsupportedInV2 tailscale.com/kube/kubetypes.KubernetesCapRule.EnforceRecorder +OmitEmptyUnsupportedInV2 tailscale.com/kube/kubetypes.KubernetesCapRule.EnableEvents +OmitEmptyUnsupportedInV2 tailscale.com/kube/kubetypes.KubernetesCapRule.EnableSessionRecordings OmitEmptyUnsupportedInV2 tailscale.com/log/sockstatlog.event.IsCellularInterface OmitEmptyUnsupportedInV2 tailscale.com/sessionrecording.CastHeader.SrcNodeUserID OmitEmptyUnsupportedInV2 tailscale.com/sessionrecording.Source.NodeUserID diff --git a/k8s-operator/api-proxy/proxy.go b/k8s-operator/api-proxy/proxy.go index 762a52f1fdbfc..fcd57cd17e006 100644 --- a/k8s-operator/api-proxy/proxy.go +++ b/k8s-operator/api-proxy/proxy.go @@ -46,6 +46,10 @@ var ( whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil)) ) +const ( + eventsEnabledVar = "TS_EXPERIMENTAL_KUBE_API_EVENTS" +) + // NewAPIServerProxy creates a new APIServerProxy that's ready to start once Run // is called. No network traffic will flow until Run is called. // @@ -97,7 +101,7 @@ func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsn upstreamURL: u, ts: ts, sendEventFunc: sessionrecording.SendEvent, - eventsEnabled: envknob.Bool("TS_EXPERIMENTAL_KUBE_API_EVENTS"), + eventsEnabled: envknob.Bool(eventsEnabledVar), } ap.rp = &httputil.ReverseProxy{ Rewrite: func(pr *httputil.ProxyRequest) { @@ -128,6 +132,10 @@ func (ap *APIServerProxy) Run(ctx context.Context) error { TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } + if ap.eventsEnabled { + ap.log.Warnf("DEPRECATED: %q environment variable is deprecated, and will be removed in v1.96. See documentation for more detail.", eventsEnabledVar) + } + mode := "noauth" if ap.authMode { mode = "auth" @@ -196,6 +204,7 @@ type APIServerProxy struct { sendEventFunc func(ap netip.AddrPort, event io.Reader, dial netx.DialFunc) error // Flag used to enable sending API requests as events to tsrecorder. + // Deprecated: events are now set via ACLs (see https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file) eventsEnabled bool } @@ -207,13 +216,34 @@ func (ap *APIServerProxy) serveDefault(w http.ResponseWriter, r *http.Request) { return } - if err = ap.recordRequestAsEvent(r, who); err != nil { - msg := fmt.Sprintf("error recording Kubernetes API request: %v", err) - ap.log.Errorf(msg) - http.Error(w, msg, http.StatusBadGateway) + c, err := determineRecorderConfig(who) + if err != nil { + ap.log.Errorf("error trying to determine whether the kubernetes api request %q needs to be recorded: %v", r.URL.String(), err) return } + if c.failOpen && len(c.recorderAddresses) == 0 { // will not record + ap.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who))) + return + } + ksr.CounterKubernetesAPIRequestEventsAttempted.Add(1) // at this point we know that users intended for this request to be recorded + if !c.failOpen && len(c.recorderAddresses) == 0 { + msg := fmt.Sprintf("forbidden: api request %q must be recorded, but no recorders are available.", r.URL.String()) + ap.log.Error(msg) + http.Error(w, msg, http.StatusForbidden) + return + } + + // NOTE: (ChaosInTheCRD) ap.eventsEnabled deprecated, remove in v1.96 + if c.enableEvents || ap.eventsEnabled { + if err = ap.recordRequestAsEvent(r, who, c.recorderAddresses, c.failOpen); err != nil { + msg := fmt.Sprintf("error recording Kubernetes API request: %v", err) + ap.log.Errorf(msg) + http.Error(w, msg, http.StatusBadGateway) + return + } + } + counterNumRequestsProxied.Add(1) ap.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who))) @@ -256,35 +286,45 @@ func (ap *APIServerProxy) sessionForProto(w http.ResponseWriter, r *http.Request return } - if err = ap.recordRequestAsEvent(r, who); err != nil { - msg := fmt.Sprintf("error recording Kubernetes API request: %v", err) - ap.log.Errorf(msg) - http.Error(w, msg, http.StatusBadGateway) - return - } - counterNumRequestsProxied.Add(1) - failOpen, addrs, err := determineRecorderConfig(who) + c, err := determineRecorderConfig(who) if err != nil { ap.log.Errorf("error trying to determine whether the 'kubectl %s' session needs to be recorded: %v", sessionType, err) return } - if failOpen && len(addrs) == 0 { // will not record + + if c.failOpen && len(c.recorderAddresses) == 0 { // will not record ap.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who))) return } - ksr.CounterSessionRecordingsAttempted.Add(1) // at this point we know that users intended for this session to be recorded - if !failOpen && len(addrs) == 0 { + ksr.CounterKubernetesAPIRequestEventsAttempted.Add(1) // at this point we know that users intended for this request to be recorded + if !c.failOpen && len(c.recorderAddresses) == 0 { msg := fmt.Sprintf("forbidden: 'kubectl %s' session must be recorded, but no recorders are available.", sessionType) ap.log.Error(msg) http.Error(w, msg, http.StatusForbidden) return } + // NOTE: (ChaosInTheCRD) ap.eventsEnabled deprecated, remove in v1.96 + if c.enableEvents || ap.eventsEnabled { + if err = ap.recordRequestAsEvent(r, who, c.recorderAddresses, c.failOpen); err != nil { + msg := fmt.Sprintf("error recording Kubernetes API request: %v", err) + ap.log.Errorf(msg) + http.Error(w, msg, http.StatusBadGateway) + return + } + } + + if !c.enableRecordings { + ap.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who))) + return + } + ksr.CounterSessionRecordingsAttempted.Add(1) // at this point we know that users intended for this session to be recorded + wantsHeader := upgradeHeaderForProto[proto] if h := r.Header.Get(upgradeHeaderKey); h != wantsHeader { msg := fmt.Sprintf("[unexpected] unable to verify that streaming protocol is %s, wants Upgrade header %q, got: %q", proto, wantsHeader, h) - if failOpen { + if c.failOpen { msg = msg + "; failure mode is 'fail open'; continuing session without recording." ap.log.Warn(msg) ap.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who))) @@ -303,8 +343,8 @@ func (ap *APIServerProxy) sessionForProto(w http.ResponseWriter, r *http.Request SessionType: sessionType, TS: ap.ts, Who: who, - Addrs: addrs, - FailOpen: failOpen, + Addrs: c.recorderAddresses, + FailOpen: c.failOpen, Pod: r.PathValue(podNameKey), Namespace: r.PathValue(namespaceNameKey), Log: ap.log, @@ -314,21 +354,9 @@ func (ap *APIServerProxy) sessionForProto(w http.ResponseWriter, r *http.Request ap.rp.ServeHTTP(h, r.WithContext(whoIsKey.WithValue(r.Context(), who))) } -func (ap *APIServerProxy) recordRequestAsEvent(req *http.Request, who *apitype.WhoIsResponse) error { - if !ap.eventsEnabled { - return nil - } - - failOpen, addrs, err := determineRecorderConfig(who) - if err != nil { - return fmt.Errorf("error trying to determine whether the kubernetes api request needs to be recorded: %w", err) - } +func (ap *APIServerProxy) recordRequestAsEvent(req *http.Request, who *apitype.WhoIsResponse, addrs []netip.AddrPort, failOpen bool) error { if len(addrs) == 0 { - if failOpen { - return nil - } else { - return fmt.Errorf("forbidden: kubernetes api request must be recorded, but no recorders are available") - } + return fmt.Errorf("no recorder addresses specified") } factory := &request.RequestInfoFactory{ @@ -537,20 +565,30 @@ func addImpersonationHeaders(r *http.Request, log *zap.SugaredLogger) error { return nil } +type recorderConfig struct { + failOpen bool + enableEvents bool + enableRecordings bool + recorderAddresses []netip.AddrPort +} + // determineRecorderConfig determines recorder config from requester's peer // capabilities. Determines whether a 'kubectl exec' session from this requester // needs to be recorded and what recorders the recording should be sent to. -func determineRecorderConfig(who *apitype.WhoIsResponse) (failOpen bool, recorderAddresses []netip.AddrPort, _ error) { +func determineRecorderConfig(who *apitype.WhoIsResponse) (c recorderConfig, _ error) { if who == nil { - return false, nil, errors.New("[unexpected] cannot determine caller") + return c, errors.New("[unexpected] cannot determine caller") } - failOpen = true + + c.failOpen = true + c.enableEvents = false + c.enableRecordings = true rules, err := tailcfg.UnmarshalCapJSON[kubetypes.KubernetesCapRule](who.CapMap, tailcfg.PeerCapabilityKubernetes) if err != nil { - return failOpen, nil, fmt.Errorf("failed to unmarshal Kubernetes capability: %w", err) + return c, fmt.Errorf("failed to unmarshal Kubernetes capability: %w", err) } if len(rules) == 0 { - return failOpen, nil, nil + return c, nil } for _, rule := range rules { @@ -559,13 +597,19 @@ func determineRecorderConfig(who *apitype.WhoIsResponse) (failOpen bool, recorde // recorders behind those addrs are online - else we // spend 30s trying to reach a recorder whose tailscale // status is offline. - recorderAddresses = append(recorderAddresses, rule.RecorderAddrs...) + c.recorderAddresses = append(c.recorderAddresses, rule.RecorderAddrs...) } if rule.EnforceRecorder { - failOpen = false + c.failOpen = false + } + if rule.EnableEvents { + c.enableEvents = true + } + if rule.EnableSessionRecordings { + c.enableRecordings = true } } - return failOpen, recorderAddresses, nil + return c, nil } var upgradeHeaderForProto = map[ksr.Protocol]string{ diff --git a/k8s-operator/api-proxy/proxy_events_test.go b/k8s-operator/api-proxy/proxy_events_test.go index 8bcf484368a35..e35be33a0e734 100644 --- a/k8s-operator/api-proxy/proxy_events_test.go +++ b/k8s-operator/api-proxy/proxy_events_test.go @@ -61,7 +61,6 @@ func TestRecordRequestAsEvent(t *testing.T) { log: zl.Sugar(), ts: &tsnet.Server{}, sendEventFunc: sender.Send, - eventsEnabled: true, } defaultWho := &apitype.WhoIsResponse{ @@ -76,7 +75,7 @@ func TestRecordRequestAsEvent(t *testing.T) { CapMap: tailcfg.PeerCapMap{ tailcfg.PeerCapabilityKubernetes: []tailcfg.RawMessage{ tailcfg.RawMessage(`{"recorderAddrs":["127.0.0.1:1234"]}`), - tailcfg.RawMessage(`{"enforceRecorder": true}`), + tailcfg.RawMessage(`{"enforceRecorder": true, "enableEvents": true}`), }, }, } @@ -310,6 +309,7 @@ func TestRecordRequestAsEvent(t *testing.T) { CapMap: tailcfg.PeerCapMap{ tailcfg.PeerCapabilityKubernetes: []tailcfg.RawMessage{ tailcfg.RawMessage(`{"recorderAddrs":["127.0.0.1:1234", "127.0.0.1:5678"]}`), + tailcfg.RawMessage(`{"enforceRecorder": true, "enableEvents": true}`), }, }, }, @@ -398,6 +398,7 @@ func TestRecordRequestAsEvent(t *testing.T) { }, setupSender: func() { sender.Reset() }, wantNumCalls: 0, + wantErr: true, }, { name: "error-sending", @@ -510,8 +511,19 @@ func TestRecordRequestAsEvent(t *testing.T) { tt.setupSender() req := tt.req() - err := ap.recordRequestAsEvent(req, tt.who) + c, err := determineRecorderConfig(tt.who) + if err != nil { + t.Fatalf("error trying to determine whether the kubernetes api request %q needs to be recorded: %v", req.URL.String(), err) + return + } + + if !c.enableEvents && tt.wantEvent != nil { + t.Errorf("expected event but events not enabled in CapMap. Want: %#v", tt.wantEvent) + return + } + + err = ap.recordRequestAsEvent(req, tt.who, c.recorderAddresses, c.failOpen) if (err != nil) != tt.wantErr { t.Fatalf("recordRequestAsEvent() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/k8s-operator/api-proxy/proxy_test.go b/k8s-operator/api-proxy/proxy_test.go index 71bf65648931c..14e6554236234 100644 --- a/k8s-operator/api-proxy/proxy_test.go +++ b/k8s-operator/api-proxy/proxy_test.go @@ -166,15 +166,15 @@ func Test_determineRecorderConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotFailOpen, gotRecorderAddresses, err := determineRecorderConfig(tt.who) + c, err := determineRecorderConfig(tt.who) if err != nil { t.Fatalf("unexpected error: %v", err) } - if gotFailOpen != tt.wantFailOpen { - t.Errorf("determineRecorderConfig() gotFailOpen = %v, want %v", gotFailOpen, tt.wantFailOpen) + if c.failOpen != tt.wantFailOpen { + t.Errorf("determineRecorderConfig() gotFailOpen = %v, want %v", c.failOpen, tt.wantFailOpen) } - if !reflect.DeepEqual(gotRecorderAddresses, tt.wantRecorderAddresses) { - t.Errorf("determineRecorderConfig() gotRecorderAddresses = %v, want %v", gotRecorderAddresses, tt.wantRecorderAddresses) + if !reflect.DeepEqual(c.recorderAddresses, tt.wantRecorderAddresses) { + t.Errorf("determineRecorderConfig() gotRecorderAddresses = %v, want %v", c.recorderAddresses, tt.wantRecorderAddresses) } }) } diff --git a/k8s-operator/sessionrecording/hijacker.go b/k8s-operator/sessionrecording/hijacker.go index 2d6c94710e866..7345a407c8faa 100644 --- a/k8s-operator/sessionrecording/hijacker.go +++ b/k8s-operator/sessionrecording/hijacker.go @@ -52,6 +52,8 @@ var ( // CounterSessionRecordingsAttempted counts the number of session recording attempts. CounterSessionRecordingsAttempted = clientmetric.NewCounter("k8s_auth_proxy_session_recordings_attempted") + CounterKubernetesAPIRequestEventsAttempted = clientmetric.NewCounter("k8s_auth_proxy_api_request_event_recording_attempted") + // counterSessionRecordingsUploaded counts the number of successfully uploaded session recordings. counterSessionRecordingsUploaded = clientmetric.NewCounter("k8s_auth_proxy_session_recordings_uploaded") ) diff --git a/kube/kubetypes/grants.go b/kube/kubetypes/grants.go index 4dc278ff14d4c..d293ae5792e41 100644 --- a/kube/kubetypes/grants.go +++ b/kube/kubetypes/grants.go @@ -38,8 +38,16 @@ type KubernetesCapRule struct { // Default is to fail open. // The field name matches `EnforceRecorder` field with equal semantics for Tailscale SSH // session recorder. - // https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-acls + // https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file EnforceRecorder bool `json:"enforceRecorder,omitempty"` + // EnableEvents defines whether kubectl API request events (beta) + // should be recorded or not. + // https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file + EnableEvents bool `json:"enableEvents,omitempty"` + // EnableSessionRecordings defines whether kubectl sessions + // (e.g., exec, attach) should be recorded or not. + // https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file + EnableSessionRecordings bool `json:"enableSessionRecordings,omitempty"` } // ImpersonateRule defines how a request from the tailnet identity matching From 1478028591283068717b68bbf4ab90ccf01457ab Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Fri, 16 Jan 2026 11:21:17 -0600 Subject: [PATCH 004/202] docs/windows/policy: use a separate value to track the configuration state of EnableDNSRegistration Policy editors, such as gpedit.msc and gpme.msc, rely on both the presence and the value of the registry value to determine whether a policy is enabled. Unless an enabledValue is specified explicitly, it defaults to REG_DWORD 1. Therefore, we cannot rely on the same registry value to track the policy configuration state when it is already used by a policy option, such as a dropdown. Otherwise, while the policy setting will be written and function correctly, it will appear as Not Configured in the policy editor due to the value mismatch (for example, REG_SZ "always" vs REG_DWORD 1). In this PR, we update the DNSRegistration policy setting to use the DNSRegistrationConfigured registry value for tracking. This change has no effect on the client side and exists solely to satisfy ADMX and policy editor requirements. Updates #14917 Signed-off-by: Nick Khyl --- docs/windows/policy/tailscale.admx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/windows/policy/tailscale.admx b/docs/windows/policy/tailscale.admx index 7bd31ac9c597d..7cc174b06a8cd 100644 --- a/docs/windows/policy/tailscale.admx +++ b/docs/windows/policy/tailscale.admx @@ -231,7 +231,7 @@ never - + From 643e91f2eb8b3e3bc7a12b3e79a2df580684e3d0 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Fri, 16 Jan 2026 14:53:23 -0500 Subject: [PATCH 005/202] net/netmon: move TailscaleInterfaceIndex out of netmon.State (#18428) fixes tailscale/tailscale#18418 Both Serve and PeerAPI broke when we moved the TailscaleInterfaceName into State, which is updated asynchronously and may not be available when we configure the listeners. This extracts the explicit interface name property from netmon.State and adds as a static struct with getters that have proper error handling. The bug is only found in sandboxed Darwin clients, where we need to know the Tailscale interface details in order to set up the listeners correctly (they must bind to our interface explicitly to escape the network sandboxing that is applied by NECP). Currently set only sandboxed macOS and Plan9 set this but it will also be useful on Windows to simplify interface filtering in netns. Signed-off-by: Jonathan Nobels --- cmd/tailscaled/tailscaled.go | 3 +- ipn/ipnlocal/local.go | 13 ++++- ipn/ipnlocal/peerapi.go | 9 +++ ipn/ipnlocal/serve.go | 17 ++++-- net/netmon/interfaces.go | 103 +++++++++++++++++++++++++++++++++++ net/netmon/loghelper_test.go | 2 +- net/netmon/netmon.go | 49 +++++------------ net/netmon/netmon_test.go | 16 +++++- net/netmon/state.go | 32 +++++------ 9 files changed, 184 insertions(+), 60 deletions(-) create mode 100644 net/netmon/interfaces.go diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 7c19ebb422b87..410ae00bc0716 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -799,8 +799,9 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo if runtime.GOOS == "plan9" { // TODO(bradfitz): why don't we do this on all platforms? + // TODO(barnstar): we do it on sandboxed darwin now // We should. Doing it just on plan9 for now conservatively. - sys.NetMon.Get().SetTailscaleInterfaceName(devName) + netmon.SetTailscaleInterfaceProps(devName, 0) } r, err := router.New(logf, dev, sys.NetMon.Get(), sys.HealthTracker.Get(), sys.Bus.Get()) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 44b12826bcc50..066d8ba0a58ef 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -565,7 +565,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo // Call our linkChange code once with the current state. // Following changes are triggered via the eventbus. - cd, err := netmon.NewChangeDelta(nil, b.interfaceState, false, netMon.TailscaleInterfaceName(), false) + cd, err := netmon.NewChangeDelta(nil, b.interfaceState, false, false) if err != nil { b.logf("[unexpected] setting initial netmon state failed: %v", err) } else { @@ -5321,7 +5321,11 @@ func (b *LocalBackend) initPeerAPIListenerLocked() { var err error skipListen := i > 0 && isNetstack if !skipListen { - ln, err = ps.listen(a.Addr(), b.interfaceState.TailscaleInterfaceIndex) + // We don't care about the error here. Not all platforms set this. + // If ps.listen needs it, it will check for zero values and error out. + tsIfIndex, _ := netmon.TailscaleInterfaceIndex() + + ln, err = ps.listen(a.Addr(), tsIfIndex) if err != nil { if peerAPIListenAsync { b.logf("[v1] possibly transient peerapi listen(%q) error, will try again on linkChange: %v", a.Addr(), err) @@ -5329,6 +5333,11 @@ func (b *LocalBackend) initPeerAPIListenerLocked() { // ("peerAPIListeners too low"). continue } + // Sandboxed macOS specifically requires the interface index to be non-zero. + if version.IsSandboxedMacOS() && tsIfIndex == 0 { + b.logf("[v1] peerapi listen(%q) error: interface index is 0 on darwin; try restarting tailscaled", a.Addr()) + continue + } b.logf("[unexpected] peerapi listen(%q) error: %v", a.Addr(), err) continue } diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 20c61c0ec6c52..318d9bf6bb72f 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -41,6 +41,8 @@ import ( "tailscale.com/wgengine/filter" ) +// initListenConfig, if non-nil, is called during peerAPIListener setup. It is used only +// on iOS and macOS to set socket options to bind the listener to the Tailscale interface. var initListenConfig func(config *net.ListenConfig, addr netip.Addr, tunIfIndex int) error // peerDNSQueryHandler is implemented by tsdns.Resolver. @@ -69,6 +71,13 @@ func (s *peerAPIServer) listen(ip netip.Addr, tunIfIndex int) (ln net.Listener, // On iOS/macOS, this sets the lc.Control hook to // setsockopt the interface index to bind to, to get // out of the network sandbox. + + // A zero tunIfIndex is invalid for peerapi. A zero value will not get us + // out of the network sandbox. Caller should log and retry. + if tunIfIndex == 0 { + return nil, fmt.Errorf("peerapi: cannot listen on %s with tunIfIndex 0", ipStr) + } + if err := initListenConfig(&lc, ip, tunIfIndex); err != nil { return nil, err } diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index 4d6055bbd81e8..9fca3db69b540 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -36,6 +36,7 @@ import ( "github.com/pires/go-proxyproto" "go4.org/mem" "tailscale.com/ipn" + "tailscale.com/net/netmon" "tailscale.com/net/netutil" "tailscale.com/syncs" "tailscale.com/tailcfg" @@ -166,16 +167,24 @@ func (s *localListener) Run() { var lc net.ListenConfig if initListenConfig != nil { + ifIndex, err := netmon.TailscaleInterfaceIndex() + if err != nil { + s.logf("localListener failed to get Tailscale interface index %v, backing off: %v", s.ap, err) + s.bo.BackOff(s.ctx, err) + continue + } + // On macOS, this sets the lc.Control hook to // setsockopt the interface index to bind to. This is - // required by the network sandbox to allow binding to - // a specific interface. Without this hook, the system - // chooses a default interface to bind to. - if err := initListenConfig(&lc, ip, s.b.interfaceState.TailscaleInterfaceIndex); err != nil { + // required by the network sandbox which will not automatically + // bind to the tailscale interface to prevent routing loops. + // Explicit binding allows us to bypass that restriction. + if err := initListenConfig(&lc, ip, ifIndex); err != nil { s.logf("localListener failed to init listen config %v, backing off: %v", s.ap, err) s.bo.BackOff(s.ctx, err) continue } + // On macOS (AppStore or macsys) and if we're binding to a privileged port, if version.IsSandboxedMacOS() && s.ap.Port() < 1024 { // On macOS, we need to bind to ""/all-interfaces due to diff --git a/net/netmon/interfaces.go b/net/netmon/interfaces.go new file mode 100644 index 0000000000000..4cf93973c6473 --- /dev/null +++ b/net/netmon/interfaces.go @@ -0,0 +1,103 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package netmon + +import ( + "errors" + "net" + + "tailscale.com/syncs" +) + +type ifProps struct { + mu syncs.Mutex + name string // interface name, if known/set + index int // interface index, if known/set +} + +// tsIfProps tracks the properties (name and index) of the tailscale interface. +// There is only one tailscale interface per tailscaled instance. +var tsIfProps ifProps + +func (p *ifProps) tsIfName() string { + p.mu.Lock() + defer p.mu.Unlock() + return p.name +} + +func (p *ifProps) tsIfIndex() int { + p.mu.Lock() + defer p.mu.Unlock() + return p.index +} + +func (p *ifProps) set(ifName string, ifIndex int) { + p.mu.Lock() + defer p.mu.Unlock() + p.name = ifName + p.index = ifIndex +} + +// TODO (barnstar): This doesn't need the Monitor receiver anymore but we're +// keeping it for API compatibility to avoid a breaking change.  This can be +// removed when the various clients have switched to SetTailscaleInterfaceProps +func (m *Monitor) SetTailscaleInterfaceName(ifName string) { + SetTailscaleInterfaceProps(ifName, 0) +} + +// SetTailscaleInterfaceProps sets the name of the Tailscale interface and +// its index for use by various listeners/dialers. If the index is zero, +// an attempt will be made to look it up by name. This makes no attempt +// to validate that the interface exists at the time of calling. +// +// If this method is called, it is the responsibility of the caller to +// update the interface name and index if they change. +// +// This should be called as early as possible during tailscaled startup. +func SetTailscaleInterfaceProps(ifName string, ifIndex int) { + if ifIndex != 0 { + tsIfProps.set(ifName, ifIndex) + return + } + + ifaces, err := net.Interfaces() + if err != nil { + return + } + + for _, iface := range ifaces { + if iface.Name == ifName { + ifIndex = iface.Index + break + } + } + + tsIfProps.set(ifName, ifIndex) +} + +// TailscaleInterfaceName returns the name of the Tailscale interface. +// For example, "tailscale0", "tun0", "utun3", etc or an error if unset. +// +// Callers must handle errors, as the Tailscale interface +// name may not be set in some environments. +func TailscaleInterfaceName() (string, error) { + name := tsIfProps.tsIfName() + if name == "" { + return "", errors.New("Tailscale interface name not set") + } + return name, nil +} + +// TailscaleInterfaceIndex returns the index of the Tailscale interface or +// an error if unset. +// +// Callers must handle errors, as the Tailscale interface +// index may not be set in some environments. +func TailscaleInterfaceIndex() (int, error) { + index := tsIfProps.tsIfIndex() + if index == 0 { + return 0, errors.New("Tailscale interface index not set") + } + return index, nil +} diff --git a/net/netmon/loghelper_test.go b/net/netmon/loghelper_test.go index 968c2fd41d950..468a12505f322 100644 --- a/net/netmon/loghelper_test.go +++ b/net/netmon/loghelper_test.go @@ -64,7 +64,7 @@ func syncTestLinkChangeLogLimiter(t *testing.T) { // InjectEvent doesn't work because it's not a major event, so we // instead inject the event ourselves. injector := eventbustest.NewInjector(t, bus) - cd, err := NewChangeDelta(nil, &State{}, true, "tailscale0", true) + cd, err := NewChangeDelta(nil, &State{}, true, true) if err != nil { t.Fatal(err) } diff --git a/net/netmon/netmon.go b/net/netmon/netmon.go index 49fb426ae1993..e18bc392dd196 100644 --- a/net/netmon/netmon.go +++ b/net/netmon/netmon.go @@ -78,8 +78,7 @@ type Monitor struct { goroutines sync.WaitGroup wallTimer *time.Timer // nil until Started; re-armed AfterFunc per tick lastWall time.Time - timeJumped bool // whether we need to send a changed=true after a big time jump - tsIfName string // tailscale interface name, if known/set ("tailscale0", "utun3", ...) + timeJumped bool // whether we need to send a changed=true after a big time jump } // ChangeFunc is a callback function registered with Monitor that's called when the @@ -103,10 +102,6 @@ type ChangeDelta struct { // come out of sleep. TimeJumped bool - // The tailscale interface name, e.g. "tailscale0", "utun3", etc. Not all - // platforms know this or set it. Copied from netmon.Monitor.tsIfName. - TailscaleIfaceName string - DefaultRouteInterface string // Computed Fields @@ -134,12 +129,11 @@ func (cd *ChangeDelta) CurrentState() *State { // NewChangeDelta builds a ChangeDelta and eagerly computes the cached fields. // forceViability, if true, forces DefaultInterfaceMaybeViable to be true regardless of the // actual state of the default interface. This is useful in testing. -func NewChangeDelta(old, new *State, timeJumped bool, tsIfName string, forceViability bool) (*ChangeDelta, error) { +func NewChangeDelta(old, new *State, timeJumped bool, forceViability bool) (*ChangeDelta, error) { cd := ChangeDelta{ - old: old, - new: new, - TimeJumped: timeJumped, - TailscaleIfaceName: tsIfName, + old: old, + new: new, + TimeJumped: timeJumped, } if cd.new == nil { @@ -162,8 +156,10 @@ func NewChangeDelta(old, new *State, timeJumped bool, tsIfName string, forceViab cd.DefaultRouteInterface = new.DefaultRouteInterface defIf := new.Interface[cd.DefaultRouteInterface] + tsIfName, err := TailscaleInterfaceName() + // The default interface is not viable if it is down or it is the Tailscale interface itself. - if !forceViability && (!defIf.IsUp() || cd.DefaultRouteInterface == tsIfName) { + if !forceViability && (!defIf.IsUp() || (err == nil && cd.DefaultRouteInterface == tsIfName)) { cd.DefaultInterfaceMaybeViable = false } else { cd.DefaultInterfaceMaybeViable = true @@ -223,10 +219,11 @@ func (cd *ChangeDelta) isInterestingInterfaceChange() bool { } // Compare interfaces in both directions. Old to new and new to old. + tsIfName, ifNameErr := TailscaleInterfaceName() for iname, oldInterface := range cd.old.Interface { - if iname == cd.TailscaleIfaceName { - // Ignore changes in the Tailscale interface itself. + if ifNameErr == nil && iname == tsIfName { + // Ignore changes in the Tailscale interface itself continue } oldIps := filterRoutableIPs(cd.old.InterfaceIPs[iname]) @@ -259,7 +256,8 @@ func (cd *ChangeDelta) isInterestingInterfaceChange() bool { } for iname, newInterface := range cd.new.Interface { - if iname == cd.TailscaleIfaceName { + if ifNameErr == nil && iname == tsIfName { + // Ignore changes in the Tailscale interface itself continue } newIps := filterRoutableIPs(cd.new.InterfaceIPs[iname]) @@ -360,24 +358,7 @@ func (m *Monitor) InterfaceState() *State { } func (m *Monitor) interfaceStateUncached() (*State, error) { - return getState(m.tsIfName) -} - -// SetTailscaleInterfaceName sets the name of the Tailscale interface. For -// example, "tailscale0", "tun0", "utun3", etc. -// -// This must be called only early in tailscaled startup before the monitor is -// used. -func (m *Monitor) SetTailscaleInterfaceName(ifName string) { - m.mu.Lock() - defer m.mu.Unlock() - m.tsIfName = ifName -} - -func (m *Monitor) TailscaleInterfaceName() string { - m.mu.Lock() - defer m.mu.Unlock() - return m.tsIfName + return getState(tsIfProps.tsIfName()) } // GatewayAndSelfIP returns the current network's default gateway, and @@ -598,7 +579,7 @@ func (m *Monitor) handlePotentialChange(newState *State, forceCallbacks bool) { return } - delta, err := NewChangeDelta(oldState, newState, timeJumped, m.tsIfName, false) + delta, err := NewChangeDelta(oldState, newState, timeJumped, false) if err != nil { m.logf("[unexpected] error creating ChangeDelta: %v", err) return diff --git a/net/netmon/netmon_test.go b/net/netmon/netmon_test.go index 8fbf512ddb50f..50519b4a9c531 100644 --- a/net/netmon/netmon_test.go +++ b/net/netmon/netmon_test.go @@ -159,7 +159,7 @@ func TestMonitorMode(t *testing.T) { // tests (*ChangeDelta).RebindRequired func TestRebindRequired(t *testing.T) { - // s1 cannot be nil by definition + // s1 must not be nil by definition tests := []struct { name string s1, s2 *State @@ -478,9 +478,11 @@ func TestRebindRequired(t *testing.T) { withIsInterestingInterface(t, func(ni Interface, pfxs []netip.Prefix) bool { return !strings.HasPrefix(ni.Name, "boring") }) + saveAndRestoreTailscaleIfaceProps(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Populate dummy interfaces where missing. for _, s := range []*State{tt.s1, tt.s2} { if s == nil { @@ -495,7 +497,8 @@ func TestRebindRequired(t *testing.T) { } } - cd, err := NewChangeDelta(tt.s1, tt.s2, false, tt.tsIfName, true) + SetTailscaleInterfaceProps(tt.tsIfName, 1) + cd, err := NewChangeDelta(tt.s1, tt.s2, false, true) if err != nil { t.Fatalf("NewChangeDelta error: %v", err) } @@ -507,6 +510,15 @@ func TestRebindRequired(t *testing.T) { } } +func saveAndRestoreTailscaleIfaceProps(t *testing.T) { + t.Helper() + index, _ := TailscaleInterfaceIndex() + name, _ := TailscaleInterfaceName() + t.Cleanup(func() { + SetTailscaleInterfaceProps(name, index) + }) +} + func withIsInterestingInterface(t *testing.T, fn func(Interface, []netip.Prefix) bool) { t.Helper() old := IsInterestingInterface diff --git a/net/netmon/state.go b/net/netmon/state.go index aefbbb22d2830..79dd8a01ba9e1 100644 --- a/net/netmon/state.go +++ b/net/netmon/state.go @@ -287,9 +287,6 @@ type State struct { // PAC is the URL to the Proxy Autoconfig URL, if applicable. PAC string - - // TailscaleInterfaceIndex is the index of the Tailscale interface - TailscaleInterfaceIndex int } func (s *State) String() string { @@ -473,15 +470,22 @@ func hasTailscaleIP(pfxs []netip.Prefix) bool { } func isTailscaleInterface(name string, ips []netip.Prefix) bool { + // Sandboxed macOS and Plan9 (and anything else that explicitly calls SetTailscaleInterfaceProps). + tsIfName, err := TailscaleInterfaceName() + if err == nil { + // If we've been told the Tailscale interface name, use that. + return name == tsIfName + } + + // The sandboxed app should (as of 1.92) set the tun interface name via SetTailscaleInterfaceProps + // early in the startup process. The non-sandboxed app does not. + // TODO (barnstar): If Wireguard created the tun device on darwin, it should know the name and it should + // be explicitly set instead checking addresses here. if runtime.GOOS == "darwin" && strings.HasPrefix(name, "utun") && hasTailscaleIP(ips) { - // On macOS in the sandboxed app (at least as of - // 2021-02-25), we often see two utun devices - // (e.g. utun4 and utun7) with the same IPv4 and IPv6 - // addresses. Just remove all utun devices with - // Tailscale IPs until we know what's happening with - // macOS NetworkExtensions and utun devices. return true } + + // Windows, Linux... return name == "Tailscale" || // as it is on Windows strings.HasPrefix(name, "tailscale") // TODO: use --tun flag value, etc; see TODO in method doc } @@ -505,18 +509,15 @@ func getState(optTSInterfaceName string) (*State, error) { s.Interface[ni.Name] = ni s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...) - // Skip uninteresting interfaces. + // Skip uninteresting interfaces if IsInterestingInterface != nil && !IsInterestingInterface(ni, pfxs) { return } - if isTailscaleInterface(ni.Name, pfxs) { - s.TailscaleInterfaceIndex = ni.Index - } - if !ifUp || isTSInterfaceName || isTailscaleInterface(ni.Name, pfxs) { return } + for _, pfx := range pfxs { if pfx.Addr().IsLoopback() { continue @@ -803,8 +804,7 @@ func (m *Monitor) HasCGNATInterface() (bool, error) { hasCGNATInterface := false cgnatRange := tsaddr.CGNATRange() err := ForeachInterface(func(i Interface, pfxs []netip.Prefix) { - isTSInterfaceName := m.tsIfName != "" && i.Name == m.tsIfName - if hasCGNATInterface || !i.IsUp() || isTSInterfaceName || isTailscaleInterface(i.Name, pfxs) { + if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) { return } for _, pfx := range pfxs { From 1b88e93ff5e6f984f52bbdbedad45db7287619fd Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Tue, 13 Jan 2026 14:26:20 -0700 Subject: [PATCH 006/202] ipn/ipnlocal: allow retrieval of serve config ETags from local API This change adds API to ipn.LocalBackend to retrieve the ETag when querying for the current serve config. This allows consumers of ipn.LocalBackend.SetServeConfig to utilize the concurrency control offered by ETags. Previous to this change, utilizing serve config ETags required copying the local backend's internal ETag calcuation. The local API server was previously copying the local backend's ETag calculation as described above. With this change, the local API server now uses the new ETag retrieval function instead. Serve config ETags are therefore now opaque to clients, in line with best practices. Fixes tailscale/corp#35857 Signed-off-by: Harry Harpham --- ipn/ipnlocal/serve.go | 35 +++++++++++++++++++++++++--------- ipn/ipnlocal/serve_test.go | 39 +++++++++++++++++--------------------- ipn/localapi/serve.go | 10 +++++----- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index 9fca3db69b540..a857147e1adab 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -302,6 +302,15 @@ func (b *LocalBackend) updateServeTCPPortNetMapAddrListenersLocked(ports []uint1 } } +func generateServeConfigETag(sc ipn.ServeConfigView) (string, error) { + j, err := json.Marshal(sc) + if err != nil { + return "", fmt.Errorf("encoding config: %w", err) + } + sum := sha256.Sum256(j) + return hex.EncodeToString(sum[:]), nil +} + // SetServeConfig establishes or replaces the current serve config. // ETag is an optional parameter to enforce Optimistic Concurrency Control. // If it is an empty string, then the config will be overwritten. @@ -336,17 +345,11 @@ func (b *LocalBackend) setServeConfigLocked(config *ipn.ServeConfig, etag string // not changed from the last config. prevConfig := b.serveConfig if etag != "" { - // Note that we marshal b.serveConfig - // and not use b.lastServeConfJSON as that might - // be a Go nil value, which produces a different - // checksum from a JSON "null" value. - prevBytes, err := json.Marshal(prevConfig) + prevETag, err := generateServeConfigETag(prevConfig) if err != nil { - return fmt.Errorf("error encoding previous config: %w", err) + return fmt.Errorf("generating ETag for previous config: %w", err) } - sum := sha256.Sum256(prevBytes) - previousEtag := hex.EncodeToString(sum[:]) - if etag != previousEtag { + if etag != prevETag { return ErrETagMismatch } } @@ -401,6 +404,20 @@ func (b *LocalBackend) ServeConfig() ipn.ServeConfigView { return b.serveConfig } +// ServeConfigETag provides a view of the current serve mappings and an ETag, +// which can later be provided to [LocalBackend.SetServeConfig] to implement +// Optimistic Concurrency Control. +// +// If serving is not configured, the returned view is not Valid. +func (b *LocalBackend) ServeConfigETag() (scv ipn.ServeConfigView, etag string, err error) { + sc := b.ServeConfig() + etag, err = generateServeConfigETag(sc) + if err != nil { + return ipn.ServeConfigView{}, "", fmt.Errorf("generating ETag: %w", err) + } + return sc, etag, nil +} + // DeleteForegroundSession deletes a ServeConfig's foreground session // in the LocalBackend if it exists. It also ensures check, delete, and // set operations happen within the same mutex lock to avoid any races. diff --git a/ipn/ipnlocal/serve_test.go b/ipn/ipnlocal/serve_test.go index 6ee2181a0aaa2..0892545cceec8 100644 --- a/ipn/ipnlocal/serve_test.go +++ b/ipn/ipnlocal/serve_test.go @@ -9,9 +9,7 @@ import ( "bytes" "cmp" "context" - "crypto/sha256" "crypto/tls" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -222,16 +220,6 @@ func TestGetServeHandler(t *testing.T) { } } -func getEtag(t *testing.T, b any) string { - t.Helper() - bts, err := json.Marshal(b) - if err != nil { - t.Fatal(err) - } - sum := sha256.Sum256(bts) - return hex.EncodeToString(sum[:]) -} - // TestServeConfigForeground tests the inter-dependency // between a ServeConfig and a WatchIPNBus: // 1. Creating a WatchIPNBus returns a sessionID, that @@ -544,8 +532,14 @@ func TestServeConfigServices(t *testing.T) { func TestServeConfigETag(t *testing.T) { b := newTestBackend(t) - // a nil config with initial etag should succeed - err := b.SetServeConfig(nil, getEtag(t, nil)) + // the etag should be valid even when there is no config + _, emptyStateETag, err := b.ServeConfigETag() + if err != nil { + t.Fatal(err) + } + + // a nil config with the empty-state etag should succeed + err = b.SetServeConfig(nil, emptyStateETag) if err != nil { t.Fatal(err) } @@ -556,7 +550,7 @@ func TestServeConfigETag(t *testing.T) { t.Fatal("expected an error but got nil") } - // a new config with no etag should succeed + // a new config with the empty-state etag should succeed conf := &ipn.ServeConfig{ Web: map[ipn.HostPort]*ipn.WebServerConfig{ "example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ @@ -564,15 +558,14 @@ func TestServeConfigETag(t *testing.T) { }}, }, } - err = b.SetServeConfig(conf, getEtag(t, nil)) + err = b.SetServeConfig(conf, emptyStateETag) if err != nil { t.Fatal(err) } - confView := b.ServeConfig() - etag := getEtag(t, confView) - if etag == "" { - t.Fatal("expected to get an etag but got an empty string") + confView, etag, err := b.ServeConfigETag() + if err != nil { + t.Fatal(err) } conf = confView.AsStruct() mak.Set(&conf.AllowFunnel, "example.ts.net:443", true) @@ -596,8 +589,10 @@ func TestServeConfigETag(t *testing.T) { } // replacing an existing config with the new etag should succeed - newCfg := b.ServeConfig() - etag = getEtag(t, newCfg) + _, etag, err = b.ServeConfigETag() + if err != nil { + t.Fatal(err) + } err = b.SetServeConfig(nil, etag) if err != nil { t.Fatal(err) diff --git a/ipn/localapi/serve.go b/ipn/localapi/serve.go index 56c8b486cf93c..efbbde06ff954 100644 --- a/ipn/localapi/serve.go +++ b/ipn/localapi/serve.go @@ -6,8 +6,6 @@ package localapi import ( - "crypto/sha256" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -31,14 +29,16 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) { http.Error(w, "serve config denied", http.StatusForbidden) return } - config := h.b.ServeConfig() + config, etag, err := h.b.ServeConfigETag() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } bts, err := json.Marshal(config) if err != nil { http.Error(w, "error encoding config: "+err.Error(), http.StatusInternalServerError) return } - sum := sha256.Sum256(bts) - etag := hex.EncodeToString(sum[:]) w.Header().Set("Etag", etag) w.Header().Set("Content-Type", "application/json") w.Write(bts) From 3840183be9d0494291ebfaf352b7b1e02a6c26ad Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Tue, 13 Jan 2026 14:36:12 -0700 Subject: [PATCH 007/202] tsnet: add support for Services This change allows tsnet nodes to act as Service hosts by adding a new function, tsnet.Server.ListenService. Invoking this function will advertise the node as a host for the Service and create a listener to receive traffic for the Service. Fixes #17697 Fixes tailscale/corp#27200 Signed-off-by: Harry Harpham --- ipn/ipnlocal/cert.go | 19 + ipn/ipnlocal/local.go | 4 + .../example/tsnet-services/tsnet-services.go | 82 ++++ ...snet_listen_service_multiple_ports_test.go | 69 +++ tsnet/example_tsnet_test.go | 55 +++ tsnet/tsnet.go | 274 +++++++++++- tsnet/tsnet_test.go | 423 +++++++++++++++++- tstest/integration/testcontrol/testcontrol.go | 92 +++- 8 files changed, 983 insertions(+), 35 deletions(-) create mode 100644 tsnet/example/tsnet-services/tsnet-services.go create mode 100644 tsnet/example_tsnet_listen_service_multiple_ports_test.go diff --git a/ipn/ipnlocal/cert.go b/ipn/ipnlocal/cert.go index 8804fcb5ce2e8..b389c93e7e971 100644 --- a/ipn/ipnlocal/cert.go +++ b/ipn/ipnlocal/cert.go @@ -107,6 +107,15 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK // If a cert is expired, or expires sooner than minValidity, it will be renewed // synchronously. Otherwise it will be renewed asynchronously. func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string, minValidity time.Duration) (*TLSCertKeyPair, error) { + b.mu.Lock() + getCertForTest := b.getCertForTest + b.mu.Unlock() + + if getCertForTest != nil { + testenv.AssertInTest() + return getCertForTest(domain) + } + if !validLookingCertDomain(domain) { return nil, errors.New("invalid domain") } @@ -303,6 +312,16 @@ func (b *LocalBackend) getCertStore() (certStore, error) { return certFileStore{dir: dir, testRoots: testX509Roots}, nil } +// ConfigureCertsForTest sets a certificate retrieval function to be used by +// this local backend, skipping the usual ACME certificate registration. Should +// only be used in tests. +func (b *LocalBackend) ConfigureCertsForTest(getCert func(hostname string) (*TLSCertKeyPair, error)) { + testenv.AssertInTest() + b.mu.Lock() + b.getCertForTest = getCert + b.mu.Unlock() +} + // certFileStore implements certStore by storing the cert & key files in the named directory. type certFileStore struct { dir string diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 066d8ba0a58ef..2f05a4dbbc9ba 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -399,6 +399,10 @@ type LocalBackend struct { // hardwareAttested is whether backend should use a hardware-backed key to // bind the node identity to this device. hardwareAttested atomic.Bool + + // getCertForTest is used to retrieve TLS certificates in tests. + // See [LocalBackend.ConfigureCertsForTest]. + getCertForTest func(hostname string) (*TLSCertKeyPair, error) } // SetHardwareAttested enables hardware attestation key signatures in map diff --git a/tsnet/example/tsnet-services/tsnet-services.go b/tsnet/example/tsnet-services/tsnet-services.go new file mode 100644 index 0000000000000..6eb1a76ab5f5c --- /dev/null +++ b/tsnet/example/tsnet-services/tsnet-services.go @@ -0,0 +1,82 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// The tsnet-services example demonstrates how to use tsnet with Services. +// +// To run this example yourself: +// +// 1. Add access controls which (i) define a new ACL tag, (ii) allow the demo +// node to host the Service, and (iii) allow peers on the tailnet to reach +// the Service. A sample ACL policy is provided below. +// +// 2. [Generate an auth key] using the Tailscale admin panel. When doing so, add +// your new tag to your key (Service hosts must be tagged nodes). +// +// 3. [Define a Service]. For the purposes of this demo, it must be defined to +// listen on TCP port 443. Note that you only need to follow Step 1 in the +// linked document. +// +// 4. Run the demo on the command line: +// +// TS_AUTHKEY= go run tsnet-services.go -service +// +// The following is a sample ACL policy for step 1: +// +// "tagOwners": { +// "tag:tsnet-demo-host": ["autogroup:member"], +// }, +// "autoApprovers": { +// "services": { +// "svc:tsnet-demo": ["tag:tsnet-demo-host"], +// }, +// }, +// "grants": [ +// "src": ["*"], +// "dst": ["svc:tsnet-demo"], +// "ip": ["*"], +// ], +// +// [Define a Service]: https://tailscale.com/kb/1552/tailscale-services#step-1-define-a-tailscale-service +// [Generate an auth key]: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + + "tailscale.com/tsnet" +) + +var ( + svcName = flag.String("service", "", "the name of your Service, e.g. svc:tsnet-demo") +) + +func main() { + flag.Parse() + if *svcName == "" { + log.Fatal("a Service name must be provided") + } + + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService(*svcName, tsnet.ServiceModeHTTP{ + HTTPS: true, + Port: 443, + }) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("Listening on https://%v\n", ln.FQDN) + + err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "

Hello, tailnet!

") + })) + log.Fatal(err) +} diff --git a/tsnet/example_tsnet_listen_service_multiple_ports_test.go b/tsnet/example_tsnet_listen_service_multiple_ports_test.go new file mode 100644 index 0000000000000..04781c2b20d16 --- /dev/null +++ b/tsnet/example_tsnet_listen_service_multiple_ports_test.go @@ -0,0 +1,69 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package tsnet_test + +import ( + "fmt" + "log" + "net/http" + _ "net/http/pprof" + "strings" + + "tailscale.com/tsnet" +) + +// This example function is in a separate file for the "net/http/pprof" import. + +// ExampleServer_ListenService_multiplePorts demonstrates how to advertise a +// Service on multiple ports. In this example, we run an HTTPS server on 443 and +// an HTTP server handling pprof requests to the same runtime on 6060. +func ExampleServer_ListenService_multiplePorts() { + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ + HTTPS: true, + Port: 443, + }) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + pprofLn, err := s.ListenService("svc:my-service", tsnet.ServiceModeTCP{ + Port: 6060, + }) + if err != nil { + log.Fatal(err) + } + defer pprofLn.Close() + + go func() { + log.Printf("Listening for pprof requests on http://%v:%d\n", pprofLn.FQDN, 6060) + + handler := func(w http.ResponseWriter, r *http.Request) { + // The pprof listener is separate from our main server, so we can + // allow users to leave off the /debug/pprof prefix. We'll just + // attach it here, then pass along to the pprof handlers, which have + // been added implicitly due to our import of net/http/pprof. + if !strings.HasPrefix("/debug/pprof", r.URL.Path) { + r.URL.Path = "/debug/pprof" + r.URL.Path + } + http.DefaultServeMux.ServeHTTP(w, r) + } + if err := http.Serve(pprofLn, http.HandlerFunc(handler)); err != nil { + log.Fatal("error serving pprof:", err) + } + }() + + log.Printf("Listening on https://%v\n", ln.FQDN) + + // Specifying a handler here means pprof endpoints will not be served by + // this server (since we are not using http.DefaultServeMux). + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "

Hello, tailnet!

") + }))) +} diff --git a/tsnet/example_tsnet_test.go b/tsnet/example_tsnet_test.go index c5a20ab77fcd5..2a3236b3b6501 100644 --- a/tsnet/example_tsnet_test.go +++ b/tsnet/example_tsnet_test.go @@ -8,6 +8,8 @@ import ( "fmt" "log" "net/http" + "net/http/httputil" + "net/url" "os" "path/filepath" @@ -200,3 +202,56 @@ func ExampleServer_ListenFunnel_funnelOnly() { fmt.Fprintln(w, "Hi there! Welcome to the tailnet!") }))) } + +// ExampleServer_ListenService demonstrates how to advertise an HTTPS Service. +func ExampleServer_ListenService() { + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ + HTTPS: true, + Port: 443, + }) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("Listening on https://%v\n", ln.FQDN) + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "

Hello, tailnet!

") + }))) +} + +// ExampleServer_ListenService_reverseProxy demonstrates how to advertise a +// Service targeting a reverse proxy. This is useful when the backing server is +// external to the tsnet application. +func ExampleServer_ListenService_reverseProxy() { + // targetAddress represents the address of the backing server. + const targetAddress = "1.2.3.4:80" + + // We will use a reverse proxy to direct traffic to the backing server. + reverseProxy := httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "http", + Host: targetAddress, + }) + + s := &tsnet.Server{ + Hostname: "tsnet-services-demo", + } + defer s.Close() + + ln, err := s.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ + HTTPS: true, + Port: 443, + }) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("Listening on https://%v\n", ln.FQDN) + log.Fatal(http.Serve(ln, reverseProxy)) +} diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 8b23b7ae3b8d3..6c840c335535e 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -52,6 +52,7 @@ import ( "tailscale.com/net/proxymux" "tailscale.com/net/socks5" "tailscale.com/net/tsdial" + "tailscale.com/tailcfg" "tailscale.com/tsd" "tailscale.com/types/bools" "tailscale.com/types/logger" @@ -166,8 +167,6 @@ type Server struct { // that the control server will allow the node to adopt that tag. AdvertiseTags []string - getCertForTesting func(*tls.ClientHelloInfo) (*tls.Certificate, error) - initOnce sync.Once initErr error lb *ipnlocal.LocalBackend @@ -1130,9 +1129,6 @@ func (s *Server) RegisterFallbackTCPHandler(cb FallbackTCPHandler) func() { // It calls GetCertificate on the localClient, passing in the ClientHelloInfo. // For testing, if s.getCertForTesting is set, it will call that instead. func (s *Server) getCert(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { - if s.getCertForTesting != nil { - return s.getCertForTesting(hi) - } lc, err := s.LocalClient() if err != nil { return nil, err @@ -1283,6 +1279,259 @@ func (s *Server) ListenFunnel(network, addr string, opts ...FunnelOption) (net.L return tls.NewListener(ln, tlsConfig), nil } +// ServiceMode defines how a Service is run. Currently supported modes are: +// - [ServiceModeTCP] +// - [ServiceModeHTTP] +// +// For more information, see [Server.ListenService]. +type ServiceMode interface { + // network is the network this Service will advertise on. Per Go convention, + // this should be lowercase, e.g. 'tcp'. + network() string +} + +// serviceModeWithPort is a convenience type to extract the port from +// ServiceMode types which have one. +type serviceModeWithPort interface { + ServiceMode + port() uint16 +} + +// ServiceModeTCP is used to configure a TCP Service via [Server.ListenService]. +type ServiceModeTCP struct { + // Port is the TCP port to advertise. If this Service needs to advertise + // multiple ports, call ListenService multiple times. + Port uint16 + + // TerminateTLS means that TLS connections will be terminated before being + // forwarded to the listener. In this case, the only server name indicator + // (SNI) permitted is the Service's fully-qualified domain name. + TerminateTLS bool + + // PROXYProtocolVersion indicates whether to send a PROXY protocol header + // before forwarding the connection to the listener and which version of the + // protocol to use. + // + // For more information, see + // https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + PROXYProtocolVersion int +} + +func (ServiceModeTCP) network() string { return "tcp" } + +func (m ServiceModeTCP) port() uint16 { return m.Port } + +// ServiceModeHTTP is used to configure an HTTP Service via +// [Server.ListenService]. +type ServiceModeHTTP struct { + // Port is the TCP port to advertise. If this Service needs to advertise + // multiple ports, call ListenService multiple times. + Port uint16 + + // HTTPS, if true, means that the listener should handle connections as + // HTTPS connections. In this case, the only server name indicator (SNI) + // permitted is the Service's fully-qualified domain name. + HTTPS bool + + // AcceptAppCaps defines the app capabilities to forward to the server. The + // keys in this map are the mount points for each set of capabilities. + // + // By example, + // + // AcceptAppCaps: map[string][]string{ + // "/": {"example.com/cap/all-paths"}, + // "/foo": {"example.com/cap/all-paths", "example.com/cap/foo"}, + // } + // + // would forward example.com/cap/all-paths to all paths on the server and + // example.com/cap/foo only to paths beginning with /foo. + // + // For more information on app capabilities, see + // https://tailscale.com/kb/1537/grants-app-capabilities + AcceptAppCaps map[string][]string + + // PROXYProtocolVersion indicates whether to send a PROXY protocol header + // before forwarding the connection to the listener and which version of the + // protocol to use. + // + // For more information, see + // https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + PROXYProtocol int +} + +func (ServiceModeHTTP) network() string { return "tcp" } + +func (m ServiceModeHTTP) port() uint16 { return m.Port } + +func (m ServiceModeHTTP) capsMap() map[string][]tailcfg.PeerCapability { + capsMap := map[string][]tailcfg.PeerCapability{} + for path, capNames := range m.AcceptAppCaps { + caps := make([]tailcfg.PeerCapability, 0, len(capNames)) + for _, c := range capNames { + caps = append(caps, tailcfg.PeerCapability(c)) + } + capsMap[path] = caps + } + return capsMap +} + +// A ServiceListener is a network listener for a Tailscale Service. For more +// information about Services, see +// https://tailscale.com/kb/1552/tailscale-services +type ServiceListener struct { + net.Listener + addr addr + + // FQDN is the fully-qualifed domain name of this Service. + FQDN string +} + +// Addr returns the listener's network address. This will be the Service's +// fully-qualified domain name (FQDN) and the port. +// +// A hostname is not truly a network address, but Services listen on multiple +// addresses (the IPv4 and IPv6 virtual IPs). +func (sl ServiceListener) Addr() net.Addr { + return sl.addr +} + +// ErrUntaggedServiceHost is returned by ListenService when run on a node +// without any ACL tags. A node must use a tag-based identity to act as a +// Service host. For more information, see: +// https://tailscale.com/kb/1552/tailscale-services#prerequisites +var ErrUntaggedServiceHost = errors.New("service hosts must be tagged nodes") + +// ListenService creates a network listener for a Tailscale Service. This will +// advertise this node as hosting the Service. Note that: +// - Approval must still be granted by an admin or by ACL auto-approval rules. +// - Service hosts must be tagged nodes. +// - A valid Service host must advertise all ports defined for the Service. +// +// To advertise a Service with multiple ports, run ListenService multiple times. +// For more information about Services, see +// https://tailscale.com/kb/1552/tailscale-services +func (s *Server) ListenService(name string, mode ServiceMode) (*ServiceListener, error) { + if err := tailcfg.ServiceName(name).Validate(); err != nil { + return nil, err + } + if mode == nil { + return nil, errors.New("mode may not be nil") + } + svcName := name + + // TODO(hwh33,tailscale/corp#35859): support TUN mode + + ctx := context.Background() + _, err := s.Up(ctx) + if err != nil { + return nil, err + } + + st := s.lb.StatusWithoutPeers() + if st.Self.Tags == nil || st.Self.Tags.Len() == 0 { + return nil, ErrUntaggedServiceHost + } + + advertisedServices := s.lb.Prefs().AdvertiseServices().AsSlice() + if !slices.Contains(advertisedServices, svcName) { + // TODO(hwh33,tailscale/corp#35860): clean these prefs up when (a) we + // exit early due to error or (b) when the returned listener is closed. + _, err = s.lb.EditPrefs(&ipn.MaskedPrefs{ + AdvertiseServicesSet: true, + Prefs: ipn.Prefs{ + AdvertiseServices: append(advertisedServices, svcName), + }, + }) + if err != nil { + return nil, fmt.Errorf("updating advertised Services: %w", err) + } + } + + srvConfig := new(ipn.ServeConfig) + sc, srvConfigETag, err := s.lb.ServeConfigETag() + if err != nil { + return nil, fmt.Errorf("fetching current serve config: %w", err) + } + if sc.Valid() { + srvConfig = sc.AsStruct() + } + + fqdn := tailcfg.ServiceName(svcName).WithoutPrefix() + "." + st.CurrentTailnet.MagicDNSSuffix + + // svcAddr is used to implement Addr() on the returned listener. + svcAddr := addr{ + network: mode.network(), + // A hostname is not a network address, but Services listen on + // multiple addresses (the IPv4 and IPv6 virtual IPs), and there's + // no clear winner here between the two. Therefore prefer the FQDN. + // + // In the case of TCP or HTTP Services, the port will be added below. + addr: fqdn, + } + if m, ok := mode.(serviceModeWithPort); ok { + if m.port() == 0 { + return nil, errors.New("must specify a port to advertise") + } + svcAddr.addr += ":" + strconv.Itoa(int(m.port())) + } + + // Start listening on a local TCP socket. + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + return nil, fmt.Errorf("starting local listener: %w", err) + } + + switch m := mode.(type) { + case ServiceModeTCP: + // Forward all connections from service-hostname:port to our socket. + srvConfig.SetTCPForwardingForService( + m.Port, ln.Addr().String(), m.TerminateTLS, + tailcfg.ServiceName(svcName), m.PROXYProtocolVersion, st.CurrentTailnet.MagicDNSSuffix) + case ServiceModeHTTP: + // For HTTP Services, proxy all connections to our socket. + mds := st.CurrentTailnet.MagicDNSSuffix + haveRootHandler := false + // We need to add a separate proxy for each mount point in the caps map. + for path, caps := range m.capsMap() { + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + h := ipn.HTTPHandler{ + AcceptAppCaps: caps, + Proxy: ln.Addr().String(), + } + if path == "/" { + haveRootHandler = true + } else { + h.Proxy += path + } + srvConfig.SetWebHandler(&h, svcName, m.Port, path, m.HTTPS, mds) + } + // We always need a root handler. + if !haveRootHandler { + h := ipn.HTTPHandler{Proxy: ln.Addr().String()} + srvConfig.SetWebHandler(&h, svcName, m.Port, "/", m.HTTPS, mds) + } + default: + ln.Close() + return nil, fmt.Errorf("unknown ServiceMode type %T", m) + } + + if err := s.lb.SetServeConfig(srvConfig, srvConfigETag); err != nil { + ln.Close() + return nil, err + } + + // TODO(hwh33,tailscale/corp#35860): clean up state (advertising prefs, + // serve config changes) when the returned listener is closed. + + return &ServiceListener{ + Listener: ln, + FQDN: fqdn, + addr: svcAddr, + }, nil +} + type listenOn string const ( @@ -1444,7 +1693,12 @@ func (ln *listener) Accept() (net.Conn, error) { } } -func (ln *listener) Addr() net.Addr { return addr{ln} } +func (ln *listener) Addr() net.Addr { + return addr{ + network: ln.keys[0].network, + addr: ln.addr, + } +} func (ln *listener) Close() error { ln.s.mu.Lock() @@ -1484,10 +1738,12 @@ func (ln *listener) handle(c net.Conn) { // Server returns the tsnet Server associated with the listener. func (ln *listener) Server() *Server { return ln.s } -type addr struct{ ln *listener } +type addr struct { + network, addr string +} -func (a addr) Network() string { return a.ln.keys[0].network } -func (a addr) String() string { return a.ln.addr } +func (a addr) Network() string { return a.network } +func (a addr) String() string { return a.addr } // cleanupListener wraps a net.Listener with a function to be run on Close. type cleanupListener struct { diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 2c8514cf42d0b..f44bacab08431 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -14,6 +14,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/json" + "encoding/pem" "errors" "flag" "fmt" @@ -28,6 +29,7 @@ import ( "path/filepath" "reflect" "runtime" + "slices" "strings" "sync" "sync/atomic" @@ -38,10 +40,12 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "golang.org/x/net/proxy" + "tailscale.com/client/local" "tailscale.com/cmd/testwrapper/flakytest" "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/store/mem" "tailscale.com/net/netns" "tailscale.com/tailcfg" @@ -51,6 +55,8 @@ import ( "tailscale.com/tstest/integration/testcontrol" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/views" + "tailscale.com/util/mak" "tailscale.com/util/must" ) @@ -136,7 +142,7 @@ func startControl(t *testing.T) (controlURL string, control *testcontrol.Server) type testCertIssuer struct { mu sync.Mutex - certs map[string]*tls.Certificate + certs map[string]ipnlocal.TLSCertKeyPair // keyed by hostname root *x509.Certificate rootKey *ecdsa.PrivateKey @@ -168,18 +174,18 @@ func newCertIssuer() *testCertIssuer { panic(err) } return &testCertIssuer{ - certs: make(map[string]*tls.Certificate), root: rootCA, rootKey: rootKey, + certs: map[string]ipnlocal.TLSCertKeyPair{}, } } -func (tci *testCertIssuer) getCert(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { +func (tci *testCertIssuer) getCert(hostname string) (*ipnlocal.TLSCertKeyPair, error) { tci.mu.Lock() defer tci.mu.Unlock() - cert, ok := tci.certs[chi.ServerName] + cert, ok := tci.certs[hostname] if ok { - return cert, nil + return &cert, nil } certPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -188,7 +194,7 @@ func (tci *testCertIssuer) getCert(chi *tls.ClientHelloInfo) (*tls.Certificate, } certTmpl := &x509.Certificate{ SerialNumber: big.NewInt(1), - DNSNames: []string{chi.ServerName}, + DNSNames: []string{hostname}, NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour), } @@ -196,12 +202,22 @@ func (tci *testCertIssuer) getCert(chi *tls.ClientHelloInfo) (*tls.Certificate, if err != nil { return nil, err } - cert = &tls.Certificate{ - Certificate: [][]byte{certDER, tci.root.Raw}, - PrivateKey: certPrivKey, + keyDER, err := x509.MarshalPKCS8PrivateKey(certPrivKey) + if err != nil { + return nil, err } - tci.certs[chi.ServerName] = cert - return cert, nil + cert = ipnlocal.TLSCertKeyPair{ + CertPEM: pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certDER, + }), + KeyPEM: pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: keyDER, + }), + } + tci.certs[hostname] = cert + return &cert, nil } func (tci *testCertIssuer) Pool() *x509.CertPool { @@ -218,12 +234,11 @@ func startServer(t *testing.T, ctx context.Context, controlURL, hostname string) tmp := filepath.Join(t.TempDir(), hostname) os.MkdirAll(tmp, 0755) s := &Server{ - Dir: tmp, - ControlURL: controlURL, - Hostname: hostname, - Store: new(mem.Store), - Ephemeral: true, - getCertForTesting: testCertRoot.getCert, + Dir: tmp, + ControlURL: controlURL, + Hostname: hostname, + Store: new(mem.Store), + Ephemeral: true, } if *verboseNodes { s.Logf = t.Logf @@ -234,6 +249,8 @@ func startServer(t *testing.T, ctx context.Context, controlURL, hostname string) if err != nil { t.Fatal(err) } + s.lb.ConfigureCertsForTest(testCertRoot.getCert) + return s, status.TailscaleIPs[0], status.Self.PublicKey } @@ -259,12 +276,11 @@ func TestDialBlocks(t *testing.T) { tmp := filepath.Join(t.TempDir(), "s2") os.MkdirAll(tmp, 0755) s2 := &Server{ - Dir: tmp, - ControlURL: controlURL, - Hostname: "s2", - Store: new(mem.Store), - Ephemeral: true, - getCertForTesting: testCertRoot.getCert, + Dir: tmp, + ControlURL: controlURL, + Hostname: "s2", + Store: new(mem.Store), + Ephemeral: true, } if *verboseNodes { s2.Logf = log.Printf @@ -842,6 +858,367 @@ func TestFunnelClose(t *testing.T) { }) } +func TestListenService(t *testing.T) { + // First test an error case which doesn't require all of the fancy setup. + t.Run("untagged_node_error", func(t *testing.T) { + ctx := t.Context() + + controlURL, _ := startControl(t) + serviceHost, _, _ := startServer(t, ctx, controlURL, "service-host") + + ln, err := serviceHost.ListenService("svc:foo", ServiceModeTCP{Port: 8080}) + if ln != nil { + ln.Close() + } + if !errors.Is(err, ErrUntaggedServiceHost) { + t.Fatalf("expected %v, got %v", ErrUntaggedServiceHost, err) + } + }) + + // Now on to the fancier tests. + + type dialFn func(context.Context, string, string) (net.Conn, error) + + // TCP helpers + acceptAndEcho := func(t *testing.T, ln net.Listener) { + t.Helper() + conn, err := ln.Accept() + if err != nil { + t.Error("accept error:", err) + return + } + defer conn.Close() + if _, err := io.Copy(conn, conn); err != nil { + t.Error("copy error:", err) + } + } + assertEcho := func(t *testing.T, conn net.Conn) { + t.Helper() + msg := "echo" + buf := make([]byte, 1024) + if _, err := conn.Write([]byte(msg)); err != nil { + t.Fatal("write failed:", err) + } + n, err := conn.Read(buf) + if err != nil { + t.Fatal("read failed:", err) + } + got := string(buf[:n]) + if got != msg { + t.Fatalf("unexpected response:\n\twant: %s\n\tgot: %s", msg, got) + } + } + + // HTTP helpers + checkAndEcho := func(t *testing.T, ln net.Listener, check func(r *http.Request)) { + t.Helper() + if check == nil { + check = func(*http.Request) {} + } + http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + check(r) + if _, err := io.Copy(w, r.Body); err != nil { + t.Error("copy error:", err) + w.WriteHeader(http.StatusInternalServerError) + } + })) + } + assertEchoHTTP := func(t *testing.T, hostname, path string, dial dialFn) { + t.Helper() + c := http.Client{ + Transport: &http.Transport{ + DialContext: dial, + }, + } + msg := "echo" + resp, err := c.Post("http://"+hostname+path, "text/plain", strings.NewReader(msg)) + if err != nil { + t.Fatal("posting request:", err) + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal("reading body:", err) + } + got := string(b) + if got != msg { + t.Fatalf("unexpected response:\n\twant: %s\n\tgot: %s", msg, got) + } + } + + tests := []struct { + name string + + // modes is used as input to [Server.ListenService]. + // + // If this slice has multiple modes, then ListenService will be invoked + // multiple times. The number of listeners provided to the run function + // (below) will always match the number of elements in this slice. + modes []ServiceMode + + extraSetup func(t *testing.T, control *testcontrol.Server) + + // run executes the test. This function does not need to close any of + // the input resources, but it should close any new resources it opens. + // listeners[i] corresponds to inputs[i]. + run func(t *testing.T, listeners []*ServiceListener, peer *Server) + }{ + { + name: "basic_TCP", + modes: []ServiceMode{ + ServiceModeTCP{Port: 99}, + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + go acceptAndEcho(t, listeners[0]) + + target := fmt.Sprintf("%s:%d", listeners[0].FQDN, 99) + conn := must.Get(peer.Dial(t.Context(), "tcp", target)) + defer conn.Close() + + assertEcho(t, conn) + }, + }, + { + name: "TLS_terminated_TCP", + modes: []ServiceMode{ + ServiceModeTCP{ + TerminateTLS: true, + Port: 443, + }, + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + go acceptAndEcho(t, listeners[0]) + + target := fmt.Sprintf("%s:%d", listeners[0].FQDN, 443) + conn := must.Get(peer.Dial(t.Context(), "tcp", target)) + defer conn.Close() + + assertEcho(t, tls.Client(conn, &tls.Config{ + ServerName: listeners[0].FQDN, + RootCAs: testCertRoot.Pool(), + })) + }, + }, + { + name: "identity_headers", + modes: []ServiceMode{ + ServiceModeHTTP{ + Port: 80, + }, + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + expectHeader := "Tailscale-User-Name" + go checkAndEcho(t, listeners[0], func(r *http.Request) { + if _, ok := r.Header[expectHeader]; !ok { + t.Error("did not see expected header:", expectHeader) + } + }) + assertEchoHTTP(t, listeners[0].FQDN, "", peer.Dial) + }, + }, + { + name: "identity_headers_TLS", + modes: []ServiceMode{ + ServiceModeHTTP{ + HTTPS: true, + Port: 80, + }, + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + expectHeader := "Tailscale-User-Name" + go checkAndEcho(t, listeners[0], func(r *http.Request) { + if _, ok := r.Header[expectHeader]; !ok { + t.Error("did not see expected header:", expectHeader) + } + }) + + dial := func(ctx context.Context, network, addr string) (net.Conn, error) { + tcpConn, err := peer.Dial(ctx, network, addr) + if err != nil { + return nil, err + } + return tls.Client(tcpConn, &tls.Config{ + ServerName: listeners[0].FQDN, + RootCAs: testCertRoot.Pool(), + }), nil + } + + assertEchoHTTP(t, listeners[0].FQDN, "", dial) + }, + }, + { + name: "app_capabilities", + modes: []ServiceMode{ + ServiceModeHTTP{ + Port: 80, + AcceptAppCaps: map[string][]string{ + "/": {"example.com/cap/all-paths"}, + "/foo": {"example.com/cap/all-paths", "example.com/cap/foo"}, + }, + }, + }, + extraSetup: func(t *testing.T, control *testcontrol.Server) { + control.SetGlobalAppCaps(tailcfg.PeerCapMap{ + "example.com/cap/all-paths": []tailcfg.RawMessage{`true`}, + "example.com/cap/foo": []tailcfg.RawMessage{`true`}, + }) + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + allPathsCap := "example.com/cap/all-paths" + fooCap := "example.com/cap/foo" + checkCaps := func(r *http.Request) { + rawCaps, ok := r.Header["Tailscale-App-Capabilities"] + if !ok { + t.Error("no app capabilities header") + return + } + if len(rawCaps) != 1 { + t.Error("expected one app capabilities header value, got", len(rawCaps)) + return + } + var caps map[string][]any + if err := json.Unmarshal([]byte(rawCaps[0]), &caps); err != nil { + t.Error("error unmarshaling app caps:", err) + return + } + if _, ok := caps[allPathsCap]; !ok { + t.Errorf("got app caps, but %v is not present; saw:\n%v", allPathsCap, caps) + } + if strings.HasPrefix(r.URL.Path, "/foo") { + if _, ok := caps[fooCap]; !ok { + t.Errorf("%v should be present for /foo request; saw:\n%v", fooCap, caps) + } + } else { + if _, ok := caps[fooCap]; ok { + t.Errorf("%v should not be present for non-/foo request; saw:\n%v", fooCap, caps) + } + } + } + + go checkAndEcho(t, listeners[0], checkCaps) + assertEchoHTTP(t, listeners[0].FQDN, "", peer.Dial) + assertEchoHTTP(t, listeners[0].FQDN, "/foo", peer.Dial) + assertEchoHTTP(t, listeners[0].FQDN, "/foo/bar", peer.Dial) + }, + }, + { + name: "multiple_ports", + modes: []ServiceMode{ + ServiceModeTCP{ + Port: 99, + }, + ServiceModeHTTP{ + Port: 80, + }, + }, + run: func(t *testing.T, listeners []*ServiceListener, peer *Server) { + go acceptAndEcho(t, listeners[0]) + + target := fmt.Sprintf("%s:%d", listeners[0].FQDN, 99) + conn := must.Get(peer.Dial(t.Context(), "tcp", target)) + defer conn.Close() + assertEcho(t, conn) + + go checkAndEcho(t, listeners[1], nil) + assertEchoHTTP(t, listeners[1].FQDN, "", peer.Dial) + }, + }, + } + + for _, tt := range tests { + // Overview: + // - start test control + // - start 2 tsnet nodes: + // one to act as Service host and a second to act as a peer client + // - configure necessary state on control mock + // - start a Service listener from the host + // - call tt.run with our test bed + // + // This ends up also testing the Service forwarding logic in + // LocalBackend, but that's useful too. + t.Run(tt.name, func(t *testing.T) { + ctx := t.Context() + + controlURL, control := startControl(t) + serviceHost, _, _ := startServer(t, ctx, controlURL, "service-host") + serviceClient, _, _ := startServer(t, ctx, controlURL, "service-client") + + const serviceName = tailcfg.ServiceName("svc:foo") + const serviceVIP = "100.11.22.33" + + // == Set up necessary state in our mock == + + // The Service host must have the 'service-host' capability, which + // is a mapping from the Service name to the Service VIP. + var serviceHostCaps map[tailcfg.ServiceName]views.Slice[netip.Addr] + mak.Set(&serviceHostCaps, serviceName, views.SliceOf([]netip.Addr{netip.MustParseAddr(serviceVIP)})) + j := must.Get(json.Marshal(serviceHostCaps)) + cm := serviceHost.lb.NetMap().SelfNode.CapMap().AsMap() + mak.Set(&cm, tailcfg.NodeAttrServiceHost, []tailcfg.RawMessage{tailcfg.RawMessage(j)}) + control.SetNodeCapMap(serviceHost.lb.NodeKey(), cm) + + // The Service host must be allowed to advertise the Service VIP. + control.SetSubnetRoutes(serviceHost.lb.NodeKey(), []netip.Prefix{ + netip.MustParsePrefix(serviceVIP + `/32`), + }) + + // The Service host must be a tagged node (any tag will do). + serviceHostNode := control.Node(serviceHost.lb.NodeKey()) + serviceHostNode.Tags = append(serviceHostNode.Tags, "some-tag") + control.UpdateNode(serviceHostNode) + + // The service client must accept routes advertised by other nodes + // (RouteAll is equivalent to --accept-routes). + must.Get(serviceClient.localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ + RouteAllSet: true, + Prefs: ipn.Prefs{ + RouteAll: true, + }, + })) + + // Set up DNS for our Service. + control.AddDNSRecords(tailcfg.DNSRecord{ + Name: serviceName.WithoutPrefix() + "." + control.MagicDNSDomain, + Value: serviceVIP, + }) + + if tt.extraSetup != nil { + tt.extraSetup(t, control) + } + + // Force netmap updates to avoid race conditions. The nodes need to + // see our control updates before we can start the test. + must.Do(control.ForceNetmapUpdate(ctx, serviceHost.lb.NodeKey())) + must.Do(control.ForceNetmapUpdate(ctx, serviceClient.lb.NodeKey())) + netmapUpToDate := func(s *Server) bool { + nm := s.lb.NetMap() + return slices.ContainsFunc(nm.DNS.ExtraRecords, func(r tailcfg.DNSRecord) bool { + return r.Value == serviceVIP + }) + } + for !netmapUpToDate(serviceClient) { + time.Sleep(10 * time.Millisecond) + } + for !netmapUpToDate(serviceHost) { + time.Sleep(10 * time.Millisecond) + } + + // == Done setting up mock state == + + // Start the Service listeners. + listeners := make([]*ServiceListener, 0, len(tt.modes)) + for _, input := range tt.modes { + ln := must.Get(serviceHost.ListenService(serviceName.String(), input)) + defer ln.Close() + listeners = append(listeners, ln) + } + + tt.run(t, listeners, serviceClient) + }) + } +} + func TestListenerClose(t *testing.T) { tstest.Shard(t) ctx := context.Background() diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go index 19964c91ff8a4..447efb0c1b15d 100644 --- a/tstest/integration/testcontrol/testcontrol.go +++ b/tstest/integration/testcontrol/testcontrol.go @@ -110,6 +110,16 @@ type Server struct { // nodeCapMaps overrides the capability map sent down to a client. nodeCapMaps map[key.NodePublic]tailcfg.NodeCapMap + // globalAppCaps configures global app capabilities, equivalent to: + // "grants": [ + // { + // "src": ["*"], + // "dst": ["*"], + // "app": + // } + // ] + globalAppCaps tailcfg.PeerCapMap + // suppressAutoMapResponses is the set of nodes that should not be sent // automatic map responses from serveMap. (They should only get manually sent ones) suppressAutoMapResponses set.Set[key.NodePublic] @@ -289,6 +299,43 @@ func (s *Server) addDebugMessage(nodeKeyDst key.NodePublic, msg any) bool { return sendUpdate(oldUpdatesCh, updateDebugInjection) } +// ForceNetmapUpdate waits for the node to get stuck in a map poll and then +// sends the current netmap (which may result in a redundant netmap). The +// intended use case is ensuring state changes propagate before running tests. +// +// This should only be called for nodes connected as streaming clients. Calling +// this with a non-streaming node will result in non-deterministic behavior. +// +// This function cannot guarantee that the node has processed the issued update, +// so tests should confirm processing by querying the node. By example: +// +// if err := s.ForceNetmapUpdate(node.Key()); err != nil { +// // handle error +// } +// for !updatesPresent(node.NetMap()) { +// time.Sleep(10 * time.Millisecond) +// } +func (s *Server) ForceNetmapUpdate(ctx context.Context, nodeKey key.NodePublic) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err := s.AwaitNodeInMapRequest(ctx, nodeKey); err != nil { + return fmt.Errorf("waiting for node to poll: %w", err) + } + mr, err := s.MapResponse(&tailcfg.MapRequest{NodeKey: nodeKey}) + if err != nil { + return fmt.Errorf("generating map response: %w", err) + } + if s.addDebugMessage(nodeKey, mr) { + return nil + } + // If we failed to send the map response, loop around and try again. + } +} + // Mark the Node key of every node as expired func (s *Server) SetExpireAllNodes(expired bool) { s.mu.Lock() @@ -531,6 +578,31 @@ func (s *Server) SetNodeCapMap(nodeKey key.NodePublic, capMap tailcfg.NodeCapMap s.updateLocked("SetNodeCapMap", s.nodeIDsLocked(0)) } +// SetGlobalAppCaps configures global app capabilities. This is equivalent to +// +// "grants": [ +// { +// "src": ["*"], +// "dst": ["*"], +// "app": +// } +// ] +func (s *Server) SetGlobalAppCaps(appCaps tailcfg.PeerCapMap) { + s.mu.Lock() + s.globalAppCaps = appCaps + s.mu.Unlock() +} + +// AddDNSRecords adds records to the server's DNS config. +func (s *Server) AddDNSRecords(records ...tailcfg.DNSRecord) { + s.mu.Lock() + defer s.mu.Unlock() + if s.DNSConfig == nil { + s.DNSConfig = new(tailcfg.DNSConfig) + } + s.DNSConfig.ExtraRecords = append(s.DNSConfig.ExtraRecords, records...) +} + // nodeIDsLocked returns the node IDs of all nodes in the server, except // for the node with the given ID. func (s *Server) nodeIDsLocked(except tailcfg.NodeID) []tailcfg.NodeID { @@ -838,6 +910,9 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key. CapMap: capMap, Capabilities: slices.Collect(maps.Keys(capMap)), } + if s.MagicDNSDomain != "" { + node.Name = node.Name + "." + s.MagicDNSDomain + "." + } s.nodes[nk] = node } requireAuth := s.RequireAuth @@ -1261,9 +1336,7 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse, dns := s.DNSConfig if dns != nil && s.MagicDNSDomain != "" { dns = dns.Clone() - dns.CertDomains = []string{ - node.Hostinfo.Hostname() + "." + s.MagicDNSDomain, - } + dns.CertDomains = append(dns.CertDomains, node.Hostinfo.Hostname()+"."+s.MagicDNSDomain) } res = &tailcfg.MapResponse{ @@ -1279,6 +1352,7 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse, s.mu.Lock() nodeMasqs := s.masquerades[node.Key] jailed := maps.Clone(s.peerIsJailed[node.Key]) + globalAppCaps := s.globalAppCaps s.mu.Unlock() for _, p := range s.AllNodes() { if p.StableID == node.StableID { @@ -1330,6 +1404,18 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse, v6Prefix, } + if globalAppCaps != nil { + res.PacketFilter = append(res.PacketFilter, tailcfg.FilterRule{ + SrcIPs: []string{"*"}, + CapGrant: []tailcfg.CapGrant{ + { + Dsts: []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}, + CapMap: globalAppCaps, + }, + }, + }) + } + // If the server is tracking TKA state, and there's a single TKA head, // add it to the MapResponse. if s.tkaStorage != nil { From 7676030355387c5cc240cdccf02f3781958f7e00 Mon Sep 17 00:00:00 2001 From: Eduardo Sorribas Date: Mon, 19 Jan 2026 15:32:13 +0100 Subject: [PATCH 008/202] net/portmapper: Stop replacing the internal port with the upnp external port (#18349) net/portmapper: Stop replacing the internal port with the upnp external port This causes the UPnP mapping to break in the next recreation of the mapping. Fixes #18348 Signed-off-by: Eduardo Sorribas --- net/portmapper/upnp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/portmapper/upnp.go b/net/portmapper/upnp.go index 34140e9473460..46d7ff70215fd 100644 --- a/net/portmapper/upnp.go +++ b/net/portmapper/upnp.go @@ -574,7 +574,7 @@ func (c *Client) getUPnPPortMapping( c.mu.Lock() defer c.mu.Unlock() c.mapping = upnp - c.localPort = externalAddrPort.Port() + c.localPort = internal.Port() return upnp.external, true } From 7213b35d85f006b662eabc2e770321ed93abfaa8 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Mon, 19 Jan 2026 16:06:40 +0000 Subject: [PATCH 009/202] k8s-operator,kube: remove enableSessionRecording from Kubernetes Cap Map (#18452) * k8s-operator,kube: removing enableSessionRecordings option. It seems like it is going to create a confusing user experience and it's going to be a very niche use case, so we have decided to defer this for now. Updates tailscale/corp#35796 Signed-off-by: chaosinthecrd * k8s-operator: adding metric for env var deprecation Signed-off-by: chaosinthecrd --------- Signed-off-by: chaosinthecrd --- k8s-operator/api-proxy/proxy.go | 14 ++++---------- kube/kubetypes/grants.go | 4 ---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/k8s-operator/api-proxy/proxy.go b/k8s-operator/api-proxy/proxy.go index fcd57cd17e006..f5f1da80f1a05 100644 --- a/k8s-operator/api-proxy/proxy.go +++ b/k8s-operator/api-proxy/proxy.go @@ -43,7 +43,9 @@ import ( var ( // counterNumRequestsproxies counts the number of API server requests proxied via this proxy. counterNumRequestsProxied = clientmetric.NewCounter("k8s_auth_proxy_requests_proxied") - whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil)) + // NOTE: adding this metric so we can keep track of users during deprecation + counterExperimentalEventsVarUsed = clientmetric.NewCounter("ts_experimental_kube_api_events_var_used") + whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil)) ) const ( @@ -133,6 +135,7 @@ func (ap *APIServerProxy) Run(ctx context.Context) error { } if ap.eventsEnabled { + counterExperimentalEventsVarUsed.Add(1) ap.log.Warnf("DEPRECATED: %q environment variable is deprecated, and will be removed in v1.96. See documentation for more detail.", eventsEnabledVar) } @@ -315,10 +318,6 @@ func (ap *APIServerProxy) sessionForProto(w http.ResponseWriter, r *http.Request } } - if !c.enableRecordings { - ap.rp.ServeHTTP(w, r.WithContext(whoIsKey.WithValue(r.Context(), who))) - return - } ksr.CounterSessionRecordingsAttempted.Add(1) // at this point we know that users intended for this session to be recorded wantsHeader := upgradeHeaderForProto[proto] @@ -568,7 +567,6 @@ func addImpersonationHeaders(r *http.Request, log *zap.SugaredLogger) error { type recorderConfig struct { failOpen bool enableEvents bool - enableRecordings bool recorderAddresses []netip.AddrPort } @@ -582,7 +580,6 @@ func determineRecorderConfig(who *apitype.WhoIsResponse) (c recorderConfig, _ er c.failOpen = true c.enableEvents = false - c.enableRecordings = true rules, err := tailcfg.UnmarshalCapJSON[kubetypes.KubernetesCapRule](who.CapMap, tailcfg.PeerCapabilityKubernetes) if err != nil { return c, fmt.Errorf("failed to unmarshal Kubernetes capability: %w", err) @@ -605,9 +602,6 @@ func determineRecorderConfig(who *apitype.WhoIsResponse) (c recorderConfig, _ er if rule.EnableEvents { c.enableEvents = true } - if rule.EnableSessionRecordings { - c.enableRecordings = true - } } return c, nil } diff --git a/kube/kubetypes/grants.go b/kube/kubetypes/grants.go index d293ae5792e41..50d7d760ff5a7 100644 --- a/kube/kubetypes/grants.go +++ b/kube/kubetypes/grants.go @@ -44,10 +44,6 @@ type KubernetesCapRule struct { // should be recorded or not. // https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file EnableEvents bool `json:"enableEvents,omitempty"` - // EnableSessionRecordings defines whether kubectl sessions - // (e.g., exec, attach) should be recorded or not. - // https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file - EnableSessionRecordings bool `json:"enableSessionRecordings,omitempty"` } // ImpersonateRule defines how a request from the tailnet identity matching From 0a5639dcc008d60fe375a6707be1fec1ffc2ec53 Mon Sep 17 00:00:00 2001 From: Alex Valiushko Date: Mon, 19 Jan 2026 18:03:30 -0800 Subject: [PATCH 010/202] net/udprelay: advertise addresses from cloud metadata service (#18368) Polls IMDS (currently only AWS) for extra IPs to advertise as udprelay. Updates #17796 Change-Id: Iaaa899ef4575dc23b09a5b713ce6693f6a6a6964 Signed-off-by: Alex Valiushko --- cmd/tailscaled/depaware.txt | 2 +- net/udprelay/server.go | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 43165ea36c6d3..da480d1a694e3 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -423,7 +423,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/cibuild from tailscale.com/health+ tailscale.com/util/clientmetric from tailscale.com/control/controlclient+ tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+ - tailscale.com/util/cloudinfo from tailscale.com/wgengine/magicsock + tailscale.com/util/cloudinfo from tailscale.com/wgengine/magicsock+ tailscale.com/util/cmpver from tailscale.com/net/dns+ tailscale.com/util/ctxkey from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/util/deephash from tailscale.com/util/syspolicy/setting diff --git a/net/udprelay/server.go b/net/udprelay/server.go index 5918863a5323f..2b6d389232832 100644 --- a/net/udprelay/server.go +++ b/net/udprelay/server.go @@ -43,6 +43,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/nettype" "tailscale.com/types/views" + "tailscale.com/util/cloudinfo" "tailscale.com/util/eventbus" "tailscale.com/util/set" "tailscale.com/util/usermetric" @@ -81,6 +82,7 @@ type Server struct { netChecker *netcheck.Client metrics *metrics netMon *netmon.Monitor + cloudInfo *cloudinfo.CloudInfo // used to query cloud metadata services mu sync.Mutex // guards the following fields macSecrets views.Slice[[blake2s.Size]byte] // [0] is most recent, max 2 elements @@ -336,6 +338,7 @@ func NewServer(logf logger.Logf, port uint16, onlyStaticAddrPorts bool, metrics onlyStaticAddrPorts: onlyStaticAddrPorts, serverEndpointByDisco: make(map[key.SortedPairOfDiscoPublic]*serverEndpoint), nextVNI: minVNI, + cloudInfo: cloudinfo.New(logf), } s.discoPublic = s.disco.Public() s.metrics = registerMetrics(metrics) @@ -402,11 +405,13 @@ func (s *Server) startPacketReaders() { func (s *Server) addrDiscoveryLoop() { defer s.wg.Done() - timer := time.NewTimer(0) // fire immediately defer timer.Stop() getAddrPorts := func() ([]netip.AddrPort, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + var addrPorts set.Set[netip.AddrPort] addrPorts.Make() @@ -425,6 +430,21 @@ func (s *Server) addrDiscoveryLoop() { } } + // Get cloud metadata service addresses. + // TODO(illotum) Same is done within magicsock, consider caching within cloudInfo + cloudIPs, err := s.cloudInfo.GetPublicIPs(ctx) + if err == nil { // Not handling the err, GetPublicIPs already printed to log. + for _, ip := range cloudIPs { + if ip.IsValid() { + if ip.Is4() { + addrPorts.Add(netip.AddrPortFrom(ip, s.uc4Port)) + } else { + addrPorts.Add(netip.AddrPortFrom(ip, s.uc6Port)) + } + } + } + } + dm := s.getDERPMap() if dm == nil { // We don't have a DERPMap which is required to dynamically @@ -434,9 +454,7 @@ func (s *Server) addrDiscoveryLoop() { } // get addrPorts as visible from DERP - netCheckerCtx, netCheckerCancel := context.WithTimeout(context.Background(), netcheck.ReportTimeout) - defer netCheckerCancel() - rep, err := s.netChecker.GetReport(netCheckerCtx, dm, &netcheck.GetReportOpts{ + rep, err := s.netChecker.GetReport(ctx, dm, &netcheck.GetReportOpts{ OnlySTUN: true, }) if err != nil { @@ -474,6 +492,8 @@ func (s *Server) addrDiscoveryLoop() { // Mirror magicsock behavior for duration between STUN. We consider // 30s a min bound for NAT timeout. timer.Reset(tstime.RandomDurationBetween(20*time.Second, 26*time.Second)) + // TODO(illotum) Pass in context bound to the [s.closeCh] lifetime, + // and do not block on getAddrPorts IO. addrPorts, err := getAddrPorts() if err != nil { s.logf("error discovering IP:port candidates: %v", err) From e30626c480535b77a5dc332cc5af2454ac8a5d77 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Tue, 20 Jan 2026 15:05:03 -0500 Subject: [PATCH 011/202] version: add support for reporting the mac variant from tailscale --version (#18462) fixes tailscale/corp#27182 tailscale version --json now includes an osVariant field that will report one of macsys, appstore or darwin. We can extend this to other platforms where tailscaled can have multiple personalities. This also adds the concept of a platform-specific callback for querying an explicit application identifier. On Apple, we can use CFBundleGetIdentifier(mainBundle) to get the bundle identifier via cgo. This removes all the ambiguity and lets us remove other less direct methods (like env vars, locations, etc). Signed-off-by: Jonathan Nobels --- version/prop.go | 81 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/version/prop.go b/version/prop.go index 0d6a5c00df375..795f3a9127be0 100644 --- a/version/prop.go +++ b/version/prop.go @@ -15,6 +15,22 @@ import ( "tailscale.com/types/lazy" ) +// AppIdentifierFn, if non-nil, is a callback function that returns the +// application identifier of the running process or an empty string if unknown. +// +// tailscale(d) implementations can set an explicit callback to return an identifier +// for the running process if such a concept exists. The Apple bundle identifier, for example. +var AppIdentifierFn func() string // or nil + +const ( + macsysBundleID = "io.tailscale.ipn.macsys" // The macsys gui app and CLI + appStoreBundleID = "io.tailscale.ipn.macos" // The App Store gui app and CLI + macsysExtBundleId = "io.tailscale.ipn.macsys.network-extension" // The macsys system extension + appStoreExtBundleId = "io.tailscale.ipn.macos.network-extension" // The App Store network extension + tvOSExtBundleId = "io.tailscale.ipn.ios.network-extension-tvos" // The tvOS network extension + iOSExtBundleId = "io.tailscale.ipn.ios.network-extension" // The iOS network extension +) + // IsMobile reports whether this is a mobile client build. func IsMobile() bool { return runtime.GOOS == "android" || runtime.GOOS == "ios" @@ -52,8 +68,8 @@ func IsMacGUIVariant() bool { // IsSandboxedMacOS reports whether this process is a sandboxed macOS // process (either the app or the extension). It is true for the Mac App Store -// and macsys (System Extension) version on macOS, and false for -// tailscaled-on-macOS. +// and macsys (only its System Extension) variants on macOS, and false for +// tailscaled and the macsys GUI process on macOS. func IsSandboxedMacOS() bool { return IsMacAppStore() || IsMacSysExt() } @@ -73,10 +89,15 @@ func IsMacSysGUI() bool { if runtime.GOOS != "darwin" { return false } - return isMacSysApp.Get(func() bool { + if AppIdentifierFn != nil { + return AppIdentifierFn() == macsysBundleID + } + + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") || - strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macsys") + strings.Contains(os.Getenv("XPC_SERVICE_NAME"), macsysBundleID) }) } @@ -90,11 +111,17 @@ func IsMacSysExt() bool { return false } return isMacSysExt.Get(func() bool { + if AppIdentifierFn != nil { + return AppIdentifierFn() == macsysExtBundleId + } + + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. exe, err := os.Executable() if err != nil { return false } - return filepath.Base(exe) == "io.tailscale.ipn.macsys.network-extension" + return filepath.Base(exe) == macsysExtBundleId }) } @@ -107,11 +134,17 @@ func IsMacAppStore() bool { return false } return isMacAppStore.Get(func() bool { + if AppIdentifierFn != nil { + id := AppIdentifierFn() + return id == appStoreBundleID || id == appStoreExtBundleId + } + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. // Both macsys and app store versions can run CLI executable with // suffix /Contents/MacOS/Tailscale. Check $HOME to filter out running // as macsys. return strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macos/") || - strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.macos") + strings.Contains(os.Getenv("XPC_SERVICE_NAME"), appStoreBundleID) }) } @@ -124,6 +157,11 @@ func IsMacAppStoreGUI() bool { return false } return isMacAppStoreGUI.Get(func() bool { + if AppIdentifierFn != nil { + return AppIdentifierFn() == appStoreBundleID + } + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. exe, err := os.Executable() if err != nil { return false @@ -143,7 +181,13 @@ func IsAppleTV() bool { return false } return isAppleTV.Get(func() bool { - return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), "io.tailscale.ipn.ios.network-extension-tvos") + if AppIdentifierFn != nil { + return AppIdentifierFn() == tvOSExtBundleId + } + + // TODO (barnstar): This check should be redundant once all relevant callers + // use AppIdentifierFn. + return strings.EqualFold(os.Getenv("XPC_SERVICE_NAME"), tvOSExtBundleId) }) } @@ -187,6 +231,23 @@ func IsUnstableBuild() bool { }) } +// osVariant returns the OS variant string for systems where we support +// multiple ways of running tailscale(d), if any. +// +// For example: "appstore", "macsys", "darwin". +func osVariant() string { + if IsMacAppStore() { + return "appstore" + } + if IsMacSys() { + return "macsys" + } + if runtime.GOOS == "darwin" { + return "darwin" + } + return "" +} + var isDev = sync.OnceValue(func() bool { return strings.Contains(Short(), "-dev") }) @@ -227,6 +288,11 @@ type Meta struct { // setting "vcs.modified" was true). GitDirty bool `json:"gitDirty,omitempty"` + // OSVariant is specific variant of the binary, if applicable. For example, + // macsys/appstore/darwin for macOS builds. Nil/empty where not supported + // or on oses without variants. + OSVariant string `json:"osVariant,omitempty"` + // ExtraGitCommit, if non-empty, is the git commit of a "supplemental" // repository at which Tailscale was built. Its format is the same as // gitCommit. @@ -264,6 +330,7 @@ func GetMeta() Meta { GitCommitTime: getEmbeddedInfo().commitTime, GitCommit: gitCommit(), GitDirty: gitDirty(), + OSVariant: osVariant(), ExtraGitCommit: extraGitCommitStamp, IsDev: isDev(), UnstableBranch: IsUnstableBuild(), From 2cb86cf65eb8908d34f93a7587807cde6ecf979c Mon Sep 17 00:00:00 2001 From: David Bond Date: Wed, 21 Jan 2026 12:35:44 +0000 Subject: [PATCH 012/202] cmd/k8s-operator,k8s-operator: Allow the use of multiple tailnets (#18344) This commit contains the implementation of multi-tailnet support within the Kubernetes Operator Each of our custom resources now expose the `spec.tailnet` field. This field is a string that must match the name of an existing `Tailnet` resource. A `Tailnet` resource looks like this: ```yaml apiVersion: tailscale.com/v1alpha1 kind: Tailnet metadata: name: example # This is the name that must be referenced by other resources spec: credentials: secretName: example-oauth ``` Each `Tailnet` references a `Secret` resource that contains a set of oauth credentials. This secret must be created in the same namespace as the operator: ```yaml apiVersion: v1 kind: Secret metadata: name: example-oauth # This is the name that's referenced by the Tailnet resource. namespace: tailscale stringData: client_id: "client-id" client_secret: "client-secret" ``` When created, the operator performs a basic check that the oauth client has access to all required scopes. This is done using read actions on devices, keys & services. While this doesn't capture a missing "write" permission, it catches completely missing permissions. Once this check passes, the `Tailnet` moves into a ready state and can be referenced. Attempting to use a `Tailnet` in a non-ready state will stall the deployment of `Connector`s, `ProxyGroup`s and `Recorder`s until the `Tailnet` becomes ready. The `spec.tailnet` field informs the operator that a `Connector`, `ProxyGroup`, or `Recorder` must be given an auth key generated using the specified oauth client. For backwards compatibility, the set of credentials the operator is configured with are considered the default. That is, where `spec.tailnet` is not set, the resource will be deployed in the same tailnet as the operator. Updates https://github.com/tailscale/corp/issues/34561 --- cmd/k8s-operator/connector.go | 4 +- cmd/k8s-operator/depaware.txt | 6 +- .../deploy/chart/templates/.gitignore | 1 + .../deploy/chart/templates/operator-rbac.yaml | 3 + .../deploy/crds/tailscale.com_connectors.yaml | 8 + .../crds/tailscale.com_proxygroups.yaml | 8 + .../deploy/crds/tailscale.com_recorders.yaml | 8 + .../deploy/crds/tailscale.com_tailnets.yaml | 141 ++++++ .../deploy/manifests/operator.yaml | 176 ++++++++ cmd/k8s-operator/generate/main.go | 3 + cmd/k8s-operator/ingress.go | 2 +- cmd/k8s-operator/operator.go | 12 + cmd/k8s-operator/proxygroup.go | 68 ++- cmd/k8s-operator/sts.go | 37 +- cmd/k8s-operator/svc.go | 3 +- cmd/k8s-operator/tailnet.go | 58 +++ cmd/k8s-operator/tsrecorder.go | 74 ++-- k8s-operator/api.md | 96 ++++ k8s-operator/apis/v1alpha1/register.go | 2 + k8s-operator/apis/v1alpha1/types_connector.go | 6 + .../apis/v1alpha1/types_proxygroup.go | 6 + k8s-operator/apis/v1alpha1/types_recorder.go | 6 + k8s-operator/apis/v1alpha1/types_tailnet.go | 69 +++ .../apis/v1alpha1/zz_generated.deepcopy.go | 112 +++++ k8s-operator/conditions.go | 20 + k8s-operator/reconciler/reconciler.go | 39 ++ k8s-operator/reconciler/reconciler_test.go | 42 ++ k8s-operator/reconciler/tailnet/mocks_test.go | 45 ++ k8s-operator/reconciler/tailnet/tailnet.go | 327 ++++++++++++++ .../reconciler/tailnet/tailnet_test.go | 411 ++++++++++++++++++ kube/kubetypes/types.go | 1 + 31 files changed, 1730 insertions(+), 64 deletions(-) create mode 100644 cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml create mode 100644 cmd/k8s-operator/tailnet.go create mode 100644 k8s-operator/apis/v1alpha1/types_tailnet.go create mode 100644 k8s-operator/reconciler/reconciler.go create mode 100644 k8s-operator/reconciler/reconciler_test.go create mode 100644 k8s-operator/reconciler/tailnet/mocks_test.go create mode 100644 k8s-operator/reconciler/tailnet/tailnet.go create mode 100644 k8s-operator/reconciler/tailnet/tailnet_test.go diff --git a/cmd/k8s-operator/connector.go b/cmd/k8s-operator/connector.go index 7fa311532238b..f4d518faa3aad 100644 --- a/cmd/k8s-operator/connector.go +++ b/cmd/k8s-operator/connector.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -207,6 +208,7 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge ProxyClassName: proxyClass, proxyType: proxyTypeConnector, LoginServer: a.ssr.loginServer, + Tailnet: cn.Spec.Tailnet, } if cn.Spec.SubnetRouter != nil && len(cn.Spec.SubnetRouter.AdvertiseRoutes) > 0 { @@ -276,7 +278,7 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge } func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger *zap.SugaredLogger, cn *tsapi.Connector) (bool, error) { - if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(cn.Name, a.tsnamespace, "connector"), proxyTypeConnector); err != nil { + if done, err := a.ssr.Cleanup(ctx, cn.Spec.Tailnet, logger, childResourceLabels(cn.Name, a.tsnamespace, "connector"), proxyTypeConnector); err != nil { return false, fmt.Errorf("failed to cleanup Connector resources: %w", err) } else if !done { logger.Debugf("Connector cleanup not done yet, waiting for next reconcile") diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index d6993465304fd..6a6e7d61f9aa3 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -725,7 +725,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ k8s.io/utils/net from k8s.io/apimachinery/pkg/util/net+ k8s.io/utils/ptr from k8s.io/client-go/tools/cache+ k8s.io/utils/trace from k8s.io/client-go/tools/cache - sigs.k8s.io/controller-runtime/pkg/builder from tailscale.com/cmd/k8s-operator + sigs.k8s.io/controller-runtime/pkg/builder from tailscale.com/cmd/k8s-operator+ sigs.k8s.io/controller-runtime/pkg/cache from sigs.k8s.io/controller-runtime/pkg/cluster+ sigs.k8s.io/controller-runtime/pkg/cache/internal from sigs.k8s.io/controller-runtime/pkg/cache sigs.k8s.io/controller-runtime/pkg/certwatcher from sigs.k8s.io/controller-runtime/pkg/metrics/server+ @@ -821,10 +821,12 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/store/kubestore from tailscale.com/cmd/k8s-operator tailscale.com/ipn/store/mem from tailscale.com/ipn/ipnlocal+ - tailscale.com/k8s-operator from tailscale.com/cmd/k8s-operator + tailscale.com/k8s-operator from tailscale.com/cmd/k8s-operator+ tailscale.com/k8s-operator/api-proxy from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/apis from tailscale.com/k8s-operator/apis/v1alpha1 tailscale.com/k8s-operator/apis/v1alpha1 from tailscale.com/cmd/k8s-operator+ + tailscale.com/k8s-operator/reconciler from tailscale.com/k8s-operator/reconciler/tailnet + tailscale.com/k8s-operator/reconciler/tailnet from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/sessionrecording from tailscale.com/k8s-operator/api-proxy tailscale.com/k8s-operator/sessionrecording/spdy from tailscale.com/k8s-operator/sessionrecording tailscale.com/k8s-operator/sessionrecording/tsrecorder from tailscale.com/k8s-operator/sessionrecording+ diff --git a/cmd/k8s-operator/deploy/chart/templates/.gitignore b/cmd/k8s-operator/deploy/chart/templates/.gitignore index ae7c682d9fd15..f480bb57d5f18 100644 --- a/cmd/k8s-operator/deploy/chart/templates/.gitignore +++ b/cmd/k8s-operator/deploy/chart/templates/.gitignore @@ -8,3 +8,4 @@ /proxyclass.yaml /proxygroup.yaml /recorder.yaml +/tailnet.yaml diff --git a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml index 5eb920a6f41c4..930eef852c9ce 100644 --- a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml @@ -37,6 +37,9 @@ rules: - apiGroups: ["tailscale.com"] resources: ["dnsconfigs", "dnsconfigs/status"] verbs: ["get", "list", "watch", "update"] +- apiGroups: ["tailscale.com"] + resources: ["tailnets", "tailnets/status"] + verbs: ["get", "list", "watch", "update"] - apiGroups: ["tailscale.com"] resources: ["recorders", "recorders/status"] verbs: ["get", "list", "watch", "update"] diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml index 74d32d53d2199..03c51c7553bf9 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_connectors.yaml @@ -181,6 +181,14 @@ spec: items: type: string pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ + tailnet: + description: |- + Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - rule: self == oldSelf + message: Connector tailnet is immutable x-kubernetes-validations: - rule: has(self.subnetRouter) || (has(self.exitNode) && self.exitNode == true) || has(self.appConnector) message: A Connector needs to have at least one of exit node, subnet router or app connector configured. diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml index 98ca1c378ab8d..0254f01b8f0bf 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygroups.yaml @@ -139,6 +139,14 @@ spec: items: type: string pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ + tailnet: + description: |- + Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - rule: self == oldSelf + message: ProxyGroup tailnet is immutable type: description: |- Type of the ProxyGroup proxies. Supported types are egress, ingress, and kube-apiserver. diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml index 3d80c55e10a73..28d2be78e509c 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml @@ -1680,6 +1680,14 @@ spec: items: type: string pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ + tailnet: + description: |- + Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - rule: self == oldSelf + message: Recorder tailnet is immutable x-kubernetes-validations: - rule: '!(self.replicas > 1 && (!has(self.storage) || !has(self.storage.s3)))' message: S3 storage must be used when deploying multiple Recorder replicas diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml new file mode 100644 index 0000000000000..200d839431573 --- /dev/null +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_tailnets.yaml @@ -0,0 +1,141 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: tailnets.tailscale.com +spec: + group: tailscale.com + names: + kind: Tailnet + listKind: TailnetList + plural: tailnets + shortNames: + - tn + singular: tailnet + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Status of the deployed Tailnet resources. + jsonPath: .status.conditions[?(@.type == "TailnetReady")].reason + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the Tailnet. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - credentials + properties: + credentials: + description: Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. + type: object + required: + - secretName + properties: + secretName: + description: |- + The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and + "client_secret". + type: string + loginUrl: + description: URL of the control plane to be used by all resources managed by the operator using this Tailnet. + type: string + status: + description: |- + Status describes the status of the Tailnet. This is set + and managed by the Tailscale operator. + type: object + properties: + conditions: + type: array + items: + description: Condition contains details for one aspect of the current state of this API Resource. + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + type: string + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true + subresources: + status: {} diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index c53f5049261e8..5a64f2c7db307 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -206,6 +206,14 @@ spec: pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ type: string type: array + tailnet: + description: |- + Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - message: Connector tailnet is immutable + rule: self == oldSelf type: object x-kubernetes-validations: - message: A Connector needs to have at least one of exit node, subnet router or app connector configured. @@ -3135,6 +3143,14 @@ spec: pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ type: string type: array + tailnet: + description: |- + Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - message: ProxyGroup tailnet is immutable + rule: self == oldSelf type: description: |- Type of the ProxyGroup proxies. Supported types are egress, ingress, and kube-apiserver. @@ -4950,6 +4966,14 @@ spec: pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$ type: string type: array + tailnet: + description: |- + Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this + name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + type: string + x-kubernetes-validations: + - message: Recorder tailnet is immutable + rule: self == oldSelf type: object x-kubernetes-validations: - message: S3 storage must be used when deploying multiple Recorder replicas @@ -5059,6 +5083,148 @@ spec: subresources: status: {} --- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: tailnets.tailscale.com +spec: + group: tailscale.com + names: + kind: Tailnet + listKind: TailnetList + plural: tailnets + shortNames: + - tn + singular: tailnet + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Status of the deployed Tailnet resources. + jsonPath: .status.conditions[?(@.type == "TailnetReady")].reason + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the Tailnet. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + credentials: + description: Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. + properties: + secretName: + description: |- + The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and + "client_secret". + type: string + required: + - secretName + type: object + loginUrl: + description: URL of the control plane to be used by all resources managed by the operator using this Tailnet. + type: string + required: + - credentials + type: object + status: + description: |- + Status describes the status of the Tailnet. This is set + and managed by the Tailscale operator. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -5141,6 +5307,16 @@ rules: - list - watch - update + - apiGroups: + - tailscale.com + resources: + - tailnets + - tailnets/status + verbs: + - get + - list + - watch + - update - apiGroups: - tailscale.com resources: diff --git a/cmd/k8s-operator/generate/main.go b/cmd/k8s-operator/generate/main.go index 08bdc350d500c..ca54e90909954 100644 --- a/cmd/k8s-operator/generate/main.go +++ b/cmd/k8s-operator/generate/main.go @@ -26,12 +26,14 @@ const ( dnsConfigCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_dnsconfigs.yaml" recorderCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_recorders.yaml" proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml" + tailnetCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_tailnets.yaml" helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates" connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml" proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml" dnsConfigCRDHelmTemplatePath = helmTemplatesPath + "/dnsconfig.yaml" recorderCRDHelmTemplatePath = helmTemplatesPath + "/recorder.yaml" proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml" + tailnetCRDHelmTemplatePath = helmTemplatesPath + "/tailnet.yaml" helmConditionalStart = "{{ if .Values.installCRDs -}}\n" helmConditionalEnd = "{{- end -}}" @@ -154,6 +156,7 @@ func generate(baseDir string) error { {dnsConfigCRDPath, dnsConfigCRDHelmTemplatePath}, {recorderCRDPath, recorderCRDHelmTemplatePath}, {proxyGroupCRDPath, proxyGroupCRDHelmTemplatePath}, + {tailnetCRDPath, tailnetCRDHelmTemplatePath}, } { if err := addCRDToHelm(crd.crdPath, crd.templatePath); err != nil { return fmt.Errorf("error adding %s CRD to Helm templates: %w", crd.crdPath, err) diff --git a/cmd/k8s-operator/ingress.go b/cmd/k8s-operator/ingress.go index 050b03f55970f..9ef173ecef746 100644 --- a/cmd/k8s-operator/ingress.go +++ b/cmd/k8s-operator/ingress.go @@ -102,7 +102,7 @@ func (a *IngressReconciler) maybeCleanup(ctx context.Context, logger *zap.Sugare return nil } - if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(ing.Name, ing.Namespace, "ingress"), proxyTypeIngressResource); err != nil { + if done, err := a.ssr.Cleanup(ctx, operatorTailnet, logger, childResourceLabels(ing.Name, ing.Namespace, "ingress"), proxyTypeIngressResource); err != nil { return fmt.Errorf("failed to cleanup: %w", err) } else if !done { logger.Debugf("cleanup not done yet, waiting for next reconcile") diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index b50be8ce7ba66..7bb8b95f0855f 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -54,6 +54,7 @@ import ( "tailscale.com/ipn/store/kubestore" apiproxy "tailscale.com/k8s-operator/api-proxy" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/tailnet" "tailscale.com/kube/kubetypes" "tailscale.com/tsnet" "tailscale.com/tstime" @@ -325,6 +326,17 @@ func runReconcilers(opts reconcilerOpts) { startlog.Fatalf("could not create manager: %v", err) } + tailnetOptions := tailnet.ReconcilerOptions{ + Client: mgr.GetClient(), + TailscaleNamespace: opts.tailscaleNamespace, + Clock: tstime.DefaultClock{}, + Logger: opts.log, + } + + if err = tailnet.NewReconciler(tailnetOptions).Register(mgr); err != nil { + startlog.Fatalf("could not register tailnet reconciler: %v", err) + } + svcFilter := handler.EnqueueRequestsFromMapFunc(serviceHandler) svcChildFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("svc")) // If a ProxyClass changes, enqueue all Services labeled with that diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index 946e017a26f00..3a50ed8fb4c2b 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -49,11 +49,12 @@ import ( ) const ( - reasonProxyGroupCreationFailed = "ProxyGroupCreationFailed" - reasonProxyGroupReady = "ProxyGroupReady" - reasonProxyGroupAvailable = "ProxyGroupAvailable" - reasonProxyGroupCreating = "ProxyGroupCreating" - reasonProxyGroupInvalid = "ProxyGroupInvalid" + reasonProxyGroupCreationFailed = "ProxyGroupCreationFailed" + reasonProxyGroupReady = "ProxyGroupReady" + reasonProxyGroupAvailable = "ProxyGroupAvailable" + reasonProxyGroupCreating = "ProxyGroupCreating" + reasonProxyGroupInvalid = "ProxyGroupInvalid" + reasonProxyGroupTailnetUnavailable = "ProxyGroupTailnetUnavailable" // Copied from k8s.io/apiserver/pkg/registry/generic/registry/store.go@cccad306d649184bf2a0e319ba830c53f65c445c optimisticLockErrorMsg = "the object has been modified; please apply your changes to the latest version and try again" @@ -117,6 +118,23 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ } else if err != nil { return reconcile.Result{}, fmt.Errorf("failed to get tailscale.com ProxyGroup: %w", err) } + + tailscaleClient := r.tsClient + if pg.Spec.Tailnet != "" { + tc, err := clientForTailnet(ctx, r.Client, r.tsNamespace, pg.Spec.Tailnet) + if err != nil { + oldPGStatus := pg.Status.DeepCopy() + nrr := ¬ReadyReason{ + reason: reasonProxyGroupTailnetUnavailable, + message: err.Error(), + } + + return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, make(map[string][]netip.AddrPort))) + } + + tailscaleClient = tc + } + if markedForDeletion(pg) { logger.Debugf("ProxyGroup is being deleted, cleaning up resources") ix := xslices.Index(pg.Finalizers, FinalizerName) @@ -125,7 +143,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ return reconcile.Result{}, nil } - if done, err := r.maybeCleanup(ctx, pg); err != nil { + if done, err := r.maybeCleanup(ctx, tailscaleClient, pg); err != nil { if strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) return reconcile.Result{}, nil @@ -144,7 +162,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ } oldPGStatus := pg.Status.DeepCopy() - staticEndpoints, nrr, err := r.reconcilePG(ctx, pg, logger) + staticEndpoints, nrr, err := r.reconcilePG(ctx, tailscaleClient, pg, logger) return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, staticEndpoints)) } @@ -152,7 +170,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ // for deletion. It is separated out from Reconcile to make a clear separation // between reconciling the ProxyGroup, and posting the status of its created // resources onto the ProxyGroup status field. -func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (map[string][]netip.AddrPort, *notReadyReason, error) { +func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (map[string][]netip.AddrPort, *notReadyReason, error) { if !slices.Contains(pg.Finalizers, FinalizerName) { // This log line is printed exactly once during initial provisioning, // because once the finalizer is in place this block gets skipped. So, @@ -193,7 +211,7 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, pg *tsapi.ProxyG return notReady(reasonProxyGroupInvalid, fmt.Sprintf("invalid ProxyGroup spec: %v", err)) } - staticEndpoints, nrr, err := r.maybeProvision(ctx, pg, proxyClass) + staticEndpoints, nrr, err := r.maybeProvision(ctx, tailscaleClient, pg, proxyClass) if err != nil { return nil, nrr, err } @@ -279,7 +297,7 @@ func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGrou return errors.Join(errs...) } -func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { +func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { logger := r.logger(pg.Name) r.mu.Lock() r.ensureAddedToGaugeForProxyGroup(pg) @@ -302,7 +320,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.Pro } } - staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, pg, proxyClass, svcToNodePorts) + staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, tailscaleClient, pg, proxyClass, svcToNodePorts) if err != nil { var selectorErr *FindStaticEndpointErr if errors.As(err, &selectorErr) { @@ -414,7 +432,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, pg *tsapi.Pro return r.notReadyErrf(pg, logger, "error reconciling metrics resources: %w", err) } - if err := r.cleanupDanglingResources(ctx, pg, proxyClass); err != nil { + if err := r.cleanupDanglingResources(ctx, tailscaleClient, pg, proxyClass); err != nil { return r.notReadyErrf(pg, logger, "error cleaning up dangling resources: %w", err) } @@ -611,7 +629,7 @@ func (r *ProxyGroupReconciler) ensureNodePortServiceCreated(ctx context.Context, // cleanupDanglingResources ensures we don't leak config secrets, state secrets, and // tailnet devices when the number of replicas specified is reduced. -func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass) error { +func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass) error { logger := r.logger(pg.Name) metadata, err := r.getNodeMetadata(ctx, pg) if err != nil { @@ -625,7 +643,7 @@ func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, pg // Dangling resource, delete the config + state Secrets, as well as // deleting the device from the tailnet. - if err := r.deleteTailnetDevice(ctx, m.tsID, logger); err != nil { + if err := r.deleteTailnetDevice(ctx, tailscaleClient, m.tsID, logger); err != nil { return err } if err := r.Delete(ctx, m.stateSecret); err != nil && !apierrors.IsNotFound(err) { @@ -668,7 +686,7 @@ func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, pg // maybeCleanup just deletes the device from the tailnet. All the kubernetes // resources linked to a ProxyGroup will get cleaned up via owner references // (which we can use because they are all in the same namespace). -func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, pg *tsapi.ProxyGroup) (bool, error) { +func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup) (bool, error) { logger := r.logger(pg.Name) metadata, err := r.getNodeMetadata(ctx, pg) @@ -677,7 +695,7 @@ func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, pg *tsapi.Proxy } for _, m := range metadata { - if err := r.deleteTailnetDevice(ctx, m.tsID, logger); err != nil { + if err := r.deleteTailnetDevice(ctx, tailscaleClient, m.tsID, logger); err != nil { return false, err } } @@ -698,9 +716,9 @@ func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, pg *tsapi.Proxy return true, nil } -func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { +func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, tailscaleClient tsClient, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { logger.Debugf("deleting device %s from control", string(id)) - if err := r.tsClient.DeleteDevice(ctx, string(id)); err != nil { + if err := tailscaleClient.DeleteDevice(ctx, string(id)); err != nil { errResp := &tailscale.ErrResponse{} if ok := errors.As(err, errResp); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", string(id)) @@ -714,7 +732,13 @@ func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, id tailc return nil } -func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass, svcToNodePorts map[string]uint16) (endpoints map[string][]netip.AddrPort, err error) { +func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( + ctx context.Context, + tailscaleClient tsClient, + pg *tsapi.ProxyGroup, + proxyClass *tsapi.ProxyClass, + svcToNodePorts map[string]uint16, +) (endpoints map[string][]netip.AddrPort, err error) { logger := r.logger(pg.Name) endpoints = make(map[string][]netip.AddrPort, pgReplicas(pg)) // keyed by Service name. for i := range pgReplicas(pg) { @@ -728,7 +752,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p } var existingCfgSecret *corev1.Secret // unmodified copy of secret - if err := r.Get(ctx, client.ObjectKeyFromObject(cfgSecret), cfgSecret); err == nil { + if err = r.Get(ctx, client.ObjectKeyFromObject(cfgSecret), cfgSecret); err == nil { logger.Debugf("Secret %s/%s already exists", cfgSecret.GetNamespace(), cfgSecret.GetName()) existingCfgSecret = cfgSecret.DeepCopy() } else if !apierrors.IsNotFound(err) { @@ -742,7 +766,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p if len(tags) == 0 { tags = r.defaultTags } - key, err := newAuthKey(ctx, r.tsClient, tags) + key, err := newAuthKey(ctx, tailscaleClient, tags) if err != nil { return nil, err } @@ -757,7 +781,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated(ctx context.Context, p Namespace: r.tsNamespace, }, } - if err := r.Get(ctx, client.ObjectKeyFromObject(stateSecret), stateSecret); err != nil && !apierrors.IsNotFound(err) { + if err = r.Get(ctx, client.ObjectKeyFromObject(stateSecret), stateSecret); err != nil && !apierrors.IsNotFound(err) { return nil, err } diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 2b6d1290e53f8..2919e535c0dca 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -107,6 +107,7 @@ const ( letsEncryptStagingEndpoint = "https://acme-staging-v02.api.letsencrypt.org/directory" mainContainerName = "tailscale" + operatorTailnet = "" ) var ( @@ -152,6 +153,9 @@ type tailscaleSTSConfig struct { // HostnamePrefix specifies the desired prefix for the device's hostname. The hostname will be suffixed with the // ordinal number generated by the StatefulSet. HostnamePrefix string + + // Tailnet specifies the Tailnet resource to use for producing auth keys. + Tailnet string } type connector struct { @@ -194,6 +198,16 @@ func IsHTTPSEnabledOnTailnet(tsnetServer tsnetServer) bool { // Provision ensures that the StatefulSet for the given service is running and // up to date. func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) (*corev1.Service, error) { + tailscaleClient := a.tsClient + if sts.Tailnet != "" { + tc, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, sts.Tailnet) + if err != nil { + return nil, err + } + + tailscaleClient = tc + } + // Do full reconcile. // TODO (don't create Service for the Connector) hsvc, err := a.reconcileHeadlessService(ctx, logger, sts) @@ -213,7 +227,7 @@ func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.Suga } sts.ProxyClass = proxyClass - secretNames, err := a.provisionSecrets(ctx, logger, sts, hsvc) + secretNames, err := a.provisionSecrets(ctx, tailscaleClient, logger, sts, hsvc) if err != nil { return nil, fmt.Errorf("failed to create or get API key secret: %w", err) } @@ -237,7 +251,18 @@ func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.Suga // Cleanup removes all resources associated that were created by Provision with // the given labels. It returns true when all resources have been removed, // otherwise it returns false and the caller should retry later. -func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, logger *zap.SugaredLogger, labels map[string]string, typ string) (done bool, _ error) { +func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, tailnet string, logger *zap.SugaredLogger, labels map[string]string, typ string) (done bool, _ error) { + tailscaleClient := a.tsClient + if tailnet != "" { + tc, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, tailnet) + if err != nil { + logger.Errorf("failed to get tailscale client: %v", err) + return false, nil + } + + tailscaleClient = tc + } + // Need to delete the StatefulSet first, and delete it with foreground // cascading deletion. That way, the pod that's writing to the Secret will // stop running before we start looking at the Secret's contents, and @@ -279,7 +304,7 @@ func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, logger *zap.Sugare for _, dev := range devices { if dev.id != "" { logger.Debugf("deleting device %s from control", string(dev.id)) - if err = a.tsClient.DeleteDevice(ctx, string(dev.id)); err != nil { + if err = tailscaleClient.DeleteDevice(ctx, string(dev.id)); err != nil { errResp := &tailscale.ErrResponse{} if ok := errors.As(err, errResp); ok && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", string(dev.id)) @@ -360,7 +385,7 @@ func (a *tailscaleSTSReconciler) reconcileHeadlessService(ctx context.Context, l return createOrUpdate(ctx, a.Client, a.operatorNamespace, hsvc, func(svc *corev1.Service) { svc.Spec = hsvc.Spec }) } -func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, logger *zap.SugaredLogger, stsC *tailscaleSTSConfig, hsvc *corev1.Service) ([]string, error) { +func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, tailscaleClient tsClient, logger *zap.SugaredLogger, stsC *tailscaleSTSConfig, hsvc *corev1.Service) ([]string, error) { secretNames := make([]string, stsC.Replicas) // Start by ensuring we have Secrets for the desired number of replicas. This will handle both creating and scaling @@ -403,7 +428,7 @@ func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, logger *z if len(tags) == 0 { tags = a.defaultTags } - authKey, err = newAuthKey(ctx, a.tsClient, tags) + authKey, err = newAuthKey(ctx, tailscaleClient, tags) if err != nil { return nil, err } @@ -477,7 +502,7 @@ func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, logger *z if dev != nil && dev.id != "" { var errResp *tailscale.ErrResponse - err = a.tsClient.DeleteDevice(ctx, string(dev.id)) + err = tailscaleClient.DeleteDevice(ctx, string(dev.id)) switch { case errors.As(err, &errResp) && errResp.Status == http.StatusNotFound: // This device has possibly already been deleted in the admin console. So we can ignore this diff --git a/cmd/k8s-operator/svc.go b/cmd/k8s-operator/svc.go index 5c163e081f5a6..ec7bb8080dec7 100644 --- a/cmd/k8s-operator/svc.go +++ b/cmd/k8s-operator/svc.go @@ -23,6 +23,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -167,7 +168,7 @@ func (a *ServiceReconciler) maybeCleanup(ctx context.Context, logger *zap.Sugare proxyTyp = proxyTypeIngressService } - if done, err := a.ssr.Cleanup(ctx, logger, childResourceLabels(svc.Name, svc.Namespace, "svc"), proxyTyp); err != nil { + if done, err := a.ssr.Cleanup(ctx, operatorTailnet, logger, childResourceLabels(svc.Name, svc.Namespace, "svc"), proxyTyp); err != nil { return fmt.Errorf("failed to cleanup: %w", err) } else if !done { logger.Debugf("cleanup not done yet, waiting for next reconcile") diff --git a/cmd/k8s-operator/tailnet.go b/cmd/k8s-operator/tailnet.go new file mode 100644 index 0000000000000..8d749545faa46 --- /dev/null +++ b/cmd/k8s-operator/tailnet.go @@ -0,0 +1,58 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package main + +import ( + "context" + "fmt" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "tailscale.com/internal/client/tailscale" + "tailscale.com/ipn" + operatorutils "tailscale.com/k8s-operator" + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" +) + +func clientForTailnet(ctx context.Context, cl client.Client, namespace, name string) (tsClient, error) { + var tn tsapi.Tailnet + if err := cl.Get(ctx, client.ObjectKey{Name: name}, &tn); err != nil { + return nil, fmt.Errorf("failed to get tailnet %q: %w", name, err) + } + + if !operatorutils.TailnetIsReady(&tn) { + return nil, fmt.Errorf("tailnet %q is not ready", name) + } + + var secret corev1.Secret + if err := cl.Get(ctx, client.ObjectKey{Name: tn.Spec.Credentials.SecretName, Namespace: namespace}, &secret); err != nil { + return nil, fmt.Errorf("failed to get Secret %q in namespace %q: %w", tn.Spec.Credentials.SecretName, namespace, err) + } + + baseURL := ipn.DefaultControlURL + if tn.Spec.LoginURL != "" { + baseURL = tn.Spec.LoginURL + } + + credentials := clientcredentials.Config{ + ClientID: string(secret.Data["client_id"]), + ClientSecret: string(secret.Data["client_secret"]), + TokenURL: baseURL + "/api/v2/oauth/token", + } + + source := credentials.TokenSource(ctx) + httpClient := oauth2.NewClient(ctx, source) + + ts := tailscale.NewClient(defaultTailnet, nil) + ts.UserAgent = "tailscale-k8s-operator" + ts.HTTPClient = httpClient + ts.BaseURL = baseURL + + return ts, nil +} diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index bfb01fa86de67..3e8608bc8db8e 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -42,10 +42,11 @@ import ( ) const ( - reasonRecorderCreationFailed = "RecorderCreationFailed" - reasonRecorderCreating = "RecorderCreating" - reasonRecorderCreated = "RecorderCreated" - reasonRecorderInvalid = "RecorderInvalid" + reasonRecorderCreationFailed = "RecorderCreationFailed" + reasonRecorderCreating = "RecorderCreating" + reasonRecorderCreated = "RecorderCreated" + reasonRecorderInvalid = "RecorderInvalid" + reasonRecorderTailnetUnavailable = "RecorderTailnetUnavailable" currentProfileKey = "_current-profile" ) @@ -84,6 +85,30 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques } else if err != nil { return reconcile.Result{}, fmt.Errorf("failed to get tailscale.com Recorder: %w", err) } + + oldTSRStatus := tsr.Status.DeepCopy() + setStatusReady := func(tsr *tsapi.Recorder, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) { + tsoperator.SetRecorderCondition(tsr, tsapi.RecorderReady, status, reason, message, tsr.Generation, r.clock, logger) + if !apiequality.Semantic.DeepEqual(oldTSRStatus, &tsr.Status) { + // An error encountered here should get returned by the Reconcile function. + if updateErr := r.Client.Status().Update(ctx, tsr); updateErr != nil { + return reconcile.Result{}, errors.Join(err, updateErr) + } + } + + return reconcile.Result{}, nil + } + + tailscaleClient := r.tsClient + if tsr.Spec.Tailnet != "" { + tc, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tsr.Spec.Tailnet) + if err != nil { + return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderTailnetUnavailable, err.Error()) + } + + tailscaleClient = tc + } + if markedForDeletion(tsr) { logger.Debugf("Recorder is being deleted, cleaning up resources") ix := xslices.Index(tsr.Finalizers, FinalizerName) @@ -92,7 +117,7 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return reconcile.Result{}, nil } - if done, err := r.maybeCleanup(ctx, tsr); err != nil { + if done, err := r.maybeCleanup(ctx, tsr, tailscaleClient); err != nil { return reconcile.Result{}, err } else if !done { logger.Debugf("Recorder resource cleanup not yet finished, will retry...") @@ -106,19 +131,6 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return reconcile.Result{}, nil } - oldTSRStatus := tsr.Status.DeepCopy() - setStatusReady := func(tsr *tsapi.Recorder, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) { - tsoperator.SetRecorderCondition(tsr, tsapi.RecorderReady, status, reason, message, tsr.Generation, r.clock, logger) - if !apiequality.Semantic.DeepEqual(oldTSRStatus, &tsr.Status) { - // An error encountered here should get returned by the Reconcile function. - if updateErr := r.Client.Status().Update(ctx, tsr); updateErr != nil { - return reconcile.Result{}, errors.Join(err, updateErr) - } - } - - return reconcile.Result{}, nil - } - if !slices.Contains(tsr.Finalizers, FinalizerName) { // This log line is printed exactly once during initial provisioning, // because once the finalizer is in place this block gets skipped. So, @@ -137,7 +149,7 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderInvalid, message) } - if err = r.maybeProvision(ctx, tsr); err != nil { + if err = r.maybeProvision(ctx, tailscaleClient, tsr); err != nil { reason := reasonRecorderCreationFailed message := fmt.Sprintf("failed creating Recorder: %s", err) if strings.Contains(err.Error(), optimisticLockErrorMsg) { @@ -155,7 +167,7 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return setStatusReady(tsr, metav1.ConditionTrue, reasonRecorderCreated, reasonRecorderCreated) } -func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Recorder) error { +func (r *RecorderReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, tsr *tsapi.Recorder) error { logger := r.logger(tsr.Name) r.mu.Lock() @@ -163,7 +175,7 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco gaugeRecorderResources.Set(int64(r.recorders.Len())) r.mu.Unlock() - if err := r.ensureAuthSecretsCreated(ctx, tsr); err != nil { + if err := r.ensureAuthSecretsCreated(ctx, tailscaleClient, tsr); err != nil { return fmt.Errorf("error creating secrets: %w", err) } @@ -241,13 +253,13 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tsr *tsapi.Reco // If we have scaled the recorder down, we will have dangling state secrets // that we need to clean up. - if err = r.maybeCleanupSecrets(ctx, tsr); err != nil { + if err = r.maybeCleanupSecrets(ctx, tailscaleClient, tsr); err != nil { return fmt.Errorf("error cleaning up Secrets: %w", err) } var devices []tsapi.RecorderTailnetDevice for replica := range replicas { - dev, ok, err := r.getDeviceInfo(ctx, tsr.Name, replica) + dev, ok, err := r.getDeviceInfo(ctx, tailscaleClient, tsr.Name, replica) switch { case err != nil: return fmt.Errorf("failed to get device info: %w", err) @@ -312,7 +324,7 @@ func (r *RecorderReconciler) maybeCleanupServiceAccounts(ctx context.Context, ts return nil } -func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tsr *tsapi.Recorder) error { +func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tailscaleClient tsClient, tsr *tsapi.Recorder) error { options := []client.ListOption{ client.InNamespace(r.tsNamespace), client.MatchingLabels(tsrLabels("recorder", tsr.Name, nil)), @@ -354,7 +366,7 @@ func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tsr *tsapi var errResp *tailscale.ErrResponse r.log.Debugf("deleting device %s", devicePrefs.Config.NodeID) - err = r.tsClient.DeleteDevice(ctx, string(devicePrefs.Config.NodeID)) + err = tailscaleClient.DeleteDevice(ctx, string(devicePrefs.Config.NodeID)) switch { case errors.As(err, &errResp) && errResp.Status == http.StatusNotFound: // This device has possibly already been deleted in the admin console. So we can ignore this @@ -375,7 +387,7 @@ func (r *RecorderReconciler) maybeCleanupSecrets(ctx context.Context, tsr *tsapi // maybeCleanup just deletes the device from the tailnet. All the kubernetes // resources linked to a Recorder will get cleaned up via owner references // (which we can use because they are all in the same namespace). -func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Recorder) (bool, error) { +func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Recorder, tailscaleClient tsClient) (bool, error) { logger := r.logger(tsr.Name) var replicas int32 = 1 @@ -399,7 +411,7 @@ func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Record nodeID := string(devicePrefs.Config.NodeID) logger.Debugf("deleting device %s from control", nodeID) - if err = r.tsClient.DeleteDevice(ctx, nodeID); err != nil { + if err = tailscaleClient.DeleteDevice(ctx, nodeID); err != nil { errResp := &tailscale.ErrResponse{} if errors.As(err, errResp) && errResp.Status == http.StatusNotFound { logger.Debugf("device %s not found, likely because it has already been deleted from control", nodeID) @@ -425,7 +437,7 @@ func (r *RecorderReconciler) maybeCleanup(ctx context.Context, tsr *tsapi.Record return true, nil } -func (r *RecorderReconciler) ensureAuthSecretsCreated(ctx context.Context, tsr *tsapi.Recorder) error { +func (r *RecorderReconciler) ensureAuthSecretsCreated(ctx context.Context, tailscaleClient tsClient, tsr *tsapi.Recorder) error { var replicas int32 = 1 if tsr.Spec.Replicas != nil { replicas = *tsr.Spec.Replicas @@ -453,7 +465,7 @@ func (r *RecorderReconciler) ensureAuthSecretsCreated(ctx context.Context, tsr * return fmt.Errorf("failed to get Secret %q: %w", key.Name, err) } - authKey, err := newAuthKey(ctx, r.tsClient, tags.Stringify()) + authKey, err := newAuthKey(ctx, tailscaleClient, tags.Stringify()) if err != nil { return err } @@ -555,7 +567,7 @@ func getDevicePrefs(secret *corev1.Secret) (prefs prefs, ok bool, err error) { return prefs, ok, nil } -func (r *RecorderReconciler) getDeviceInfo(ctx context.Context, tsrName string, replica int32) (d tsapi.RecorderTailnetDevice, ok bool, err error) { +func (r *RecorderReconciler) getDeviceInfo(ctx context.Context, tailscaleClient tsClient, tsrName string, replica int32) (d tsapi.RecorderTailnetDevice, ok bool, err error) { secret, err := r.getStateSecret(ctx, tsrName, replica) if err != nil || secret == nil { return tsapi.RecorderTailnetDevice{}, false, err @@ -569,7 +581,7 @@ func (r *RecorderReconciler) getDeviceInfo(ctx context.Context, tsrName string, // TODO(tomhjp): The profile info doesn't include addresses, which is why we // need the API. Should maybe update tsrecorder to write IPs to the state // Secret like containerboot does. - device, err := r.tsClient.Device(ctx, string(prefs.Config.NodeID), nil) + device, err := tailscaleClient.Device(ctx, string(prefs.Config.NodeID), nil) if err != nil { return tsapi.RecorderTailnetDevice{}, false, fmt.Errorf("failed to get device info from API: %w", err) } diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 3a4e692d902ec..31f351013164a 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -18,6 +18,8 @@ - [ProxyGroupList](#proxygrouplist) - [Recorder](#recorder) - [RecorderList](#recorderlist) +- [Tailnet](#tailnet) +- [TailnetList](#tailnetlist) @@ -139,6 +141,7 @@ _Appears in:_ | `appConnector` _[AppConnector](#appconnector)_ | AppConnector defines whether the Connector device should act as a Tailscale app connector. A Connector that is
configured as an app connector cannot be a subnet router or an exit node. If this field is unset, the
Connector does not act as an app connector.
Note that you will need to manually configure the permissions and the domains for the app connector via the
Admin panel.
Note also that the main tested and supported use case of this config option is to deploy an app connector on
Kubernetes to access SaaS applications available on the public internet. Using the app connector to expose
cluster workloads or other internal workloads to tailnet might work, but this is not a use case that we have
tested or optimised for.
If you are using the app connector to access SaaS applications because you need a predictable egress IP that
can be whitelisted, it is also your responsibility to ensure that cluster traffic from the connector flows
via that predictable IP, for example by enforcing that cluster egress traffic is routed via an egress NAT
device with a static IP address.
https://tailscale.com/kb/1281/app-connectors | | | | `exitNode` _boolean_ | ExitNode defines whether the Connector device should act as a Tailscale exit node. Defaults to false.
This field is mutually exclusive with the appConnector field.
https://tailscale.com/kb/1103/exit-nodes | | | | `replicas` _integer_ | Replicas specifies how many devices to create. Set this to enable
high availability for app connectors, subnet routers, or exit nodes.
https://tailscale.com/kb/1115/high-availability. Defaults to 1. | | Minimum: 0
| +| `tailnet` _string_ | Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | #### ConnectorStatus @@ -741,6 +744,7 @@ _Appears in:_ | `hostnamePrefix` _[HostnamePrefix](#hostnameprefix)_ | HostnamePrefix is the hostname prefix to use for tailnet devices created
by the ProxyGroup. Each device will have the integer number from its
StatefulSet pod appended to this prefix to form the full hostname.
HostnamePrefix can contain lower case letters, numbers and dashes, it
must not start with a dash and must be between 1 and 62 characters long. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}$`
Type: string
| | `proxyClass` _string_ | ProxyClass is the name of the ProxyClass custom resource that contains
configuration options that should be applied to the resources created
for this ProxyGroup. If unset, and there is no default ProxyClass
configured, the operator will create resources with the default
configuration. | | | | `kubeAPIServer` _[KubeAPIServerConfig](#kubeapiserverconfig)_ | KubeAPIServer contains configuration specific to the kube-apiserver
ProxyGroup type. This field is only used when Type is set to "kube-apiserver". | | | +| `tailnet` _string_ | Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | #### ProxyGroupStatus @@ -901,6 +905,7 @@ _Appears in:_ | `enableUI` _boolean_ | Set to true to enable the Recorder UI. The UI lists and plays recorded sessions.
The UI will be served at :443. Defaults to false.
Corresponds to --ui tsrecorder flag https://tailscale.com/kb/1246/tailscale-ssh-session-recording#deploy-a-recorder-node.
Required if S3 storage is not set up, to ensure that recordings are accessible. | | | | `storage` _[Storage](#storage)_ | Configure where to store session recordings. By default, recordings will
be stored in a local ephemeral volume, and will not be persisted past the
lifetime of a specific pod. | | | | `replicas` _integer_ | Replicas specifies how many instances of tsrecorder to run. Defaults to 1. | | Minimum: 0
| +| `tailnet` _string_ | Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | #### RecorderStatefulSet @@ -1154,6 +1159,44 @@ _Appears in:_ +#### Tailnet + + + + + + + +_Appears in:_ +- [TailnetList](#tailnetlist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `Tailnet` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[TailnetSpec](#tailnetspec)_ | Spec describes the desired state of the Tailnet.
More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status | | | +| `status` _[TailnetStatus](#tailnetstatus)_ | Status describes the status of the Tailnet. This is set
and managed by the Tailscale operator. | | | + + +#### TailnetCredentials + + + + + + + +_Appears in:_ +- [TailnetSpec](#tailnetspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `secretName` _string_ | The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and
"client_secret". | | | + + #### TailnetDevice @@ -1172,6 +1215,59 @@ _Appears in:_ | `staticEndpoints` _string array_ | StaticEndpoints are user configured, 'static' endpoints by which tailnet peers can reach this device. | | | +#### TailnetList + + + + + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `TailnetList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[Tailnet](#tailnet) array_ | | | | + + +#### TailnetSpec + + + + + + + +_Appears in:_ +- [Tailnet](#tailnet) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `loginUrl` _string_ | URL of the control plane to be used by all resources managed by the operator using this Tailnet. | | | +| `credentials` _[TailnetCredentials](#tailnetcredentials)_ | Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. | | | + + +#### TailnetStatus + + + + + + + +_Appears in:_ +- [Tailnet](#tailnet) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#condition-v1-meta) array_ | | | | + + #### TailscaleConfig diff --git a/k8s-operator/apis/v1alpha1/register.go b/k8s-operator/apis/v1alpha1/register.go index 0880ac975732e..993a119fad2eb 100644 --- a/k8s-operator/apis/v1alpha1/register.go +++ b/k8s-operator/apis/v1alpha1/register.go @@ -67,6 +67,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &RecorderList{}, &ProxyGroup{}, &ProxyGroupList{}, + &Tailnet{}, + &TailnetList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/k8s-operator/apis/v1alpha1/types_connector.go b/k8s-operator/apis/v1alpha1/types_connector.go index 58457500f6c34..ebedea18f0e98 100644 --- a/k8s-operator/apis/v1alpha1/types_connector.go +++ b/k8s-operator/apis/v1alpha1/types_connector.go @@ -133,6 +133,12 @@ type ConnectorSpec struct { // +optional // +kubebuilder:validation:Minimum=0 Replicas *int32 `json:"replicas,omitempty"` + + // Tailnet specifies the tailnet this Connector should join. If blank, the default tailnet is used. When set, this + // name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Connector tailnet is immutable" + Tailnet string `json:"tailnet,omitempty"` } // SubnetRouter defines subnet routes that should be exposed to tailnet via a diff --git a/k8s-operator/apis/v1alpha1/types_proxygroup.go b/k8s-operator/apis/v1alpha1/types_proxygroup.go index 28fd9e00973c5..8cbcc2d196e51 100644 --- a/k8s-operator/apis/v1alpha1/types_proxygroup.go +++ b/k8s-operator/apis/v1alpha1/types_proxygroup.go @@ -97,6 +97,12 @@ type ProxyGroupSpec struct { // ProxyGroup type. This field is only used when Type is set to "kube-apiserver". // +optional KubeAPIServer *KubeAPIServerConfig `json:"kubeAPIServer,omitempty"` + + // Tailnet specifies the tailnet this ProxyGroup should join. If blank, the default tailnet is used. When set, this + // name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ProxyGroup tailnet is immutable" + Tailnet string `json:"tailnet,omitempty"` } type ProxyGroupStatus struct { diff --git a/k8s-operator/apis/v1alpha1/types_recorder.go b/k8s-operator/apis/v1alpha1/types_recorder.go index 67cffbf09e969..d5a22e82c2dbc 100644 --- a/k8s-operator/apis/v1alpha1/types_recorder.go +++ b/k8s-operator/apis/v1alpha1/types_recorder.go @@ -81,6 +81,12 @@ type RecorderSpec struct { // +optional // +kubebuilder:validation:Minimum=0 Replicas *int32 `json:"replicas,omitzero"` + + // Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this + // name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Recorder tailnet is immutable" + Tailnet string `json:"tailnet,omitempty"` } type RecorderStatefulSet struct { diff --git a/k8s-operator/apis/v1alpha1/types_tailnet.go b/k8s-operator/apis/v1alpha1/types_tailnet.go new file mode 100644 index 0000000000000..a3a17374be5cd --- /dev/null +++ b/k8s-operator/apis/v1alpha1/types_tailnet.go @@ -0,0 +1,69 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Code comments on these types should be treated as user facing documentation- +// they will appear on the Tailnet CRD i.e. if someone runs kubectl explain tailnet. + +var TailnetKind = "Tailnet" + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,shortName=tn +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "TailnetReady")].reason`,description="Status of the deployed Tailnet resources." + +type Tailnet struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitzero"` + + // Spec describes the desired state of the Tailnet. + // More info: + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + Spec TailnetSpec `json:"spec"` + + // Status describes the status of the Tailnet. This is set + // and managed by the Tailscale operator. + // +optional + Status TailnetStatus `json:"status"` +} + +// +kubebuilder:object:root=true + +type TailnetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Tailnet `json:"items"` +} + +type TailnetSpec struct { + // URL of the control plane to be used by all resources managed by the operator using this Tailnet. + // +optional + LoginURL string `json:"loginUrl,omitempty"` + // Denotes the location of the OAuth credentials to use for authenticating with this Tailnet. + Credentials TailnetCredentials `json:"credentials"` +} + +type TailnetCredentials struct { + // The name of the secret containing the OAuth credentials. This secret must contain two fields "client_id" and + // "client_secret". + SecretName string `json:"secretName"` +} + +type TailnetStatus struct { + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions"` +} + +// TailnetReady is set to True if the Tailnet is available for use by operator workloads. +const TailnetReady ConditionType = `TailnetReady` diff --git a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go index ff0f3f6ace415..4743a5156c16b 100644 --- a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go +++ b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go @@ -1365,6 +1365,48 @@ func (in Tags) DeepCopy() Tags { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tailnet) DeepCopyInto(out *Tailnet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tailnet. +func (in *Tailnet) DeepCopy() *Tailnet { + if in == nil { + return nil + } + out := new(Tailnet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tailnet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetCredentials) DeepCopyInto(out *TailnetCredentials) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetCredentials. +func (in *TailnetCredentials) DeepCopy() *TailnetCredentials { + if in == nil { + return nil + } + out := new(TailnetCredentials) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TailnetDevice) DeepCopyInto(out *TailnetDevice) { *out = *in @@ -1390,6 +1432,76 @@ func (in *TailnetDevice) DeepCopy() *TailnetDevice { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetList) DeepCopyInto(out *TailnetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tailnet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetList. +func (in *TailnetList) DeepCopy() *TailnetList { + if in == nil { + return nil + } + out := new(TailnetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TailnetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetSpec) DeepCopyInto(out *TailnetSpec) { + *out = *in + out.Credentials = in.Credentials +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetSpec. +func (in *TailnetSpec) DeepCopy() *TailnetSpec { + if in == nil { + return nil + } + out := new(TailnetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TailnetStatus) DeepCopyInto(out *TailnetStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TailnetStatus. +func (in *TailnetStatus) DeepCopy() *TailnetStatus { + if in == nil { + return nil + } + out := new(TailnetStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TailscaleConfig) DeepCopyInto(out *TailscaleConfig) { *out = *in diff --git a/k8s-operator/conditions.go b/k8s-operator/conditions.go index ae465a728f0ff..bce6e39bdb142 100644 --- a/k8s-operator/conditions.go +++ b/k8s-operator/conditions.go @@ -13,6 +13,7 @@ import ( xslices "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/tstime" ) @@ -91,6 +92,14 @@ func SetProxyGroupCondition(pg *tsapi.ProxyGroup, conditionType tsapi.ConditionT pg.Status.Conditions = conds } +// SetTailnetCondition ensures that Tailnet status has a condition with the +// given attributes. LastTransitionTime gets set every time condition's status +// changes. +func SetTailnetCondition(tn *tsapi.Tailnet, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, clock tstime.Clock, logger *zap.SugaredLogger) { + conds := updateCondition(tn.Status.Conditions, conditionType, status, reason, message, tn.Generation, clock, logger) + tn.Status.Conditions = conds +} + func updateCondition(conds []metav1.Condition, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) []metav1.Condition { newCondition := metav1.Condition{ Type: string(conditionType), @@ -187,3 +196,14 @@ func SvcIsReady(svc *corev1.Service) bool { cond := svc.Status.Conditions[idx] return cond.Status == metav1.ConditionTrue } + +func TailnetIsReady(tn *tsapi.Tailnet) bool { + idx := xslices.IndexFunc(tn.Status.Conditions, func(cond metav1.Condition) bool { + return cond.Type == string(tsapi.TailnetReady) + }) + if idx == -1 { + return false + } + cond := tn.Status.Conditions[idx] + return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == tn.Generation +} diff --git a/k8s-operator/reconciler/reconciler.go b/k8s-operator/reconciler/reconciler.go new file mode 100644 index 0000000000000..2751790964577 --- /dev/null +++ b/k8s-operator/reconciler/reconciler.go @@ -0,0 +1,39 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +// Package reconciler provides utilities for working with Kubernetes resources within controller reconciliation +// loops. +package reconciler + +import ( + "slices" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + // FinalizerName is the common finalizer used across all Tailscale Kubernetes resources. + FinalizerName = "tailscale.com/finalizer" +) + +// SetFinalizer adds the finalizer to the resource if not already present. +func SetFinalizer(obj client.Object) { + if idx := slices.Index(obj.GetFinalizers(), FinalizerName); idx >= 0 { + return + } + + obj.SetFinalizers(append(obj.GetFinalizers(), FinalizerName)) +} + +// RemoveFinalizer removes the finalizer from the resource if present. +func RemoveFinalizer(obj client.Object) { + idx := slices.Index(obj.GetFinalizers(), FinalizerName) + if idx < 0 { + return + } + + finalizers := obj.GetFinalizers() + obj.SetFinalizers(append(finalizers[:idx], finalizers[idx+1:]...)) +} diff --git a/k8s-operator/reconciler/reconciler_test.go b/k8s-operator/reconciler/reconciler_test.go new file mode 100644 index 0000000000000..573cd4d9db8da --- /dev/null +++ b/k8s-operator/reconciler/reconciler_test.go @@ -0,0 +1,42 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package reconciler_test + +import ( + "slices" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "tailscale.com/k8s-operator/reconciler" +) + +func TestFinalizers(t *testing.T) { + t.Parallel() + + object := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + StringData: map[string]string{ + "hello": "world", + }, + } + + reconciler.SetFinalizer(object) + + if !slices.Contains(object.Finalizers, reconciler.FinalizerName) { + t.Fatalf("object does not have finalizer %q: %v", reconciler.FinalizerName, object.Finalizers) + } + + reconciler.RemoveFinalizer(object) + + if slices.Contains(object.Finalizers, reconciler.FinalizerName) { + t.Fatalf("object still has finalizer %q: %v", reconciler.FinalizerName, object.Finalizers) + } +} diff --git a/k8s-operator/reconciler/tailnet/mocks_test.go b/k8s-operator/reconciler/tailnet/mocks_test.go new file mode 100644 index 0000000000000..7f3f2ddb91085 --- /dev/null +++ b/k8s-operator/reconciler/tailnet/mocks_test.go @@ -0,0 +1,45 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package tailnet_test + +import ( + "context" + "io" + + "tailscale.com/internal/client/tailscale" +) + +type ( + MockTailnetClient struct { + ErrorOnDevices bool + ErrorOnKeys bool + ErrorOnServices bool + } +) + +func (m MockTailnetClient) Devices(_ context.Context, _ *tailscale.DeviceFieldsOpts) ([]*tailscale.Device, error) { + if m.ErrorOnDevices { + return nil, io.EOF + } + + return nil, nil +} + +func (m MockTailnetClient) Keys(_ context.Context) ([]string, error) { + if m.ErrorOnKeys { + return nil, io.EOF + } + + return nil, nil +} + +func (m MockTailnetClient) ListVIPServices(_ context.Context) (*tailscale.VIPServiceList, error) { + if m.ErrorOnServices { + return nil, io.EOF + } + + return nil, nil +} diff --git a/k8s-operator/reconciler/tailnet/tailnet.go b/k8s-operator/reconciler/tailnet/tailnet.go new file mode 100644 index 0000000000000..fe445a36323be --- /dev/null +++ b/k8s-operator/reconciler/tailnet/tailnet.go @@ -0,0 +1,327 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +// Package tailnet provides reconciliation logic for the Tailnet custom resource definition. It is responsible for +// ensuring the referenced OAuth credentials are valid and have the required scopes to be able to generate authentication +// keys, manage devices & manage VIP services. +package tailnet + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "go.uber.org/zap" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "tailscale.com/internal/client/tailscale" + "tailscale.com/ipn" + operatorutils "tailscale.com/k8s-operator" + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler" + "tailscale.com/kube/kubetypes" + "tailscale.com/tstime" + "tailscale.com/util/clientmetric" + "tailscale.com/util/set" +) + +type ( + // The Reconciler type is a reconcile.TypedReconciler implementation used to manage the reconciliation of + // Tailnet custom resources. + Reconciler struct { + client.Client + + tailscaleNamespace string + clock tstime.Clock + logger *zap.SugaredLogger + clientFunc func(*tsapi.Tailnet, *corev1.Secret) TailscaleClient + + // Metrics related fields + mu sync.Mutex + tailnets set.Slice[types.UID] + } + + // The ReconcilerOptions type contains configuration values for the Reconciler. + ReconcilerOptions struct { + // The client for interacting with the Kubernetes API. + Client client.Client + // The namespace the operator is installed in. This reconciler expects Tailnet OAuth credentials to be stored + // in Secret resources within this namespace. + TailscaleNamespace string + // Controls which clock to use for performing time-based functions. This is typically modified for use + // in tests. + Clock tstime.Clock + // The logger to use for this Reconciler. + Logger *zap.SugaredLogger + // ClientFunc is a function that takes tailscale credentials and returns an implementation for the Tailscale + // HTTP API. This should generally be nil unless needed for testing. + ClientFunc func(*tsapi.Tailnet, *corev1.Secret) TailscaleClient + } + + // The TailscaleClient interface describes types that interact with the Tailscale HTTP API. + TailscaleClient interface { + Devices(context.Context, *tailscale.DeviceFieldsOpts) ([]*tailscale.Device, error) + Keys(ctx context.Context) ([]string, error) + ListVIPServices(ctx context.Context) (*tailscale.VIPServiceList, error) + } +) + +const reconcilerName = "tailnet-reconciler" + +// NewReconciler returns a new instance of the Reconciler type. It watches specifically for changes to Tailnet custom +// resources. The ReconcilerOptions can be used to modify the behaviour of the Reconciler. +func NewReconciler(options ReconcilerOptions) *Reconciler { + return &Reconciler{ + Client: options.Client, + tailscaleNamespace: options.TailscaleNamespace, + clock: options.Clock, + logger: options.Logger.Named(reconcilerName), + clientFunc: options.ClientFunc, + } +} + +// Register the Reconciler onto the given manager.Manager implementation. +func (r *Reconciler) Register(mgr manager.Manager) error { + return builder. + ControllerManagedBy(mgr). + For(&tsapi.Tailnet{}). + Named(reconcilerName). + Complete(r) +} + +var ( + // gaugeTailnetResources tracks the overall number of Tailnet resources currently managed by this operator instance. + gaugeTailnetResources = clientmetric.NewGauge(kubetypes.MetricTailnetCount) +) + +// Reconcile is invoked when a change occurs to Tailnet resources within the cluster. On create/update, the Tailnet +// resource is validated ensuring that the specified Secret exists and contains valid OAuth credentials that have +// required permissions to perform all necessary functions by the operator. +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + var tailnet tsapi.Tailnet + err := r.Get(ctx, req.NamespacedName, &tailnet) + switch { + case apierrors.IsNotFound(err): + return reconcile.Result{}, nil + case err != nil: + return reconcile.Result{}, fmt.Errorf("failed to get Tailnet %q: %w", req.NamespacedName, err) + } + + if !tailnet.DeletionTimestamp.IsZero() { + return r.delete(ctx, &tailnet) + } + + return r.createOrUpdate(ctx, &tailnet) +} + +func (r *Reconciler) delete(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) { + reconciler.RemoveFinalizer(tailnet) + if err := r.Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to remove finalizer from Tailnet %q: %w", tailnet.Name, err) + } + + r.mu.Lock() + r.tailnets.Remove(tailnet.UID) + r.mu.Unlock() + gaugeTailnetResources.Set(int64(r.tailnets.Len())) + + return reconcile.Result{}, nil +} + +// Constants for condition reasons. +const ( + ReasonInvalidOAuth = "InvalidOAuth" + ReasonInvalidSecret = "InvalidSecret" + ReasonValid = "TailnetValid" +) + +func (r *Reconciler) createOrUpdate(ctx context.Context, tailnet *tsapi.Tailnet) (reconcile.Result, error) { + r.mu.Lock() + r.tailnets.Add(tailnet.UID) + r.mu.Unlock() + gaugeTailnetResources.Set(int64(r.tailnets.Len())) + + name := types.NamespacedName{Name: tailnet.Spec.Credentials.SecretName, Namespace: r.tailscaleNamespace} + + var secret corev1.Secret + err := r.Get(ctx, name, &secret) + + // The referenced Secret does not exist within the tailscale namespace, so we'll mark the Tailnet as not ready + // for use. + if apierrors.IsNotFound(err) { + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionFalse, + ReasonInvalidSecret, + fmt.Sprintf("referenced secret %q does not exist in namespace %q", name.Name, r.tailscaleNamespace), + r.clock, + r.logger, + ) + + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + return reconcile.Result{}, nil + } + + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to get secret %q: %w", name, err) + } + + // We first ensure that the referenced secret contains the required fields. Otherwise, we set the Tailnet as + // invalid. The operator will not allow the use of this Tailnet while it is in an invalid state. + if ok := r.ensureSecret(tailnet, &secret); !ok { + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + return reconcile.Result{RequeueAfter: time.Minute / 2}, nil + } + + tsClient := r.createClient(ctx, tailnet, &secret) + + // Second, we ensure the OAuth credentials supplied in the secret are valid and have the required scopes to access + // the various API endpoints required by the operator. + if ok := r.ensurePermissions(ctx, tsClient, tailnet); !ok { + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + // We provide a requeue duration here as a user will likely want to go and modify their scopes and come back. + // This should save them having to delete and recreate the resource. + return reconcile.Result{RequeueAfter: time.Minute / 2}, nil + } + + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionTrue, + ReasonValid, + ReasonValid, + r.clock, + r.logger, + ) + + if err = r.Status().Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to update Tailnet status for %q: %w", tailnet.Name, err) + } + + reconciler.SetFinalizer(tailnet) + if err = r.Update(ctx, tailnet); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to add finalizer to Tailnet %q: %w", tailnet.Name, err) + } + + return reconcile.Result{}, nil +} + +// Constants for OAuth credential fields within the Secret referenced by the Tailnet. +const ( + clientIDKey = "client_id" + clientSecretKey = "client_secret" +) + +func (r *Reconciler) createClient(ctx context.Context, tailnet *tsapi.Tailnet, secret *corev1.Secret) TailscaleClient { + if r.clientFunc != nil { + return r.clientFunc(tailnet, secret) + } + + baseURL := ipn.DefaultControlURL + if tailnet.Spec.LoginURL != "" { + baseURL = tailnet.Spec.LoginURL + } + + credentials := clientcredentials.Config{ + ClientID: string(secret.Data[clientIDKey]), + ClientSecret: string(secret.Data[clientSecretKey]), + TokenURL: baseURL + "/api/v2/oauth/token", + } + + source := credentials.TokenSource(ctx) + httpClient := oauth2.NewClient(ctx, source) + + tsClient := tailscale.NewClient("-", nil) + tsClient.UserAgent = "tailscale-k8s-operator" + tsClient.HTTPClient = httpClient + tsClient.BaseURL = baseURL + + return tsClient +} + +func (r *Reconciler) ensurePermissions(ctx context.Context, tsClient TailscaleClient, tailnet *tsapi.Tailnet) bool { + // Perform basic list requests here to confirm that the OAuth credentials referenced on the Tailnet resource + // can perform the basic operations required for the operator to function. This has a caveat of only performing + // read actions, as we don't want to create arbitrary keys and VIP services. However, it will catch when a user + // has completely forgotten an entire scope that's required. + var errs error + if _, err := tsClient.Devices(ctx, nil); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to list devices: %w", err)) + } + + if _, err := tsClient.Keys(ctx); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to list auth keys: %w", err)) + } + + if _, err := tsClient.ListVIPServices(ctx); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to list tailscale services: %w", err)) + } + + if errs != nil { + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionFalse, + ReasonInvalidOAuth, + errs.Error(), + r.clock, + r.logger, + ) + + return false + } + + return true +} + +func (r *Reconciler) ensureSecret(tailnet *tsapi.Tailnet, secret *corev1.Secret) bool { + var message string + + switch { + case len(secret.Data) == 0: + message = fmt.Sprintf("Secret %q is empty", secret.Name) + case len(secret.Data[clientIDKey]) == 0: + message = fmt.Sprintf("Secret %q is missing the client_id field", secret.Name) + case len(secret.Data[clientSecretKey]) == 0: + message = fmt.Sprintf("Secret %q is missing the client_secret field", secret.Name) + } + + if message == "" { + return true + } + + operatorutils.SetTailnetCondition( + tailnet, + tsapi.TailnetReady, + metav1.ConditionFalse, + ReasonInvalidSecret, + message, + r.clock, + r.logger, + ) + + return false +} diff --git a/k8s-operator/reconciler/tailnet/tailnet_test.go b/k8s-operator/reconciler/tailnet/tailnet_test.go new file mode 100644 index 0000000000000..471752b86080d --- /dev/null +++ b/k8s-operator/reconciler/tailnet/tailnet_test.go @@ -0,0 +1,411 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package tailnet_test + +import ( + "testing" + + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/tailnet" + "tailscale.com/tstest" +) + +func TestReconciler_Reconcile(t *testing.T) { + t.Parallel() + clock := tstest.NewClock(tstest.ClockOpts{}) + logger, err := zap.NewDevelopment() + if err != nil { + t.Fatal(err) + } + + tt := []struct { + Name string + Request reconcile.Request + Tailnet *tsapi.Tailnet + Secret *corev1.Secret + ExpectsError bool + ExpectedConditions []metav1.Condition + ClientFunc func(*tsapi.Tailnet, *corev1.Secret) tailnet.TailscaleClient + }{ + { + Name: "ignores unknown tailnet requests", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + }, + { + Name: "invalid status for missing secret", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `referenced secret "test" does not exist in namespace "tailscale"`, + }, + }, + }, + { + Name: "invalid status for empty secret", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `Secret "test" is empty`, + }, + }, + }, + { + Name: "invalid status for missing client id", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_secret": []byte("test"), + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `Secret "test" is missing the client_id field`, + }, + }, + }, + { + Name: "invalid status for missing client secret", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + }, + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidSecret, + Message: `Secret "test" is missing the client_secret field`, + }, + }, + }, + { + Name: "invalid status for bad devices scope", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{ErrorOnDevices: true} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidOAuth, + Message: `failed to list devices: EOF`, + }, + }, + }, + { + Name: "invalid status for bad services scope", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{ErrorOnServices: true} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidOAuth, + Message: `failed to list tailscale services: EOF`, + }, + }, + }, + { + Name: "invalid status for bad keys scope", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{ErrorOnKeys: true} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionFalse, + Reason: tailnet.ReasonInvalidOAuth, + Message: `failed to list auth keys: EOF`, + }, + }, + }, + { + Name: "ready when valid and scopes are correct", + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "default", + }, + }, + Tailnet: &tsapi.Tailnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: tsapi.TailnetSpec{ + Credentials: tsapi.TailnetCredentials{ + SecretName: "test", + }, + }, + }, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "tailscale", + }, + Data: map[string][]byte{ + "client_id": []byte("test"), + "client_secret": []byte("test"), + }, + }, + ClientFunc: func(_ *tsapi.Tailnet, _ *corev1.Secret) tailnet.TailscaleClient { + return &MockTailnetClient{} + }, + ExpectedConditions: []metav1.Condition{ + { + Type: string(tsapi.TailnetReady), + Status: metav1.ConditionTrue, + Reason: tailnet.ReasonValid, + Message: tailnet.ReasonValid, + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + builder := fake.NewClientBuilder().WithScheme(tsapi.GlobalScheme) + if tc.Tailnet != nil { + builder = builder.WithObjects(tc.Tailnet).WithStatusSubresource(tc.Tailnet) + } + if tc.Secret != nil { + builder = builder.WithObjects(tc.Secret) + } + + fc := builder.Build() + opts := tailnet.ReconcilerOptions{ + Client: fc, + Clock: clock, + Logger: logger.Sugar(), + ClientFunc: tc.ClientFunc, + TailscaleNamespace: "tailscale", + } + + reconciler := tailnet.NewReconciler(opts) + _, err = reconciler.Reconcile(t.Context(), tc.Request) + if tc.ExpectsError && err == nil { + t.Fatalf("expected error, got none") + } + + if !tc.ExpectsError && err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(tc.ExpectedConditions) == 0 { + return + } + + var tn tsapi.Tailnet + if err = fc.Get(t.Context(), tc.Request.NamespacedName, &tn); err != nil { + t.Fatal(err) + } + + if len(tn.Status.Conditions) != len(tc.ExpectedConditions) { + t.Fatalf("expected %v condition(s), got %v", len(tc.ExpectedConditions), len(tn.Status.Conditions)) + } + + for i, expected := range tc.ExpectedConditions { + actual := tn.Status.Conditions[i] + + if actual.Type != expected.Type { + t.Errorf("expected %v, got %v", expected.Type, actual.Type) + } + + if actual.Status != expected.Status { + t.Errorf("expected %v, got %v", expected.Status, actual.Status) + } + + if actual.Reason != expected.Reason { + t.Errorf("expected %v, got %v", expected.Reason, actual.Reason) + } + + if actual.Message != expected.Message { + t.Errorf("expected %v, got %v", expected.Message, actual.Message) + } + } + + if err = fc.Delete(t.Context(), &tn); err != nil { + t.Fatal(err) + } + + if _, err = reconciler.Reconcile(t.Context(), tc.Request); err != nil { + t.Fatal(err) + } + + err = fc.Get(t.Context(), tc.Request.NamespacedName, &tn) + if !apierrors.IsNotFound(err) { + t.Fatalf("expected not found error, got %v", err) + } + }) + } +} diff --git a/kube/kubetypes/types.go b/kube/kubetypes/types.go index 44b01fe1ad1f5..b8b94a4b21a5d 100644 --- a/kube/kubetypes/types.go +++ b/kube/kubetypes/types.go @@ -33,6 +33,7 @@ const ( MetricProxyGroupEgressCount = "k8s_proxygroup_egress_resources" MetricProxyGroupIngressCount = "k8s_proxygroup_ingress_resources" MetricProxyGroupAPIServerCount = "k8s_proxygroup_kube_apiserver_resources" + MetricTailnetCount = "k8s_tailnet_resources" // Keys that containerboot writes to state file that can be used to determine its state. // fields set in Tailscale state Secret. These are mostly used by the Tailscale Kubernetes operator to determine From 6dc0bd834c0858332aff1579bc4559934f6d242c Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 12 Jan 2026 11:43:41 -0800 Subject: [PATCH 013/202] util/limiter: don't panic when dumping a new Limiter Fixes #18439 Signed-off-by: Josh Bleecher Snyder --- util/limiter/limiter.go | 3 +++ util/limiter/limiter_test.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/util/limiter/limiter.go b/util/limiter/limiter.go index b86efdf29cfd0..b5fbb6fa6b2f7 100644 --- a/util/limiter/limiter.go +++ b/util/limiter/limiter.go @@ -187,6 +187,9 @@ func (lm *Limiter[K]) collectDump(now time.Time) []dumpEntry[K] { lm.mu.Lock() defer lm.mu.Unlock() + if lm.cache == nil { + return nil + } ret := make([]dumpEntry[K], 0, lm.cache.Len()) lm.cache.ForEach(func(k K, v *bucket) { lm.updateBucketLocked(v, now) // so stats are accurate diff --git a/util/limiter/limiter_test.go b/util/limiter/limiter_test.go index 77b1d562b23fb..d3f3e307a2b82 100644 --- a/util/limiter/limiter_test.go +++ b/util/limiter/limiter_test.go @@ -5,6 +5,7 @@ package limiter import ( "bytes" + "io" "strings" "testing" "time" @@ -175,6 +176,10 @@ func TestDumpHTML(t *testing.T) { } } +func TestDumpHTMLEmpty(t *testing.T) { + new(Limiter[string]).DumpHTML(io.Discard, false) // should not panic +} + func allowed(t *testing.T, limiter *Limiter[string], key string, count int, now time.Time) { t.Helper() for i := range count { From 4b7585df77e593ec6e57d9f55ce1296dc5bc6aaf Mon Sep 17 00:00:00 2001 From: Alex Valiushko Date: Wed, 21 Jan 2026 21:55:37 -0800 Subject: [PATCH 014/202] net/udprelay: add tailscaled_peer_relay_endpoints gauge (#18265) New gauge reflects endpoints state via labels: - open, when both peers are connected and ready to talk, and - connecting. when at least one peer hasn't connected yet. Corresponding client metrics are logged as - udprelay_endpoints_connecting - udprelay_endpoints_open Updates tailscale/corp#30820 Change-Id: Idb1baa90a38c97847e14f9b2390093262ad0ea23 Signed-off-by: Alex Valiushko --- net/udprelay/metrics.go | 59 ++++++++++++++++++++++- net/udprelay/metrics_test.go | 57 +++++++++++++++++++++- net/udprelay/server.go | 92 ++++++++++++++++++++++++++++-------- net/udprelay/server_test.go | 74 +++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 24 deletions(-) diff --git a/net/udprelay/metrics.go b/net/udprelay/metrics.go index b7c0710c2afc1..235029bf425ce 100644 --- a/net/udprelay/metrics.go +++ b/net/udprelay/metrics.go @@ -22,6 +22,17 @@ var ( cMetricForwarded46Bytes = clientmetric.NewAggregateCounter("udprelay_forwarded_bytes_udp4_udp6") cMetricForwarded64Bytes = clientmetric.NewAggregateCounter("udprelay_forwarded_bytes_udp6_udp4") cMetricForwarded66Bytes = clientmetric.NewAggregateCounter("udprelay_forwarded_bytes_udp6_udp6") + + // cMetricEndpoints is initialized here with no other writes, making it safe for concurrent reads. + // + // [clientmetric.Gauge] does not let us embed existing counters, so + // [metrics.updateEndpoint] records data into client and user gauges independently. + // + // Transitions to and from [endpointClosed] are not recorded. + cMetricEndpoints = map[endpointState]*clientmetric.Metric{ + endpointConnecting: clientmetric.NewGauge("udprelay_endpoints_connecting"), + endpointOpen: clientmetric.NewGauge("udprelay_endpoints_open"), + } ) type transport string @@ -36,6 +47,10 @@ type forwardedLabel struct { transportOut transport `prom:"transport_out"` } +type endpointLabel struct { + state endpointState `prom:"state"` +} + type metrics struct { forwarded44Packets expvar.Int forwarded46Packets expvar.Int @@ -46,6 +61,11 @@ type metrics struct { forwarded46Bytes expvar.Int forwarded64Bytes expvar.Int forwarded66Bytes expvar.Int + + // endpoints are set in [registerMetrics] and safe for concurrent reads. + // + // Transitions to and from [endpointClosed] are not recorded + endpoints map[endpointState]*expvar.Int } // registerMetrics publishes user and client metric counters for peer relay server. @@ -65,6 +85,12 @@ func registerMetrics(reg *usermetric.Registry) *metrics { "counter", "Number of bytes forwarded via Peer Relay", ) + uMetricEndpoints = usermetric.NewMultiLabelMapWithRegistry[endpointLabel]( + reg, + "tailscaled_peer_relay_endpoints", + "gauge", + "Number of allocated Peer Relay endpoints", + ) forwarded44 = forwardedLabel{transportIn: transportUDP4, transportOut: transportUDP4} forwarded46 = forwardedLabel{transportIn: transportUDP4, transportOut: transportUDP6} forwarded64 = forwardedLabel{transportIn: transportUDP6, transportOut: transportUDP4} @@ -83,6 +109,13 @@ func registerMetrics(reg *usermetric.Registry) *metrics { uMetricForwardedBytes.Set(forwarded64, &m.forwarded64Bytes) uMetricForwardedBytes.Set(forwarded66, &m.forwarded66Bytes) + m.endpoints = map[endpointState]*expvar.Int{ + endpointConnecting: {}, + endpointOpen: {}, + } + uMetricEndpoints.Set(endpointLabel{endpointOpen}, m.endpoints[endpointOpen]) + uMetricEndpoints.Set(endpointLabel{endpointConnecting}, m.endpoints[endpointConnecting]) + // Publish client metrics. cMetricForwarded44Packets.Register(&m.forwarded44Packets) cMetricForwarded46Packets.Register(&m.forwarded46Packets) @@ -96,6 +129,26 @@ func registerMetrics(reg *usermetric.Registry) *metrics { return m } +type endpointUpdater interface { + updateEndpoint(before, after endpointState) +} + +// updateEndpoint updates the endpoints gauge according to states left and entered. +// It records client-metric gauges independently, see [cMetricEndpoints] doc. +func (m *metrics) updateEndpoint(before, after endpointState) { + if before == after { + return + } + if uMetricEndpointsBefore, ok := m.endpoints[before]; ok && before != endpointClosed { + uMetricEndpointsBefore.Add(-1) + cMetricEndpoints[before].Add(-1) + } + if uMetricEndpointsAfter, ok := m.endpoints[after]; ok && after != endpointClosed { + uMetricEndpointsAfter.Add(1) + cMetricEndpoints[after].Add(1) + } +} + // countForwarded records user and client metrics according to the // inbound and outbound address families. func (m *metrics) countForwarded(in4, out4 bool, bytes, packets int64) { @@ -114,8 +167,7 @@ func (m *metrics) countForwarded(in4, out4 bool, bytes, packets int64) { } } -// deregisterMetrics unregisters the underlying expvar counters -// from clientmetrics. +// deregisterMetrics clears clientmetrics counters and resets gauges to zero. func deregisterMetrics() { cMetricForwarded44Packets.UnregisterAll() cMetricForwarded46Packets.UnregisterAll() @@ -125,4 +177,7 @@ func deregisterMetrics() { cMetricForwarded46Bytes.UnregisterAll() cMetricForwarded64Bytes.UnregisterAll() cMetricForwarded66Bytes.UnregisterAll() + for _, v := range cMetricEndpoints { + v.Set(0) + } } diff --git a/net/udprelay/metrics_test.go b/net/udprelay/metrics_test.go index 5c6a751134e8b..0b7650534f884 100644 --- a/net/udprelay/metrics_test.go +++ b/net/udprelay/metrics_test.go @@ -4,6 +4,7 @@ package udprelay import ( + "fmt" "slices" "testing" @@ -11,7 +12,7 @@ import ( "tailscale.com/util/usermetric" ) -func TestMetrics(t *testing.T) { +func TestMetricsLifecycle(t *testing.T) { c := qt.New(t) deregisterMetrics() r := &usermetric.Registry{} @@ -22,6 +23,7 @@ func TestMetrics(t *testing.T) { want := []string{ "tailscaled_peer_relay_forwarded_packets_total", "tailscaled_peer_relay_forwarded_bytes_total", + "tailscaled_peer_relay_endpoints", } slices.Sort(have) slices.Sort(want) @@ -51,4 +53,57 @@ func TestMetrics(t *testing.T) { c.Assert(m.forwarded66Packets.Value(), qt.Equals, int64(4)) c.Assert(cMetricForwarded66Bytes.Value(), qt.Equals, int64(4)) c.Assert(cMetricForwarded66Packets.Value(), qt.Equals, int64(4)) + + // Validate client metrics deregistration. + m.updateEndpoint(endpointClosed, endpointOpen) + deregisterMetrics() + c.Check(cMetricForwarded44Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded44Packets.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded46Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded46Packets.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded64Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded64Packets.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded66Bytes.Value(), qt.Equals, int64(0)) + c.Check(cMetricForwarded66Packets.Value(), qt.Equals, int64(0)) + for k := range cMetricEndpoints { + c.Check(cMetricEndpoints[k].Value(), qt.Equals, int64(0)) + } +} + +func TestMetricsEndpointTransitions(t *testing.T) { + c := qt.New(t) + var states = []endpointState{ + endpointClosed, + endpointConnecting, + endpointOpen, + } + for _, a := range states { + for _, b := range states { + t.Run(fmt.Sprintf("%s-%s", a, b), func(t *testing.T) { + deregisterMetrics() + r := &usermetric.Registry{} + m := registerMetrics(r) + m.updateEndpoint(a, b) + var wantA, wantB int64 + switch { + case a == b: + wantA, wantB = 0, 0 + case a == endpointClosed: + wantA, wantB = 0, 1 + case b == endpointClosed: + wantA, wantB = -1, 0 + default: + wantA, wantB = -1, 1 + } + if a != endpointClosed { + c.Check(m.endpoints[a].Value(), qt.Equals, wantA) + c.Check(cMetricEndpoints[a].Value(), qt.Equals, wantA) + } + if b != endpointClosed { + c.Check(m.endpoints[b].Value(), qt.Equals, wantB) + c.Check(cMetricEndpoints[b].Value(), qt.Equals, wantB) + } + }) + } + } } diff --git a/net/udprelay/server.go b/net/udprelay/server.go index 2b6d389232832..38ee04df9e1ca 100644 --- a/net/udprelay/server.go +++ b/net/udprelay/server.go @@ -122,6 +122,7 @@ type serverEndpoint struct { allocatedAt mono.Time mu sync.Mutex // guards the following fields + closed bool // signals that no new data should be accepted inProgressGeneration [2]uint32 // or zero if a handshake has never started, or has just completed boundAddrPorts [2]netip.AddrPort // or zero value if a handshake has never completed for that relay leg lastSeen [2]mono.Time @@ -151,9 +152,15 @@ func blakeMACFromBindMsg(blakeKey [blake2s.Size]byte, src netip.AddrPort, msg di return out, nil } -func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex int, discoMsg disco.Message, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time) (write []byte, to netip.AddrPort) { +func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex int, discoMsg disco.Message, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time, m endpointUpdater) (write []byte, to netip.AddrPort) { e.mu.Lock() defer e.mu.Unlock() + lastState := e.stateLocked() + + if lastState == endpointClosed { + // endpoint was closed in [Server.endpointGC] + return nil, netip.AddrPort{} + } if senderIndex != 0 && senderIndex != 1 { return nil, netip.AddrPort{} @@ -230,6 +237,7 @@ func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex if bytes.Equal(mac[:], discoMsg.Challenge[:]) { // Handshake complete. Update the binding for this sender. e.boundAddrPorts[senderIndex] = from + m.updateEndpoint(lastState, e.stateLocked()) e.lastSeen[senderIndex] = now // record last seen as bound time e.inProgressGeneration[senderIndex] = 0 // reset to zero, which indicates there is no in-progress handshake return nil, netip.AddrPort{} @@ -243,7 +251,7 @@ func (e *serverEndpoint) handleDiscoControlMsg(from netip.AddrPort, senderIndex } } -func (e *serverEndpoint) handleSealedDiscoControlMsg(from netip.AddrPort, b []byte, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time) (write []byte, to netip.AddrPort) { +func (e *serverEndpoint) handleSealedDiscoControlMsg(from netip.AddrPort, b []byte, serverDisco key.DiscoPublic, macSecrets views.Slice[[blake2s.Size]byte], now mono.Time, m endpointUpdater) (write []byte, to netip.AddrPort) { senderRaw, isDiscoMsg := disco.Source(b) if !isDiscoMsg { // Not a Disco message @@ -274,7 +282,7 @@ func (e *serverEndpoint) handleSealedDiscoControlMsg(from netip.AddrPort, b []by return nil, netip.AddrPort{} } - return e.handleDiscoControlMsg(from, senderIndex, discoMsg, serverDisco, macSecrets, now) + return e.handleDiscoControlMsg(from, senderIndex, discoMsg, serverDisco, macSecrets, now, m) } func (e *serverEndpoint) handleDataPacket(from netip.AddrPort, b []byte, now mono.Time) (write []byte, to netip.AddrPort) { @@ -284,6 +292,10 @@ func (e *serverEndpoint) handleDataPacket(from netip.AddrPort, b []byte, now mon // not a control packet, but serverEndpoint isn't bound return nil, netip.AddrPort{} } + if e.stateLocked() == endpointClosed { + // endpoint was closed in [Server.endpointGC] + return nil, netip.AddrPort{} + } switch { case from == e.boundAddrPorts[0]: e.lastSeen[0] = now @@ -301,9 +313,21 @@ func (e *serverEndpoint) handleDataPacket(from netip.AddrPort, b []byte, now mon } } -func (e *serverEndpoint) isExpired(now mono.Time, bindLifetime, steadyStateLifetime time.Duration) bool { +// maybeExpire checks if the endpoint has expired according to the provided timeouts and sets its closed state accordingly. +// True is returned if the endpoint was expired and closed. +func (e *serverEndpoint) maybeExpire(now mono.Time, bindLifetime, steadyStateLifetime time.Duration, m endpointUpdater) bool { e.mu.Lock() defer e.mu.Unlock() + before := e.stateLocked() + if e.isExpiredLocked(now, bindLifetime, steadyStateLifetime) { + e.closed = true + m.updateEndpoint(before, e.stateLocked()) + return true + } + return false +} + +func (e *serverEndpoint) isExpiredLocked(now mono.Time, bindLifetime, steadyStateLifetime time.Duration) bool { if !e.isBoundLocked() { if now.Sub(e.allocatedAt) > bindLifetime { return true @@ -323,6 +347,31 @@ func (e *serverEndpoint) isBoundLocked() bool { e.boundAddrPorts[1].IsValid() } +// stateLocked returns current endpointState according to the +// peers handshake status. +func (e *serverEndpoint) stateLocked() endpointState { + switch { + case e == nil, e.closed: + return endpointClosed + case e.boundAddrPorts[0].IsValid() && e.boundAddrPorts[1].IsValid(): + return endpointOpen + default: + return endpointConnecting + } +} + +// endpointState canonicalizes endpoint state names, +// see [serverEndpoint.stateLocked]. +// +// Usermetrics can't handle Stringer, must be a string enum. +type endpointState string + +const ( + endpointClosed endpointState = "closed" // unallocated, not tracked in metrics + endpointConnecting endpointState = "connecting" // at least one peer has not completed handshake + endpointOpen endpointState = "open" // ready to forward +) + // NewServer constructs a [Server] listening on port. If port is zero, then // port selection is left up to the host networking stack. If // onlyStaticAddrPorts is true, then dynamic addr:port discovery will be @@ -703,33 +752,33 @@ func (s *Server) Close() error { clear(s.serverEndpointByDisco) s.closed = true s.bus.Close() + deregisterMetrics() }) return nil } +func (s *Server) endpointGC(bindLifetime, steadyStateLifetime time.Duration) { + now := mono.Now() + // TODO: consider performance implications of scanning all endpoints and + // holding s.mu for the duration. Keep it simple (and slow) for now. + s.mu.Lock() + defer s.mu.Unlock() + for k, v := range s.serverEndpointByDisco { + if v.maybeExpire(now, bindLifetime, steadyStateLifetime, s.metrics) { + delete(s.serverEndpointByDisco, k) + s.serverEndpointByVNI.Delete(v.vni) + } + } +} + func (s *Server) endpointGCLoop() { defer s.wg.Done() ticker := time.NewTicker(s.bindLifetime) defer ticker.Stop() - - gc := func() { - now := mono.Now() - // TODO: consider performance implications of scanning all endpoints and - // holding s.mu for the duration. Keep it simple (and slow) for now. - s.mu.Lock() - defer s.mu.Unlock() - for k, v := range s.serverEndpointByDisco { - if v.isExpired(now, s.bindLifetime, s.steadyStateLifetime) { - delete(s.serverEndpointByDisco, k) - s.serverEndpointByVNI.Delete(v.vni) - } - } - } - for { select { case <-ticker.C: - gc() + s.endpointGC(s.bindLifetime, s.steadyStateLifetime) case <-s.closeCh: return } @@ -773,7 +822,7 @@ func (s *Server) handlePacket(from netip.AddrPort, b []byte) (write []byte, to n } msg := b[packet.GeneveFixedHeaderLength:] secrets := s.getMACSecrets(now) - write, to = e.(*serverEndpoint).handleSealedDiscoControlMsg(from, msg, s.discoPublic, secrets, now) + write, to = e.(*serverEndpoint).handleSealedDiscoControlMsg(from, msg, s.discoPublic, secrets, now, s.metrics) isDataPacket = false return } @@ -1015,6 +1064,7 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv s.serverEndpointByVNI.Store(e.vni, e) s.logf("allocated endpoint vni=%d lamportID=%d disco[0]=%v disco[1]=%v", e.vni, e.lamportID, pair.Get()[0].ShortString(), pair.Get()[1].ShortString()) + s.metrics.updateEndpoint(endpointClosed, endpointConnecting) return endpoint.ServerEndpoint{ ServerDisco: s.discoPublic, ClientDisco: pair.Get(), diff --git a/net/udprelay/server_test.go b/net/udprelay/server_test.go index 59917e1c6ef52..cb6b05eea2108 100644 --- a/net/udprelay/server_test.go +++ b/net/udprelay/server_test.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "net" "net/netip" + "sync" "testing" "time" @@ -21,6 +22,7 @@ import ( "tailscale.com/tstime/mono" "tailscale.com/types/key" "tailscale.com/types/views" + "tailscale.com/util/mak" "tailscale.com/util/usermetric" ) @@ -471,3 +473,75 @@ func TestServer_maybeRotateMACSecretLocked(t *testing.T) { qt.Assert(t, macSecret, qt.Not(qt.Equals), s.macSecrets.At(1)) qt.Assert(t, s.macSecrets.At(0), qt.Not(qt.Equals), s.macSecrets.At(1)) } + +func TestServer_endpointGC(t *testing.T) { + for _, tc := range []struct { + name string + addrs [2]netip.AddrPort + lastSeen [2]mono.Time + allocatedAt mono.Time + wantRemoved bool + }{ + { + name: "unbound_endpoint_expired", + allocatedAt: mono.Now().Add(-2 * defaultBindLifetime), + wantRemoved: true, + }, + { + name: "unbound_endpoint_kept", + allocatedAt: mono.Now(), + wantRemoved: false, + }, + { + name: "bound_endpoint_expired_a", + addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")}, + lastSeen: [2]mono.Time{mono.Now().Add(-2 * defaultSteadyStateLifetime), mono.Now()}, + wantRemoved: true, + }, + { + name: "bound_endpoint_expired_b", + addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")}, + lastSeen: [2]mono.Time{mono.Now(), mono.Now().Add(-2 * defaultSteadyStateLifetime)}, + wantRemoved: true, + }, + { + name: "bound_endpoint_kept", + addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")}, + lastSeen: [2]mono.Time{mono.Now(), mono.Now()}, + wantRemoved: false, + }, + } { + t.Run(tc.name, func(t *testing.T) { + disco1 := key.NewDisco() + disco2 := key.NewDisco() + pair := key.NewSortedPairOfDiscoPublic(disco1.Public(), disco2.Public()) + ep := &serverEndpoint{ + discoPubKeys: pair, + vni: 1, + lastSeen: tc.lastSeen, + boundAddrPorts: tc.addrs, + allocatedAt: tc.allocatedAt, + } + s := &Server{serverEndpointByVNI: sync.Map{}, metrics: &metrics{}} + mak.Set(&s.serverEndpointByDisco, pair, ep) + s.serverEndpointByVNI.Store(ep.vni, ep) + s.endpointGC(defaultBindLifetime, defaultSteadyStateLifetime) + removed := len(s.serverEndpointByDisco) > 0 + if tc.wantRemoved { + if removed { + t.Errorf("expected endpoint to be removed from Server") + } + if !ep.closed { + t.Errorf("expected endpoint to be closed") + } + } else { + if !removed { + t.Errorf("expected endpoint to remain in Server") + } + if ep.closed { + t.Errorf("expected endpoint to remain open") + } + } + }) + } +} From 151644f647d9388bb4cb1ae1c4c155b8d8de4cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20Lensb=C3=B8l?= Date: Thu, 22 Jan 2026 14:50:24 -0500 Subject: [PATCH 015/202] wgengine: send disco key via TSMP on first contact (#18215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we have not yet communicated with a peer, send a TSMPDiscoAdvertisement to let the peer know of our disco key. This is in most cases redundant, but will allow us to set up direct connections when the client cannot access control. Some parts taken from: #18073 Updates #12639 Signed-off-by: Claus Lensbøl --- wgengine/magicsock/endpoint.go | 1 + wgengine/magicsock/magicsock.go | 61 ++++++++++++++++++++++++++++++--- wgengine/userspace.go | 9 +++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go index eda589e14b1b6..586a2dc75c5cc 100644 --- a/wgengine/magicsock/endpoint.go +++ b/wgengine/magicsock/endpoint.go @@ -80,6 +80,7 @@ type endpoint struct { lastSendAny mono.Time // last time there were outgoing packets sent this peer from any trigger, internal or external to magicsock lastFullPing mono.Time // last time we pinged all disco or wireguard only endpoints lastUDPRelayPathDiscovery mono.Time // last time we ran UDP relay path discovery + sentDiscoKeyAdvertisement bool // wether we sent a TSMPDiscoAdvertisement or not to this endpoint derpAddr netip.AddrPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients) bestAddr addrQuality // best non-DERP path; zero if none; mutate via setBestAddrLocked() diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 8fbd07013797d..1c13093478a2e 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -179,9 +179,10 @@ type Conn struct { // A publisher for synchronization points to ensure correct ordering of // config changes between magicsock and wireguard. - syncPub *eventbus.Publisher[syncPoint] - allocRelayEndpointPub *eventbus.Publisher[UDPRelayAllocReq] - portUpdatePub *eventbus.Publisher[router.PortUpdate] + syncPub *eventbus.Publisher[syncPoint] + allocRelayEndpointPub *eventbus.Publisher[UDPRelayAllocReq] + portUpdatePub *eventbus.Publisher[router.PortUpdate] + tsmpDiscoKeyAvailablePub *eventbus.Publisher[NewDiscoKeyAvailable] // pconn4 and pconn6 are the underlying UDP sockets used to // send/receive packets for wireguard and other magicsock @@ -696,6 +697,7 @@ func NewConn(opts Options) (*Conn, error) { c.syncPub = eventbus.Publish[syncPoint](ec) c.allocRelayEndpointPub = eventbus.Publish[UDPRelayAllocReq](ec) c.portUpdatePub = eventbus.Publish[router.PortUpdate](ec) + c.tsmpDiscoKeyAvailablePub = eventbus.Publish[NewDiscoKeyAvailable](ec) eventbus.SubscribeFunc(ec, c.onPortMapChanged) eventbus.SubscribeFunc(ec, c.onFilterUpdate) eventbus.SubscribeFunc(ec, c.onNodeViewsUpdate) @@ -1249,7 +1251,8 @@ func (c *Conn) DiscoPublicKey() key.DiscoPublic { // RotateDiscoKey generates a new discovery key pair and updates the connection // to use it. This invalidates all existing disco sessions and will cause peers -// to re-establish discovery sessions with the new key. +// to re-establish discovery sessions with the new key. Addtionally, the +// lastTSMPDiscoAdvertisement on all endpoints is reset to 0. // // This is primarily for debugging and testing purposes, a future enhancement // should provide a mechanism for seamless rotation by supporting short term use @@ -1263,6 +1266,11 @@ func (c *Conn) RotateDiscoKey() { newShort := c.discoAtomic.Short() c.discoInfo = make(map[key.DiscoPublic]*discoInfo) connCtx := c.connCtx + for _, endpoint := range c.peerMap.byEpAddr { + endpoint.ep.mu.Lock() + endpoint.ep.sentDiscoKeyAdvertisement = false + endpoint.ep.mu.Unlock() + } c.mu.Unlock() c.logf("magicsock: rotated disco key from %v to %v", oldShort, newShort) @@ -2247,6 +2255,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src epAddr, shouldBeRelayHandshake if debugDisco() { c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?) via %s", sender, via) } + metricRecvDiscoBadKey.Add(1) return } @@ -2654,6 +2663,8 @@ func (c *Conn) enqueueCallMeMaybe(derpAddr netip.AddrPort, de *endpoint) { return } + c.maybeSendTSMPDiscoAdvert(de) + eps := make([]netip.AddrPort, 0, len(c.lastEndpoints)) for _, ep := range c.lastEndpoints { eps = append(eps, ep.Addr) @@ -4314,3 +4325,45 @@ func (c *Conn) HandleDiscoKeyAdvertisement(node tailcfg.NodeView, update packet. c.logf("magicsock: updated disco key for peer %v to %v", nodeKey.ShortString(), discoKey.ShortString()) metricTSMPDiscoKeyAdvertisementApplied.Add(1) } + +// NewDiscoKeyAvailable is an eventbus topic that is emitted when we're sending +// a packet to a node and observe we haven't told it our current DiscoKey before. +// +// The publisher is magicsock, when we're sending a packet. +// The subscriber is userspaceEngine, which sends a TSMP packet, also via +// magicsock. This doesn't recurse infinitely because we only publish it once per +// DiscoKey. +// In the common case, a DiscoKey is not rotated within a process generation +// (as of 2026-01-21), except with debug commands to simulate process restarts. +// +// The address is the first node address (tailscale address) of the node. It +// does not matter if the address is v4/v6, the receiver should handle either. +// +// Since we have not yet communicated with the node at the time we are +// sending this event, the resulting TSMPDiscoKeyAdvertisement will with all +// likelihood be transmitted via DERP. +type NewDiscoKeyAvailable struct { + NodeFirstAddr netip.Addr + NodeID tailcfg.NodeID +} + +// maybeSendTSMPDiscoAdvert conditionally emits an event indicating that we +// should send our DiscoKey to the first node address of the magicksock endpoint. +// The event is only emitted if we have not yet contacted that endpoint since +// the DiscoKey changed. +// +// This condition is most likely met only once per endpoint, after the start of +// tailscaled, but not until we contact the endpoint for the first time. +// +// We do not need the Conn to be locked, but the endpoint should be. +func (c *Conn) maybeSendTSMPDiscoAdvert(de *endpoint) { + de.mu.Lock() + defer de.mu.Unlock() + if !de.sentDiscoKeyAdvertisement { + de.sentDiscoKeyAdvertisement = true + c.tsmpDiscoKeyAvailablePub.Publish(NewDiscoKeyAvailable{ + NodeFirstAddr: de.nodeAddr, + NodeID: de.nodeID, + }) + } +} diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 875011a9c3e05..dbc8e8b573c49 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -54,6 +54,7 @@ import ( "tailscale.com/util/execqueue" "tailscale.com/util/mak" "tailscale.com/util/set" + "tailscale.com/util/singleflight" "tailscale.com/util/testenv" "tailscale.com/util/usermetric" "tailscale.com/version" @@ -568,6 +569,14 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } e.magicConn.HandleDiscoKeyAdvertisement(peer.Node, pkt) }) + var tsmpRequestGroup singleflight.Group[netip.Addr, struct{}] + eventbus.SubscribeFunc(ec, func(req magicsock.NewDiscoKeyAvailable) { + go tsmpRequestGroup.Do(req.NodeFirstAddr, func() (struct{}, error) { + e.sendTSMPDiscoAdvertisement(req.NodeFirstAddr) + e.logf("wgengine: sending TSMP disco key advertisement to %v", req.NodeFirstAddr) + return struct{}{}, nil + }) + }) e.eventClient = ec e.logf("Engine created.") return e, nil From c062230cce0e0e3d3940578b046d97ceb88128b9 Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Thu, 22 Jan 2026 13:05:37 -0700 Subject: [PATCH 016/202] tsnet: clarify that ListenService starts the server if necessary Every other listen method on tsnet.Server makes this clarification, so should ListenService. Fixes tailscale/corp#36207 Signed-off-by: Harry Harpham --- tsnet/tsnet.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 6c840c335535e..bf7e694df28dd 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -1410,6 +1410,8 @@ var ErrUntaggedServiceHost = errors.New("service hosts must be tagged nodes") // To advertise a Service with multiple ports, run ListenService multiple times. // For more information about Services, see // https://tailscale.com/kb/1552/tailscale-services +// +// This function will start the server if it is not already started. func (s *Server) ListenService(name string, mode ServiceMode) (*ServiceListener, error) { if err := tailcfg.ServiceName(name).Validate(); err != nil { return nil, err From 63d563e7340b4712b9f2933f663057ce2dcfa4a4 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Thu, 15 Jan 2026 20:35:41 -0800 Subject: [PATCH 017/202] tsnet: add support for a user-supplied tun.Device tsnet users can now provide a tun.Device, including any custom implementation that conforms to the interface. netstack has a new option CheckLocalTransportEndpoints that when used alongside a TUN enables netstack listens and dials to correctly capture traffic associated with those sockets. tsnet with a TUN sets this option, while all other builds leave this at false to preserve existing performance. Updates #18423 Signed-off-by: James Tucker --- tsnet/tsnet.go | 88 ++++- tsnet/tsnet_test.go | 673 ++++++++++++++++++++++++++++++++++ wgengine/netstack/netstack.go | 86 ++++- 3 files changed, 842 insertions(+), 5 deletions(-) diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index bf7e694df28dd..d627d55b37314 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -26,6 +26,7 @@ import ( "sync" "time" + "github.com/tailscale/wireguard-go/tun" "tailscale.com/client/local" "tailscale.com/control/controlclient" "tailscale.com/envknob" @@ -167,6 +168,11 @@ type Server struct { // that the control server will allow the node to adopt that tag. AdvertiseTags []string + // Tun, if non-nil, specifies a custom tun.Device to use for packet I/O. + // + // This field must be set before calling Start. + Tun tun.Device + initOnce sync.Once initErr error lb *ipnlocal.LocalBackend @@ -659,6 +665,7 @@ func (s *Server) start() (reterr error) { s.dialer = &tsdial.Dialer{Logf: tsLogf} // mutated below (before used) s.dialer.SetBus(sys.Bus.Get()) eng, err := wgengine.NewUserspaceEngine(tsLogf, wgengine.Config{ + Tun: s.Tun, EventBus: sys.Bus.Get(), ListenPort: s.Port, NetMon: s.netMon, @@ -682,8 +689,16 @@ func (s *Server) start() (reterr error) { } sys.Tun.Get().Start() sys.Set(ns) - ns.ProcessLocalIPs = true - ns.ProcessSubnets = true + if s.Tun == nil { + // Only process packets in netstack when using the default fake TUN. + // When a TUN is provided, let packets flow through it instead. + ns.ProcessLocalIPs = true + ns.ProcessSubnets = true + } else { + // When using a TUN, check gVisor for registered endpoints to handle + // packets for tsnet listeners and outbound connection replies. + ns.CheckLocalTransportEndpoints = true + } ns.GetTCPHandlerForFlow = s.getTCPHandlerForFlow ns.GetUDPHandlerForFlow = s.getUDPHandlerForFlow s.netstack = ns @@ -1072,10 +1087,34 @@ func (s *Server) ListenPacket(network, addr string) (net.PacketConn, error) { network = "udp6" } } - if err := s.Start(); err != nil { + + netLn, err := s.listen(network, addr, listenOnTailnet) + if err != nil { return nil, err } - return s.netstack.ListenPacket(network, ap.String()) + ln := netLn.(*listener) + + pc, err := s.netstack.ListenPacket(network, ap.String()) + if err != nil { + ln.Close() + return nil, err + } + + return &udpPacketConn{ + PacketConn: pc, + ln: ln, + }, nil +} + +// udpPacketConn wraps a net.PacketConn to unregister from s.listeners on Close. +type udpPacketConn struct { + net.PacketConn + ln *listener +} + +func (c *udpPacketConn) Close() error { + c.ln.Close() + return c.PacketConn.Close() } // ListenTLS announces only on the Tailscale network. @@ -1611,10 +1650,37 @@ func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, erro closedc: make(chan struct{}), conn: make(chan net.Conn), } + + // When using a TUN with TCP, create a gVisor TCP listener. + if s.Tun != nil && (network == "" || network == "tcp" || network == "tcp4" || network == "tcp6") { + var nsNetwork string + nsAddr := host + switch { + case network == "tcp4" || network == "tcp6": + nsNetwork = network + case host.Addr().Is4(): + nsNetwork = "tcp4" + case host.Addr().Is6(): + nsNetwork = "tcp6" + default: + // Wildcard address: use tcp6 for dual-stack (accepts both v4 and v6). + nsNetwork = "tcp6" + nsAddr = netip.AddrPortFrom(netip.IPv6Unspecified(), host.Port()) + } + gonetLn, err := s.netstack.ListenTCP(nsNetwork, nsAddr.String()) + if err != nil { + return nil, fmt.Errorf("tsnet: %w", err) + } + ln.gonetLn = gonetLn + } + s.mu.Lock() for _, key := range keys { if _, ok := s.listeners[key]; ok { s.mu.Unlock() + if ln.gonetLn != nil { + ln.gonetLn.Close() + } return nil, fmt.Errorf("tsnet: listener already open for %s, %s", network, addr) } } @@ -1684,9 +1750,17 @@ type listener struct { conn chan net.Conn // unbuffered, never closed closedc chan struct{} // closed on [listener.Close] closed bool // guarded by s.mu + + // gonetLn, if set, is the gonet.Listener that handles new connections. + // gonetLn is set by [listen] when a TUN is in use and terminates the listener. + // gonetLn is nil when TUN is nil. + gonetLn net.Listener } func (ln *listener) Accept() (net.Conn, error) { + if ln.gonetLn != nil { + return ln.gonetLn.Accept() + } select { case c := <-ln.conn: return c, nil @@ -1696,6 +1770,9 @@ func (ln *listener) Accept() (net.Conn, error) { } func (ln *listener) Addr() net.Addr { + if ln.gonetLn != nil { + return ln.gonetLn.Addr() + } return addr{ network: ln.keys[0].network, addr: ln.addr, @@ -1721,6 +1798,9 @@ func (ln *listener) closeLocked() error { } close(ln.closedc) ln.closed = true + if ln.gonetLn != nil { + ln.gonetLn.Close() + } return nil } diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index f44bacab08431..2c6970fa3b723 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -39,6 +39,7 @@ import ( "github.com/google/go-cmp/cmp" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" + "github.com/tailscale/wireguard-go/tun" "golang.org/x/net/proxy" "tailscale.com/client/local" @@ -48,11 +49,13 @@ import ( "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/store/mem" "tailscale.com/net/netns" + "tailscale.com/net/packet" "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/tstest/deptest" "tailscale.com/tstest/integration" "tailscale.com/tstest/integration/testcontrol" + "tailscale.com/types/ipproto" "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/types/views" @@ -1860,6 +1863,676 @@ func mustDirect(t *testing.T, logf logger.Logf, lc1, lc2 *local.Client) { t.Error("magicsock did not find a direct path from lc1 to lc2") } +// chanTUN is a tun.Device for testing that uses channels for packet I/O. +// Inbound receives packets written to the TUN (from the perspective of the network stack). +// Outbound is for injecting packets to be read from the TUN. +type chanTUN struct { + Inbound chan []byte // packets written to TUN + Outbound chan []byte // packets to read from TUN + closed chan struct{} + events chan tun.Event +} + +func newChanTUN() *chanTUN { + t := &chanTUN{ + Inbound: make(chan []byte, 10), + Outbound: make(chan []byte, 10), + closed: make(chan struct{}), + events: make(chan tun.Event, 1), + } + t.events <- tun.EventUp + return t +} + +func (t *chanTUN) File() *os.File { panic("not implemented") } + +func (t *chanTUN) Close() error { + select { + case <-t.closed: + default: + close(t.closed) + close(t.Inbound) + } + return nil +} + +func (t *chanTUN) Read(bufs [][]byte, sizes []int, offset int) (int, error) { + select { + case <-t.closed: + return 0, io.EOF + case pkt := <-t.Outbound: + sizes[0] = copy(bufs[0][offset:], pkt) + return 1, nil + } +} + +func (t *chanTUN) Write(bufs [][]byte, offset int) (int, error) { + for _, buf := range bufs { + pkt := buf[offset:] + if len(pkt) == 0 { + continue + } + select { + case <-t.closed: + return 0, errors.New("closed") + case t.Inbound <- slices.Clone(pkt): + } + } + return len(bufs), nil +} + +func (t *chanTUN) MTU() (int, error) { return 1280, nil } +func (t *chanTUN) Name() (string, error) { return "chantun", nil } +func (t *chanTUN) Events() <-chan tun.Event { return t.events } +func (t *chanTUN) BatchSize() int { return 1 } + +// listenTest provides common setup for listener and TUN tests. +type listenTest struct { + s1, s2 *Server + s1ip4, s1ip6 netip.Addr + s2ip4, s2ip6 netip.Addr + tun *chanTUN // nil for netstack mode +} + +// setupListenTest creates two tsnet servers for testing. +// If useTUN is true, s2 uses a chanTUN; otherwise it uses netstack only. +func setupListenTest(t *testing.T, useTUN bool) *listenTest { + t.Helper() + tstest.Shard(t) + tstest.ResourceCheck(t) + ctx := t.Context() + controlURL, _ := startControl(t) + s1, _, _ := startServer(t, ctx, controlURL, "s1") + + tmp := filepath.Join(t.TempDir(), "s2") + must.Do(os.MkdirAll(tmp, 0755)) + s2 := &Server{ + Dir: tmp, + ControlURL: controlURL, + Hostname: "s2", + Store: new(mem.Store), + Ephemeral: true, + } + + var tun *chanTUN + if useTUN { + tun = newChanTUN() + s2.Tun = tun + } + + if *verboseNodes { + s2.Logf = t.Logf + } + t.Cleanup(func() { s2.Close() }) + + s2status, err := s2.Up(ctx) + if err != nil { + t.Fatal(err) + } + + s1ip4, s1ip6 := s1.TailscaleIPs() + s2ip4 := s2status.TailscaleIPs[0] + var s2ip6 netip.Addr + if len(s2status.TailscaleIPs) > 1 { + s2ip6 = s2status.TailscaleIPs[1] + } + + lc1 := must.Get(s1.LocalClient()) + must.Get(lc1.Ping(ctx, s2ip4, tailcfg.PingTSMP)) + + return &listenTest{ + s1: s1, + s2: s2, + s1ip4: s1ip4, + s1ip6: s1ip6, + s2ip4: s2ip4, + s2ip6: s2ip6, + tun: tun, + } +} + +// echoUDP returns an IP packet with src/dst and ports swapped, with checksums recomputed. +func echoUDP(pkt []byte) []byte { + var p packet.Parsed + p.Decode(pkt) + if p.IPProto != ipproto.UDP { + return nil + } + switch p.IPVersion { + case 4: + h := p.UDP4Header() + h.ToResponse() + return packet.Generate(h, p.Payload()) + case 6: + h := packet.UDP6Header{ + IP6Header: p.IP6Header(), + SrcPort: p.Src.Port(), + DstPort: p.Dst.Port(), + } + h.ToResponse() + return packet.Generate(h, p.Payload()) + } + return nil +} + +func TestTUN(t *testing.T) { + tt := setupListenTest(t, true) + + go func() { + for pkt := range tt.tun.Inbound { + var p packet.Parsed + p.Decode(pkt) + if p.Dst.Port() == 9999 { + tt.tun.Outbound <- echoUDP(pkt) + } + } + }() + + test := func(t *testing.T, s2ip netip.Addr) { + conn, err := tt.s1.Dial(t.Context(), "udp", netip.AddrPortFrom(s2ip, 9999).String()) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + want := "hello from s1" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatal(err) + } + + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + t.Fatalf("reading echo response: %v", err) + } + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + } + + t.Run("IPv4", func(t *testing.T) { test(t, tt.s2ip4) }) + t.Run("IPv6", func(t *testing.T) { test(t, tt.s2ip6) }) +} + +// TestTUNDNS tests that a TUN can send DNS queries to quad-100 and receive +// responses. This verifies that handleLocalPackets intercepts outbound traffic +// to the service IP. +func TestTUNDNS(t *testing.T) { + tt := setupListenTest(t, true) + + test := func(t *testing.T, srcIP netip.Addr, serviceIP netip.Addr) { + tt.tun.Outbound <- buildDNSQuery("s2", srcIP) + + ipVersion := uint8(4) + if srcIP.Is6() { + ipVersion = 6 + } + for { + select { + case pkt := <-tt.tun.Inbound: + var p packet.Parsed + p.Decode(pkt) + if p.IPVersion != ipVersion || p.IPProto != ipproto.UDP { + continue + } + if p.Src.Addr() == serviceIP && p.Src.Port() == 53 { + if len(p.Payload()) < 12 { + t.Fatalf("DNS response too short: %d bytes", len(p.Payload())) + } + return // success + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for DNS response") + } + } + } + + t.Run("IPv4", func(t *testing.T) { + test(t, tt.s2ip4, netip.MustParseAddr("100.100.100.100")) + }) + t.Run("IPv6", func(t *testing.T) { + test(t, tt.s2ip6, netip.MustParseAddr("fd7a:115c:a1e0::53")) + }) +} + +// TestListenPacket tests UDP listeners (ListenPacket) in both netstack and TUN modes. +func TestListenPacket(t *testing.T) { + testListenPacket := func(t *testing.T, lt *listenTest, listenIP netip.Addr) { + pc, err := lt.s2.ListenPacket("udp", netip.AddrPortFrom(listenIP, 0).String()) + if err != nil { + t.Fatal(err) + } + defer pc.Close() + + echoErr := make(chan error, 1) + go func() { + buf := make([]byte, 1500) + n, addr, err := pc.ReadFrom(buf) + if err != nil { + echoErr <- err + return + } + _, err = pc.WriteTo(buf[:n], addr) + if err != nil { + echoErr <- err + return + } + }() + + conn, err := lt.s1.Dial(t.Context(), "udp", pc.LocalAddr().String()) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + want := "hello udp" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatal(err) + } + + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + select { + case e := <-echoErr: + t.Fatalf("echo error: %v; read error: %v", e, err) + default: + t.Fatalf("Read failed: %v", err) + } + } + + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupListenTest(t, false) + t.Run("IPv4", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip4) }) + t.Run("IPv6", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip6) }) + }) + + t.Run("TUN", func(t *testing.T) { + lt := setupListenTest(t, true) + t.Run("IPv4", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip4) }) + t.Run("IPv6", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip6) }) + }) +} + +// TestListenTCP tests TCP listeners with concrete addresses in both netstack +// and TUN modes. +func TestListenTCP(t *testing.T) { + testListenTCP := func(t *testing.T, lt *listenTest, listenIP netip.Addr) { + ln, err := lt.s2.Listen("tcp", netip.AddrPortFrom(listenIP, 0).String()) + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + echoErr := make(chan error, 1) + go func() { + conn, err := ln.Accept() + if err != nil { + echoErr <- err + return + } + defer conn.Close() + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + echoErr <- err + return + } + _, err = conn.Write(buf[:n]) + if err != nil { + echoErr <- err + return + } + }() + + conn, err := lt.s1.Dial(t.Context(), "tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer conn.Close() + + want := "hello tcp" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatalf("Write failed: %v", err) + } + + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + select { + case e := <-echoErr: + t.Fatalf("echo error: %v; read error: %v", e, err) + default: + t.Fatalf("Read failed: %v", err) + } + } + + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupListenTest(t, false) + t.Run("IPv4", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip4) }) + t.Run("IPv6", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip6) }) + }) + + t.Run("TUN", func(t *testing.T) { + lt := setupListenTest(t, true) + t.Run("IPv4", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip4) }) + t.Run("IPv6", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip6) }) + }) +} + +// TestListenTCPDualStack tests TCP listeners with wildcard addresses (dual-stack) +// in both netstack and TUN modes. +func TestListenTCPDualStack(t *testing.T) { + testListenTCPDualStack := func(t *testing.T, lt *listenTest, dialIP netip.Addr) { + ln, err := lt.s2.Listen("tcp", ":0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + _, portStr, err := net.SplitHostPort(ln.Addr().String()) + if err != nil { + t.Fatalf("parsing listener address %q: %v", ln.Addr().String(), err) + } + + echoErr := make(chan error, 1) + go func() { + conn, err := ln.Accept() + if err != nil { + echoErr <- err + return + } + defer conn.Close() + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + echoErr <- err + return + } + _, err = conn.Write(buf[:n]) + if err != nil { + echoErr <- err + return + } + }() + + dialAddr := net.JoinHostPort(dialIP.String(), portStr) + conn, err := lt.s1.Dial(t.Context(), "tcp", dialAddr) + if err != nil { + t.Fatalf("Dial(%q) failed: %v", dialAddr, err) + } + defer conn.Close() + + want := "hello tcp dualstack" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatalf("Write failed: %v", err) + } + + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + select { + case e := <-echoErr: + t.Fatalf("echo error: %v; read error: %v", e, err) + default: + t.Fatalf("Read failed: %v", err) + } + } + + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupListenTest(t, false) + t.Run("DialIPv4", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip4) }) + t.Run("DialIPv6", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip6) }) + }) + + t.Run("TUN", func(t *testing.T) { + lt := setupListenTest(t, true) + t.Run("DialIPv4", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip4) }) + t.Run("DialIPv6", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip6) }) + }) +} + +// TestDialTCP tests TCP dialing from s2 to s1 in both netstack and TUN modes. +// In TUN mode, this verifies that outbound TCP connections and their replies +// are handled by netstack without packets escaping to the TUN. +func TestDialTCP(t *testing.T) { + testDialTCP := func(t *testing.T, lt *listenTest, listenIP netip.Addr) { + ln, err := lt.s1.Listen("tcp", netip.AddrPortFrom(listenIP, 0).String()) + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + echoErr := make(chan error, 1) + go func() { + conn, err := ln.Accept() + if err != nil { + echoErr <- err + return + } + defer conn.Close() + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + echoErr <- err + return + } + _, err = conn.Write(buf[:n]) + if err != nil { + echoErr <- err + return + } + }() + + conn, err := lt.s2.Dial(t.Context(), "tcp", ln.Addr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer conn.Close() + + want := "hello tcp dial" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatalf("Write failed: %v", err) + } + + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + select { + case e := <-echoErr: + t.Fatalf("echo error: %v; read error: %v", e, err) + default: + t.Fatalf("Read failed: %v", err) + } + } + + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupListenTest(t, false) + t.Run("IPv4", func(t *testing.T) { testDialTCP(t, lt, lt.s1ip4) }) + t.Run("IPv6", func(t *testing.T) { testDialTCP(t, lt, lt.s1ip6) }) + }) + + t.Run("TUN", func(t *testing.T) { + lt := setupListenTest(t, true) + + var escapedTCPPackets atomic.Int32 + var wg sync.WaitGroup + wg.Go(func() { + for pkt := range lt.tun.Inbound { + var p packet.Parsed + p.Decode(pkt) + if p.IPProto == ipproto.TCP { + escapedTCPPackets.Add(1) + t.Logf("TCP packet escaped to TUN: %v -> %v", p.Src, p.Dst) + } + } + }) + + t.Run("IPv4", func(t *testing.T) { testDialTCP(t, lt, lt.s1ip4) }) + t.Run("IPv6", func(t *testing.T) { testDialTCP(t, lt, lt.s1ip6) }) + + lt.tun.Close() + wg.Wait() + if escaped := escapedTCPPackets.Load(); escaped > 0 { + t.Errorf("%d TCP packets escaped to TUN", escaped) + } + }) +} + +// TestDialUDP tests UDP dialing from s2 to s1 in both netstack and TUN modes. +// In TUN mode, this verifies that outbound UDP connections register endpoints +// with gVisor, allowing reply packets to be routed through netstack instead of +// escaping to the TUN. +func TestDialUDP(t *testing.T) { + testDialUDP := func(t *testing.T, lt *listenTest, listenIP netip.Addr) { + pc, err := lt.s1.ListenPacket("udp", netip.AddrPortFrom(listenIP, 0).String()) + if err != nil { + t.Fatal(err) + } + defer pc.Close() + + echoErr := make(chan error, 1) + go func() { + buf := make([]byte, 1500) + n, addr, err := pc.ReadFrom(buf) + if err != nil { + echoErr <- err + return + } + _, err = pc.WriteTo(buf[:n], addr) + if err != nil { + echoErr <- err + return + } + }() + + conn, err := lt.s2.Dial(t.Context(), "udp", pc.LocalAddr().String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer conn.Close() + + want := "hello udp dial" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatalf("Write failed: %v", err) + } + + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + select { + case e := <-echoErr: + t.Fatalf("echo error: %v; read error: %v", e, err) + default: + t.Fatalf("Read failed: %v", err) + } + } + + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupListenTest(t, false) + t.Run("IPv4", func(t *testing.T) { testDialUDP(t, lt, lt.s1ip4) }) + t.Run("IPv6", func(t *testing.T) { testDialUDP(t, lt, lt.s1ip6) }) + }) + + t.Run("TUN", func(t *testing.T) { + lt := setupListenTest(t, true) + + var escapedUDPPackets atomic.Int32 + var wg sync.WaitGroup + wg.Go(func() { + for pkt := range lt.tun.Inbound { + var p packet.Parsed + p.Decode(pkt) + if p.IPProto == ipproto.UDP { + escapedUDPPackets.Add(1) + t.Logf("UDP packet escaped to TUN: %v -> %v", p.Src, p.Dst) + } + } + }) + + t.Run("IPv4", func(t *testing.T) { testDialUDP(t, lt, lt.s1ip4) }) + t.Run("IPv6", func(t *testing.T) { testDialUDP(t, lt, lt.s1ip6) }) + + lt.tun.Close() + wg.Wait() + if escaped := escapedUDPPackets.Load(); escaped > 0 { + t.Errorf("%d UDP packets escaped to TUN", escaped) + } + }) +} + +// buildDNSQuery builds a UDP/IP packet containing a DNS query for name to the +// Tailscale service IP (100.100.100.100 for IPv4, fd7a:115c:a1e0::53 for IPv6). +func buildDNSQuery(name string, srcIP netip.Addr) []byte { + qtype := byte(0x01) // Type A for IPv4 + if srcIP.Is6() { + qtype = 0x1c // Type AAAA for IPv6 + } + dns := []byte{ + 0x12, 0x34, // ID + 0x01, 0x00, // Flags: standard query, recursion desired + 0x00, 0x01, // QDCOUNT: 1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ANCOUNT, NSCOUNT, ARCOUNT + } + for _, label := range strings.Split(name, ".") { + dns = append(dns, byte(len(label))) + dns = append(dns, label...) + } + dns = append(dns, 0x00, 0x00, qtype, 0x00, 0x01) // null, Type A/AAAA, Class IN + + if srcIP.Is4() { + h := packet.UDP4Header{ + IP4Header: packet.IP4Header{ + Src: srcIP, + Dst: netip.MustParseAddr("100.100.100.100"), + }, + SrcPort: 12345, + DstPort: 53, + } + return packet.Generate(h, dns) + } + h := packet.UDP6Header{ + IP6Header: packet.IP6Header{ + Src: srcIP, + Dst: netip.MustParseAddr("fd7a:115c:a1e0::53"), + }, + SrcPort: 12345, + DstPort: 53, + } + return packet.Generate(h, dns) +} + func TestDeps(t *testing.T) { tstest.Shard(t) deptest.DepChecker{ diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index c2b5d8a3266c7..e05846e150a27 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -165,6 +165,17 @@ type Impl struct { // over the UDP flow. GetUDPHandlerForFlow func(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) + // CheckLocalTransportEndpoints, if true, causes netstack to check if gVisor + // has a registered endpoint for incoming packets to local IPs. This is used + // by tsnet to intercept packets for registered listeners and outbound + // connections when ProcessLocalIPs is false (i.e., when using a TUN). + // It can only be set before calling Start. + // TODO(raggi): refactor the way we handle both CheckLocalTransportEndpoints + // and the earlier netstack registrations for serve, funnel, peerAPI and so + // on. Currently this optimizes away cost for tailscaled in TUN mode, while + // enabling extension support when using tsnet in TUN mode. See #18423. + CheckLocalTransportEndpoints bool + // ProcessLocalIPs is whether netstack should handle incoming // traffic directed at the Node.Addresses (local IPs). // It can only be set before calling Start. @@ -1109,6 +1120,45 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool { if ns.ProcessSubnets && !isLocal { return true } + if isLocal && ns.CheckLocalTransportEndpoints { + // Handle packets to registered listeners and replies to outbound + // connections by checking if gVisor has a registered endpoint. + // This covers TCP listeners, UDP listeners, and outbound TCP replies. + if p.IPProto == ipproto.TCP || p.IPProto == ipproto.UDP { + var netProto tcpip.NetworkProtocolNumber + var id stack.TransportEndpointID + if p.Dst.Addr().Is4() { + netProto = ipv4.ProtocolNumber + id = stack.TransportEndpointID{ + LocalAddress: tcpip.AddrFrom4(p.Dst.Addr().As4()), + LocalPort: p.Dst.Port(), + RemoteAddress: tcpip.AddrFrom4(p.Src.Addr().As4()), + RemotePort: p.Src.Port(), + } + } else { + netProto = ipv6.ProtocolNumber + id = stack.TransportEndpointID{ + LocalAddress: tcpip.AddrFrom16(p.Dst.Addr().As16()), + LocalPort: p.Dst.Port(), + RemoteAddress: tcpip.AddrFrom16(p.Src.Addr().As16()), + RemotePort: p.Src.Port(), + } + } + var transProto tcpip.TransportProtocolNumber + if p.IPProto == ipproto.TCP { + transProto = tcp.ProtocolNumber + } else { + transProto = udp.ProtocolNumber + } + ep := ns.ipstack.FindTransportEndpoint(netProto, transProto, id, nicID) + if debugNetstack() { + ns.logf("[v2] FindTransportEndpoint: id=%+v found=%v", id, ep != nil) + } + if ep != nil { + return true + } + } + } return false } @@ -1575,7 +1625,7 @@ func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet. func (ns *Impl) ListenPacket(network, address string) (net.PacketConn, error) { ap, err := netip.ParseAddrPort(address) if err != nil { - return nil, fmt.Errorf("netstack: ParseAddrPort(%q): %v", address, err) + return nil, fmt.Errorf("netstack: ParseAddrPort(%q): %w", address, err) } var networkProto tcpip.NetworkProtocolNumber @@ -1612,6 +1662,40 @@ func (ns *Impl) ListenPacket(network, address string) (net.PacketConn, error) { return gonet.NewUDPConn(&wq, ep), nil } +// ListenTCP listens for TCP connections on the given address. +func (ns *Impl) ListenTCP(network, address string) (*gonet.TCPListener, error) { + ap, err := netip.ParseAddrPort(address) + if err != nil { + return nil, fmt.Errorf("netstack: ParseAddrPort(%q): %w", address, err) + } + + var networkProto tcpip.NetworkProtocolNumber + switch network { + case "tcp4": + networkProto = ipv4.ProtocolNumber + if ap.Addr().IsValid() && !ap.Addr().Is4() { + return nil, fmt.Errorf("netstack: tcp4 requires an IPv4 address") + } + case "tcp6": + networkProto = ipv6.ProtocolNumber + if ap.Addr().IsValid() && !ap.Addr().Is6() { + return nil, fmt.Errorf("netstack: tcp6 requires an IPv6 address") + } + default: + return nil, fmt.Errorf("netstack: unsupported network %q", network) + } + + localAddress := tcpip.FullAddress{ + NIC: nicID, + Port: ap.Port(), + } + if ap.Addr().IsValid() && !ap.Addr().IsUnspecified() { + localAddress.Addr = tcpip.AddrFromSlice(ap.Addr().AsSlice()) + } + + return gonet.ListenTCP(ns.ipstack, localAddress, networkProto) +} + func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) { sess := r.ID() if debugNetstack() { From df547517251b8ef6ce68eeb74df6b3d0b3b50360 Mon Sep 17 00:00:00 2001 From: Francois Marier Date: Fri, 23 Jan 2026 08:30:19 -0800 Subject: [PATCH 018/202] scripts/installer.sh: allow running dnf5 install script twice (#18492) `dnf config-manager addrepo` will fail if the Tailscale repo is already installed. Without the --overwrite flag, the installer will error out instead of succeeding like with dnf3. Fixes #18491 Signed-off-by: Francois Marier --- scripts/installer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/installer.sh b/scripts/installer.sh index 89d54a4311d01..76e8943e9931f 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -603,7 +603,7 @@ main() { $SUDO dnf config-manager --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" elif [ "$DNF_VERSION" = "5" ]; then # Already installed config-manager, above. - $SUDO dnf config-manager addrepo --from-repofile="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" + $SUDO dnf config-manager addrepo --overwrite --from-repofile="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" else echo "unexpected: unknown dnf version $DNF_VERSION" exit 1 From ce12863ee5af44fd25d9e9ad84fc56449f87a72f Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Fri, 23 Jan 2026 10:09:46 -0800 Subject: [PATCH 019/202] ipn/ipnlocal: manage per-profile subdirectories in TailscaleVarRoot (#18485) In order to better manage per-profile data resources on the client, add methods to the LocalBackend to support creation of per-profile directory structures in local storage. These methods build on the existing TailscaleVarRoot config, and have the same limitation (i.e., if no local storage is available, it will report an error when used). The immediate motivation is to support netmap caching, but we can also use this mechanism for other per-profile resources including pending taildrop files and Tailnet Lock authority caches. This commit only adds the directory-management plumbing; later commits will handle migrating taildrop, TKA, etc. to this mechanism, as well as caching network maps. Updates #12639 Change-Id: Ia75741955c7bf885e49c1ad99f856f669a754169 Signed-off-by: M. J. Fromberger --- ipn/ipnlocal/local.go | 61 ++++++++++++++++++++++++++++++++++++++ ipn/ipnlocal/local_test.go | 50 +++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 2f05a4dbbc9ba..bbd2aa2e0e425 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -21,6 +21,7 @@ import ( "net/netip" "net/url" "os" + "path/filepath" "reflect" "runtime" "slices" @@ -165,6 +166,10 @@ var ( // errManagedByPolicy indicates the operation is blocked // because the target state is managed by a GP/MDM policy. errManagedByPolicy = errors.New("managed by policy") + + // ErrProfileStorageUnavailable indicates that profile-specific local data + // storage is not available; see [LocalBackend.ProfileMkdirAll]. + ErrProfileStorageUnavailable = errors.New("profile local data storage unavailable") ) // LocalBackend is the glue between the major pieces of the Tailscale @@ -5228,6 +5233,56 @@ func (b *LocalBackend) TailscaleVarRoot() string { return "" } +// ProfileMkdirAll creates (if necessary) and returns the path of a directory +// specific to the specified login profile, inside Tailscale's writable storage +// area. If subs are provided, they are joined to the base path to form the +// subdirectory path. +// +// It reports [ErrProfileStorageUnavailable] if there's no configured or +// discovered storage location, or if there was an error making the +// subdirectory. +func (b *LocalBackend) ProfileMkdirAll(id ipn.ProfileID, subs ...string) (string, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.profileMkdirAllLocked(id, subs...) +} + +// profileDataPathLocked returns a path of a profile-specific (sub)directory +// inside the writable storage area for the given profile ID. It does not +// create or verify the existence of the path in the filesystem. +// If b.varRoot == "", it returns "". It panics if id is empty. +// +// The caller must hold b.mu. +func (b *LocalBackend) profileDataPathLocked(id ipn.ProfileID, subs ...string) string { + if id == "" { + panic("invalid empty profile ID") + } + vr := b.TailscaleVarRoot() + if vr == "" { + return "" + } + return filepath.Join(append([]string{vr, "profile-data", string(id)}, subs...)...) +} + +// profileMkdirAllLocked implements ProfileMkdirAll. +// The caller must hold b.mu. +func (b *LocalBackend) profileMkdirAllLocked(id ipn.ProfileID, subs ...string) (string, error) { + if id == "" { + return "", errProfileNotFound + } + if vr := b.TailscaleVarRoot(); vr == "" { + return "", ErrProfileStorageUnavailable + } + + // Use the LoginProfile ID rather than the UserProfile ID, as the latter may + // change over time. + dir := b.profileDataPathLocked(id, subs...) + if err := os.MkdirAll(dir, 0700); err != nil { + return "", fmt.Errorf("create profile directory: %w", err) + } + return dir, nil +} + // closePeerAPIListenersLocked closes any existing PeerAPI listeners // and clears out the PeerAPI server state. // @@ -7011,6 +7066,12 @@ func (b *LocalBackend) DeleteProfile(p ipn.ProfileID) error { } return err } + // Make a best-effort to remove the profile-specific data directory, if one exists. + if pd := b.profileDataPathLocked(p); pd != "" { + if err := os.RemoveAll(pd); err != nil { + b.logf("warning: removing profile data for %q: %v", p, err) + } + } if !needToRestart { return nil } diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index bcc5ebaf26dbf..23a3161ca47f1 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -2306,6 +2306,56 @@ func TestDNSConfigForNetmapForExitNodeConfigs(t *testing.T) { } } +func TestProfileMkdirAll(t *testing.T) { + t.Run("NoVarRoot", func(t *testing.T) { + b := newTestBackend(t) + b.SetVarRoot("") + + got, err := b.ProfileMkdirAll(b.CurrentProfile().ID()) + if got != "" || !errors.Is(err, ErrProfileStorageUnavailable) { + t.Errorf(`ProfileMkdirAll: got %q, %v; want "", %v`, got, err, ErrProfileStorageUnavailable) + } + }) + + t.Run("InvalidProfileID", func(t *testing.T) { + b := newTestBackend(t) + got, err := b.ProfileMkdirAll("") + if got != "" || !errors.Is(err, errProfileNotFound) { + t.Errorf("ProfileMkdirAll: got %q, %v; want %q, %v", got, err, "", errProfileNotFound) + } + }) + + t.Run("ProfileRoot", func(t *testing.T) { + b := newTestBackend(t) + want := filepath.Join(b.TailscaleVarRoot(), "profile-data", "id0") + + got, err := b.ProfileMkdirAll(b.CurrentProfile().ID()) + if err != nil || got != want { + t.Errorf("ProfileMkdirAll: got %q, %v, want %q, nil", got, err, want) + } + if fi, err := os.Stat(got); err != nil { + t.Errorf("Check directory: %v", err) + } else if !fi.IsDir() { + t.Errorf("Path %q is not a directory", got) + } + }) + + t.Run("ProfileSubdir", func(t *testing.T) { + b := newTestBackend(t) + want := filepath.Join(b.TailscaleVarRoot(), "profile-data", "id0", "a", "b") + + got, err := b.ProfileMkdirAll(b.CurrentProfile().ID(), "a", "b") + if err != nil || got != want { + t.Errorf("ProfileMkdirAll: got %q, %v, want %q, nil", got, err, want) + } + if fi, err := os.Stat(got); err != nil { + t.Errorf("Check directory: %v", err) + } else if !fi.IsDir() { + t.Errorf("Path %q is not a directory", got) + } + }) +} + func TestOfferingAppConnector(t *testing.T) { for _, shouldStore := range []bool{false, true} { b := newTestBackend(t) From 3ec5be3f510f74738179c1023468343a62a7e00f Mon Sep 17 00:00:00 2001 From: Will Norris Date: Fri, 23 Jan 2026 13:21:57 -0800 Subject: [PATCH 020/202] all: remove AUTHORS file and references to it 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 --- AUTHORS | 17 ----------------- Dockerfile | 2 +- Dockerfile.base | 2 +- LICENSE | 2 +- appc/appconnector.go | 2 +- appc/appconnector_test.go | 2 +- appc/appctest/appctest.go | 2 +- appc/conn25.go | 2 +- appc/conn25_test.go | 2 +- appc/ippool.go | 2 +- appc/ippool_test.go | 2 +- appc/observe.go | 2 +- appc/observe_disabled.go | 2 +- assert_ts_toolchain_match.go | 2 +- atomicfile/atomicfile.go | 2 +- atomicfile/atomicfile_notwindows.go | 2 +- atomicfile/atomicfile_test.go | 2 +- atomicfile/atomicfile_windows.go | 2 +- atomicfile/atomicfile_windows_test.go | 2 +- atomicfile/mksyscall.go | 2 +- chirp/chirp.go | 2 +- chirp/chirp_test.go | 2 +- client/local/cert.go | 2 +- client/local/debugportmapper.go | 2 +- client/local/local.go | 2 +- client/local/local_test.go | 2 +- client/local/serve.go | 2 +- client/local/syspolicy.go | 2 +- client/local/tailnetlock.go | 2 +- client/systray/logo.go | 2 +- client/systray/startup-creator.go | 2 +- client/systray/systray.go | 2 +- client/tailscale/acl.go | 2 +- client/tailscale/apitype/apitype.go | 2 +- client/tailscale/apitype/controltype.go | 2 +- client/tailscale/cert.go | 2 +- client/tailscale/devices.go | 2 +- client/tailscale/dns.go | 2 +- client/tailscale/example/servetls/servetls.go | 2 +- client/tailscale/keys.go | 2 +- client/tailscale/localclient_aliases.go | 2 +- client/tailscale/required_version.go | 2 +- client/tailscale/routes.go | 2 +- client/tailscale/tailnet.go | 2 +- client/tailscale/tailscale.go | 2 +- client/tailscale/tailscale_test.go | 2 +- client/web/assets.go | 2 +- client/web/auth.go | 2 +- client/web/qnap.go | 2 +- client/web/src/api.ts | 2 +- client/web/src/components/acl-tag.tsx | 2 +- client/web/src/components/address-copy-card.tsx | 2 +- client/web/src/components/app.tsx | 2 +- .../web/src/components/control-components.tsx | 2 +- .../web/src/components/exit-node-selector.tsx | 2 +- client/web/src/components/login-toggle.tsx | 2 +- client/web/src/components/nice-ip.tsx | 2 +- client/web/src/components/update-available.tsx | 2 +- .../components/views/device-details-view.tsx | 2 +- .../src/components/views/disconnected-view.tsx | 2 +- client/web/src/components/views/home-view.tsx | 2 +- client/web/src/components/views/login-view.tsx | 2 +- client/web/src/components/views/ssh-view.tsx | 2 +- .../src/components/views/subnet-router-view.tsx | 2 +- .../web/src/components/views/updating-view.tsx | 2 +- client/web/src/hooks/auth.ts | 2 +- client/web/src/hooks/exit-nodes.ts | 2 +- client/web/src/hooks/self-update.ts | 2 +- client/web/src/hooks/toaster.ts | 2 +- client/web/src/hooks/ts-web-connected.ts | 2 +- client/web/src/index.tsx | 4 ++-- client/web/src/types.ts | 2 +- client/web/src/ui/badge.tsx | 2 +- client/web/src/ui/button.tsx | 2 +- client/web/src/ui/card.tsx | 2 +- client/web/src/ui/collapsible.tsx | 2 +- client/web/src/ui/dialog.tsx | 2 +- client/web/src/ui/empty-state.tsx | 2 +- client/web/src/ui/input.tsx | 2 +- client/web/src/ui/loading-dots.tsx | 2 +- client/web/src/ui/popover.tsx | 2 +- client/web/src/ui/portal-container-context.tsx | 2 +- client/web/src/ui/profile-pic.tsx | 2 +- client/web/src/ui/quick-copy.tsx | 2 +- client/web/src/ui/search-input.tsx | 2 +- client/web/src/ui/spinner.tsx | 2 +- client/web/src/ui/toaster.tsx | 2 +- client/web/src/ui/toggle.tsx | 2 +- client/web/src/utils/clipboard.ts | 2 +- client/web/src/utils/util.test.ts | 2 +- client/web/src/utils/util.ts | 2 +- client/web/synology.go | 2 +- client/web/web.go | 2 +- client/web/web_test.go | 2 +- clientupdate/clientupdate.go | 2 +- clientupdate/clientupdate_downloads.go | 2 +- clientupdate/clientupdate_not_downloads.go | 2 +- clientupdate/clientupdate_notwindows.go | 2 +- clientupdate/clientupdate_test.go | 2 +- clientupdate/clientupdate_windows.go | 2 +- clientupdate/distsign/distsign.go | 2 +- clientupdate/distsign/distsign_test.go | 2 +- clientupdate/distsign/roots.go | 2 +- clientupdate/distsign/roots_test.go | 2 +- cmd/addlicense/main.go | 4 ++-- cmd/build-webclient/build-webclient.go | 2 +- cmd/checkmetrics/checkmetrics.go | 2 +- cmd/cigocacher/cigocacher.go | 2 +- cmd/cigocacher/disk.go | 2 +- cmd/cigocacher/disk_notwindows.go | 2 +- cmd/cigocacher/disk_windows.go | 2 +- cmd/cigocacher/http.go | 2 +- cmd/cloner/cloner.go | 2 +- cmd/cloner/cloner_test.go | 2 +- cmd/cloner/clonerex/clonerex.go | 2 +- cmd/cloner/clonerex/clonerex_clone.go | 2 +- cmd/connector-gen/advertise-routes.go | 2 +- cmd/connector-gen/aws.go | 2 +- cmd/connector-gen/connector-gen.go | 2 +- cmd/connector-gen/github.go | 2 +- cmd/containerboot/egressservices.go | 2 +- cmd/containerboot/egressservices_test.go | 2 +- cmd/containerboot/forwarding.go | 2 +- cmd/containerboot/ingressservices.go | 2 +- cmd/containerboot/ingressservices_test.go | 2 +- cmd/containerboot/kube.go | 2 +- cmd/containerboot/kube_test.go | 2 +- cmd/containerboot/main.go | 2 +- cmd/containerboot/main_test.go | 2 +- cmd/containerboot/serve.go | 2 +- cmd/containerboot/serve_test.go | 2 +- cmd/containerboot/settings.go | 2 +- cmd/containerboot/settings_test.go | 2 +- cmd/containerboot/tailscaled.go | 2 +- cmd/derper/ace.go | 2 +- cmd/derper/bootstrap_dns.go | 2 +- cmd/derper/bootstrap_dns_test.go | 2 +- cmd/derper/cert.go | 2 +- cmd/derper/cert_test.go | 2 +- cmd/derper/derper.go | 2 +- cmd/derper/derper_test.go | 2 +- cmd/derper/mesh.go | 2 +- cmd/derper/websocket.go | 2 +- cmd/derpprobe/derpprobe.go | 2 +- cmd/dist/dist.go | 2 +- cmd/distsign/distsign.go | 2 +- cmd/featuretags/featuretags.go | 2 +- cmd/get-authkey/main.go | 2 +- cmd/gitops-pusher/cache.go | 2 +- cmd/gitops-pusher/gitops-pusher.go | 2 +- cmd/gitops-pusher/gitops-pusher_test.go | 2 +- cmd/hello/hello.go | 2 +- cmd/jsonimports/format.go | 2 +- cmd/jsonimports/format_test.go | 2 +- cmd/jsonimports/jsonimports.go | 2 +- cmd/k8s-nameserver/main.go | 2 +- cmd/k8s-nameserver/main_test.go | 2 +- cmd/k8s-operator/api-server-proxy-pg.go | 2 +- cmd/k8s-operator/api-server-proxy-pg_test.go | 2 +- cmd/k8s-operator/api-server-proxy.go | 2 +- cmd/k8s-operator/connector.go | 2 +- cmd/k8s-operator/connector_test.go | 2 +- cmd/k8s-operator/deploy/chart/Chart.yaml | 2 +- .../chart/templates/apiserverproxy-rbac.yaml | 2 +- .../deploy/chart/templates/deployment.yaml | 2 +- .../deploy/chart/templates/oauth-secret.yaml | 2 +- .../deploy/chart/templates/operator-rbac.yaml | 2 +- .../deploy/chart/templates/proxy-rbac.yaml | 2 +- cmd/k8s-operator/deploy/chart/values.yaml | 2 +- .../deploy/manifests/authproxy-rbac.yaml | 2 +- cmd/k8s-operator/deploy/manifests/operator.yaml | 2 +- .../deploy/manifests/templates/01-header.yaml | 2 +- cmd/k8s-operator/dnsrecords.go | 2 +- cmd/k8s-operator/dnsrecords_test.go | 2 +- cmd/k8s-operator/e2e/doc.go | 2 +- cmd/k8s-operator/e2e/ingress_test.go | 2 +- cmd/k8s-operator/e2e/main_test.go | 2 +- cmd/k8s-operator/e2e/pebble.go | 2 +- cmd/k8s-operator/e2e/proxy_test.go | 2 +- cmd/k8s-operator/e2e/setup.go | 2 +- cmd/k8s-operator/e2e/ssh.go | 2 +- cmd/k8s-operator/egress-eps.go | 2 +- cmd/k8s-operator/egress-eps_test.go | 2 +- cmd/k8s-operator/egress-pod-readiness.go | 2 +- cmd/k8s-operator/egress-pod-readiness_test.go | 2 +- cmd/k8s-operator/egress-services-readiness.go | 2 +- .../egress-services-readiness_test.go | 2 +- cmd/k8s-operator/egress-services.go | 2 +- cmd/k8s-operator/egress-services_test.go | 2 +- cmd/k8s-operator/generate/main.go | 2 +- cmd/k8s-operator/generate/main_test.go | 2 +- cmd/k8s-operator/ingress-for-pg.go | 2 +- cmd/k8s-operator/ingress-for-pg_test.go | 2 +- cmd/k8s-operator/ingress.go | 2 +- cmd/k8s-operator/ingress_test.go | 2 +- cmd/k8s-operator/logger.go | 2 +- cmd/k8s-operator/metrics_resources.go | 2 +- cmd/k8s-operator/nameserver.go | 2 +- cmd/k8s-operator/nameserver_test.go | 2 +- cmd/k8s-operator/nodeport-service-ports.go | 2 +- .../nodeport-services-ports_test.go | 2 +- cmd/k8s-operator/operator.go | 2 +- cmd/k8s-operator/operator_test.go | 2 +- cmd/k8s-operator/proxyclass.go | 2 +- cmd/k8s-operator/proxyclass_test.go | 2 +- cmd/k8s-operator/proxygroup.go | 2 +- cmd/k8s-operator/proxygroup_specs.go | 2 +- cmd/k8s-operator/proxygroup_test.go | 2 +- cmd/k8s-operator/sts.go | 2 +- cmd/k8s-operator/sts_test.go | 2 +- cmd/k8s-operator/svc-for-pg.go | 2 +- cmd/k8s-operator/svc-for-pg_test.go | 2 +- cmd/k8s-operator/svc.go | 2 +- cmd/k8s-operator/tailnet.go | 2 +- cmd/k8s-operator/testutils_test.go | 2 +- cmd/k8s-operator/tsclient.go | 2 +- cmd/k8s-operator/tsclient_test.go | 2 +- cmd/k8s-operator/tsrecorder.go | 2 +- cmd/k8s-operator/tsrecorder_specs.go | 2 +- cmd/k8s-operator/tsrecorder_specs_test.go | 2 +- cmd/k8s-operator/tsrecorder_test.go | 2 +- cmd/k8s-proxy/internal/config/config.go | 2 +- cmd/k8s-proxy/internal/config/config_test.go | 2 +- cmd/k8s-proxy/k8s-proxy.go | 2 +- cmd/mkmanifest/main.go | 2 +- cmd/mkpkg/main.go | 2 +- cmd/mkversion/mkversion.go | 2 +- cmd/nardump/nardump.go | 2 +- cmd/nardump/nardump_test.go | 2 +- cmd/natc/ippool/consensusippool.go | 2 +- cmd/natc/ippool/consensusippool_test.go | 2 +- cmd/natc/ippool/consensusippoolserialize.go | 2 +- cmd/natc/ippool/ippool.go | 2 +- cmd/natc/ippool/ippool_test.go | 2 +- cmd/natc/ippool/ipx.go | 2 +- cmd/natc/ippool/ipx_test.go | 2 +- cmd/natc/natc.go | 2 +- cmd/natc/natc_test.go | 2 +- cmd/netlogfmt/main.go | 2 +- cmd/nginx-auth/nginx-auth.go | 2 +- cmd/omitsize/omitsize.go | 2 +- cmd/pgproxy/pgproxy.go | 2 +- cmd/printdep/printdep.go | 2 +- cmd/proxy-test-server/proxy-test-server.go | 2 +- cmd/proxy-to-grafana/proxy-to-grafana.go | 2 +- cmd/proxy-to-grafana/proxy-to-grafana_test.go | 2 +- cmd/sniproxy/handlers.go | 2 +- cmd/sniproxy/handlers_test.go | 2 +- cmd/sniproxy/server.go | 2 +- cmd/sniproxy/server_test.go | 2 +- cmd/sniproxy/sniproxy.go | 2 +- cmd/sniproxy/sniproxy_test.go | 2 +- cmd/speedtest/speedtest.go | 2 +- cmd/ssh-auth-none-demo/ssh-auth-none-demo.go | 2 +- cmd/stunc/stunc.go | 2 +- cmd/stund/stund.go | 2 +- cmd/stunstamp/stunstamp.go | 2 +- cmd/stunstamp/stunstamp_default.go | 2 +- cmd/stunstamp/stunstamp_linux.go | 2 +- cmd/sync-containers/main.go | 2 +- cmd/systray/systray.go | 2 +- cmd/tailscale/cli/appcroutes.go | 2 +- cmd/tailscale/cli/bugreport.go | 2 +- cmd/tailscale/cli/cert.go | 2 +- cmd/tailscale/cli/cli.go | 2 +- cmd/tailscale/cli/cli_test.go | 2 +- cmd/tailscale/cli/configure-jetkvm.go | 2 +- cmd/tailscale/cli/configure-kube.go | 2 +- cmd/tailscale/cli/configure-kube_omit.go | 2 +- cmd/tailscale/cli/configure-kube_test.go | 2 +- cmd/tailscale/cli/configure-synology-cert.go | 2 +- .../cli/configure-synology-cert_test.go | 2 +- cmd/tailscale/cli/configure-synology.go | 2 +- cmd/tailscale/cli/configure.go | 2 +- cmd/tailscale/cli/configure_apple-all.go | 2 +- cmd/tailscale/cli/configure_apple.go | 2 +- cmd/tailscale/cli/configure_linux-all.go | 2 +- cmd/tailscale/cli/configure_linux.go | 2 +- cmd/tailscale/cli/debug-capture.go | 2 +- cmd/tailscale/cli/debug-peer-relay.go | 2 +- cmd/tailscale/cli/debug-portmap.go | 2 +- cmd/tailscale/cli/debug.go | 2 +- cmd/tailscale/cli/diag.go | 2 +- cmd/tailscale/cli/dns-query.go | 2 +- cmd/tailscale/cli/dns-status.go | 2 +- cmd/tailscale/cli/dns.go | 2 +- cmd/tailscale/cli/down.go | 2 +- cmd/tailscale/cli/drive.go | 2 +- cmd/tailscale/cli/exitnode.go | 2 +- cmd/tailscale/cli/exitnode_test.go | 2 +- cmd/tailscale/cli/ffcomplete/complete.go | 2 +- cmd/tailscale/cli/ffcomplete/complete_omit.go | 2 +- cmd/tailscale/cli/ffcomplete/ffcomplete.go | 2 +- .../cli/ffcomplete/internal/complete.go | 2 +- .../cli/ffcomplete/internal/complete_test.go | 2 +- cmd/tailscale/cli/ffcomplete/scripts.go | 2 +- cmd/tailscale/cli/ffcomplete/scripts_omit.go | 2 +- cmd/tailscale/cli/file.go | 2 +- cmd/tailscale/cli/funnel.go | 2 +- cmd/tailscale/cli/id-token.go | 2 +- cmd/tailscale/cli/ip.go | 2 +- cmd/tailscale/cli/jsonoutput/jsonoutput.go | 2 +- .../cli/jsonoutput/network-lock-log.go | 2 +- .../cli/jsonoutput/network-lock-status.go | 2 +- cmd/tailscale/cli/licenses.go | 2 +- cmd/tailscale/cli/login.go | 2 +- cmd/tailscale/cli/logout.go | 2 +- cmd/tailscale/cli/maybe_syspolicy.go | 2 +- cmd/tailscale/cli/metrics.go | 2 +- cmd/tailscale/cli/nc.go | 2 +- cmd/tailscale/cli/netcheck.go | 2 +- cmd/tailscale/cli/network-lock.go | 2 +- cmd/tailscale/cli/network-lock_test.go | 2 +- cmd/tailscale/cli/ping.go | 2 +- cmd/tailscale/cli/risks.go | 2 +- cmd/tailscale/cli/serve_legacy.go | 2 +- cmd/tailscale/cli/serve_legacy_test.go | 2 +- cmd/tailscale/cli/serve_v2.go | 2 +- cmd/tailscale/cli/serve_v2_test.go | 2 +- cmd/tailscale/cli/serve_v2_unix_test.go | 2 +- cmd/tailscale/cli/set.go | 2 +- cmd/tailscale/cli/set_test.go | 2 +- cmd/tailscale/cli/ssh.go | 2 +- cmd/tailscale/cli/ssh_exec.go | 2 +- cmd/tailscale/cli/ssh_exec_js.go | 2 +- cmd/tailscale/cli/ssh_exec_windows.go | 2 +- cmd/tailscale/cli/ssh_unix.go | 2 +- cmd/tailscale/cli/status.go | 2 +- cmd/tailscale/cli/switch.go | 2 +- cmd/tailscale/cli/syspolicy.go | 2 +- cmd/tailscale/cli/systray.go | 2 +- cmd/tailscale/cli/systray_omit.go | 2 +- cmd/tailscale/cli/up.go | 2 +- cmd/tailscale/cli/up_test.go | 2 +- cmd/tailscale/cli/update.go | 2 +- cmd/tailscale/cli/version.go | 2 +- cmd/tailscale/cli/web.go | 2 +- cmd/tailscale/cli/web_test.go | 2 +- cmd/tailscale/cli/whois.go | 2 +- cmd/tailscale/deps_test.go | 2 +- cmd/tailscale/generate.go | 2 +- cmd/tailscale/tailscale.go | 2 +- cmd/tailscale/tailscale_test.go | 2 +- cmd/tailscaled/childproc/childproc.go | 2 +- cmd/tailscaled/debug.go | 2 +- cmd/tailscaled/debug_forcereflect.go | 2 +- cmd/tailscaled/deps_test.go | 2 +- cmd/tailscaled/flag.go | 2 +- cmd/tailscaled/generate.go | 2 +- cmd/tailscaled/install_darwin.go | 2 +- cmd/tailscaled/install_windows.go | 2 +- cmd/tailscaled/netstack.go | 2 +- cmd/tailscaled/proxy.go | 2 +- cmd/tailscaled/required_version.go | 2 +- cmd/tailscaled/sigpipe.go | 2 +- cmd/tailscaled/ssh.go | 2 +- cmd/tailscaled/tailscaled.go | 2 +- cmd/tailscaled/tailscaled_bird.go | 2 +- cmd/tailscaled/tailscaled_drive.go | 2 +- cmd/tailscaled/tailscaled_notwindows.go | 2 +- cmd/tailscaled/tailscaled_test.go | 2 +- cmd/tailscaled/tailscaled_windows.go | 2 +- .../tailscaledhooks/tailscaledhooks.go | 2 +- cmd/tailscaled/webclient.go | 2 +- cmd/tailscaled/with_cli.go | 2 +- cmd/testcontrol/testcontrol.go | 2 +- cmd/testwrapper/args.go | 2 +- cmd/testwrapper/args_test.go | 2 +- cmd/testwrapper/flakytest/flakytest.go | 2 +- cmd/testwrapper/flakytest/flakytest_test.go | 2 +- cmd/testwrapper/testwrapper.go | 2 +- cmd/testwrapper/testwrapper_test.go | 2 +- cmd/tl-longchain/tl-longchain.go | 2 +- cmd/tsconnect/build-pkg.go | 2 +- cmd/tsconnect/build.go | 2 +- cmd/tsconnect/common.go | 2 +- cmd/tsconnect/dev-pkg.go | 2 +- cmd/tsconnect/dev.go | 2 +- cmd/tsconnect/package.json.tmpl | 2 +- cmd/tsconnect/serve.go | 2 +- cmd/tsconnect/src/app/app.tsx | 2 +- cmd/tsconnect/src/app/go-panic-display.tsx | 2 +- cmd/tsconnect/src/app/header.tsx | 2 +- cmd/tsconnect/src/app/index.css | 2 +- cmd/tsconnect/src/app/index.ts | 2 +- cmd/tsconnect/src/app/ssh.tsx | 2 +- cmd/tsconnect/src/app/url-display.tsx | 2 +- cmd/tsconnect/src/lib/js-state-store.ts | 2 +- cmd/tsconnect/src/lib/ssh.ts | 2 +- cmd/tsconnect/src/pkg/pkg.css | 2 +- cmd/tsconnect/src/pkg/pkg.ts | 2 +- cmd/tsconnect/src/types/esbuild.d.ts | 2 +- cmd/tsconnect/src/types/wasm_js.d.ts | 2 +- cmd/tsconnect/tsconnect.go | 2 +- cmd/tsconnect/wasm/wasm_js.go | 2 +- cmd/tsidp/tsidp.go | 2 +- cmd/tsidp/tsidp_test.go | 2 +- cmd/tsidp/ui.go | 2 +- cmd/tsshd/tsshd.go | 2 +- cmd/tta/fw_linux.go | 2 +- cmd/tta/tta.go | 2 +- cmd/vet/jsontags/analyzer.go | 2 +- cmd/vet/jsontags/iszero.go | 2 +- cmd/vet/jsontags/report.go | 2 +- cmd/vet/vet.go | 2 +- cmd/viewer/tests/tests.go | 2 +- cmd/viewer/tests/tests_clone.go | 2 +- cmd/viewer/tests/tests_view.go | 2 +- cmd/viewer/viewer.go | 2 +- cmd/viewer/viewer_test.go | 2 +- cmd/vnet/vnet-main.go | 2 +- cmd/xdpderper/xdpderper.go | 2 +- control/controlbase/conn.go | 2 +- control/controlbase/conn_test.go | 2 +- control/controlbase/handshake.go | 2 +- control/controlbase/handshake_test.go | 2 +- control/controlbase/interop_test.go | 2 +- control/controlbase/messages.go | 2 +- control/controlclient/auto.go | 2 +- control/controlclient/client.go | 2 +- control/controlclient/controlclient_test.go | 2 +- control/controlclient/direct.go | 2 +- control/controlclient/direct_test.go | 2 +- control/controlclient/errors.go | 2 +- control/controlclient/map.go | 2 +- control/controlclient/map_test.go | 2 +- control/controlclient/sign.go | 2 +- control/controlclient/sign_supported.go | 2 +- control/controlclient/sign_supported_test.go | 2 +- control/controlclient/sign_unsupported.go | 2 +- control/controlclient/status.go | 2 +- control/controlhttp/client.go | 2 +- control/controlhttp/client_common.go | 2 +- control/controlhttp/client_js.go | 2 +- control/controlhttp/constants.go | 2 +- .../controlhttpcommon/controlhttpcommon.go | 2 +- .../controlhttpserver/controlhttpserver.go | 2 +- control/controlhttp/http_test.go | 2 +- control/controlknobs/controlknobs.go | 2 +- control/controlknobs/controlknobs_test.go | 2 +- control/ts2021/client.go | 2 +- control/ts2021/client_test.go | 2 +- control/ts2021/conn.go | 2 +- derp/client_test.go | 2 +- derp/derp.go | 2 +- derp/derp_client.go | 2 +- derp/derp_test.go | 2 +- derp/derpconst/derpconst.go | 2 +- derp/derphttp/derphttp_client.go | 2 +- derp/derphttp/derphttp_test.go | 2 +- derp/derphttp/export_test.go | 2 +- derp/derphttp/mesh_client.go | 2 +- derp/derphttp/websocket.go | 2 +- derp/derphttp/websocket_stub.go | 2 +- derp/derpserver/derpserver.go | 2 +- derp/derpserver/derpserver_default.go | 2 +- derp/derpserver/derpserver_linux.go | 2 +- derp/derpserver/derpserver_test.go | 2 +- derp/derpserver/handler.go | 2 +- derp/export_test.go | 2 +- derp/xdp/headers/update.go | 2 +- derp/xdp/xdp.go | 2 +- derp/xdp/xdp_default.go | 2 +- derp/xdp/xdp_linux.go | 2 +- derp/xdp/xdp_linux_test.go | 2 +- disco/disco.go | 2 +- disco/disco_fuzzer.go | 2 +- disco/disco_test.go | 2 +- disco/pcap.go | 2 +- docs/k8s/Makefile | 2 +- docs/k8s/proxy.yaml | 2 +- docs/k8s/role.yaml | 2 +- docs/k8s/rolebinding.yaml | 2 +- docs/k8s/sa.yaml | 2 +- docs/k8s/sidecar.yaml | 2 +- docs/k8s/subnet.yaml | 2 +- docs/k8s/userspace-sidecar.yaml | 2 +- docs/sysv/tailscale.init | 2 +- docs/webhooks/example.go | 2 +- doctor/doctor.go | 2 +- doctor/doctor_test.go | 2 +- doctor/ethtool/ethtool.go | 2 +- doctor/ethtool/ethtool_linux.go | 2 +- doctor/ethtool/ethtool_other.go | 2 +- doctor/permissions/permissions.go | 2 +- doctor/permissions/permissions_bsd.go | 2 +- doctor/permissions/permissions_linux.go | 2 +- doctor/permissions/permissions_other.go | 2 +- doctor/permissions/permissions_test.go | 2 +- doctor/routetable/routetable.go | 2 +- drive/drive_clone.go | 2 +- drive/drive_view.go | 2 +- drive/driveimpl/birthtiming.go | 2 +- drive/driveimpl/birthtiming_test.go | 2 +- drive/driveimpl/compositedav/compositedav.go | 2 +- drive/driveimpl/compositedav/rewriting.go | 2 +- drive/driveimpl/compositedav/stat_cache.go | 2 +- drive/driveimpl/compositedav/stat_cache_test.go | 2 +- drive/driveimpl/connlistener.go | 2 +- drive/driveimpl/connlistener_test.go | 2 +- drive/driveimpl/dirfs/dirfs.go | 2 +- drive/driveimpl/dirfs/dirfs_test.go | 2 +- drive/driveimpl/dirfs/mkdir.go | 2 +- drive/driveimpl/dirfs/openfile.go | 2 +- drive/driveimpl/dirfs/removeall.go | 2 +- drive/driveimpl/dirfs/rename.go | 2 +- drive/driveimpl/dirfs/stat.go | 2 +- drive/driveimpl/drive_test.go | 2 +- drive/driveimpl/fileserver.go | 2 +- drive/driveimpl/local_impl.go | 2 +- drive/driveimpl/remote_impl.go | 2 +- drive/driveimpl/shared/pathutil.go | 2 +- drive/driveimpl/shared/pathutil_test.go | 2 +- drive/driveimpl/shared/readonlydir.go | 2 +- drive/driveimpl/shared/stat.go | 2 +- drive/driveimpl/shared/xml.go | 2 +- drive/local.go | 2 +- drive/remote.go | 2 +- drive/remote_nonunix.go | 2 +- drive/remote_permissions.go | 2 +- drive/remote_permissions_test.go | 2 +- drive/remote_test.go | 2 +- drive/remote_unix.go | 2 +- envknob/envknob.go | 2 +- envknob/envknob_nottest.go | 2 +- envknob/envknob_testable.go | 2 +- envknob/featureknob/featureknob.go | 2 +- envknob/logknob/logknob.go | 2 +- envknob/logknob/logknob_test.go | 2 +- feature/ace/ace.go | 2 +- feature/appconnectors/appconnectors.go | 2 +- feature/buildfeatures/buildfeatures.go | 2 +- feature/buildfeatures/feature_ace_disabled.go | 2 +- feature/buildfeatures/feature_ace_enabled.go | 2 +- feature/buildfeatures/feature_acme_disabled.go | 2 +- feature/buildfeatures/feature_acme_enabled.go | 2 +- .../feature_advertiseexitnode_disabled.go | 2 +- .../feature_advertiseexitnode_enabled.go | 2 +- .../feature_advertiseroutes_disabled.go | 2 +- .../feature_advertiseroutes_enabled.go | 2 +- .../feature_appconnectors_disabled.go | 2 +- .../feature_appconnectors_enabled.go | 2 +- feature/buildfeatures/feature_aws_disabled.go | 2 +- feature/buildfeatures/feature_aws_enabled.go | 2 +- .../feature_bakedroots_disabled.go | 2 +- .../buildfeatures/feature_bakedroots_enabled.go | 2 +- feature/buildfeatures/feature_bird_disabled.go | 2 +- feature/buildfeatures/feature_bird_enabled.go | 2 +- feature/buildfeatures/feature_c2n_disabled.go | 2 +- feature/buildfeatures/feature_c2n_enabled.go | 2 +- .../feature_cachenetmap_disabled.go | 2 +- .../feature_cachenetmap_enabled.go | 2 +- .../feature_captiveportal_disabled.go | 2 +- .../feature_captiveportal_enabled.go | 2 +- .../buildfeatures/feature_capture_disabled.go | 2 +- .../buildfeatures/feature_capture_enabled.go | 2 +- .../feature_cliconndiag_disabled.go | 2 +- .../feature_cliconndiag_enabled.go | 2 +- .../feature_clientmetrics_disabled.go | 2 +- .../feature_clientmetrics_enabled.go | 2 +- .../feature_clientupdate_disabled.go | 2 +- .../feature_clientupdate_enabled.go | 2 +- feature/buildfeatures/feature_cloud_disabled.go | 2 +- feature/buildfeatures/feature_cloud_enabled.go | 2 +- .../feature_completion_disabled.go | 2 +- .../buildfeatures/feature_completion_enabled.go | 2 +- feature/buildfeatures/feature_dbus_disabled.go | 2 +- feature/buildfeatures/feature_dbus_enabled.go | 2 +- feature/buildfeatures/feature_debug_disabled.go | 2 +- feature/buildfeatures/feature_debug_enabled.go | 2 +- .../feature_debugeventbus_disabled.go | 2 +- .../feature_debugeventbus_enabled.go | 2 +- .../feature_debugportmapper_disabled.go | 2 +- .../feature_debugportmapper_enabled.go | 2 +- .../feature_desktop_sessions_disabled.go | 2 +- .../feature_desktop_sessions_enabled.go | 2 +- feature/buildfeatures/feature_dns_disabled.go | 2 +- feature/buildfeatures/feature_dns_enabled.go | 2 +- .../buildfeatures/feature_doctor_disabled.go | 2 +- feature/buildfeatures/feature_doctor_enabled.go | 2 +- feature/buildfeatures/feature_drive_disabled.go | 2 +- feature/buildfeatures/feature_drive_enabled.go | 2 +- feature/buildfeatures/feature_gro_disabled.go | 2 +- feature/buildfeatures/feature_gro_enabled.go | 2 +- .../buildfeatures/feature_health_disabled.go | 2 +- feature/buildfeatures/feature_health_enabled.go | 2 +- .../feature_hujsonconf_disabled.go | 2 +- .../buildfeatures/feature_hujsonconf_enabled.go | 2 +- .../feature_identityfederation_disabled.go | 2 +- .../feature_identityfederation_enabled.go | 2 +- .../buildfeatures/feature_iptables_disabled.go | 2 +- .../buildfeatures/feature_iptables_enabled.go | 2 +- feature/buildfeatures/feature_kube_disabled.go | 2 +- feature/buildfeatures/feature_kube_enabled.go | 2 +- .../buildfeatures/feature_lazywg_disabled.go | 2 +- feature/buildfeatures/feature_lazywg_enabled.go | 2 +- .../buildfeatures/feature_linkspeed_disabled.go | 2 +- .../buildfeatures/feature_linkspeed_enabled.go | 2 +- .../feature_linuxdnsfight_disabled.go | 2 +- .../feature_linuxdnsfight_enabled.go | 2 +- .../feature_listenrawdisco_disabled.go | 2 +- .../feature_listenrawdisco_enabled.go | 2 +- .../buildfeatures/feature_logtail_disabled.go | 2 +- .../buildfeatures/feature_logtail_enabled.go | 2 +- .../buildfeatures/feature_netlog_disabled.go | 2 +- feature/buildfeatures/feature_netlog_enabled.go | 2 +- .../buildfeatures/feature_netstack_disabled.go | 2 +- .../buildfeatures/feature_netstack_enabled.go | 2 +- .../feature_networkmanager_disabled.go | 2 +- .../feature_networkmanager_enabled.go | 2 +- .../buildfeatures/feature_oauthkey_disabled.go | 2 +- .../buildfeatures/feature_oauthkey_enabled.go | 2 +- .../buildfeatures/feature_osrouter_disabled.go | 2 +- .../buildfeatures/feature_osrouter_enabled.go | 2 +- .../feature_outboundproxy_disabled.go | 2 +- .../feature_outboundproxy_enabled.go | 2 +- .../feature_peerapiclient_disabled.go | 2 +- .../feature_peerapiclient_enabled.go | 2 +- .../feature_peerapiserver_disabled.go | 2 +- .../feature_peerapiserver_enabled.go | 2 +- .../buildfeatures/feature_portlist_disabled.go | 2 +- .../buildfeatures/feature_portlist_enabled.go | 2 +- .../feature_portmapper_disabled.go | 2 +- .../buildfeatures/feature_portmapper_enabled.go | 2 +- .../buildfeatures/feature_posture_disabled.go | 2 +- .../buildfeatures/feature_posture_enabled.go | 2 +- .../buildfeatures/feature_qrcodes_disabled.go | 2 +- .../buildfeatures/feature_qrcodes_enabled.go | 2 +- .../feature_relayserver_disabled.go | 2 +- .../feature_relayserver_enabled.go | 2 +- .../buildfeatures/feature_resolved_disabled.go | 2 +- .../buildfeatures/feature_resolved_enabled.go | 2 +- .../buildfeatures/feature_sdnotify_disabled.go | 2 +- .../buildfeatures/feature_sdnotify_enabled.go | 2 +- feature/buildfeatures/feature_serve_disabled.go | 2 +- feature/buildfeatures/feature_serve_enabled.go | 2 +- feature/buildfeatures/feature_ssh_disabled.go | 2 +- feature/buildfeatures/feature_ssh_enabled.go | 2 +- .../buildfeatures/feature_synology_disabled.go | 2 +- .../buildfeatures/feature_synology_enabled.go | 2 +- .../buildfeatures/feature_syspolicy_disabled.go | 2 +- .../buildfeatures/feature_syspolicy_enabled.go | 2 +- .../buildfeatures/feature_systray_disabled.go | 2 +- .../buildfeatures/feature_systray_enabled.go | 2 +- .../buildfeatures/feature_taildrop_disabled.go | 2 +- .../buildfeatures/feature_taildrop_enabled.go | 2 +- .../feature_tailnetlock_disabled.go | 2 +- .../feature_tailnetlock_enabled.go | 2 +- feature/buildfeatures/feature_tap_disabled.go | 2 +- feature/buildfeatures/feature_tap_enabled.go | 2 +- feature/buildfeatures/feature_tpm_disabled.go | 2 +- feature/buildfeatures/feature_tpm_enabled.go | 2 +- .../feature_unixsocketidentity_disabled.go | 2 +- .../feature_unixsocketidentity_enabled.go | 2 +- .../feature_useexitnode_disabled.go | 2 +- .../feature_useexitnode_enabled.go | 2 +- .../buildfeatures/feature_useproxy_disabled.go | 2 +- .../buildfeatures/feature_useproxy_enabled.go | 2 +- .../feature_usermetrics_disabled.go | 2 +- .../feature_usermetrics_enabled.go | 2 +- .../buildfeatures/feature_useroutes_disabled.go | 2 +- .../buildfeatures/feature_useroutes_enabled.go | 2 +- .../buildfeatures/feature_wakeonlan_disabled.go | 2 +- .../buildfeatures/feature_wakeonlan_enabled.go | 2 +- .../buildfeatures/feature_webclient_disabled.go | 2 +- .../buildfeatures/feature_webclient_enabled.go | 2 +- feature/buildfeatures/gen.go | 4 ++-- feature/c2n/c2n.go | 2 +- feature/capture/capture.go | 2 +- feature/capture/dissector/dissector.go | 2 +- feature/clientupdate/clientupdate.go | 2 +- feature/condlite/expvar/expvar.go | 2 +- feature/condlite/expvar/omit.go | 2 +- feature/condregister/condregister.go | 2 +- feature/condregister/identityfederation/doc.go | 2 +- .../maybe_identityfederation.go | 2 +- feature/condregister/maybe_ace.go | 2 +- feature/condregister/maybe_appconnectors.go | 2 +- feature/condregister/maybe_c2n.go | 2 +- feature/condregister/maybe_capture.go | 2 +- feature/condregister/maybe_clientupdate.go | 2 +- feature/condregister/maybe_conn25.go | 2 +- feature/condregister/maybe_debugportmapper.go | 2 +- feature/condregister/maybe_doctor.go | 2 +- feature/condregister/maybe_drive.go | 2 +- feature/condregister/maybe_linkspeed.go | 2 +- feature/condregister/maybe_linuxdnsfight.go | 2 +- feature/condregister/maybe_osrouter.go | 2 +- feature/condregister/maybe_portlist.go | 2 +- feature/condregister/maybe_posture.go | 2 +- feature/condregister/maybe_relayserver.go | 2 +- feature/condregister/maybe_sdnotify.go | 2 +- feature/condregister/maybe_store_aws.go | 2 +- feature/condregister/maybe_store_kube.go | 2 +- feature/condregister/maybe_syspolicy.go | 2 +- feature/condregister/maybe_taildrop.go | 2 +- feature/condregister/maybe_tap.go | 2 +- feature/condregister/maybe_tpm.go | 2 +- feature/condregister/maybe_wakeonlan.go | 2 +- feature/condregister/oauthkey/doc.go | 2 +- feature/condregister/oauthkey/maybe_oauthkey.go | 2 +- feature/condregister/portmapper/doc.go | 2 +- .../condregister/portmapper/maybe_portmapper.go | 2 +- feature/condregister/useproxy/doc.go | 2 +- feature/condregister/useproxy/useproxy.go | 2 +- feature/conn25/conn25.go | 2 +- feature/debugportmapper/debugportmapper.go | 2 +- feature/doctor/doctor.go | 2 +- feature/drive/drive.go | 2 +- feature/feature.go | 2 +- feature/featuretags/featuretags.go | 2 +- feature/featuretags/featuretags_test.go | 2 +- feature/hooks.go | 2 +- .../identityfederation/identityfederation.go | 2 +- .../identityfederation_test.go | 2 +- feature/linkspeed/doc.go | 2 +- feature/linkspeed/linkspeed_linux.go | 2 +- feature/linuxdnsfight/linuxdnsfight.go | 2 +- feature/linuxdnsfight/linuxdnsfight_test.go | 2 +- feature/oauthkey/oauthkey.go | 2 +- feature/oauthkey/oauthkey_test.go | 2 +- feature/portlist/portlist.go | 2 +- feature/portmapper/portmapper.go | 2 +- feature/posture/posture.go | 2 +- feature/relayserver/relayserver.go | 2 +- feature/relayserver/relayserver_test.go | 2 +- feature/sdnotify.go | 2 +- feature/sdnotify/sdnotify.go | 2 +- feature/sdnotify/sdnotify_linux.go | 2 +- feature/syspolicy/syspolicy.go | 2 +- feature/taildrop/delete.go | 2 +- feature/taildrop/delete_test.go | 2 +- feature/taildrop/doc.go | 2 +- feature/taildrop/ext.go | 2 +- feature/taildrop/fileops.go | 2 +- feature/taildrop/fileops_fs.go | 2 +- feature/taildrop/integration_test.go | 2 +- feature/taildrop/localapi.go | 2 +- feature/taildrop/paths.go | 2 +- feature/taildrop/peerapi.go | 2 +- feature/taildrop/peerapi_test.go | 2 +- feature/taildrop/resume.go | 2 +- feature/taildrop/resume_test.go | 2 +- feature/taildrop/retrieve.go | 2 +- feature/taildrop/send.go | 2 +- feature/taildrop/send_test.go | 2 +- feature/taildrop/taildrop.go | 2 +- feature/taildrop/taildrop_test.go | 2 +- feature/taildrop/target_test.go | 2 +- feature/tap/tap_linux.go | 2 +- feature/tpm/attestation.go | 2 +- feature/tpm/attestation_test.go | 2 +- feature/tpm/tpm.go | 2 +- feature/tpm/tpm_linux.go | 2 +- feature/tpm/tpm_other.go | 2 +- feature/tpm/tpm_test.go | 2 +- feature/tpm/tpm_windows.go | 2 +- feature/useproxy/useproxy.go | 2 +- feature/wakeonlan/wakeonlan.go | 2 +- gokrazy/build.go | 2 +- gokrazy/tidy-deps.go | 2 +- gomod_test.go | 2 +- header.txt | 2 +- health/args.go | 2 +- health/health.go | 2 +- health/health_test.go | 2 +- health/healthmsg/healthmsg.go | 2 +- health/state.go | 2 +- health/usermetrics.go | 2 +- health/usermetrics_omit.go | 2 +- health/warnings.go | 2 +- hostinfo/hostinfo.go | 2 +- hostinfo/hostinfo_container_linux_test.go | 2 +- hostinfo/hostinfo_darwin.go | 2 +- hostinfo/hostinfo_freebsd.go | 2 +- hostinfo/hostinfo_linux.go | 2 +- hostinfo/hostinfo_linux_test.go | 2 +- hostinfo/hostinfo_plan9.go | 2 +- hostinfo/hostinfo_test.go | 2 +- hostinfo/hostinfo_uname.go | 2 +- hostinfo/hostinfo_windows.go | 2 +- hostinfo/packagetype_container.go | 2 +- internal/client/tailscale/identityfederation.go | 2 +- internal/client/tailscale/oauthkeys.go | 2 +- internal/client/tailscale/tailscale.go | 2 +- internal/client/tailscale/vip_service.go | 2 +- internal/tooldeps/tooldeps.go | 2 +- ipn/auditlog/auditlog.go | 2 +- ipn/auditlog/auditlog_test.go | 2 +- ipn/auditlog/extension.go | 2 +- ipn/auditlog/store.go | 2 +- ipn/backend.go | 2 +- ipn/backend_test.go | 2 +- ipn/conf.go | 2 +- ipn/conffile/cloudconf.go | 2 +- ipn/conffile/conffile.go | 2 +- ipn/conffile/conffile_hujson.go | 2 +- ipn/conffile/serveconf.go | 2 +- ipn/desktop/doc.go | 2 +- ipn/desktop/extension.go | 2 +- ipn/desktop/mksyscall.go | 2 +- ipn/desktop/session.go | 2 +- ipn/desktop/sessions.go | 2 +- ipn/desktop/sessions_notwindows.go | 2 +- ipn/desktop/sessions_windows.go | 2 +- ipn/doc.go | 2 +- ipn/ipn_clone.go | 2 +- ipn/ipn_test.go | 2 +- ipn/ipn_view.go | 2 +- ipn/ipnauth/access.go | 2 +- ipn/ipnauth/actor.go | 2 +- ipn/ipnauth/actor_windows.go | 2 +- ipn/ipnauth/ipnauth.go | 2 +- ipn/ipnauth/ipnauth_omit_unixsocketidentity.go | 2 +- ipn/ipnauth/ipnauth_unix_creds.go | 2 +- ipn/ipnauth/ipnauth_windows.go | 2 +- ipn/ipnauth/policy.go | 2 +- ipn/ipnauth/self.go | 2 +- ipn/ipnauth/test_actor.go | 2 +- ipn/ipnext/ipnext.go | 2 +- ipn/ipnlocal/breaktcp_darwin.go | 2 +- ipn/ipnlocal/breaktcp_linux.go | 2 +- ipn/ipnlocal/bus.go | 2 +- ipn/ipnlocal/bus_test.go | 2 +- ipn/ipnlocal/c2n.go | 2 +- ipn/ipnlocal/c2n_pprof.go | 2 +- ipn/ipnlocal/c2n_test.go | 2 +- ipn/ipnlocal/captiveportal.go | 2 +- ipn/ipnlocal/cert.go | 2 +- ipn/ipnlocal/cert_disabled.go | 2 +- ipn/ipnlocal/cert_test.go | 2 +- ipn/ipnlocal/dnsconfig_test.go | 2 +- ipn/ipnlocal/drive.go | 2 +- ipn/ipnlocal/drive_test.go | 2 +- ipn/ipnlocal/drive_tomove.go | 2 +- ipn/ipnlocal/expiry.go | 2 +- ipn/ipnlocal/expiry_test.go | 2 +- ipn/ipnlocal/extension_host.go | 2 +- ipn/ipnlocal/extension_host_test.go | 2 +- ipn/ipnlocal/hwattest.go | 2 +- ipn/ipnlocal/local.go | 2 +- ipn/ipnlocal/local_test.go | 2 +- ipn/ipnlocal/loglines_test.go | 2 +- ipn/ipnlocal/netstack.go | 2 +- ipn/ipnlocal/network-lock.go | 2 +- ipn/ipnlocal/network-lock_test.go | 2 +- ipn/ipnlocal/node_backend.go | 2 +- ipn/ipnlocal/node_backend_test.go | 2 +- ipn/ipnlocal/peerapi.go | 2 +- ipn/ipnlocal/peerapi_drive.go | 2 +- ipn/ipnlocal/peerapi_macios_ext.go | 2 +- ipn/ipnlocal/peerapi_test.go | 2 +- ipn/ipnlocal/prefs_metrics.go | 2 +- ipn/ipnlocal/profiles.go | 2 +- ipn/ipnlocal/profiles_notwindows.go | 2 +- ipn/ipnlocal/profiles_test.go | 2 +- ipn/ipnlocal/profiles_windows.go | 2 +- ipn/ipnlocal/serve.go | 2 +- ipn/ipnlocal/serve_disabled.go | 2 +- ipn/ipnlocal/serve_test.go | 2 +- ipn/ipnlocal/serve_unix_test.go | 2 +- ipn/ipnlocal/ssh.go | 2 +- ipn/ipnlocal/ssh_stub.go | 2 +- ipn/ipnlocal/ssh_test.go | 2 +- ipn/ipnlocal/state_test.go | 2 +- ipn/ipnlocal/tailnetlock_disabled.go | 2 +- ipn/ipnlocal/web_client.go | 2 +- ipn/ipnlocal/web_client_stub.go | 2 +- ipn/ipnserver/actor.go | 2 +- ipn/ipnserver/proxyconnect.go | 2 +- ipn/ipnserver/proxyconnect_js.go | 2 +- ipn/ipnserver/server.go | 2 +- ipn/ipnserver/server_fortest.go | 2 +- ipn/ipnserver/server_test.go | 2 +- ipn/ipnserver/waiterset_test.go | 2 +- ipn/ipnstate/ipnstate.go | 2 +- ipn/ipnstate/ipnstate_clone.go | 2 +- ipn/lapitest/backend.go | 2 +- ipn/lapitest/client.go | 2 +- ipn/lapitest/example_test.go | 2 +- ipn/lapitest/opts.go | 2 +- ipn/lapitest/server.go | 2 +- ipn/localapi/cert.go | 2 +- ipn/localapi/debug.go | 2 +- ipn/localapi/debugderp.go | 2 +- ipn/localapi/disabled_stubs.go | 2 +- ipn/localapi/localapi.go | 2 +- ipn/localapi/localapi_drive.go | 2 +- ipn/localapi/localapi_test.go | 2 +- ipn/localapi/pprof.go | 2 +- ipn/localapi/serve.go | 2 +- ipn/localapi/syspolicy_api.go | 2 +- ipn/localapi/tailnetlock.go | 2 +- ipn/policy/policy.go | 2 +- ipn/prefs.go | 2 +- ipn/prefs_test.go | 2 +- ipn/serve.go | 2 +- ipn/serve_expand_test.go | 2 +- ipn/serve_test.go | 2 +- ipn/store.go | 2 +- ipn/store/awsstore/store_aws.go | 2 +- ipn/store/awsstore/store_aws_test.go | 2 +- ipn/store/kubestore/store_kube.go | 2 +- ipn/store/kubestore/store_kube_test.go | 2 +- ipn/store/mem/store_mem.go | 2 +- ipn/store/stores.go | 2 +- ipn/store/stores_test.go | 2 +- ipn/store_test.go | 2 +- jsondb/db.go | 2 +- jsondb/db_test.go | 2 +- k8s-operator/api-docs-config.yaml | 2 +- k8s-operator/api-proxy/doc.go | 2 +- k8s-operator/api-proxy/proxy.go | 2 +- k8s-operator/api-proxy/proxy_events_test.go | 2 +- k8s-operator/api-proxy/proxy_test.go | 2 +- k8s-operator/apis/doc.go | 2 +- k8s-operator/apis/v1alpha1/doc.go | 2 +- k8s-operator/apis/v1alpha1/register.go | 2 +- k8s-operator/apis/v1alpha1/types_connector.go | 2 +- k8s-operator/apis/v1alpha1/types_proxyclass.go | 2 +- k8s-operator/apis/v1alpha1/types_proxygroup.go | 2 +- k8s-operator/apis/v1alpha1/types_recorder.go | 2 +- k8s-operator/apis/v1alpha1/types_tailnet.go | 2 +- k8s-operator/apis/v1alpha1/types_tsdnsconfig.go | 2 +- .../apis/v1alpha1/zz_generated.deepcopy.go | 2 +- k8s-operator/conditions.go | 2 +- k8s-operator/conditions_test.go | 2 +- k8s-operator/reconciler/reconciler.go | 2 +- k8s-operator/reconciler/reconciler_test.go | 2 +- k8s-operator/reconciler/tailnet/mocks_test.go | 2 +- k8s-operator/reconciler/tailnet/tailnet.go | 2 +- k8s-operator/reconciler/tailnet/tailnet_test.go | 2 +- k8s-operator/sessionrecording/fakes/fakes.go | 2 +- k8s-operator/sessionrecording/hijacker.go | 2 +- k8s-operator/sessionrecording/hijacker_test.go | 2 +- k8s-operator/sessionrecording/spdy/conn.go | 2 +- k8s-operator/sessionrecording/spdy/conn_test.go | 2 +- k8s-operator/sessionrecording/spdy/frame.go | 2 +- .../sessionrecording/spdy/frame_test.go | 2 +- .../sessionrecording/spdy/zlib-reader.go | 2 +- .../sessionrecording/tsrecorder/tsrecorder.go | 2 +- k8s-operator/sessionrecording/ws/conn.go | 2 +- k8s-operator/sessionrecording/ws/conn_test.go | 2 +- k8s-operator/sessionrecording/ws/message.go | 2 +- .../sessionrecording/ws/message_test.go | 2 +- k8s-operator/utils.go | 2 +- kube/certs/certs.go | 2 +- kube/certs/certs_test.go | 2 +- kube/egressservices/egressservices.go | 2 +- kube/egressservices/egressservices_test.go | 2 +- kube/health/healthz.go | 2 +- kube/ingressservices/ingressservices.go | 2 +- kube/k8s-proxy/conf/conf.go | 2 +- kube/k8s-proxy/conf/conf_test.go | 2 +- kube/kubeapi/api.go | 2 +- kube/kubeclient/client.go | 2 +- kube/kubeclient/client_test.go | 2 +- kube/kubeclient/fake_client.go | 2 +- kube/kubetypes/grants.go | 2 +- kube/kubetypes/types.go | 2 +- kube/kubetypes/types_test.go | 2 +- kube/localclient/fake-client.go | 2 +- kube/localclient/local-client.go | 2 +- kube/metrics/metrics.go | 2 +- kube/services/services.go | 2 +- kube/state/state.go | 2 +- kube/state/state_test.go | 2 +- license_test.go | 4 ++-- licenses/licenses.go | 2 +- log/filelogger/log.go | 2 +- log/filelogger/log_test.go | 2 +- log/sockstatlog/logger.go | 2 +- log/sockstatlog/logger_test.go | 2 +- logpolicy/logpolicy.go | 2 +- logpolicy/logpolicy_test.go | 2 +- logpolicy/maybe_syspolicy.go | 2 +- logtail/buffer.go | 2 +- logtail/config.go | 2 +- logtail/example/logadopt/logadopt.go | 2 +- logtail/example/logreprocess/demo.sh | 2 +- logtail/example/logreprocess/logreprocess.go | 2 +- logtail/example/logtail/logtail.go | 2 +- logtail/filch/filch.go | 2 +- logtail/filch/filch_omit.go | 2 +- logtail/filch/filch_stub.go | 2 +- logtail/filch/filch_test.go | 2 +- logtail/filch/filch_unix.go | 2 +- logtail/filch/filch_windows.go | 2 +- logtail/logtail.go | 2 +- logtail/logtail_omit.go | 2 +- logtail/logtail_test.go | 2 +- maths/ewma.go | 2 +- maths/ewma_test.go | 2 +- metrics/fds_linux.go | 2 +- metrics/fds_notlinux.go | 2 +- metrics/metrics.go | 2 +- metrics/metrics_test.go | 2 +- metrics/multilabelmap.go | 2 +- metrics/multilabelmap_test.go | 2 +- net/ace/ace.go | 2 +- net/art/art_test.go | 2 +- net/art/stride_table.go | 2 +- net/art/stride_table_test.go | 2 +- net/art/table.go | 2 +- net/art/table_test.go | 2 +- net/bakedroots/bakedroots.go | 2 +- net/bakedroots/bakedroots_test.go | 2 +- net/batching/conn.go | 2 +- net/batching/conn_default.go | 2 +- net/batching/conn_linux.go | 2 +- net/batching/conn_linux_test.go | 2 +- net/captivedetection/captivedetection.go | 2 +- net/captivedetection/captivedetection_test.go | 2 +- net/captivedetection/endpoints.go | 2 +- net/captivedetection/rawconn.go | 2 +- net/captivedetection/rawconn_apple.go | 2 +- net/connectproxy/connectproxy.go | 2 +- net/dns/config.go | 2 +- net/dns/dbus.go | 2 +- net/dns/debian_resolvconf.go | 2 +- net/dns/direct.go | 2 +- net/dns/direct_linux_test.go | 2 +- net/dns/direct_test.go | 2 +- net/dns/direct_unix_test.go | 2 +- net/dns/dns_clone.go | 2 +- net/dns/dns_view.go | 2 +- net/dns/flush_default.go | 2 +- net/dns/flush_windows.go | 2 +- net/dns/ini.go | 2 +- net/dns/ini_test.go | 2 +- net/dns/manager.go | 2 +- net/dns/manager_darwin.go | 2 +- net/dns/manager_default.go | 2 +- net/dns/manager_freebsd.go | 2 +- net/dns/manager_linux.go | 2 +- net/dns/manager_linux_test.go | 2 +- net/dns/manager_openbsd.go | 2 +- net/dns/manager_plan9.go | 2 +- net/dns/manager_plan9_test.go | 2 +- net/dns/manager_solaris.go | 2 +- net/dns/manager_tcp_test.go | 2 +- net/dns/manager_test.go | 2 +- net/dns/manager_windows.go | 2 +- net/dns/manager_windows_test.go | 2 +- net/dns/nm.go | 2 +- net/dns/noop.go | 2 +- net/dns/nrpt_windows.go | 2 +- net/dns/openresolv.go | 2 +- net/dns/osconfig.go | 2 +- net/dns/osconfig_test.go | 2 +- net/dns/publicdns/publicdns.go | 2 +- net/dns/publicdns/publicdns_test.go | 2 +- net/dns/resolvconf-workaround.sh | 2 +- net/dns/resolvconf.go | 2 +- net/dns/resolvconffile/resolvconffile.go | 2 +- net/dns/resolvconffile/resolvconffile_test.go | 2 +- net/dns/resolvconfpath_default.go | 2 +- net/dns/resolvconfpath_gokrazy.go | 2 +- net/dns/resolvd.go | 2 +- net/dns/resolved.go | 2 +- net/dns/resolver/debug.go | 2 +- net/dns/resolver/doh_test.go | 2 +- net/dns/resolver/forwarder.go | 2 +- net/dns/resolver/forwarder_test.go | 2 +- net/dns/resolver/macios_ext.go | 2 +- net/dns/resolver/tsdns.go | 2 +- net/dns/resolver/tsdns_server_test.go | 2 +- net/dns/resolver/tsdns_test.go | 2 +- net/dns/utf.go | 2 +- net/dns/utf_test.go | 2 +- net/dns/wsl_windows.go | 2 +- net/dnscache/dnscache.go | 2 +- net/dnscache/dnscache_test.go | 2 +- net/dnscache/messagecache.go | 2 +- net/dnscache/messagecache_test.go | 2 +- net/dnsfallback/dnsfallback.go | 2 +- net/dnsfallback/dnsfallback_test.go | 2 +- net/dnsfallback/update-dns-fallbacks.go | 2 +- net/flowtrack/flowtrack.go | 2 +- net/flowtrack/flowtrack_test.go | 2 +- net/ipset/ipset.go | 2 +- net/ipset/ipset_test.go | 2 +- net/ktimeout/ktimeout.go | 2 +- net/ktimeout/ktimeout_default.go | 2 +- net/ktimeout/ktimeout_linux.go | 2 +- net/ktimeout/ktimeout_linux_test.go | 2 +- net/ktimeout/ktimeout_test.go | 2 +- net/memnet/conn.go | 2 +- net/memnet/conn_test.go | 2 +- net/memnet/listener.go | 2 +- net/memnet/listener_test.go | 2 +- net/memnet/memnet.go | 2 +- net/memnet/memnet_test.go | 2 +- net/memnet/pipe.go | 2 +- net/memnet/pipe_test.go | 2 +- net/netaddr/netaddr.go | 2 +- net/netcheck/captiveportal.go | 2 +- net/netcheck/netcheck.go | 2 +- net/netcheck/netcheck_test.go | 2 +- net/netcheck/standalone.go | 2 +- net/neterror/neterror.go | 2 +- net/neterror/neterror_linux.go | 2 +- net/neterror/neterror_linux_test.go | 2 +- net/neterror/neterror_windows.go | 2 +- net/netkernelconf/netkernelconf.go | 2 +- net/netkernelconf/netkernelconf_default.go | 2 +- net/netkernelconf/netkernelconf_linux.go | 2 +- net/netknob/netknob.go | 2 +- net/netmon/defaultroute_bsd.go | 2 +- net/netmon/defaultroute_darwin.go | 2 +- net/netmon/interfaces.go | 2 +- net/netmon/interfaces_android.go | 2 +- net/netmon/interfaces_bsd.go | 2 +- net/netmon/interfaces_darwin.go | 2 +- net/netmon/interfaces_darwin_test.go | 2 +- net/netmon/interfaces_default_route_test.go | 2 +- net/netmon/interfaces_defaultrouteif_todo.go | 2 +- net/netmon/interfaces_freebsd.go | 2 +- net/netmon/interfaces_linux.go | 2 +- net/netmon/interfaces_linux_test.go | 2 +- net/netmon/interfaces_test.go | 2 +- net/netmon/interfaces_windows.go | 2 +- net/netmon/interfaces_windows_test.go | 2 +- net/netmon/loghelper.go | 2 +- net/netmon/loghelper_test.go | 2 +- net/netmon/netmon.go | 2 +- net/netmon/netmon_darwin.go | 2 +- net/netmon/netmon_darwin_test.go | 2 +- net/netmon/netmon_freebsd.go | 2 +- net/netmon/netmon_linux.go | 2 +- net/netmon/netmon_linux_test.go | 2 +- net/netmon/netmon_polling.go | 2 +- net/netmon/netmon_test.go | 2 +- net/netmon/netmon_windows.go | 2 +- net/netmon/polling.go | 2 +- net/netmon/state.go | 2 +- net/netns/mksyscall.go | 2 +- net/netns/netns.go | 2 +- net/netns/netns_android.go | 2 +- net/netns/netns_darwin.go | 2 +- net/netns/netns_darwin_test.go | 2 +- net/netns/netns_default.go | 2 +- net/netns/netns_dw.go | 2 +- net/netns/netns_linux.go | 2 +- net/netns/netns_linux_test.go | 2 +- net/netns/netns_test.go | 2 +- net/netns/netns_windows.go | 2 +- net/netns/netns_windows_test.go | 2 +- net/netns/socks.go | 2 +- net/netstat/netstat.go | 2 +- net/netstat/netstat_noimpl.go | 2 +- net/netstat/netstat_test.go | 2 +- net/netstat/netstat_windows.go | 2 +- net/netutil/default_interface_portable.go | 2 +- net/netutil/default_interface_portable_test.go | 2 +- net/netutil/ip_forward.go | 2 +- net/netutil/netutil.go | 2 +- net/netutil/netutil_test.go | 2 +- net/netutil/routes.go | 2 +- net/netx/netx.go | 2 +- net/packet/capture.go | 2 +- net/packet/checksum/checksum.go | 2 +- net/packet/checksum/checksum_test.go | 2 +- net/packet/doc.go | 2 +- net/packet/geneve.go | 2 +- net/packet/geneve_test.go | 2 +- net/packet/header.go | 2 +- net/packet/icmp.go | 2 +- net/packet/icmp4.go | 2 +- net/packet/icmp6.go | 2 +- net/packet/icmp6_test.go | 2 +- net/packet/ip4.go | 2 +- net/packet/ip6.go | 2 +- net/packet/packet.go | 2 +- net/packet/packet_test.go | 2 +- net/packet/tsmp.go | 2 +- net/packet/tsmp_test.go | 2 +- net/packet/udp4.go | 2 +- net/packet/udp6.go | 2 +- net/ping/ping.go | 2 +- net/ping/ping_test.go | 2 +- net/portmapper/disabled_stubs.go | 2 +- net/portmapper/igd_test.go | 2 +- net/portmapper/legacy_upnp.go | 2 +- net/portmapper/pcp.go | 2 +- net/portmapper/pcp_test.go | 2 +- net/portmapper/pcpresultcode_string.go | 2 +- net/portmapper/pmpresultcode_string.go | 2 +- net/portmapper/portmapper.go | 2 +- net/portmapper/portmapper_test.go | 2 +- net/portmapper/portmappertype/portmappertype.go | 2 +- net/portmapper/select_test.go | 2 +- net/portmapper/upnp.go | 2 +- net/portmapper/upnp_test.go | 2 +- net/proxymux/mux.go | 2 +- net/proxymux/mux_test.go | 2 +- net/routetable/routetable.go | 2 +- net/routetable/routetable_bsd.go | 2 +- net/routetable/routetable_bsd_test.go | 2 +- net/routetable/routetable_darwin.go | 2 +- net/routetable/routetable_freebsd.go | 2 +- net/routetable/routetable_linux.go | 2 +- net/routetable/routetable_linux_test.go | 2 +- net/routetable/routetable_other.go | 2 +- net/sockopts/sockopts.go | 2 +- net/sockopts/sockopts_default.go | 2 +- net/sockopts/sockopts_linux.go | 2 +- net/sockopts/sockopts_notwindows.go | 2 +- net/sockopts/sockopts_unix_test.go | 2 +- net/sockopts/sockopts_windows.go | 2 +- net/socks5/socks5.go | 2 +- net/socks5/socks5_test.go | 2 +- net/sockstats/sockstats.go | 2 +- net/sockstats/sockstats_noop.go | 2 +- net/sockstats/sockstats_tsgo.go | 2 +- net/sockstats/sockstats_tsgo_darwin.go | 2 +- net/sockstats/sockstats_tsgo_test.go | 2 +- net/speedtest/speedtest.go | 2 +- net/speedtest/speedtest_client.go | 2 +- net/speedtest/speedtest_server.go | 2 +- net/speedtest/speedtest_test.go | 2 +- net/stun/stun.go | 2 +- net/stun/stun_fuzzer.go | 2 +- net/stun/stun_test.go | 2 +- net/stun/stuntest/stuntest.go | 2 +- net/stunserver/stunserver.go | 2 +- net/stunserver/stunserver_test.go | 2 +- net/tcpinfo/tcpinfo.go | 2 +- net/tcpinfo/tcpinfo_darwin.go | 2 +- net/tcpinfo/tcpinfo_linux.go | 2 +- net/tcpinfo/tcpinfo_other.go | 2 +- net/tcpinfo/tcpinfo_test.go | 2 +- net/tlsdial/blockblame/blockblame.go | 2 +- net/tlsdial/blockblame/blockblame_test.go | 2 +- net/tlsdial/deps_test.go | 2 +- net/tlsdial/tlsdial.go | 2 +- net/tlsdial/tlsdial_test.go | 2 +- net/tsaddr/tsaddr.go | 2 +- net/tsaddr/tsaddr_test.go | 2 +- net/tsdial/dnsmap.go | 2 +- net/tsdial/dnsmap_test.go | 2 +- net/tsdial/dohclient.go | 2 +- net/tsdial/dohclient_test.go | 2 +- net/tsdial/peerapi_macios_ext.go | 2 +- net/tsdial/tsdial.go | 2 +- net/tshttpproxy/mksyscall.go | 2 +- net/tshttpproxy/tshttpproxy.go | 2 +- net/tshttpproxy/tshttpproxy_linux.go | 2 +- net/tshttpproxy/tshttpproxy_synology.go | 2 +- net/tshttpproxy/tshttpproxy_synology_test.go | 2 +- net/tshttpproxy/tshttpproxy_test.go | 2 +- net/tshttpproxy/tshttpproxy_windows.go | 2 +- net/tstun/fake.go | 2 +- net/tstun/ifstatus_noop.go | 2 +- net/tstun/ifstatus_windows.go | 2 +- net/tstun/mtu.go | 2 +- net/tstun/mtu_test.go | 2 +- net/tstun/netstack_disabled.go | 2 +- net/tstun/netstack_enabled.go | 2 +- net/tstun/tstun_stub.go | 2 +- net/tstun/tun.go | 2 +- net/tstun/tun_linux.go | 2 +- net/tstun/tun_macos.go | 2 +- net/tstun/tun_notwindows.go | 2 +- net/tstun/tun_windows.go | 2 +- net/tstun/wrap.go | 2 +- net/tstun/wrap_linux.go | 2 +- net/tstun/wrap_noop.go | 2 +- net/tstun/wrap_test.go | 2 +- net/udprelay/endpoint/endpoint.go | 2 +- net/udprelay/endpoint/endpoint_test.go | 2 +- net/udprelay/metrics.go | 2 +- net/udprelay/metrics_test.go | 2 +- net/udprelay/server.go | 2 +- net/udprelay/server_linux.go | 2 +- net/udprelay/server_notlinux.go | 2 +- net/udprelay/server_test.go | 2 +- net/udprelay/status/status.go | 2 +- net/wsconn/wsconn.go | 2 +- omit/aws_def.go | 2 +- omit/aws_omit.go | 2 +- omit/omit.go | 2 +- packages/deb/deb.go | 2 +- packages/deb/deb_test.go | 2 +- paths/migrate.go | 2 +- paths/paths.go | 2 +- paths/paths_unix.go | 2 +- paths/paths_windows.go | 2 +- pkgdoc_test.go | 2 +- portlist/clean.go | 2 +- portlist/clean_test.go | 2 +- portlist/netstat.go | 2 +- portlist/netstat_test.go | 2 +- portlist/poller.go | 2 +- portlist/portlist.go | 2 +- portlist/portlist_linux.go | 2 +- portlist/portlist_linux_test.go | 2 +- portlist/portlist_macos.go | 2 +- portlist/portlist_plan9.go | 2 +- portlist/portlist_test.go | 2 +- portlist/portlist_windows.go | 2 +- posture/doc.go | 2 +- posture/hwaddr.go | 2 +- posture/serialnumber_macos.go | 2 +- posture/serialnumber_macos_test.go | 2 +- posture/serialnumber_notmacos.go | 2 +- posture/serialnumber_notmacos_test.go | 2 +- posture/serialnumber_stub.go | 2 +- posture/serialnumber_syspolicy.go | 2 +- posture/serialnumber_test.go | 2 +- prober/derp.go | 2 +- prober/derp_test.go | 2 +- prober/dns.go | 2 +- prober/dns_example_test.go | 2 +- prober/dns_test.go | 2 +- prober/histogram.go | 2 +- prober/histogram_test.go | 2 +- prober/http.go | 2 +- prober/prober.go | 2 +- prober/prober_test.go | 2 +- prober/status.go | 2 +- prober/tcp.go | 2 +- prober/tls.go | 2 +- prober/tls_test.go | 2 +- prober/tun_darwin.go | 2 +- prober/tun_default.go | 2 +- prober/tun_linux.go | 2 +- proxymap/proxymap.go | 2 +- release/dist/cli/cli.go | 2 +- release/dist/dist.go | 2 +- release/dist/memoize.go | 2 +- release/dist/qnap/pkgs.go | 2 +- release/dist/qnap/targets.go | 2 +- release/dist/synology/pkgs.go | 2 +- release/dist/synology/targets.go | 2 +- release/dist/unixpkgs/pkgs.go | 2 +- release/dist/unixpkgs/targets.go | 2 +- release/release.go | 2 +- safesocket/basic_test.go | 2 +- safesocket/pipe_windows.go | 2 +- safesocket/pipe_windows_test.go | 2 +- safesocket/safesocket.go | 2 +- safesocket/safesocket_darwin.go | 2 +- safesocket/safesocket_darwin_test.go | 2 +- safesocket/safesocket_js.go | 2 +- safesocket/safesocket_plan9.go | 2 +- safesocket/safesocket_ps.go | 2 +- safesocket/safesocket_test.go | 2 +- safesocket/unixsocket.go | 2 +- safeweb/http.go | 2 +- safeweb/http_test.go | 2 +- scripts/installer.sh | 2 +- sessionrecording/connect.go | 2 +- sessionrecording/connect_test.go | 2 +- sessionrecording/event.go | 2 +- sessionrecording/header.go | 2 +- ssh/tailssh/accept_env.go | 2 +- ssh/tailssh/accept_env_test.go | 2 +- ssh/tailssh/auditd_linux.go | 2 +- ssh/tailssh/auditd_linux_test.go | 2 +- ssh/tailssh/incubator.go | 2 +- ssh/tailssh/incubator_linux.go | 2 +- ssh/tailssh/incubator_plan9.go | 2 +- ssh/tailssh/privs_test.go | 2 +- ssh/tailssh/tailssh.go | 2 +- ssh/tailssh/tailssh_integration_test.go | 2 +- ssh/tailssh/tailssh_test.go | 2 +- ssh/tailssh/user.go | 2 +- syncs/locked.go | 2 +- syncs/locked_test.go | 2 +- syncs/mutex.go | 2 +- syncs/mutex_debug.go | 2 +- syncs/pool.go | 2 +- syncs/pool_test.go | 2 +- syncs/shardedint.go | 2 +- syncs/shardedint_test.go | 2 +- syncs/shardedmap.go | 2 +- syncs/shardedmap_test.go | 2 +- syncs/shardvalue.go | 2 +- syncs/shardvalue_go.go | 2 +- syncs/shardvalue_tailscale.go | 2 +- syncs/shardvalue_test.go | 2 +- syncs/syncs.go | 2 +- syncs/syncs_test.go | 2 +- tailcfg/c2ntypes.go | 2 +- tailcfg/derpmap.go | 2 +- tailcfg/proto_port_range.go | 2 +- tailcfg/proto_port_range_test.go | 2 +- tailcfg/tailcfg.go | 2 +- tailcfg/tailcfg_clone.go | 2 +- tailcfg/tailcfg_test.go | 2 +- tailcfg/tailcfg_view.go | 2 +- tailcfg/tka.go | 2 +- tka/aum.go | 2 +- tka/aum_test.go | 2 +- tka/builder.go | 2 +- tka/builder_test.go | 2 +- tka/chaintest_test.go | 2 +- tka/deeplink.go | 2 +- tka/deeplink_test.go | 2 +- tka/disabled_stub.go | 2 +- tka/key.go | 2 +- tka/key_test.go | 2 +- tka/scenario_test.go | 2 +- tka/sig.go | 2 +- tka/sig_test.go | 2 +- tka/state.go | 2 +- tka/state_test.go | 2 +- tka/sync.go | 2 +- tka/sync_test.go | 2 +- tka/tailchonk.go | 2 +- tka/tailchonk_test.go | 2 +- tka/tka.go | 2 +- tka/tka_clone.go | 2 +- tka/tka_test.go | 2 +- tka/verify.go | 2 +- tka/verify_disabled.go | 2 +- tool/gocross/autoflags.go | 2 +- tool/gocross/autoflags_test.go | 2 +- tool/gocross/env.go | 2 +- tool/gocross/env_test.go | 2 +- tool/gocross/exec_other.go | 2 +- tool/gocross/exec_unix.go | 2 +- tool/gocross/gocross-wrapper.ps1 | 2 +- tool/gocross/gocross-wrapper.sh | 2 +- tool/gocross/gocross.go | 2 +- tool/gocross/gocross_test.go | 2 +- tool/gocross/gocross_wrapper_test.go | 2 +- tool/gocross/gocross_wrapper_windows_test.go | 2 +- tool/gocross/goroot.go | 2 +- tool/gocross/toolchain.go | 2 +- tool/listpkgs/listpkgs.go | 2 +- tsconsensus/authorization.go | 2 +- tsconsensus/authorization_test.go | 2 +- tsconsensus/bolt_store.go | 2 +- tsconsensus/bolt_store_no_bolt.go | 2 +- tsconsensus/http.go | 2 +- tsconsensus/monitor.go | 2 +- tsconsensus/tsconsensus.go | 2 +- tsconsensus/tsconsensus_test.go | 2 +- tsconst/health.go | 2 +- tsconst/linuxfw.go | 2 +- tsconst/tsconst.go | 2 +- tsconst/webclient.go | 2 +- tsd/tsd.go | 2 +- tsnet/example/tshello/tshello.go | 2 +- tsnet/example/tsnet-funnel/tsnet-funnel.go | 2 +- .../tsnet-http-client/tsnet-http-client.go | 2 +- tsnet/example/tsnet-services/tsnet-services.go | 2 +- tsnet/example/web-client/web-client.go | 2 +- tsnet/example_tshello_test.go | 2 +- ..._tsnet_listen_service_multiple_ports_test.go | 2 +- tsnet/example_tsnet_test.go | 2 +- tsnet/packet_filter_test.go | 2 +- tsnet/tsnet.go | 2 +- tsnet/tsnet_test.go | 2 +- tstest/allocs.go | 2 +- tstest/archtest/archtest_test.go | 2 +- tstest/archtest/qemu_test.go | 2 +- tstest/chonktest/chonktest.go | 2 +- tstest/chonktest/tailchonk_test.go | 2 +- tstest/clock.go | 2 +- tstest/clock_test.go | 2 +- tstest/deptest/deptest.go | 2 +- tstest/deptest/deptest_test.go | 2 +- tstest/integration/capmap_test.go | 2 +- tstest/integration/gen_deps.go | 4 ++-- tstest/integration/integration.go | 2 +- tstest/integration/integration_test.go | 2 +- tstest/integration/nat/nat_test.go | 2 +- .../integration/tailscaled_deps_test_darwin.go | 2 +- .../integration/tailscaled_deps_test_freebsd.go | 2 +- .../integration/tailscaled_deps_test_linux.go | 2 +- .../integration/tailscaled_deps_test_openbsd.go | 2 +- .../integration/tailscaled_deps_test_windows.go | 2 +- tstest/integration/testcontrol/testcontrol.go | 2 +- tstest/integration/vms/derive_bindhost_test.go | 2 +- tstest/integration/vms/distros.go | 2 +- tstest/integration/vms/distros_test.go | 2 +- tstest/integration/vms/dns_tester.go | 2 +- tstest/integration/vms/doc.go | 2 +- tstest/integration/vms/harness_test.go | 2 +- tstest/integration/vms/nixos_test.go | 2 +- tstest/integration/vms/top_level_test.go | 2 +- tstest/integration/vms/udp_tester.go | 2 +- tstest/integration/vms/vm_setup_test.go | 2 +- tstest/integration/vms/vms_steps_test.go | 2 +- tstest/integration/vms/vms_test.go | 2 +- tstest/iosdeps/iosdeps.go | 2 +- tstest/iosdeps/iosdeps_test.go | 2 +- tstest/jsdeps/jsdeps.go | 2 +- tstest/jsdeps/jsdeps_test.go | 2 +- tstest/kernel_linux.go | 2 +- tstest/kernel_other.go | 2 +- tstest/log.go | 2 +- tstest/log_test.go | 2 +- tstest/mts/mts.go | 2 +- tstest/natlab/firewall.go | 2 +- tstest/natlab/nat.go | 2 +- tstest/natlab/natlab.go | 2 +- tstest/natlab/natlab_test.go | 2 +- tstest/natlab/vnet/conf.go | 2 +- tstest/natlab/vnet/conf_test.go | 2 +- tstest/natlab/vnet/easyaf.go | 2 +- tstest/natlab/vnet/nat.go | 2 +- tstest/natlab/vnet/pcap.go | 2 +- tstest/natlab/vnet/vip.go | 2 +- tstest/natlab/vnet/vnet.go | 2 +- tstest/natlab/vnet/vnet_test.go | 2 +- tstest/nettest/nettest.go | 2 +- tstest/reflect.go | 2 +- tstest/resource.go | 2 +- tstest/resource_test.go | 2 +- tstest/tailmac/Swift/Common/Config.swift | 2 +- tstest/tailmac/Swift/Common/Notifications.swift | 2 +- .../Swift/Common/TailMacConfigHelper.swift | 2 +- tstest/tailmac/Swift/Host/AppDelegate.swift | 2 +- tstest/tailmac/Swift/Host/HostCli.swift | 2 +- tstest/tailmac/Swift/Host/VMController.swift | 2 +- tstest/tailmac/Swift/TailMac/RestoreImage.swift | 2 +- tstest/tailmac/Swift/TailMac/TailMac.swift | 2 +- tstest/tailmac/Swift/TailMac/VMInstaller.swift | 2 +- tstest/tkatest/tkatest.go | 2 +- tstest/tlstest/tlstest.go | 2 +- tstest/tlstest/tlstest_test.go | 2 +- tstest/tools/tools.go | 2 +- tstest/tstest.go | 2 +- tstest/tstest_test.go | 2 +- tstest/typewalk/typewalk.go | 2 +- tstime/jitter.go | 2 +- tstime/jitter_test.go | 2 +- tstime/mono/mono.go | 2 +- tstime/mono/mono_test.go | 2 +- tstime/rate/rate.go | 2 +- tstime/rate/rate_test.go | 2 +- tstime/rate/value.go | 2 +- tstime/rate/value_test.go | 2 +- tstime/tstime.go | 2 +- tstime/tstime_test.go | 2 +- tsweb/debug.go | 2 +- tsweb/debug_test.go | 2 +- tsweb/log.go | 2 +- tsweb/pprof_default.go | 2 +- tsweb/pprof_js.go | 2 +- tsweb/promvarz/promvarz.go | 2 +- tsweb/promvarz/promvarz_test.go | 2 +- tsweb/request_id.go | 2 +- tsweb/tsweb.go | 2 +- tsweb/tsweb_test.go | 2 +- tsweb/varz/varz.go | 2 +- tsweb/varz/varz_test.go | 2 +- types/appctype/appconnector.go | 2 +- types/appctype/appconnector_test.go | 2 +- types/bools/bools.go | 2 +- types/bools/bools_test.go | 2 +- types/dnstype/dnstype.go | 2 +- types/dnstype/dnstype_clone.go | 2 +- types/dnstype/dnstype_test.go | 2 +- types/dnstype/dnstype_view.go | 2 +- types/empty/message.go | 2 +- types/flagtype/flagtype.go | 2 +- types/geo/doc.go | 2 +- types/geo/point.go | 2 +- types/geo/point_test.go | 2 +- types/geo/quantize.go | 2 +- types/geo/quantize_test.go | 2 +- types/geo/units.go | 2 +- types/geo/units_test.go | 2 +- types/iox/io.go | 2 +- types/iox/io_test.go | 2 +- types/ipproto/ipproto.go | 2 +- types/ipproto/ipproto_test.go | 2 +- types/jsonx/json.go | 2 +- types/jsonx/json_test.go | 2 +- types/key/chal.go | 2 +- types/key/control.go | 2 +- types/key/control_test.go | 2 +- types/key/derp.go | 2 +- types/key/derp_test.go | 2 +- types/key/disco.go | 2 +- types/key/disco_test.go | 2 +- types/key/doc.go | 2 +- types/key/hardware_attestation.go | 2 +- types/key/machine.go | 2 +- types/key/machine_test.go | 2 +- types/key/nl.go | 2 +- types/key/nl_test.go | 2 +- types/key/node.go | 2 +- types/key/node_test.go | 2 +- types/key/util.go | 2 +- types/key/util_test.go | 2 +- types/lazy/deferred.go | 2 +- types/lazy/deferred_test.go | 2 +- types/lazy/lazy.go | 2 +- types/lazy/map.go | 2 +- types/lazy/map_test.go | 2 +- types/lazy/sync_test.go | 2 +- types/lazy/unsync.go | 2 +- types/lazy/unsync_test.go | 2 +- types/logger/logger.go | 2 +- types/logger/logger_test.go | 2 +- types/logger/rusage.go | 2 +- types/logger/rusage_stub.go | 2 +- types/logger/rusage_syscall.go | 2 +- types/logger/tokenbucket.go | 2 +- types/logid/id.go | 2 +- types/logid/id_test.go | 2 +- types/mapx/ordered.go | 2 +- types/mapx/ordered_test.go | 2 +- types/netlogfunc/netlogfunc.go | 2 +- types/netlogtype/netlogtype.go | 2 +- types/netlogtype/netlogtype_test.go | 2 +- types/netmap/netmap.go | 2 +- types/netmap/netmap_test.go | 2 +- types/netmap/nodemut.go | 2 +- types/netmap/nodemut_test.go | 2 +- types/nettype/nettype.go | 2 +- types/opt/bool.go | 2 +- types/opt/bool_test.go | 2 +- types/opt/value.go | 2 +- types/opt/value_test.go | 2 +- types/persist/persist.go | 2 +- types/persist/persist_clone.go | 2 +- types/persist/persist_test.go | 2 +- types/persist/persist_view.go | 2 +- types/prefs/item.go | 2 +- types/prefs/list.go | 2 +- types/prefs/map.go | 2 +- types/prefs/options.go | 2 +- types/prefs/prefs.go | 2 +- types/prefs/prefs_clone_test.go | 2 +- .../prefs/prefs_example/prefs_example_clone.go | 2 +- types/prefs/prefs_example/prefs_example_view.go | 2 +- types/prefs/prefs_example/prefs_test.go | 2 +- types/prefs/prefs_example/prefs_types.go | 2 +- types/prefs/prefs_test.go | 2 +- types/prefs/prefs_view_test.go | 2 +- types/prefs/struct_list.go | 2 +- types/prefs/struct_map.go | 2 +- types/preftype/netfiltermode.go | 2 +- types/ptr/ptr.go | 2 +- types/result/result.go | 2 +- types/structs/structs.go | 2 +- types/tkatype/tkatype.go | 2 +- types/tkatype/tkatype_test.go | 2 +- types/views/views.go | 2 +- types/views/views_test.go | 2 +- util/backoff/backoff.go | 2 +- util/checkchange/checkchange.go | 2 +- util/cibuild/cibuild.go | 2 +- util/clientmetric/clientmetric.go | 2 +- util/clientmetric/clientmetric_test.go | 2 +- util/clientmetric/omit.go | 2 +- util/cloudenv/cloudenv.go | 2 +- util/cloudenv/cloudenv_test.go | 2 +- util/cloudinfo/cloudinfo.go | 2 +- util/cloudinfo/cloudinfo_nocloud.go | 2 +- util/cloudinfo/cloudinfo_test.go | 2 +- util/cmpver/version.go | 2 +- util/cmpver/version_test.go | 2 +- util/codegen/codegen.go | 4 ++-- util/codegen/codegen_test.go | 2 +- util/cstruct/cstruct.go | 2 +- util/cstruct/cstruct_example_test.go | 2 +- util/cstruct/cstruct_test.go | 2 +- util/ctxkey/key.go | 2 +- util/ctxkey/key_test.go | 2 +- util/deephash/debug.go | 2 +- util/deephash/deephash.go | 2 +- util/deephash/deephash_test.go | 2 +- util/deephash/pointer.go | 2 +- util/deephash/pointer_norace.go | 2 +- util/deephash/pointer_race.go | 2 +- util/deephash/tailscale_types_test.go | 2 +- util/deephash/testtype/testtype.go | 2 +- util/deephash/types.go | 2 +- util/deephash/types_test.go | 2 +- util/dirwalk/dirwalk.go | 2 +- util/dirwalk/dirwalk_linux.go | 2 +- util/dirwalk/dirwalk_test.go | 2 +- util/dnsname/dnsname.go | 2 +- util/dnsname/dnsname_test.go | 2 +- util/eventbus/bench_test.go | 2 +- util/eventbus/bus.go | 2 +- util/eventbus/bus_test.go | 2 +- util/eventbus/client.go | 2 +- util/eventbus/debug-demo/main.go | 2 +- util/eventbus/debug.go | 2 +- util/eventbus/debughttp.go | 2 +- util/eventbus/debughttp_off.go | 2 +- util/eventbus/doc.go | 2 +- util/eventbus/eventbustest/doc.go | 2 +- util/eventbus/eventbustest/eventbustest.go | 2 +- util/eventbus/eventbustest/eventbustest_test.go | 2 +- util/eventbus/eventbustest/examples_test.go | 2 +- util/eventbus/fetch-htmx.go | 2 +- util/eventbus/monitor.go | 2 +- util/eventbus/publish.go | 2 +- util/eventbus/queue.go | 2 +- util/eventbus/subscribe.go | 2 +- util/execqueue/execqueue.go | 2 +- util/execqueue/execqueue_test.go | 2 +- util/expvarx/expvarx.go | 2 +- util/expvarx/expvarx_test.go | 2 +- util/goroutines/goroutines.go | 2 +- util/goroutines/goroutines_test.go | 2 +- util/goroutines/tracker.go | 2 +- util/groupmember/groupmember.go | 2 +- util/hashx/block512.go | 2 +- util/hashx/block512_test.go | 2 +- util/httphdr/httphdr.go | 2 +- util/httphdr/httphdr_test.go | 2 +- util/httpm/httpm.go | 2 +- util/httpm/httpm_test.go | 2 +- util/limiter/limiter.go | 2 +- util/limiter/limiter_test.go | 2 +- util/lineiter/lineiter.go | 2 +- util/lineiter/lineiter_test.go | 2 +- util/lineread/lineread.go | 2 +- util/linuxfw/detector.go | 2 +- util/linuxfw/fake.go | 2 +- util/linuxfw/fake_netfilter.go | 2 +- util/linuxfw/helpers.go | 2 +- util/linuxfw/iptables.go | 2 +- util/linuxfw/iptables_disabled.go | 2 +- util/linuxfw/iptables_for_svcs.go | 2 +- util/linuxfw/iptables_for_svcs_test.go | 2 +- util/linuxfw/iptables_runner.go | 2 +- util/linuxfw/iptables_runner_test.go | 2 +- util/linuxfw/linuxfw.go | 2 +- util/linuxfw/linuxfwtest/linuxfwtest.go | 2 +- .../linuxfwtest/linuxfwtest_unsupported.go | 2 +- util/linuxfw/nftables.go | 2 +- util/linuxfw/nftables_for_svcs.go | 2 +- util/linuxfw/nftables_for_svcs_test.go | 2 +- util/linuxfw/nftables_runner.go | 2 +- util/linuxfw/nftables_runner_test.go | 2 +- util/linuxfw/nftables_types.go | 2 +- util/lru/lru.go | 2 +- util/lru/lru_test.go | 2 +- util/mak/mak.go | 2 +- util/mak/mak_test.go | 2 +- util/multierr/multierr.go | 2 +- util/multierr/multierr_test.go | 2 +- util/must/must.go | 2 +- util/nocasemaps/nocase.go | 2 +- util/nocasemaps/nocase_test.go | 2 +- util/osdiag/internal/wsc/wsc_windows.go | 2 +- util/osdiag/mksyscall.go | 2 +- util/osdiag/osdiag.go | 2 +- util/osdiag/osdiag_notwindows.go | 2 +- util/osdiag/osdiag_windows.go | 2 +- util/osdiag/osdiag_windows_test.go | 2 +- util/osshare/filesharingstatus_noop.go | 2 +- util/osshare/filesharingstatus_windows.go | 2 +- util/osuser/group_ids.go | 2 +- util/osuser/group_ids_test.go | 2 +- util/osuser/user.go | 2 +- util/pidowner/pidowner.go | 2 +- util/pidowner/pidowner_linux.go | 2 +- util/pidowner/pidowner_noimpl.go | 2 +- util/pidowner/pidowner_test.go | 2 +- util/pidowner/pidowner_windows.go | 2 +- util/pool/pool.go | 2 +- util/pool/pool_test.go | 2 +- util/precompress/precompress.go | 2 +- util/progresstracking/progresstracking.go | 2 +- util/prompt/prompt.go | 2 +- util/qrcodes/format.go | 2 +- util/qrcodes/qrcodes.go | 2 +- util/qrcodes/qrcodes_disabled.go | 2 +- util/qrcodes/qrcodes_linux.go | 2 +- util/qrcodes/qrcodes_notlinux.go | 2 +- util/quarantine/quarantine.go | 2 +- util/quarantine/quarantine_darwin.go | 2 +- util/quarantine/quarantine_default.go | 2 +- util/quarantine/quarantine_windows.go | 2 +- util/race/race.go | 2 +- util/race/race_test.go | 2 +- util/racebuild/off.go | 2 +- util/racebuild/on.go | 2 +- util/racebuild/racebuild.go | 2 +- util/rands/cheap.go | 2 +- util/rands/cheap_test.go | 2 +- util/rands/rands.go | 2 +- util/rands/rands_test.go | 2 +- util/reload/reload.go | 2 +- util/reload/reload_test.go | 2 +- util/ringlog/ringlog.go | 2 +- util/ringlog/ringlog_test.go | 2 +- util/safediff/diff.go | 2 +- util/safediff/diff_test.go | 2 +- util/set/handle.go | 2 +- util/set/intset.go | 2 +- util/set/intset_test.go | 2 +- util/set/set.go | 2 +- util/set/set_test.go | 2 +- util/set/slice.go | 2 +- util/set/slice_test.go | 2 +- util/set/smallset.go | 2 +- util/set/smallset_test.go | 2 +- util/singleflight/singleflight.go | 2 +- util/singleflight/singleflight_test.go | 2 +- util/slicesx/slicesx.go | 2 +- util/slicesx/slicesx_test.go | 2 +- util/stringsx/stringsx.go | 2 +- util/stringsx/stringsx_test.go | 2 +- util/syspolicy/internal/internal.go | 2 +- util/syspolicy/internal/loggerx/logger.go | 2 +- util/syspolicy/internal/loggerx/logger_test.go | 2 +- util/syspolicy/internal/metrics/metrics.go | 2 +- util/syspolicy/internal/metrics/metrics_test.go | 2 +- util/syspolicy/internal/metrics/test_handler.go | 2 +- util/syspolicy/pkey/pkey.go | 2 +- util/syspolicy/policy_keys.go | 2 +- util/syspolicy/policy_keys_test.go | 2 +- util/syspolicy/policyclient/policyclient.go | 2 +- util/syspolicy/policytest/policytest.go | 2 +- util/syspolicy/ptype/ptype.go | 2 +- util/syspolicy/ptype/ptype_test.go | 2 +- util/syspolicy/rsop/change_callbacks.go | 2 +- util/syspolicy/rsop/resultant_policy.go | 2 +- util/syspolicy/rsop/resultant_policy_test.go | 2 +- util/syspolicy/rsop/rsop.go | 2 +- util/syspolicy/rsop/store_registration.go | 2 +- util/syspolicy/setting/errors.go | 2 +- util/syspolicy/setting/origin.go | 2 +- util/syspolicy/setting/policy_scope.go | 2 +- util/syspolicy/setting/policy_scope_test.go | 2 +- util/syspolicy/setting/raw_item.go | 2 +- util/syspolicy/setting/raw_item_test.go | 2 +- util/syspolicy/setting/setting.go | 2 +- util/syspolicy/setting/setting_test.go | 2 +- util/syspolicy/setting/snapshot.go | 2 +- util/syspolicy/setting/snapshot_test.go | 2 +- util/syspolicy/setting/summary.go | 2 +- util/syspolicy/source/env_policy_store.go | 2 +- util/syspolicy/source/env_policy_store_test.go | 2 +- util/syspolicy/source/policy_reader.go | 2 +- util/syspolicy/source/policy_reader_test.go | 2 +- util/syspolicy/source/policy_source.go | 2 +- util/syspolicy/source/policy_store_windows.go | 2 +- .../source/policy_store_windows_test.go | 2 +- util/syspolicy/source/test_store.go | 2 +- util/syspolicy/syspolicy.go | 2 +- util/syspolicy/syspolicy_test.go | 2 +- util/syspolicy/syspolicy_windows.go | 2 +- util/sysresources/memory.go | 2 +- util/sysresources/memory_bsd.go | 2 +- util/sysresources/memory_darwin.go | 2 +- util/sysresources/memory_linux.go | 2 +- util/sysresources/memory_unsupported.go | 2 +- util/sysresources/sysresources.go | 2 +- util/sysresources/sysresources_test.go | 2 +- util/testenv/testenv.go | 2 +- util/testenv/testenv_test.go | 2 +- util/topk/topk.go | 2 +- util/topk/topk_test.go | 2 +- util/truncate/truncate.go | 2 +- util/truncate/truncate_test.go | 2 +- util/usermetric/metrics.go | 2 +- util/usermetric/omit.go | 2 +- util/usermetric/usermetric.go | 2 +- util/usermetric/usermetric_test.go | 2 +- util/vizerror/vizerror.go | 2 +- util/vizerror/vizerror_test.go | 2 +- .../authenticode/authenticode_windows.go | 2 +- util/winutil/authenticode/mksyscall.go | 2 +- util/winutil/conpty/conpty_windows.go | 2 +- util/winutil/gp/gp_windows.go | 2 +- util/winutil/gp/gp_windows_test.go | 2 +- util/winutil/gp/mksyscall.go | 2 +- util/winutil/gp/policylock_windows.go | 2 +- util/winutil/gp/watcher_windows.go | 2 +- util/winutil/mksyscall.go | 2 +- util/winutil/policy/policy_windows.go | 2 +- util/winutil/policy/policy_windows_test.go | 2 +- util/winutil/restartmgr_windows.go | 2 +- util/winutil/restartmgr_windows_test.go | 2 +- util/winutil/s4u/lsa_windows.go | 2 +- util/winutil/s4u/mksyscall.go | 2 +- util/winutil/s4u/s4u_windows.go | 2 +- util/winutil/startupinfo_windows.go | 2 +- util/winutil/svcdiag_windows.go | 2 +- .../restartableprocess_windows.go | 2 +- util/winutil/userprofile_windows.go | 2 +- util/winutil/userprofile_windows_test.go | 2 +- util/winutil/winenv/mksyscall.go | 2 +- util/winutil/winenv/winenv_windows.go | 2 +- util/winutil/winutil.go | 2 +- util/winutil/winutil_notwindows.go | 2 +- util/winutil/winutil_windows.go | 2 +- util/winutil/winutil_windows_test.go | 2 +- util/zstdframe/options.go | 2 +- util/zstdframe/zstd.go | 2 +- util/zstdframe/zstd_test.go | 2 +- version-embed.go | 2 +- version/cmdname.go | 2 +- version/cmdname_ios.go | 2 +- version/cmp.go | 2 +- version/cmp_test.go | 2 +- version/distro/distro.go | 2 +- version/distro/distro_test.go | 2 +- version/exename.go | 2 +- version/export_test.go | 2 +- version/mkversion/mkversion.go | 2 +- version/mkversion/mkversion_test.go | 2 +- version/modinfo_test.go | 2 +- version/print.go | 2 +- version/prop.go | 2 +- version/race.go | 2 +- version/race_off.go | 2 +- version/version.go | 2 +- version/version_checkformat.go | 2 +- version/version_internal_test.go | 2 +- version/version_test.go | 2 +- version_tailscale_test.go | 2 +- version_test.go | 2 +- wf/firewall.go | 2 +- wgengine/bench/bench.go | 2 +- wgengine/bench/bench_test.go | 2 +- wgengine/bench/trafficgen.go | 2 +- wgengine/bench/wg.go | 2 +- wgengine/filter/filter.go | 2 +- wgengine/filter/filter_test.go | 2 +- wgengine/filter/filtertype/filtertype.go | 2 +- wgengine/filter/filtertype/filtertype_clone.go | 2 +- wgengine/filter/match.go | 2 +- wgengine/filter/tailcfg.go | 2 +- wgengine/magicsock/blockforever_conn.go | 2 +- wgengine/magicsock/debughttp.go | 2 +- wgengine/magicsock/debugknobs.go | 2 +- wgengine/magicsock/debugknobs_stubs.go | 2 +- wgengine/magicsock/derp.go | 2 +- wgengine/magicsock/derp_test.go | 2 +- wgengine/magicsock/disco_atomic.go | 2 +- wgengine/magicsock/disco_atomic_test.go | 2 +- wgengine/magicsock/discopingpurpose_string.go | 2 +- wgengine/magicsock/endpoint.go | 2 +- wgengine/magicsock/endpoint_default.go | 2 +- wgengine/magicsock/endpoint_stub.go | 2 +- wgengine/magicsock/endpoint_test.go | 2 +- wgengine/magicsock/endpoint_tracker.go | 2 +- wgengine/magicsock/endpoint_tracker_test.go | 2 +- wgengine/magicsock/magicsock.go | 2 +- wgengine/magicsock/magicsock_default.go | 2 +- wgengine/magicsock/magicsock_linux.go | 2 +- wgengine/magicsock/magicsock_linux_test.go | 2 +- wgengine/magicsock/magicsock_notplan9.go | 2 +- wgengine/magicsock/magicsock_plan9.go | 2 +- wgengine/magicsock/magicsock_test.go | 2 +- wgengine/magicsock/peermap.go | 2 +- wgengine/magicsock/peermap_test.go | 2 +- wgengine/magicsock/peermtu.go | 2 +- wgengine/magicsock/peermtu_darwin.go | 2 +- wgengine/magicsock/peermtu_linux.go | 2 +- wgengine/magicsock/peermtu_stubs.go | 2 +- wgengine/magicsock/peermtu_unix.go | 2 +- wgengine/magicsock/rebinding_conn.go | 2 +- wgengine/magicsock/relaymanager.go | 2 +- wgengine/magicsock/relaymanager_test.go | 2 +- wgengine/mem_ios.go | 2 +- wgengine/netlog/netlog.go | 2 +- wgengine/netlog/netlog_omit.go | 2 +- wgengine/netlog/netlog_test.go | 2 +- wgengine/netlog/record.go | 2 +- wgengine/netlog/record_test.go | 2 +- wgengine/netstack/gro/gro.go | 2 +- wgengine/netstack/gro/gro_default.go | 2 +- wgengine/netstack/gro/gro_disabled.go | 2 +- wgengine/netstack/gro/gro_test.go | 2 +- wgengine/netstack/gro/netstack_disabled.go | 2 +- wgengine/netstack/link_endpoint.go | 2 +- wgengine/netstack/netstack.go | 2 +- wgengine/netstack/netstack_linux.go | 2 +- wgengine/netstack/netstack_tcpbuf_default.go | 2 +- wgengine/netstack/netstack_tcpbuf_ios.go | 2 +- wgengine/netstack/netstack_test.go | 2 +- wgengine/netstack/netstack_userping.go | 2 +- wgengine/netstack/netstack_userping_apple.go | 2 +- wgengine/netstack/netstack_userping_test.go | 2 +- wgengine/pendopen.go | 2 +- wgengine/pendopen_omit.go | 2 +- wgengine/router/callback.go | 2 +- wgengine/router/consolidating_router.go | 2 +- wgengine/router/consolidating_router_test.go | 2 +- .../router/osrouter/ifconfig_windows_test.go | 2 +- wgengine/router/osrouter/osrouter.go | 2 +- wgengine/router/osrouter/osrouter_test.go | 2 +- wgengine/router/osrouter/router_freebsd.go | 2 +- wgengine/router/osrouter/router_linux.go | 2 +- wgengine/router/osrouter/router_linux_test.go | 2 +- wgengine/router/osrouter/router_openbsd.go | 2 +- wgengine/router/osrouter/router_plan9.go | 2 +- .../router/osrouter/router_userspace_bsd.go | 2 +- wgengine/router/osrouter/router_windows.go | 2 +- wgengine/router/osrouter/router_windows_test.go | 2 +- wgengine/router/osrouter/runner.go | 2 +- wgengine/router/router.go | 2 +- wgengine/router/router_fake.go | 2 +- wgengine/router/router_test.go | 2 +- wgengine/userspace.go | 2 +- wgengine/userspace_ext_test.go | 2 +- wgengine/userspace_test.go | 2 +- wgengine/watchdog.go | 2 +- wgengine/watchdog_omit.go | 2 +- wgengine/watchdog_test.go | 2 +- wgengine/wgcfg/config.go | 2 +- wgengine/wgcfg/config_test.go | 2 +- wgengine/wgcfg/device.go | 2 +- wgengine/wgcfg/device_test.go | 2 +- wgengine/wgcfg/nmcfg/nmcfg.go | 2 +- wgengine/wgcfg/parser.go | 2 +- wgengine/wgcfg/parser_test.go | 2 +- wgengine/wgcfg/wgcfg_clone.go | 2 +- wgengine/wgcfg/writer.go | 2 +- wgengine/wgengine.go | 2 +- wgengine/wgint/wgint.go | 2 +- wgengine/wgint/wgint_test.go | 2 +- wgengine/wglog/wglog.go | 2 +- wgengine/wglog/wglog_test.go | 2 +- wgengine/winnet/winnet.go | 2 +- wgengine/winnet/winnet_windows.go | 2 +- wif/wif.go | 2 +- words/words.go | 2 +- words/words_test.go | 2 +- 2026 files changed, 2031 insertions(+), 2048 deletions(-) delete mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 03d5932c04746..0000000000000 --- a/AUTHORS +++ /dev/null @@ -1,17 +0,0 @@ -# This is the official list of Tailscale -# authors for copyright purposes. -# -# Names should be added to this file as one of -# Organization's name -# Individual's name -# Individual's name -# -# Please keep the list sorted. -# -# You do not need to add entries to this list, and we don't actively -# populate this list. If you do want to be acknowledged explicitly as -# a copyright holder, though, then please send a PR referencing your -# earlier contributions and clarifying whether it's you or your -# company that owns the rights to your contribution. - -Tailscale Inc. diff --git a/Dockerfile b/Dockerfile index 7122f99782fec..413a4b8211465 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # Note that this Dockerfile is currently NOT used to build any of the published diff --git a/Dockerfile.base b/Dockerfile.base index 9b7ae512b9945..295950c461339 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause FROM alpine:3.22 diff --git a/LICENSE b/LICENSE index 394db19e4aa5c..ed6e4bb6d6ba6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020 Tailscale Inc & AUTHORS. +Copyright (c) 2020 Tailscale Inc & contributors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/appc/appconnector.go b/appc/appconnector.go index d41f9e8ba6357..ee495bd10f100 100644 --- a/appc/appconnector.go +++ b/appc/appconnector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appc implements App Connectors. diff --git a/appc/appconnector_test.go b/appc/appconnector_test.go index 5c362d6fd1217..a860da6a7c737 100644 --- a/appc/appconnector_test.go +++ b/appc/appconnector_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc diff --git a/appc/appctest/appctest.go b/appc/appctest/appctest.go index 9726a2b97d72b..c5eabf6761ec3 100644 --- a/appc/appctest/appctest.go +++ b/appc/appctest/appctest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appctest contains code to help test App Connectors. diff --git a/appc/conn25.go b/appc/conn25.go index b4890c26c0268..2c3e8c519a976 100644 --- a/appc/conn25.go +++ b/appc/conn25.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc diff --git a/appc/conn25_test.go b/appc/conn25_test.go index ab6c4be37c592..76cc6cf8c69f4 100644 --- a/appc/conn25_test.go +++ b/appc/conn25_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc diff --git a/appc/ippool.go b/appc/ippool.go index a2e86a7c296a8..702f79ddef8d8 100644 --- a/appc/ippool.go +++ b/appc/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc diff --git a/appc/ippool_test.go b/appc/ippool_test.go index 64b76738f661e..8ac457c117475 100644 --- a/appc/ippool_test.go +++ b/appc/ippool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appc diff --git a/appc/observe.go b/appc/observe.go index 06dc04f9dcfdf..3cb2db662b564 100644 --- a/appc/observe.go +++ b/appc/observe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_appconnectors diff --git a/appc/observe_disabled.go b/appc/observe_disabled.go index 45aa285eaa758..743c28a590f8e 100644 --- a/appc/observe_disabled.go +++ b/appc/observe_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_appconnectors diff --git a/assert_ts_toolchain_match.go b/assert_ts_toolchain_match.go index 40b24b334674f..f0760ec039414 100644 --- a/assert_ts_toolchain_match.go +++ b/assert_ts_toolchain_match.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go diff --git a/atomicfile/atomicfile.go b/atomicfile/atomicfile.go index 9cae9bb750fa8..1fa4c0641f74e 100644 --- a/atomicfile/atomicfile.go +++ b/atomicfile/atomicfile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package atomicfile contains code related to writing to filesystems diff --git a/atomicfile/atomicfile_notwindows.go b/atomicfile/atomicfile_notwindows.go index 1ce2bb8acda7a..7104ddd5d9ff6 100644 --- a/atomicfile/atomicfile_notwindows.go +++ b/atomicfile/atomicfile_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/atomicfile/atomicfile_test.go b/atomicfile/atomicfile_test.go index a081c90409788..6dbf4eb430372 100644 --- a/atomicfile/atomicfile_test.go +++ b/atomicfile/atomicfile_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !windows diff --git a/atomicfile/atomicfile_windows.go b/atomicfile/atomicfile_windows.go index c67762df2b56c..d9c6ecf32ac5e 100644 --- a/atomicfile/atomicfile_windows.go +++ b/atomicfile/atomicfile_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package atomicfile diff --git a/atomicfile/atomicfile_windows_test.go b/atomicfile/atomicfile_windows_test.go index 4dec1493e0224..8748fc324f61a 100644 --- a/atomicfile/atomicfile_windows_test.go +++ b/atomicfile/atomicfile_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package atomicfile diff --git a/atomicfile/mksyscall.go b/atomicfile/mksyscall.go index d8951a77c5ac6..2b0e4f9e58939 100644 --- a/atomicfile/mksyscall.go +++ b/atomicfile/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package atomicfile diff --git a/chirp/chirp.go b/chirp/chirp.go index 9653877221778..ed87542bc9a93 100644 --- a/chirp/chirp.go +++ b/chirp/chirp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package chirp implements a client to communicate with the BIRD Internet diff --git a/chirp/chirp_test.go b/chirp/chirp_test.go index c545c277d6e87..eedc17f48afa9 100644 --- a/chirp/chirp_test.go +++ b/chirp/chirp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package chirp diff --git a/client/local/cert.go b/client/local/cert.go index bfaac7303297b..701bfe026ceed 100644 --- a/client/local/cert.go +++ b/client/local/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme diff --git a/client/local/debugportmapper.go b/client/local/debugportmapper.go index 04ed1c109a54f..1cbb3ee0a303e 100644 --- a/client/local/debugportmapper.go +++ b/client/local/debugportmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debugportmapper diff --git a/client/local/local.go b/client/local/local.go index 195a91b1ef4a9..465ba0d67c820 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package local contains a Go client for the Tailscale LocalAPI. diff --git a/client/local/local_test.go b/client/local/local_test.go index 0e01e74cd1813..a5377fbd677a9 100644 --- a/client/local/local_test.go +++ b/client/local/local_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/local/serve.go b/client/local/serve.go index 51d15e7e5439b..7f9a16a03f825 100644 --- a/client/local/serve.go +++ b/client/local/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/client/local/syspolicy.go b/client/local/syspolicy.go index 6eff177833786..49708fa154d9a 100644 --- a/client/local/syspolicy.go +++ b/client/local/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/client/local/tailnetlock.go b/client/local/tailnetlock.go index 9d37d2f3553d5..0084cb42e3ab0 100644 --- a/client/local/tailnetlock.go +++ b/client/local/tailnetlock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/client/systray/logo.go b/client/systray/logo.go index 3467d1b741f93..4cd19778dc3a7 100644 --- a/client/systray/logo.go +++ b/client/systray/logo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin diff --git a/client/systray/startup-creator.go b/client/systray/startup-creator.go index cb354856d7f97..34d85e6175fc6 100644 --- a/client/systray/startup-creator.go +++ b/client/systray/startup-creator.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin diff --git a/client/systray/systray.go b/client/systray/systray.go index b9e8fcc59043c..8c30dbf05ef3e 100644 --- a/client/systray/systray.go +++ b/client/systray/systray.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin diff --git a/client/tailscale/acl.go b/client/tailscale/acl.go index 929ec2b3b1ca9..e69d45a2bff5d 100644 --- a/client/tailscale/acl.go +++ b/client/tailscale/acl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/apitype/apitype.go b/client/tailscale/apitype/apitype.go index 6d239d082cd95..d7d1440be9f8a 100644 --- a/client/tailscale/apitype/apitype.go +++ b/client/tailscale/apitype/apitype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package apitype contains types for the Tailscale LocalAPI and control plane API. diff --git a/client/tailscale/apitype/controltype.go b/client/tailscale/apitype/controltype.go index d9d79f0ade38b..2259bb8861aad 100644 --- a/client/tailscale/apitype/controltype.go +++ b/client/tailscale/apitype/controltype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package apitype diff --git a/client/tailscale/cert.go b/client/tailscale/cert.go index 4f351ab990984..797c5535d17f5 100644 --- a/client/tailscale/cert.go +++ b/client/tailscale/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme diff --git a/client/tailscale/devices.go b/client/tailscale/devices.go index 0664f9e63edb1..2b2cf7a0cd049 100644 --- a/client/tailscale/devices.go +++ b/client/tailscale/devices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/dns.go b/client/tailscale/dns.go index bbdc7c56c65f7..427caea0fc593 100644 --- a/client/tailscale/dns.go +++ b/client/tailscale/dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/example/servetls/servetls.go b/client/tailscale/example/servetls/servetls.go index 0ade420887634..864dafd07b242 100644 --- a/client/tailscale/example/servetls/servetls.go +++ b/client/tailscale/example/servetls/servetls.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The servetls program shows how to run an HTTPS server diff --git a/client/tailscale/keys.go b/client/tailscale/keys.go index 79e19e99880f7..6edbae034a759 100644 --- a/client/tailscale/keys.go +++ b/client/tailscale/keys.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/client/tailscale/localclient_aliases.go b/client/tailscale/localclient_aliases.go index e3492e841b1c9..98a72068a5eba 100644 --- a/client/tailscale/localclient_aliases.go +++ b/client/tailscale/localclient_aliases.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/client/tailscale/required_version.go b/client/tailscale/required_version.go index d6bca1c6d8ff9..fb994e55fb604 100644 --- a/client/tailscale/required_version.go +++ b/client/tailscale/required_version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !go1.23 diff --git a/client/tailscale/routes.go b/client/tailscale/routes.go index b72f2743ff9fb..aa6e49e3b7fc2 100644 --- a/client/tailscale/routes.go +++ b/client/tailscale/routes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/tailnet.go b/client/tailscale/tailnet.go index 9453962c908c8..75ca7dfd60a33 100644 --- a/client/tailscale/tailnet.go +++ b/client/tailscale/tailnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index 76e44454b2fc2..d5585a052bb99 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/client/tailscale/tailscale_test.go b/client/tailscale/tailscale_test.go index 67379293bd580..fe2fbe383b679 100644 --- a/client/tailscale/tailscale_test.go +++ b/client/tailscale/tailscale_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/client/web/assets.go b/client/web/assets.go index c4f4e9e3bcf66..b9e4226299dd1 100644 --- a/client/web/assets.go +++ b/client/web/assets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package web diff --git a/client/web/auth.go b/client/web/auth.go index 27eb24ee444c5..4e25b049b30ac 100644 --- a/client/web/auth.go +++ b/client/web/auth.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package web diff --git a/client/web/qnap.go b/client/web/qnap.go index 9bde64bf5885b..132b95aed086d 100644 --- a/client/web/qnap.go +++ b/client/web/qnap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // qnap.go contains handlers and logic, such as authentication, diff --git a/client/web/src/api.ts b/client/web/src/api.ts index e780c76459dfd..246f74ff231c2 100644 --- a/client/web/src/api.ts +++ b/client/web/src/api.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback } from "react" diff --git a/client/web/src/components/acl-tag.tsx b/client/web/src/components/acl-tag.tsx index cb34375ed293c..95ab764c4a56d 100644 --- a/client/web/src/components/acl-tag.tsx +++ b/client/web/src/components/acl-tag.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/address-copy-card.tsx b/client/web/src/components/address-copy-card.tsx index 6b4f25bed73f4..697086f15c58d 100644 --- a/client/web/src/components/address-copy-card.tsx +++ b/client/web/src/components/address-copy-card.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as Primitive from "@radix-ui/react-popover" diff --git a/client/web/src/components/app.tsx b/client/web/src/components/app.tsx index 981dd8889c4b2..b885125b7f278 100644 --- a/client/web/src/components/app.tsx +++ b/client/web/src/components/app.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/control-components.tsx b/client/web/src/components/control-components.tsx index ffb0a2999558f..42ed25107c986 100644 --- a/client/web/src/components/control-components.tsx +++ b/client/web/src/components/control-components.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/exit-node-selector.tsx b/client/web/src/components/exit-node-selector.tsx index c0fd5e730b04c..a564ebbfc56b1 100644 --- a/client/web/src/components/exit-node-selector.tsx +++ b/client/web/src/components/exit-node-selector.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/login-toggle.tsx b/client/web/src/components/login-toggle.tsx index f5c4efe3ce2ac..397cb2ee1f8f1 100644 --- a/client/web/src/components/login-toggle.tsx +++ b/client/web/src/components/login-toggle.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/nice-ip.tsx b/client/web/src/components/nice-ip.tsx index f00d763f96db9..1f90d1cd73802 100644 --- a/client/web/src/components/nice-ip.tsx +++ b/client/web/src/components/nice-ip.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/update-available.tsx b/client/web/src/components/update-available.tsx index 763007de889c7..9d678d9073aa7 100644 --- a/client/web/src/components/update-available.tsx +++ b/client/web/src/components/update-available.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/views/device-details-view.tsx b/client/web/src/components/views/device-details-view.tsx index fa58e52aea473..e24aacf520743 100644 --- a/client/web/src/components/views/device-details-view.tsx +++ b/client/web/src/components/views/device-details-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/disconnected-view.tsx b/client/web/src/components/views/disconnected-view.tsx index 492c69e469ef1..5ec86aae4643b 100644 --- a/client/web/src/components/views/disconnected-view.tsx +++ b/client/web/src/components/views/disconnected-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/views/home-view.tsx b/client/web/src/components/views/home-view.tsx index 8073823466b34..e9051f22ba1cd 100644 --- a/client/web/src/components/views/home-view.tsx +++ b/client/web/src/components/views/home-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/login-view.tsx b/client/web/src/components/views/login-view.tsx index f8c15b16dbcaa..a6c4a9ae2c7ab 100644 --- a/client/web/src/components/views/login-view.tsx +++ b/client/web/src/components/views/login-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/components/views/ssh-view.tsx b/client/web/src/components/views/ssh-view.tsx index 1337b9fdd10fd..67c324fa53563 100644 --- a/client/web/src/components/views/ssh-view.tsx +++ b/client/web/src/components/views/ssh-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/subnet-router-view.tsx b/client/web/src/components/views/subnet-router-view.tsx index 26369112c1cbf..7f4c682996033 100644 --- a/client/web/src/components/views/subnet-router-view.tsx +++ b/client/web/src/components/views/subnet-router-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/components/views/updating-view.tsx b/client/web/src/components/views/updating-view.tsx index 0c844c7d09faa..d39dc5c63fd27 100644 --- a/client/web/src/components/views/updating-view.tsx +++ b/client/web/src/components/views/updating-view.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/hooks/auth.ts b/client/web/src/hooks/auth.ts index 51eb0c400bae9..c3d0cdc877022 100644 --- a/client/web/src/hooks/auth.ts +++ b/client/web/src/hooks/auth.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback, useEffect, useState } from "react" diff --git a/client/web/src/hooks/exit-nodes.ts b/client/web/src/hooks/exit-nodes.ts index 5e47fbc227cd4..78f8a383dbb00 100644 --- a/client/web/src/hooks/exit-nodes.ts +++ b/client/web/src/hooks/exit-nodes.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useMemo } from "react" diff --git a/client/web/src/hooks/self-update.ts b/client/web/src/hooks/self-update.ts index eb10463c1abe1..e63d6eddaeebf 100644 --- a/client/web/src/hooks/self-update.ts +++ b/client/web/src/hooks/self-update.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback, useEffect, useState } from "react" diff --git a/client/web/src/hooks/toaster.ts b/client/web/src/hooks/toaster.ts index 41fb4f42d0918..8c30cab58a6a5 100644 --- a/client/web/src/hooks/toaster.ts +++ b/client/web/src/hooks/toaster.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useRawToasterForHook } from "src/ui/toaster" diff --git a/client/web/src/hooks/ts-web-connected.ts b/client/web/src/hooks/ts-web-connected.ts index 3145663d7654d..bd020c9e9b595 100644 --- a/client/web/src/hooks/ts-web-connected.ts +++ b/client/web/src/hooks/ts-web-connected.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useCallback, useEffect, useState } from "react" diff --git a/client/web/src/index.tsx b/client/web/src/index.tsx index 31ac7890f45f2..2b970ebca8ed7 100644 --- a/client/web/src/index.tsx +++ b/client/web/src/index.tsx @@ -1,10 +1,10 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Preserved js license comment for web client app. /** * @license - * Copyright (c) Tailscale Inc & AUTHORS + * Copyright (c) Tailscale Inc & contributors * SPDX-License-Identifier: BSD-3-Clause */ diff --git a/client/web/src/types.ts b/client/web/src/types.ts index 62fa4c59f1fbf..ebf11d442fc52 100644 --- a/client/web/src/types.ts +++ b/client/web/src/types.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { assertNever } from "src/utils/util" diff --git a/client/web/src/ui/badge.tsx b/client/web/src/ui/badge.tsx index c0caa6403b37e..de8c21e3568ab 100644 --- a/client/web/src/ui/badge.tsx +++ b/client/web/src/ui/badge.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/button.tsx b/client/web/src/ui/button.tsx index 18dc2939f1889..e38f58f02bd2e 100644 --- a/client/web/src/ui/button.tsx +++ b/client/web/src/ui/button.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/card.tsx b/client/web/src/ui/card.tsx index 4e17c3df6d29e..7d3c9b89e8202 100644 --- a/client/web/src/ui/card.tsx +++ b/client/web/src/ui/card.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/collapsible.tsx b/client/web/src/ui/collapsible.tsx index 6aa8c0b9f5ca1..bd0b0eedad84a 100644 --- a/client/web/src/ui/collapsible.tsx +++ b/client/web/src/ui/collapsible.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as Primitive from "@radix-ui/react-collapsible" diff --git a/client/web/src/ui/dialog.tsx b/client/web/src/ui/dialog.tsx index d5af834ce05d2..6b3bb792b8565 100644 --- a/client/web/src/ui/dialog.tsx +++ b/client/web/src/ui/dialog.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as DialogPrimitive from "@radix-ui/react-dialog" diff --git a/client/web/src/ui/empty-state.tsx b/client/web/src/ui/empty-state.tsx index 6ac7fd4fa87e6..3964a55590ab4 100644 --- a/client/web/src/ui/empty-state.tsx +++ b/client/web/src/ui/empty-state.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/input.tsx b/client/web/src/ui/input.tsx index 756e0fc2ea4d6..7cff6bf5bf074 100644 --- a/client/web/src/ui/input.tsx +++ b/client/web/src/ui/input.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/loading-dots.tsx b/client/web/src/ui/loading-dots.tsx index 6b47552a95844..83c60da93a937 100644 --- a/client/web/src/ui/loading-dots.tsx +++ b/client/web/src/ui/loading-dots.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/popover.tsx b/client/web/src/ui/popover.tsx index c0f01c833f465..0139894bb5e4a 100644 --- a/client/web/src/ui/popover.tsx +++ b/client/web/src/ui/popover.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import * as PopoverPrimitive from "@radix-ui/react-popover" diff --git a/client/web/src/ui/portal-container-context.tsx b/client/web/src/ui/portal-container-context.tsx index d25b30bae1731..922cd0d14ea52 100644 --- a/client/web/src/ui/portal-container-context.tsx +++ b/client/web/src/ui/portal-container-context.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import React from "react" diff --git a/client/web/src/ui/profile-pic.tsx b/client/web/src/ui/profile-pic.tsx index 343fb29b490e0..4bbdad878ab4a 100644 --- a/client/web/src/ui/profile-pic.tsx +++ b/client/web/src/ui/profile-pic.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/quick-copy.tsx b/client/web/src/ui/quick-copy.tsx index bc8f916c84144..0c51f820ccf33 100644 --- a/client/web/src/ui/quick-copy.tsx +++ b/client/web/src/ui/quick-copy.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/search-input.tsx b/client/web/src/ui/search-input.tsx index debba371caec6..9b99984acc014 100644 --- a/client/web/src/ui/search-input.tsx +++ b/client/web/src/ui/search-input.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/spinner.tsx b/client/web/src/ui/spinner.tsx index 51f6e887b836d..be3dc8d5b4fa5 100644 --- a/client/web/src/ui/spinner.tsx +++ b/client/web/src/ui/spinner.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/toaster.tsx b/client/web/src/ui/toaster.tsx index 18f491f3b2552..677ccde4d5d9b 100644 --- a/client/web/src/ui/toaster.tsx +++ b/client/web/src/ui/toaster.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/ui/toggle.tsx b/client/web/src/ui/toggle.tsx index 4922830058f16..83ca92608a4dc 100644 --- a/client/web/src/ui/toggle.tsx +++ b/client/web/src/ui/toggle.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import cx from "classnames" diff --git a/client/web/src/utils/clipboard.ts b/client/web/src/utils/clipboard.ts index f003bc24079ab..3ca5f281ebb93 100644 --- a/client/web/src/utils/clipboard.ts +++ b/client/web/src/utils/clipboard.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { isPromise } from "src/utils/util" diff --git a/client/web/src/utils/util.test.ts b/client/web/src/utils/util.test.ts index 148f6cc365589..2a598d6505654 100644 --- a/client/web/src/utils/util.test.ts +++ b/client/web/src/utils/util.test.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { isTailscaleIPv6, pluralize } from "src/utils/util" diff --git a/client/web/src/utils/util.ts b/client/web/src/utils/util.ts index 5f8eda7b77572..81fc904034c08 100644 --- a/client/web/src/utils/util.ts +++ b/client/web/src/utils/util.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** diff --git a/client/web/synology.go b/client/web/synology.go index 922489d78af16..e39cbc9c5c82e 100644 --- a/client/web/synology.go +++ b/client/web/synology.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // synology.go contains handlers and logic, such as authentication, diff --git a/client/web/web.go b/client/web/web.go index dbd3d5df0be86..f8a9e7c1769a2 100644 --- a/client/web/web.go +++ b/client/web/web.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package web provides the Tailscale client for web. diff --git a/client/web/web_test.go b/client/web/web_test.go index 9ba16bccf4884..6b9a51002b33b 100644 --- a/client/web/web_test.go +++ b/client/web/web_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package web diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 3a0a8d03e0425..e75e425a455b8 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package clientupdate implements tailscale client update for all supported diff --git a/clientupdate/clientupdate_downloads.go b/clientupdate/clientupdate_downloads.go index 18d3176b42afe..9458f88fe8a18 100644 --- a/clientupdate/clientupdate_downloads.go +++ b/clientupdate/clientupdate_downloads.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || windows diff --git a/clientupdate/clientupdate_not_downloads.go b/clientupdate/clientupdate_not_downloads.go index 057b4f2cd7574..aaffb76f05b3e 100644 --- a/clientupdate/clientupdate_not_downloads.go +++ b/clientupdate/clientupdate_not_downloads.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !((linux && !android) || windows) diff --git a/clientupdate/clientupdate_notwindows.go b/clientupdate/clientupdate_notwindows.go index edadc210c8a15..12035ff73495a 100644 --- a/clientupdate/clientupdate_notwindows.go +++ b/clientupdate/clientupdate_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index b265d56411bdc..089936a3120f1 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package clientupdate diff --git a/clientupdate/clientupdate_windows.go b/clientupdate/clientupdate_windows.go index 5faeda6dd70e3..70a3c509121ea 100644 --- a/clientupdate/clientupdate_windows.go +++ b/clientupdate/clientupdate_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Windows-specific stuff that can't go in clientupdate.go because it needs diff --git a/clientupdate/distsign/distsign.go b/clientupdate/distsign/distsign.go index 954403ae0c62c..c804b855cfc1d 100644 --- a/clientupdate/distsign/distsign.go +++ b/clientupdate/distsign/distsign.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package distsign implements signature and validation of arbitrary diff --git a/clientupdate/distsign/distsign_test.go b/clientupdate/distsign/distsign_test.go index 09a701f499198..0d454771fc9a4 100644 --- a/clientupdate/distsign/distsign_test.go +++ b/clientupdate/distsign/distsign_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distsign diff --git a/clientupdate/distsign/roots.go b/clientupdate/distsign/roots.go index d5b47b7b62e92..2fab3aab90373 100644 --- a/clientupdate/distsign/roots.go +++ b/clientupdate/distsign/roots.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distsign diff --git a/clientupdate/distsign/roots_test.go b/clientupdate/distsign/roots_test.go index 7a94529538ef1..562b06c1c29c1 100644 --- a/clientupdate/distsign/roots_test.go +++ b/clientupdate/distsign/roots_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distsign diff --git a/cmd/addlicense/main.go b/cmd/addlicense/main.go index 1cd1b0f19354a..35d97b72f70b4 100644 --- a/cmd/addlicense/main.go +++ b/cmd/addlicense/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program addlicense adds a license header to a file. @@ -67,7 +67,7 @@ func check(err error) { } var license = ` -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause `[1:] diff --git a/cmd/build-webclient/build-webclient.go b/cmd/build-webclient/build-webclient.go index f92c0858fae25..949d9ef349ef1 100644 --- a/cmd/build-webclient/build-webclient.go +++ b/cmd/build-webclient/build-webclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The build-webclient tool generates the static resources needed for the diff --git a/cmd/checkmetrics/checkmetrics.go b/cmd/checkmetrics/checkmetrics.go index fb9e8ab4c61ec..5612ffbf512f9 100644 --- a/cmd/checkmetrics/checkmetrics.go +++ b/cmd/checkmetrics/checkmetrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // checkmetrics validates that all metrics in the tailscale client-metrics diff --git a/cmd/cigocacher/cigocacher.go b/cmd/cigocacher/cigocacher.go index 872cb195355b5..b308afd06d688 100644 --- a/cmd/cigocacher/cigocacher.go +++ b/cmd/cigocacher/cigocacher.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // cigocacher is an opinionated-to-Tailscale client for gocached. It connects diff --git a/cmd/cigocacher/disk.go b/cmd/cigocacher/disk.go index 57a9b80d5609e..e04dac0509300 100644 --- a/cmd/cigocacher/disk.go +++ b/cmd/cigocacher/disk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/cigocacher/disk_notwindows.go b/cmd/cigocacher/disk_notwindows.go index 705ed92e3d8de..353b734ab9ce7 100644 --- a/cmd/cigocacher/disk_notwindows.go +++ b/cmd/cigocacher/disk_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/cmd/cigocacher/disk_windows.go b/cmd/cigocacher/disk_windows.go index 9efae2c632087..686bcf2b0d68b 100644 --- a/cmd/cigocacher/disk_windows.go +++ b/cmd/cigocacher/disk_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/cigocacher/http.go b/cmd/cigocacher/http.go index 55735f089655e..16d0ae899acbc 100644 --- a/cmd/cigocacher/http.go +++ b/cmd/cigocacher/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/cloner/cloner.go b/cmd/cloner/cloner.go index a81bd10bd5401..a3f0684faa589 100644 --- a/cmd/cloner/cloner.go +++ b/cmd/cloner/cloner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Cloner is a tool to automate the creation of a Clone method. diff --git a/cmd/cloner/cloner_test.go b/cmd/cloner/cloner_test.go index 754a4ac49a220..b06f5c4fa5610 100644 --- a/cmd/cloner/cloner_test.go +++ b/cmd/cloner/cloner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/cloner/clonerex/clonerex.go b/cmd/cloner/clonerex/clonerex.go index b9f6d60dedb35..1007d0c6b646d 100644 --- a/cmd/cloner/clonerex/clonerex.go +++ b/cmd/cloner/clonerex/clonerex.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type SliceContainer,InterfaceContainer,MapWithPointers,DeeplyNestedMap diff --git a/cmd/cloner/clonerex/clonerex_clone.go b/cmd/cloner/clonerex/clonerex_clone.go index 13e1276c4e4b8..5c161239fc992 100644 --- a/cmd/cloner/clonerex/clonerex_clone.go +++ b/cmd/cloner/clonerex/clonerex_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/cmd/connector-gen/advertise-routes.go b/cmd/connector-gen/advertise-routes.go index 446f4906a4d65..57c101e27af7c 100644 --- a/cmd/connector-gen/advertise-routes.go +++ b/cmd/connector-gen/advertise-routes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/connector-gen/aws.go b/cmd/connector-gen/aws.go index bd2632ae27960..b0d6566b915f6 100644 --- a/cmd/connector-gen/aws.go +++ b/cmd/connector-gen/aws.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/connector-gen/connector-gen.go b/cmd/connector-gen/connector-gen.go index 6947f6410a96f..8693a1bf0490f 100644 --- a/cmd/connector-gen/connector-gen.go +++ b/cmd/connector-gen/connector-gen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // connector-gen is a tool to generate app connector configuration and flags from service provider address data. diff --git a/cmd/connector-gen/github.go b/cmd/connector-gen/github.go index def40872d52c1..a0162aa06cae3 100644 --- a/cmd/connector-gen/github.go +++ b/cmd/connector-gen/github.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/containerboot/egressservices.go b/cmd/containerboot/egressservices.go index 21d9f0bcb9a2b..6526c255eeed7 100644 --- a/cmd/containerboot/egressservices.go +++ b/cmd/containerboot/egressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/egressservices_test.go b/cmd/containerboot/egressservices_test.go index 724626b072c2b..0d8504bdad7fd 100644 --- a/cmd/containerboot/egressservices_test.go +++ b/cmd/containerboot/egressservices_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/forwarding.go b/cmd/containerboot/forwarding.go index 04d34836c92d8..0ec9c36c0bd30 100644 --- a/cmd/containerboot/forwarding.go +++ b/cmd/containerboot/forwarding.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/ingressservices.go b/cmd/containerboot/ingressservices.go index 1a2da95675f4e..d76bf86e0b8ec 100644 --- a/cmd/containerboot/ingressservices.go +++ b/cmd/containerboot/ingressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/ingressservices_test.go b/cmd/containerboot/ingressservices_test.go index 228bbb159f463..46330103e343b 100644 --- a/cmd/containerboot/ingressservices_test.go +++ b/cmd/containerboot/ingressservices_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/kube.go b/cmd/containerboot/kube.go index e566fa483447c..4943bddba7ad4 100644 --- a/cmd/containerboot/kube.go +++ b/cmd/containerboot/kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/kube_test.go b/cmd/containerboot/kube_test.go index c33714ed12ace..bc80e9cdf2cb3 100644 --- a/cmd/containerboot/kube_test.go +++ b/cmd/containerboot/kube_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/main.go b/cmd/containerboot/main.go index a520b5756ade5..9d8d3f02328e8 100644 --- a/cmd/containerboot/main.go +++ b/cmd/containerboot/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/main_test.go b/cmd/containerboot/main_test.go index 7007cc15202d9..6eeb59c9b2e7e 100644 --- a/cmd/containerboot/main_test.go +++ b/cmd/containerboot/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/serve.go b/cmd/containerboot/serve.go index 5fa8e580d5828..bc154c7e9f258 100644 --- a/cmd/containerboot/serve.go +++ b/cmd/containerboot/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/serve_test.go b/cmd/containerboot/serve_test.go index fc18f254dad05..0683346f7159a 100644 --- a/cmd/containerboot/serve_test.go +++ b/cmd/containerboot/serve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/settings.go b/cmd/containerboot/settings.go index aab2b86314e23..c35fc14079d85 100644 --- a/cmd/containerboot/settings.go +++ b/cmd/containerboot/settings.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/settings_test.go b/cmd/containerboot/settings_test.go index 576ea7f3eef3e..5fa0c7dd10724 100644 --- a/cmd/containerboot/settings_test.go +++ b/cmd/containerboot/settings_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/containerboot/tailscaled.go b/cmd/containerboot/tailscaled.go index e5b0b8b8ed1b1..9990600c84c65 100644 --- a/cmd/containerboot/tailscaled.go +++ b/cmd/containerboot/tailscaled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/derper/ace.go b/cmd/derper/ace.go index 56fb68c336cd3..ae2d0cbebb413 100644 --- a/cmd/derper/ace.go +++ b/cmd/derper/ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO: docs about all this diff --git a/cmd/derper/bootstrap_dns.go b/cmd/derper/bootstrap_dns.go index a58f040bae687..9abc95df56878 100644 --- a/cmd/derper/bootstrap_dns.go +++ b/cmd/derper/bootstrap_dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/bootstrap_dns_test.go b/cmd/derper/bootstrap_dns_test.go index 9b99103abfe33..5b765f6d37b5f 100644 --- a/cmd/derper/bootstrap_dns_test.go +++ b/cmd/derper/bootstrap_dns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/cert.go b/cmd/derper/cert.go index dfd7769905132..979c0d671517f 100644 --- a/cmd/derper/cert.go +++ b/cmd/derper/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/cert_test.go b/cmd/derper/cert_test.go index b4e18f6951ae0..e111ed76b7a97 100644 --- a/cmd/derper/cert_test.go +++ b/cmd/derper/cert_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index ddf45747ac9fe..87f9a0bc084e4 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The derper binary is a simple DERP server. diff --git a/cmd/derper/derper_test.go b/cmd/derper/derper_test.go index d27f8cb20144d..0a2fd8787d61d 100644 --- a/cmd/derper/derper_test.go +++ b/cmd/derper/derper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/mesh.go b/cmd/derper/mesh.go index 909b5f2ca18c4..34ea7da856220 100644 --- a/cmd/derper/mesh.go +++ b/cmd/derper/mesh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derper/websocket.go b/cmd/derper/websocket.go index 82fd30bed165a..1929f16906659 100644 --- a/cmd/derper/websocket.go +++ b/cmd/derper/websocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/derpprobe/derpprobe.go b/cmd/derpprobe/derpprobe.go index 5d2179b512c23..549364e5e8f6a 100644 --- a/cmd/derpprobe/derpprobe.go +++ b/cmd/derpprobe/derpprobe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The derpprobe binary probes derpers. diff --git a/cmd/dist/dist.go b/cmd/dist/dist.go index c7406298d8188..88b9e6fba9133 100644 --- a/cmd/dist/dist.go +++ b/cmd/dist/dist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The dist command builds Tailscale release packages for distribution. diff --git a/cmd/distsign/distsign.go b/cmd/distsign/distsign.go index 051afabcd0b71..e0dba27206be9 100644 --- a/cmd/distsign/distsign.go +++ b/cmd/distsign/distsign.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command distsign tests downloads and signature validating for packages diff --git a/cmd/featuretags/featuretags.go b/cmd/featuretags/featuretags.go index 8c8a2ceaf54ff..f3aae68cc8b17 100644 --- a/cmd/featuretags/featuretags.go +++ b/cmd/featuretags/featuretags.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The featuretags command helps other build tools select Tailscale's Go build diff --git a/cmd/get-authkey/main.go b/cmd/get-authkey/main.go index ec7ab5d2c6158..da98decda6ae5 100644 --- a/cmd/get-authkey/main.go +++ b/cmd/get-authkey/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // get-authkey allocates an authkey using an OAuth API client diff --git a/cmd/gitops-pusher/cache.go b/cmd/gitops-pusher/cache.go index 6792e5e63e9cc..af5c4606c0d50 100644 --- a/cmd/gitops-pusher/cache.go +++ b/cmd/gitops-pusher/cache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/gitops-pusher/gitops-pusher.go b/cmd/gitops-pusher/gitops-pusher.go index 0cbbda88a18b9..39a60d3064432 100644 --- a/cmd/gitops-pusher/gitops-pusher.go +++ b/cmd/gitops-pusher/gitops-pusher.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command gitops-pusher allows users to use a GitOps flow for managing Tailscale ACLs. diff --git a/cmd/gitops-pusher/gitops-pusher_test.go b/cmd/gitops-pusher/gitops-pusher_test.go index e08b06c9cd194..bc339ae6a0b84 100644 --- a/cmd/gitops-pusher/gitops-pusher_test.go +++ b/cmd/gitops-pusher/gitops-pusher_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index fa116b28b15ab..710de49cd67a8 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The hello binary runs hello.ts.net. diff --git a/cmd/jsonimports/format.go b/cmd/jsonimports/format.go index 6dbd175583a4d..e990d0e6745c3 100644 --- a/cmd/jsonimports/format.go +++ b/cmd/jsonimports/format.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/jsonimports/format_test.go b/cmd/jsonimports/format_test.go index 28654eb4550ee..fb3d6fa09698d 100644 --- a/cmd/jsonimports/format_test.go +++ b/cmd/jsonimports/format_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/jsonimports/jsonimports.go b/cmd/jsonimports/jsonimports.go index 4be2e10cbe091..6903844e121ca 100644 --- a/cmd/jsonimports/jsonimports.go +++ b/cmd/jsonimports/jsonimports.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The jsonimports tool formats all Go source files in the repository diff --git a/cmd/k8s-nameserver/main.go b/cmd/k8s-nameserver/main.go index 84e65452d2334..1b219fb1ab924 100644 --- a/cmd/k8s-nameserver/main.go +++ b/cmd/k8s-nameserver/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-nameserver/main_test.go b/cmd/k8s-nameserver/main_test.go index bca010048664a..0624800836675 100644 --- a/cmd/k8s-nameserver/main_test.go +++ b/cmd/k8s-nameserver/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/api-server-proxy-pg.go b/cmd/k8s-operator/api-server-proxy-pg.go index 1a81e4967e5d8..ff04d553a7da3 100644 --- a/cmd/k8s-operator/api-server-proxy-pg.go +++ b/cmd/k8s-operator/api-server-proxy-pg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/api-server-proxy-pg_test.go b/cmd/k8s-operator/api-server-proxy-pg_test.go index dee5057236675..d7e88123fb28b 100644 --- a/cmd/k8s-operator/api-server-proxy-pg_test.go +++ b/cmd/k8s-operator/api-server-proxy-pg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/k8s-operator/api-server-proxy.go b/cmd/k8s-operator/api-server-proxy.go index 70333d2c48d41..492590c9fecd6 100644 --- a/cmd/k8s-operator/api-server-proxy.go +++ b/cmd/k8s-operator/api-server-proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/connector.go b/cmd/k8s-operator/connector.go index f4d518faa3aad..0c2d32482e78b 100644 --- a/cmd/k8s-operator/connector.go +++ b/cmd/k8s-operator/connector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/connector_test.go b/cmd/k8s-operator/connector_test.go index afc7d2d6e3975..7866f3e002921 100644 --- a/cmd/k8s-operator/connector_test.go +++ b/cmd/k8s-operator/connector_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/deploy/chart/Chart.yaml b/cmd/k8s-operator/deploy/chart/Chart.yaml index 9db6389d1d944..b16fc4c37fb8a 100644 --- a/cmd/k8s-operator/deploy/chart/Chart.yaml +++ b/cmd/k8s-operator/deploy/chart/Chart.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v2 diff --git a/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml index d6e9d1bf48ef8..2ca4f398ad2da 100644 --- a/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/apiserverproxy-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # If old setting used, enable both old (operator) and new (ProxyGroup) workflows. diff --git a/cmd/k8s-operator/deploy/chart/templates/deployment.yaml b/cmd/k8s-operator/deploy/chart/templates/deployment.yaml index df9cb8ce1bcb0..0c0cb64cbb4ed 100644 --- a/cmd/k8s-operator/deploy/chart/templates/deployment.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/deployment.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: apps/v1 diff --git a/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml b/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml index 759ba341a8f21..34928d6dcd6c8 100644 --- a/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/oauth-secret.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause {{ if and .Values.oauth .Values.oauth.clientId (not .Values.oauth.audience) -}} diff --git a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml index 930eef852c9ce..92decef17aab4 100644 --- a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 diff --git a/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml index fa552a7c7e39a..89d6736b790a1 100644 --- a/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/proxy-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 diff --git a/cmd/k8s-operator/deploy/chart/values.yaml b/cmd/k8s-operator/deploy/chart/values.yaml index eb11fc7f27a86..8517d77aa5c84 100644 --- a/cmd/k8s-operator/deploy/chart/values.yaml +++ b/cmd/k8s-operator/deploy/chart/values.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # Operator oauth credentials. If unset a Secret named operator-oauth must be diff --git a/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml b/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml index 5818fa69fff7d..2dc9cad228ffa 100644 --- a/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml +++ b/cmd/k8s-operator/deploy/manifests/authproxy-rbac.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index 5a64f2c7db307..4c9822847d677 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 diff --git a/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml b/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml index a96d4c37ee421..800025e90003b 100644 --- a/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml +++ b/cmd/k8s-operator/deploy/manifests/templates/01-header.yaml @@ -1,3 +1,3 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause diff --git a/cmd/k8s-operator/dnsrecords.go b/cmd/k8s-operator/dnsrecords.go index 1a9395aa00aa9..e75bcd4c2e1da 100644 --- a/cmd/k8s-operator/dnsrecords.go +++ b/cmd/k8s-operator/dnsrecords.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/dnsrecords_test.go b/cmd/k8s-operator/dnsrecords_test.go index 13898078fd4ba..0d89c4a863e4d 100644 --- a/cmd/k8s-operator/dnsrecords_test.go +++ b/cmd/k8s-operator/dnsrecords_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/e2e/doc.go b/cmd/k8s-operator/e2e/doc.go index 40fa1f36a1d82..c0cc363160f70 100644 --- a/cmd/k8s-operator/e2e/doc.go +++ b/cmd/k8s-operator/e2e/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package e2e runs end-to-end tests for the Tailscale Kubernetes operator. diff --git a/cmd/k8s-operator/e2e/ingress_test.go b/cmd/k8s-operator/e2e/ingress_test.go index c5b238e852b89..eb05efa0cd1b8 100644 --- a/cmd/k8s-operator/e2e/ingress_test.go +++ b/cmd/k8s-operator/e2e/ingress_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e diff --git a/cmd/k8s-operator/e2e/main_test.go b/cmd/k8s-operator/e2e/main_test.go index 68f10dbb064cf..cb5c35c0054b2 100644 --- a/cmd/k8s-operator/e2e/main_test.go +++ b/cmd/k8s-operator/e2e/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e diff --git a/cmd/k8s-operator/e2e/pebble.go b/cmd/k8s-operator/e2e/pebble.go index a3175a4edc771..a3ccb50cd0493 100644 --- a/cmd/k8s-operator/e2e/pebble.go +++ b/cmd/k8s-operator/e2e/pebble.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e diff --git a/cmd/k8s-operator/e2e/proxy_test.go b/cmd/k8s-operator/e2e/proxy_test.go index b61d6d5763810..f7d11d278ef77 100644 --- a/cmd/k8s-operator/e2e/proxy_test.go +++ b/cmd/k8s-operator/e2e/proxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e diff --git a/cmd/k8s-operator/e2e/setup.go b/cmd/k8s-operator/e2e/setup.go index 00e75ddd5b3eb..baf763ac61a60 100644 --- a/cmd/k8s-operator/e2e/setup.go +++ b/cmd/k8s-operator/e2e/setup.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e diff --git a/cmd/k8s-operator/e2e/ssh.go b/cmd/k8s-operator/e2e/ssh.go index 407e4e085b7a9..8000d13267262 100644 --- a/cmd/k8s-operator/e2e/ssh.go +++ b/cmd/k8s-operator/e2e/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package e2e diff --git a/cmd/k8s-operator/egress-eps.go b/cmd/k8s-operator/egress-eps.go index 88da9935320bf..5181edf49a26c 100644 --- a/cmd/k8s-operator/egress-eps.go +++ b/cmd/k8s-operator/egress-eps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-eps_test.go b/cmd/k8s-operator/egress-eps_test.go index bd80112aeb8a2..47acb64f27458 100644 --- a/cmd/k8s-operator/egress-eps_test.go +++ b/cmd/k8s-operator/egress-eps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-pod-readiness.go b/cmd/k8s-operator/egress-pod-readiness.go index ebab23ed06337..a8f306353d880 100644 --- a/cmd/k8s-operator/egress-pod-readiness.go +++ b/cmd/k8s-operator/egress-pod-readiness.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-pod-readiness_test.go b/cmd/k8s-operator/egress-pod-readiness_test.go index 3c35d9043ebe6..baa1442671907 100644 --- a/cmd/k8s-operator/egress-pod-readiness_test.go +++ b/cmd/k8s-operator/egress-pod-readiness_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services-readiness.go b/cmd/k8s-operator/egress-services-readiness.go index 80f3c7d285141..965dc08f87f1d 100644 --- a/cmd/k8s-operator/egress-services-readiness.go +++ b/cmd/k8s-operator/egress-services-readiness.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services-readiness_test.go b/cmd/k8s-operator/egress-services-readiness_test.go index fdff4fafa3240..ba89903df2f29 100644 --- a/cmd/k8s-operator/egress-services-readiness_test.go +++ b/cmd/k8s-operator/egress-services-readiness_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services.go b/cmd/k8s-operator/egress-services.go index 05be8efed9402..90ab2c88270ee 100644 --- a/cmd/k8s-operator/egress-services.go +++ b/cmd/k8s-operator/egress-services.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/egress-services_test.go b/cmd/k8s-operator/egress-services_test.go index 202804d3011fd..45861449191cb 100644 --- a/cmd/k8s-operator/egress-services_test.go +++ b/cmd/k8s-operator/egress-services_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/generate/main.go b/cmd/k8s-operator/generate/main.go index ca54e90909954..9a910da3eb945 100644 --- a/cmd/k8s-operator/generate/main.go +++ b/cmd/k8s-operator/generate/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/generate/main_test.go b/cmd/k8s-operator/generate/main_test.go index 5ea7fec80971a..775d16ba1e827 100644 --- a/cmd/k8s-operator/generate/main_test.go +++ b/cmd/k8s-operator/generate/main_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 && !windows diff --git a/cmd/k8s-operator/ingress-for-pg.go b/cmd/k8s-operator/ingress-for-pg.go index 1b35d853688cd..5966ace3c388e 100644 --- a/cmd/k8s-operator/ingress-for-pg.go +++ b/cmd/k8s-operator/ingress-for-pg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/ingress-for-pg_test.go b/cmd/k8s-operator/ingress-for-pg_test.go index 0f5527185a738..f285bd8ee947d 100644 --- a/cmd/k8s-operator/ingress-for-pg_test.go +++ b/cmd/k8s-operator/ingress-for-pg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/ingress.go b/cmd/k8s-operator/ingress.go index 9ef173ecef746..4952e789f6a02 100644 --- a/cmd/k8s-operator/ingress.go +++ b/cmd/k8s-operator/ingress.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/ingress_test.go b/cmd/k8s-operator/ingress_test.go index 52afc3be40c50..aac40897cc88e 100644 --- a/cmd/k8s-operator/ingress_test.go +++ b/cmd/k8s-operator/ingress_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/logger.go b/cmd/k8s-operator/logger.go index 46b1fc0c82d48..45018e37eaf30 100644 --- a/cmd/k8s-operator/logger.go +++ b/cmd/k8s-operator/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/metrics_resources.go b/cmd/k8s-operator/metrics_resources.go index 0579e34661a11..afb055018bb13 100644 --- a/cmd/k8s-operator/metrics_resources.go +++ b/cmd/k8s-operator/metrics_resources.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/nameserver.go b/cmd/k8s-operator/nameserver.go index 39db5f0f9cf16..522b460031530 100644 --- a/cmd/k8s-operator/nameserver.go +++ b/cmd/k8s-operator/nameserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/nameserver_test.go b/cmd/k8s-operator/nameserver_test.go index 858cd973d82c2..531190cf21dc2 100644 --- a/cmd/k8s-operator/nameserver_test.go +++ b/cmd/k8s-operator/nameserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/nodeport-service-ports.go b/cmd/k8s-operator/nodeport-service-ports.go index a9504e3e94f88..f8d28860bf84e 100644 --- a/cmd/k8s-operator/nodeport-service-ports.go +++ b/cmd/k8s-operator/nodeport-service-ports.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/k8s-operator/nodeport-services-ports_test.go b/cmd/k8s-operator/nodeport-services-ports_test.go index 9418bb8446bd8..9c147f79aecbd 100644 --- a/cmd/k8s-operator/nodeport-services-ports_test.go +++ b/cmd/k8s-operator/nodeport-services-ports_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index 7bb8b95f0855f..4f48c1812643a 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/operator_test.go b/cmd/k8s-operator/operator_test.go index d0f42fe6dfad5..53d16fbd225f3 100644 --- a/cmd/k8s-operator/operator_test.go +++ b/cmd/k8s-operator/operator_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxyclass.go b/cmd/k8s-operator/proxyclass.go index 2d51b351d3907..c0ea46116373b 100644 --- a/cmd/k8s-operator/proxyclass.go +++ b/cmd/k8s-operator/proxyclass.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxyclass_test.go b/cmd/k8s-operator/proxyclass_test.go index ae0f63d99ea4d..171cfc5904cd3 100644 --- a/cmd/k8s-operator/proxyclass_test.go +++ b/cmd/k8s-operator/proxyclass_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index 3a50ed8fb4c2b..13c3d7b715e50 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxygroup_specs.go b/cmd/k8s-operator/proxygroup_specs.go index 930b7049d8ea9..6bce004eaa88d 100644 --- a/cmd/k8s-operator/proxygroup_specs.go +++ b/cmd/k8s-operator/proxygroup_specs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/proxygroup_test.go b/cmd/k8s-operator/proxygroup_test.go index 2bcc9fb7a9720..c58bd2bb71dc5 100644 --- a/cmd/k8s-operator/proxygroup_test.go +++ b/cmd/k8s-operator/proxygroup_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 2919e535c0dca..e81fe2d66f6ed 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/sts_test.go b/cmd/k8s-operator/sts_test.go index afe54ed98bc49..81c0d25ec0ba4 100644 --- a/cmd/k8s-operator/sts_test.go +++ b/cmd/k8s-operator/sts_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/svc-for-pg.go b/cmd/k8s-operator/svc-for-pg.go index 144d3755811da..e0383824a6313 100644 --- a/cmd/k8s-operator/svc-for-pg.go +++ b/cmd/k8s-operator/svc-for-pg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/svc-for-pg_test.go b/cmd/k8s-operator/svc-for-pg_test.go index baaa07727df06..d01f8e983ad75 100644 --- a/cmd/k8s-operator/svc-for-pg_test.go +++ b/cmd/k8s-operator/svc-for-pg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/svc.go b/cmd/k8s-operator/svc.go index ec7bb8080dec7..31be22aa12ca3 100644 --- a/cmd/k8s-operator/svc.go +++ b/cmd/k8s-operator/svc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tailnet.go b/cmd/k8s-operator/tailnet.go index 8d749545faa46..57c749bec31ee 100644 --- a/cmd/k8s-operator/tailnet.go +++ b/cmd/k8s-operator/tailnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/testutils_test.go b/cmd/k8s-operator/testutils_test.go index b0e2cfd734fad..0e4a3eee40e73 100644 --- a/cmd/k8s-operator/testutils_test.go +++ b/cmd/k8s-operator/testutils_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsclient.go b/cmd/k8s-operator/tsclient.go index d22fa1797dd5c..063c2f768c6c6 100644 --- a/cmd/k8s-operator/tsclient.go +++ b/cmd/k8s-operator/tsclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsclient_test.go b/cmd/k8s-operator/tsclient_test.go index 16de512d5809f..c08705c78ed8b 100644 --- a/cmd/k8s-operator/tsclient_test.go +++ b/cmd/k8s-operator/tsclient_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index 3e8608bc8db8e..60ed24a7006b1 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder_specs.go b/cmd/k8s-operator/tsrecorder_specs.go index b4a10f2962ae9..ab06c01f81b7d 100644 --- a/cmd/k8s-operator/tsrecorder_specs.go +++ b/cmd/k8s-operator/tsrecorder_specs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder_specs_test.go b/cmd/k8s-operator/tsrecorder_specs_test.go index 0d78129fc76b3..47997d1d31b0f 100644 --- a/cmd/k8s-operator/tsrecorder_specs_test.go +++ b/cmd/k8s-operator/tsrecorder_specs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-operator/tsrecorder_test.go b/cmd/k8s-operator/tsrecorder_test.go index f7ff797b1ebba..bea734d865f66 100644 --- a/cmd/k8s-operator/tsrecorder_test.go +++ b/cmd/k8s-operator/tsrecorder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-proxy/internal/config/config.go b/cmd/k8s-proxy/internal/config/config.go index 0f0bd1bfcf39d..91b4c54a5c32d 100644 --- a/cmd/k8s-proxy/internal/config/config.go +++ b/cmd/k8s-proxy/internal/config/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/k8s-proxy/internal/config/config_test.go b/cmd/k8s-proxy/internal/config/config_test.go index bcb1b9ebd14e6..ac6c6cf93f623 100644 --- a/cmd/k8s-proxy/internal/config/config_test.go +++ b/cmd/k8s-proxy/internal/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package config diff --git a/cmd/k8s-proxy/k8s-proxy.go b/cmd/k8s-proxy/k8s-proxy.go index 9b2bb67494659..e00d43a948dba 100644 --- a/cmd/k8s-proxy/k8s-proxy.go +++ b/cmd/k8s-proxy/k8s-proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/mkmanifest/main.go b/cmd/mkmanifest/main.go index fb3c729f12d21..d08700341e7dc 100644 --- a/cmd/mkmanifest/main.go +++ b/cmd/mkmanifest/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The mkmanifest command is a simple helper utility to create a '.syso' file diff --git a/cmd/mkpkg/main.go b/cmd/mkpkg/main.go index 5e26b07f8f9f8..6f4de7e299b50 100644 --- a/cmd/mkpkg/main.go +++ b/cmd/mkpkg/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // mkpkg builds the Tailscale rpm and deb packages. diff --git a/cmd/mkversion/mkversion.go b/cmd/mkversion/mkversion.go index c8c8bf17930f6..ec9b0bb85ace4 100644 --- a/cmd/mkversion/mkversion.go +++ b/cmd/mkversion/mkversion.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // mkversion gets version info from git and outputs a bunch of shell variables diff --git a/cmd/nardump/nardump.go b/cmd/nardump/nardump.go index f8947b02b852c..c8db24cb6736d 100644 --- a/cmd/nardump/nardump.go +++ b/cmd/nardump/nardump.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // nardump is like nix-store --dump, but in Go, writing a NAR diff --git a/cmd/nardump/nardump_test.go b/cmd/nardump/nardump_test.go index 3b87e7962d638..c1ca825e1e288 100644 --- a/cmd/nardump/nardump_test.go +++ b/cmd/nardump/nardump_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/natc/ippool/consensusippool.go b/cmd/natc/ippool/consensusippool.go index bfa909b69a3b4..d595d3e7ddc7a 100644 --- a/cmd/natc/ippool/consensusippool.go +++ b/cmd/natc/ippool/consensusippool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/consensusippool_test.go b/cmd/natc/ippool/consensusippool_test.go index 242cdffaf26d3..fe42b2b223a8b 100644 --- a/cmd/natc/ippool/consensusippool_test.go +++ b/cmd/natc/ippool/consensusippool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/consensusippoolserialize.go b/cmd/natc/ippool/consensusippoolserialize.go index 97dc02f2c7d7c..be3312d300bad 100644 --- a/cmd/natc/ippool/consensusippoolserialize.go +++ b/cmd/natc/ippool/consensusippoolserialize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/ippool.go b/cmd/natc/ippool/ippool.go index 5a2dcbec911e0..641702f5d31e8 100644 --- a/cmd/natc/ippool/ippool.go +++ b/cmd/natc/ippool/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // ippool implements IP address storage, creation, and retrieval for cmd/natc diff --git a/cmd/natc/ippool/ippool_test.go b/cmd/natc/ippool/ippool_test.go index 8d474f86a97ed..405ec61564ed8 100644 --- a/cmd/natc/ippool/ippool_test.go +++ b/cmd/natc/ippool/ippool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/ipx.go b/cmd/natc/ippool/ipx.go index 8259a56dbf30e..4f52d6ede049a 100644 --- a/cmd/natc/ippool/ipx.go +++ b/cmd/natc/ippool/ipx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/ippool/ipx_test.go b/cmd/natc/ippool/ipx_test.go index 2e2b9d3d45baf..cb6889b683978 100644 --- a/cmd/natc/ippool/ipx_test.go +++ b/cmd/natc/ippool/ipx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ippool diff --git a/cmd/natc/natc.go b/cmd/natc/natc.go index a4f53d657d98e..11975b7d2e1a6 100644 --- a/cmd/natc/natc.go +++ b/cmd/natc/natc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The natc command is a work-in-progress implementation of a NAT based diff --git a/cmd/natc/natc_test.go b/cmd/natc/natc_test.go index c0a66deb8a4da..e1cc061234d0e 100644 --- a/cmd/natc/natc_test.go +++ b/cmd/natc/natc_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/netlogfmt/main.go b/cmd/netlogfmt/main.go index 0af52f862936c..212b36fb6b0ae 100644 --- a/cmd/netlogfmt/main.go +++ b/cmd/netlogfmt/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // netlogfmt parses a stream of JSON log messages from stdin and diff --git a/cmd/nginx-auth/nginx-auth.go b/cmd/nginx-auth/nginx-auth.go index 09da74da1d3c8..6b791eb6c35fa 100644 --- a/cmd/nginx-auth/nginx-auth.go +++ b/cmd/nginx-auth/nginx-auth.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/cmd/omitsize/omitsize.go b/cmd/omitsize/omitsize.go index 35e03d268e186..84863865991bc 100644 --- a/cmd/omitsize/omitsize.go +++ b/cmd/omitsize/omitsize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The omitsize tool prints out how large the Tailscale binaries are with diff --git a/cmd/pgproxy/pgproxy.go b/cmd/pgproxy/pgproxy.go index e102c8ae47411..ded6fa695fe88 100644 --- a/cmd/pgproxy/pgproxy.go +++ b/cmd/pgproxy/pgproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The pgproxy server is a proxy for the Postgres wire protocol. diff --git a/cmd/printdep/printdep.go b/cmd/printdep/printdep.go index 044283209c08c..c4ba5b79a3357 100644 --- a/cmd/printdep/printdep.go +++ b/cmd/printdep/printdep.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The printdep command is a build system tool for printing out information diff --git a/cmd/proxy-test-server/proxy-test-server.go b/cmd/proxy-test-server/proxy-test-server.go index 9f8c94a384ea5..2c705670446ba 100644 --- a/cmd/proxy-test-server/proxy-test-server.go +++ b/cmd/proxy-test-server/proxy-test-server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The proxy-test-server command is a simple HTTP proxy server for testing diff --git a/cmd/proxy-to-grafana/proxy-to-grafana.go b/cmd/proxy-to-grafana/proxy-to-grafana.go index 27f5e338c8d65..23f2640597d59 100644 --- a/cmd/proxy-to-grafana/proxy-to-grafana.go +++ b/cmd/proxy-to-grafana/proxy-to-grafana.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // proxy-to-grafana is a reverse proxy which identifies users based on their diff --git a/cmd/proxy-to-grafana/proxy-to-grafana_test.go b/cmd/proxy-to-grafana/proxy-to-grafana_test.go index 4831d54364943..be217043f12d3 100644 --- a/cmd/proxy-to-grafana/proxy-to-grafana_test.go +++ b/cmd/proxy-to-grafana/proxy-to-grafana_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/handlers.go b/cmd/sniproxy/handlers.go index 1973eecc017a3..157b9b75f885a 100644 --- a/cmd/sniproxy/handlers.go +++ b/cmd/sniproxy/handlers.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/handlers_test.go b/cmd/sniproxy/handlers_test.go index 4f9fc6a34b184..ad0637421cecc 100644 --- a/cmd/sniproxy/handlers_test.go +++ b/cmd/sniproxy/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/server.go b/cmd/sniproxy/server.go index b322b6f4b1137..0ff301fe92136 100644 --- a/cmd/sniproxy/server.go +++ b/cmd/sniproxy/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/server_test.go b/cmd/sniproxy/server_test.go index d56f2aa754f85..8e06e8abedf8c 100644 --- a/cmd/sniproxy/server_test.go +++ b/cmd/sniproxy/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sniproxy/sniproxy.go b/cmd/sniproxy/sniproxy.go index 2115c8095b351..45503feca8718 100644 --- a/cmd/sniproxy/sniproxy.go +++ b/cmd/sniproxy/sniproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The sniproxy is an outbound SNI proxy. It receives TLS connections over diff --git a/cmd/sniproxy/sniproxy_test.go b/cmd/sniproxy/sniproxy_test.go index 65e059efaa1d4..a404799d29d7d 100644 --- a/cmd/sniproxy/sniproxy_test.go +++ b/cmd/sniproxy/sniproxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/speedtest/speedtest.go b/cmd/speedtest/speedtest.go index 9a457ed6c7486..2cea97b1edef1 100644 --- a/cmd/speedtest/speedtest.go +++ b/cmd/speedtest/speedtest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program speedtest provides the speedtest command. The reason to keep it separate from diff --git a/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go b/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go index 39af584ecd481..3c3ade3cd35a3 100644 --- a/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go +++ b/cmd/ssh-auth-none-demo/ssh-auth-none-demo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // ssh-auth-none-demo is a demo SSH server that's meant to run on the diff --git a/cmd/stunc/stunc.go b/cmd/stunc/stunc.go index c4b2eedd39f90..e51cd15ba2248 100644 --- a/cmd/stunc/stunc.go +++ b/cmd/stunc/stunc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command stunc makes a STUN request to a STUN server and prints the result. diff --git a/cmd/stund/stund.go b/cmd/stund/stund.go index 1055d966f42c5..a27e520444464 100644 --- a/cmd/stund/stund.go +++ b/cmd/stund/stund.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The stund binary is a standalone STUN server. diff --git a/cmd/stunstamp/stunstamp.go b/cmd/stunstamp/stunstamp.go index 153dc9303bbb0..cfedd82bdd5cc 100644 --- a/cmd/stunstamp/stunstamp.go +++ b/cmd/stunstamp/stunstamp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The stunstamp binary measures round-trip latency with DERPs. diff --git a/cmd/stunstamp/stunstamp_default.go b/cmd/stunstamp/stunstamp_default.go index a244d9aea6410..3f6613cd060ee 100644 --- a/cmd/stunstamp/stunstamp_default.go +++ b/cmd/stunstamp/stunstamp_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/cmd/stunstamp/stunstamp_linux.go b/cmd/stunstamp/stunstamp_linux.go index 387805feff2f1..201e2f83b384c 100644 --- a/cmd/stunstamp/stunstamp_linux.go +++ b/cmd/stunstamp/stunstamp_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/sync-containers/main.go b/cmd/sync-containers/main.go index 63efa54531b10..ab2a38bd66dab 100644 --- a/cmd/sync-containers/main.go +++ b/cmd/sync-containers/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/systray/systray.go b/cmd/systray/systray.go index d35595e258e0f..9dc35f1420bee 100644 --- a/cmd/systray/systray.go +++ b/cmd/systray/systray.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo || !darwin diff --git a/cmd/tailscale/cli/appcroutes.go b/cmd/tailscale/cli/appcroutes.go index 4a1ba87e35bcc..2ea001aec9c84 100644 --- a/cmd/tailscale/cli/appcroutes.go +++ b/cmd/tailscale/cli/appcroutes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/bugreport.go b/cmd/tailscale/cli/bugreport.go index 50e6ffd82bedc..3ffaffa8b1fa5 100644 --- a/cmd/tailscale/cli/bugreport.go +++ b/cmd/tailscale/cli/bugreport.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/cert.go b/cmd/tailscale/cli/cert.go index 171eebe1eafc9..f38ddbacf1804 100644 --- a/cmd/tailscale/cli/cert.go +++ b/cmd/tailscale/cli/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 5ebc23a5befea..4d16cfe699537 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cli contains the cmd/tailscale CLI code in a package that can be included diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 8762b7aaeb905..41824701df551 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure-jetkvm.go b/cmd/tailscale/cli/configure-jetkvm.go index c80bf673605cf..1956ac836fe74 100644 --- a/cmd/tailscale/cli/configure-jetkvm.go +++ b/cmd/tailscale/cli/configure-jetkvm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && arm diff --git a/cmd/tailscale/cli/configure-kube.go b/cmd/tailscale/cli/configure-kube.go index bf5624856167a..3dcec250f01ef 100644 --- a/cmd/tailscale/cli/configure-kube.go +++ b/cmd/tailscale/cli/configure-kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_kube diff --git a/cmd/tailscale/cli/configure-kube_omit.go b/cmd/tailscale/cli/configure-kube_omit.go index 130f2870fab44..946fa2294d5aa 100644 --- a/cmd/tailscale/cli/configure-kube_omit.go +++ b/cmd/tailscale/cli/configure-kube_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_kube diff --git a/cmd/tailscale/cli/configure-kube_test.go b/cmd/tailscale/cli/configure-kube_test.go index 0c8b6b2b6cc0e..2df54d5751497 100644 --- a/cmd/tailscale/cli/configure-kube_test.go +++ b/cmd/tailscale/cli/configure-kube_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_kube diff --git a/cmd/tailscale/cli/configure-synology-cert.go b/cmd/tailscale/cli/configure-synology-cert.go index b5168ef92d11f..0f38f2df2941c 100644 --- a/cmd/tailscale/cli/configure-synology-cert.go +++ b/cmd/tailscale/cli/configure-synology-cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_acme && !ts_omit_synology diff --git a/cmd/tailscale/cli/configure-synology-cert_test.go b/cmd/tailscale/cli/configure-synology-cert_test.go index c7da5622fb629..08369c135f154 100644 --- a/cmd/tailscale/cli/configure-synology-cert_test.go +++ b/cmd/tailscale/cli/configure-synology-cert_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_acme diff --git a/cmd/tailscale/cli/configure-synology.go b/cmd/tailscale/cli/configure-synology.go index f0f05f75765b9..4cfd4160e066a 100644 --- a/cmd/tailscale/cli/configure-synology.go +++ b/cmd/tailscale/cli/configure-synology.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure.go b/cmd/tailscale/cli/configure.go index 20236eb28b5f5..e7a6448e70822 100644 --- a/cmd/tailscale/cli/configure.go +++ b/cmd/tailscale/cli/configure.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure_apple-all.go b/cmd/tailscale/cli/configure_apple-all.go index 5f0da9b95420e..95e9259e96cf7 100644 --- a/cmd/tailscale/cli/configure_apple-all.go +++ b/cmd/tailscale/cli/configure_apple-all.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure_apple.go b/cmd/tailscale/cli/configure_apple.go index c0d99b90aa2c4..465bc7a47ed2c 100644 --- a/cmd/tailscale/cli/configure_apple.go +++ b/cmd/tailscale/cli/configure_apple.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/cmd/tailscale/cli/configure_linux-all.go b/cmd/tailscale/cli/configure_linux-all.go index e645e9654dfe5..2db970eeef497 100644 --- a/cmd/tailscale/cli/configure_linux-all.go +++ b/cmd/tailscale/cli/configure_linux-all.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/configure_linux.go b/cmd/tailscale/cli/configure_linux.go index 4bbde872140ca..ccde06c72ddbc 100644 --- a/cmd/tailscale/cli/configure_linux.go +++ b/cmd/tailscale/cli/configure_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_systray diff --git a/cmd/tailscale/cli/debug-capture.go b/cmd/tailscale/cli/debug-capture.go index a54066fa614cb..ce282b291a587 100644 --- a/cmd/tailscale/cli/debug-capture.go +++ b/cmd/tailscale/cli/debug-capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_capture diff --git a/cmd/tailscale/cli/debug-peer-relay.go b/cmd/tailscale/cli/debug-peer-relay.go index bef8b83693aca..1b28c3f6bb1a4 100644 --- a/cmd/tailscale/cli/debug-peer-relay.go +++ b/cmd/tailscale/cli/debug-peer-relay.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_relayserver diff --git a/cmd/tailscale/cli/debug-portmap.go b/cmd/tailscale/cli/debug-portmap.go index d8db1442c7073..a876971ef00b4 100644 --- a/cmd/tailscale/cli/debug-portmap.go +++ b/cmd/tailscale/cli/debug-portmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_debugportmapper diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index ccbfb59de9221..f406b9f226249 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/diag.go b/cmd/tailscale/cli/diag.go index 3b2aa504b9ea7..8a244ba8817bb 100644 --- a/cmd/tailscale/cli/diag.go +++ b/cmd/tailscale/cli/diag.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux || windows || darwin) && !ts_omit_cliconndiag diff --git a/cmd/tailscale/cli/dns-query.go b/cmd/tailscale/cli/dns-query.go index 11f64453732fa..88a897f21ed8d 100644 --- a/cmd/tailscale/cli/dns-query.go +++ b/cmd/tailscale/cli/dns-query.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/dns-status.go b/cmd/tailscale/cli/dns-status.go index 8c18622ce45af..f63f418281987 100644 --- a/cmd/tailscale/cli/dns-status.go +++ b/cmd/tailscale/cli/dns-status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/dns.go b/cmd/tailscale/cli/dns.go index 086abefd6b2bf..d8db5d466d6b2 100644 --- a/cmd/tailscale/cli/dns.go +++ b/cmd/tailscale/cli/dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/down.go b/cmd/tailscale/cli/down.go index 224198a98deb5..6fecbd76cec12 100644 --- a/cmd/tailscale/cli/down.go +++ b/cmd/tailscale/cli/down.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/drive.go b/cmd/tailscale/cli/drive.go index 131f468477314..280ff3172fb92 100644 --- a/cmd/tailscale/cli/drive.go +++ b/cmd/tailscale/cli/drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive && !ts_mac_gui diff --git a/cmd/tailscale/cli/exitnode.go b/cmd/tailscale/cli/exitnode.go index b47b9f0bd4949..0445b66ae14ff 100644 --- a/cmd/tailscale/cli/exitnode.go +++ b/cmd/tailscale/cli/exitnode.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/exitnode_test.go b/cmd/tailscale/cli/exitnode_test.go index cc38fd3a4d39e..9a77cf5d7d3fd 100644 --- a/cmd/tailscale/cli/exitnode_test.go +++ b/cmd/tailscale/cli/exitnode_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ffcomplete/complete.go b/cmd/tailscale/cli/ffcomplete/complete.go index fbd5b9d62823d..7d280f691a407 100644 --- a/cmd/tailscale/cli/ffcomplete/complete.go +++ b/cmd/tailscale/cli/ffcomplete/complete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && !ts_omit_completion diff --git a/cmd/tailscale/cli/ffcomplete/complete_omit.go b/cmd/tailscale/cli/ffcomplete/complete_omit.go index bafc059e7b71d..06efa63fcd3a7 100644 --- a/cmd/tailscale/cli/ffcomplete/complete_omit.go +++ b/cmd/tailscale/cli/ffcomplete/complete_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && ts_omit_completion diff --git a/cmd/tailscale/cli/ffcomplete/ffcomplete.go b/cmd/tailscale/cli/ffcomplete/ffcomplete.go index 4b8207ec60a0c..e6af2515ff26f 100644 --- a/cmd/tailscale/cli/ffcomplete/ffcomplete.go +++ b/cmd/tailscale/cli/ffcomplete/ffcomplete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ffcomplete diff --git a/cmd/tailscale/cli/ffcomplete/internal/complete.go b/cmd/tailscale/cli/ffcomplete/internal/complete.go index b6c39dc837215..911972518d331 100644 --- a/cmd/tailscale/cli/ffcomplete/internal/complete.go +++ b/cmd/tailscale/cli/ffcomplete/internal/complete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package internal contains internal code for the ffcomplete package. diff --git a/cmd/tailscale/cli/ffcomplete/internal/complete_test.go b/cmd/tailscale/cli/ffcomplete/internal/complete_test.go index c216bdeec500d..2bba72283b044 100644 --- a/cmd/tailscale/cli/ffcomplete/internal/complete_test.go +++ b/cmd/tailscale/cli/ffcomplete/internal/complete_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package internal_test diff --git a/cmd/tailscale/cli/ffcomplete/scripts.go b/cmd/tailscale/cli/ffcomplete/scripts.go index 8218683afa349..bccebed7feec1 100644 --- a/cmd/tailscale/cli/ffcomplete/scripts.go +++ b/cmd/tailscale/cli/ffcomplete/scripts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && !ts_omit_completion && !ts_omit_completion_scripts diff --git a/cmd/tailscale/cli/ffcomplete/scripts_omit.go b/cmd/tailscale/cli/ffcomplete/scripts_omit.go index b5d520c3fe1d9..4c082d9d1ed06 100644 --- a/cmd/tailscale/cli/ffcomplete/scripts_omit.go +++ b/cmd/tailscale/cli/ffcomplete/scripts_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && !ts_omit_completion && ts_omit_completion_scripts diff --git a/cmd/tailscale/cli/file.go b/cmd/tailscale/cli/file.go index e0879197e2dbb..94b36f535bcab 100644 --- a/cmd/tailscale/cli/file.go +++ b/cmd/tailscale/cli/file.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_taildrop diff --git a/cmd/tailscale/cli/funnel.go b/cmd/tailscale/cli/funnel.go index 34b0c74c23949..f16f571e09508 100644 --- a/cmd/tailscale/cli/funnel.go +++ b/cmd/tailscale/cli/funnel.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/cmd/tailscale/cli/id-token.go b/cmd/tailscale/cli/id-token.go index a4d02c95a82c1..e2707ee84ca42 100644 --- a/cmd/tailscale/cli/id-token.go +++ b/cmd/tailscale/cli/id-token.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go index 8379329120436..01373a073b169 100644 --- a/cmd/tailscale/cli/ip.go +++ b/cmd/tailscale/cli/ip.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/jsonoutput/jsonoutput.go b/cmd/tailscale/cli/jsonoutput/jsonoutput.go index aa49acc28baae..69e7374d93342 100644 --- a/cmd/tailscale/cli/jsonoutput/jsonoutput.go +++ b/cmd/tailscale/cli/jsonoutput/jsonoutput.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsonoutput provides stable and versioned JSON serialisation for CLI output. diff --git a/cmd/tailscale/cli/jsonoutput/network-lock-log.go b/cmd/tailscale/cli/jsonoutput/network-lock-log.go index 88e449db36d2a..c3190e6bac9c7 100644 --- a/cmd/tailscale/cli/jsonoutput/network-lock-log.go +++ b/cmd/tailscale/cli/jsonoutput/network-lock-log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/cmd/tailscale/cli/jsonoutput/network-lock-status.go b/cmd/tailscale/cli/jsonoutput/network-lock-status.go index 0c6481093c9d6..a1d95b871549c 100644 --- a/cmd/tailscale/cli/jsonoutput/network-lock-status.go +++ b/cmd/tailscale/cli/jsonoutput/network-lock-status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/cmd/tailscale/cli/licenses.go b/cmd/tailscale/cli/licenses.go index bede827edf693..35d636aa2eb84 100644 --- a/cmd/tailscale/cli/licenses.go +++ b/cmd/tailscale/cli/licenses.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/login.go b/cmd/tailscale/cli/login.go index fb5b786920660..bdf97c70f8e1d 100644 --- a/cmd/tailscale/cli/login.go +++ b/cmd/tailscale/cli/login.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/logout.go b/cmd/tailscale/cli/logout.go index fbc39473026a1..90843edc2e299 100644 --- a/cmd/tailscale/cli/logout.go +++ b/cmd/tailscale/cli/logout.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/maybe_syspolicy.go b/cmd/tailscale/cli/maybe_syspolicy.go index 937a278334fd9..a66c1a65df5e4 100644 --- a/cmd/tailscale/cli/maybe_syspolicy.go +++ b/cmd/tailscale/cli/maybe_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/cmd/tailscale/cli/metrics.go b/cmd/tailscale/cli/metrics.go index dbdedd5a61037..d16ce76d2725f 100644 --- a/cmd/tailscale/cli/metrics.go +++ b/cmd/tailscale/cli/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/nc.go b/cmd/tailscale/cli/nc.go index 4ea62255412ea..34490ec212557 100644 --- a/cmd/tailscale/cli/nc.go +++ b/cmd/tailscale/cli/nc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/netcheck.go b/cmd/tailscale/cli/netcheck.go index a8a8992f5ba23..c9cbce29a32cf 100644 --- a/cmd/tailscale/cli/netcheck.go +++ b/cmd/tailscale/cli/netcheck.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/network-lock.go b/cmd/tailscale/cli/network-lock.go index 3b374ece2543f..d8cff4aca402d 100644 --- a/cmd/tailscale/cli/network-lock.go +++ b/cmd/tailscale/cli/network-lock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/cmd/tailscale/cli/network-lock_test.go b/cmd/tailscale/cli/network-lock_test.go index aa777ff922ba1..596a51b8a2deb 100644 --- a/cmd/tailscale/cli/network-lock_test.go +++ b/cmd/tailscale/cli/network-lock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ping.go b/cmd/tailscale/cli/ping.go index 8ece7c93d2311..1e8bbd23f15e8 100644 --- a/cmd/tailscale/cli/ping.go +++ b/cmd/tailscale/cli/ping.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/risks.go b/cmd/tailscale/cli/risks.go index d4572842bf758..058eff1f8501a 100644 --- a/cmd/tailscale/cli/risks.go +++ b/cmd/tailscale/cli/risks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/serve_legacy.go b/cmd/tailscale/cli/serve_legacy.go index 0e9b7d0227ccf..837d8851368e4 100644 --- a/cmd/tailscale/cli/serve_legacy.go +++ b/cmd/tailscale/cli/serve_legacy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/cmd/tailscale/cli/serve_legacy_test.go b/cmd/tailscale/cli/serve_legacy_test.go index 819017ad81bb5..27cbb5712dd0f 100644 --- a/cmd/tailscale/cli/serve_legacy_test.go +++ b/cmd/tailscale/cli/serve_legacy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 6a29074817a59..06a4ce1bbde3e 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/cmd/tailscale/cli/serve_v2_test.go b/cmd/tailscale/cli/serve_v2_test.go index a56fece3e8c59..7b27de6f2eb26 100644 --- a/cmd/tailscale/cli/serve_v2_test.go +++ b/cmd/tailscale/cli/serve_v2_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/serve_v2_unix_test.go b/cmd/tailscale/cli/serve_v2_unix_test.go index 9064655981288..671cdfbfbd1bc 100644 --- a/cmd/tailscale/cli/serve_v2_unix_test.go +++ b/cmd/tailscale/cli/serve_v2_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go index 31662392f8437..615900833596c 100644 --- a/cmd/tailscale/cli/set.go +++ b/cmd/tailscale/cli/set.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/set_test.go b/cmd/tailscale/cli/set_test.go index a2f211f8cdc36..63fa3c05c48b3 100644 --- a/cmd/tailscale/cli/set_test.go +++ b/cmd/tailscale/cli/set_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh.go b/cmd/tailscale/cli/ssh.go index 9275c9a1c2814..bea18f7abf6ac 100644 --- a/cmd/tailscale/cli/ssh.go +++ b/cmd/tailscale/cli/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh_exec.go b/cmd/tailscale/cli/ssh_exec.go index 10e52903dea64..ecfd3c4e6052a 100644 --- a/cmd/tailscale/cli/ssh_exec.go +++ b/cmd/tailscale/cli/ssh_exec.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !windows diff --git a/cmd/tailscale/cli/ssh_exec_js.go b/cmd/tailscale/cli/ssh_exec_js.go index 40effc7cafc7e..bf631c3b82d24 100644 --- a/cmd/tailscale/cli/ssh_exec_js.go +++ b/cmd/tailscale/cli/ssh_exec_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh_exec_windows.go b/cmd/tailscale/cli/ssh_exec_windows.go index e249afe667401..85e1518175609 100644 --- a/cmd/tailscale/cli/ssh_exec_windows.go +++ b/cmd/tailscale/cli/ssh_exec_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/ssh_unix.go b/cmd/tailscale/cli/ssh_unix.go index 71c0caaa69ad5..768d71116cf2c 100644 --- a/cmd/tailscale/cli/ssh_unix.go +++ b/cmd/tailscale/cli/ssh_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !wasm && !windows && !plan9 diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 89b18335b4ee0..ae4df4da9b51b 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/switch.go b/cmd/tailscale/cli/switch.go index b315a21e7437f..34ed2c7687c67 100644 --- a/cmd/tailscale/cli/switch.go +++ b/cmd/tailscale/cli/switch.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/syspolicy.go b/cmd/tailscale/cli/syspolicy.go index 97f3f2122b40c..e44b01d5ffa15 100644 --- a/cmd/tailscale/cli/syspolicy.go +++ b/cmd/tailscale/cli/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/cmd/tailscale/cli/systray.go b/cmd/tailscale/cli/systray.go index 827e8a9a40a30..ca0840fe9271e 100644 --- a/cmd/tailscale/cli/systray.go +++ b/cmd/tailscale/cli/systray.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_systray diff --git a/cmd/tailscale/cli/systray_omit.go b/cmd/tailscale/cli/systray_omit.go index 8d93fd84b52a9..83ec199a7d895 100644 --- a/cmd/tailscale/cli/systray_omit.go +++ b/cmd/tailscale/cli/systray_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || ts_omit_systray diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index bf0315860fbeb..cdb1d38234cec 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/up_test.go b/cmd/tailscale/cli/up_test.go index bb172f9063f59..9af8eae7d9994 100644 --- a/cmd/tailscale/cli/up_test.go +++ b/cmd/tailscale/cli/up_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/update.go b/cmd/tailscale/cli/update.go index 7eb0dccace7a8..291bf4330cd63 100644 --- a/cmd/tailscale/cli/update.go +++ b/cmd/tailscale/cli/update.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index b25502d5a4be5..f23ee0b69f834 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/web.go b/cmd/tailscale/cli/web.go index 2713f730bf600..c13cad2d645ce 100644 --- a/cmd/tailscale/cli/web.go +++ b/cmd/tailscale/cli/web.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_webclient diff --git a/cmd/tailscale/cli/web_test.go b/cmd/tailscale/cli/web_test.go index f2470b364c41e..727c5644be0b4 100644 --- a/cmd/tailscale/cli/web_test.go +++ b/cmd/tailscale/cli/web_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/cli/whois.go b/cmd/tailscale/cli/whois.go index 44ff68dec8777..b2ad74149635b 100644 --- a/cmd/tailscale/cli/whois.go +++ b/cmd/tailscale/cli/whois.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cli diff --git a/cmd/tailscale/deps_test.go b/cmd/tailscale/deps_test.go index 132940e3cc937..ea7bb15d3a895 100644 --- a/cmd/tailscale/deps_test.go +++ b/cmd/tailscale/deps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscale/generate.go b/cmd/tailscale/generate.go index 5c2e9be915980..36a4fa671dddb 100644 --- a/cmd/tailscale/generate.go +++ b/cmd/tailscale/generate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscale/tailscale.go b/cmd/tailscale/tailscale.go index f6adb6c197071..57a51840832b5 100644 --- a/cmd/tailscale/tailscale.go +++ b/cmd/tailscale/tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tailscale command is the Tailscale command-line client. It interacts diff --git a/cmd/tailscale/tailscale_test.go b/cmd/tailscale/tailscale_test.go index a7a3c2323cb8f..ca064b6b7a28a 100644 --- a/cmd/tailscale/tailscale_test.go +++ b/cmd/tailscale/tailscale_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/childproc/childproc.go b/cmd/tailscaled/childproc/childproc.go index cc83a06c6ee7c..7d89b314af820 100644 --- a/cmd/tailscaled/childproc/childproc.go +++ b/cmd/tailscaled/childproc/childproc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package childproc allows other packages to register "tailscaled be-child" diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/debug.go index 8208a6e3c6354..360075f5b0e2b 100644 --- a/cmd/tailscaled/debug.go +++ b/cmd/tailscaled/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/cmd/tailscaled/debug_forcereflect.go b/cmd/tailscaled/debug_forcereflect.go index 7378753ceb64c..088010d7db29a 100644 --- a/cmd/tailscaled/debug_forcereflect.go +++ b/cmd/tailscaled/debug_forcereflect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_debug_forcereflect diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index 64d1beca7cd75..d06924b927a97 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/flag.go b/cmd/tailscaled/flag.go index f640aceed45d8..357210a29c426 100644 --- a/cmd/tailscaled/flag.go +++ b/cmd/tailscaled/flag.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/generate.go b/cmd/tailscaled/generate.go index 5c2e9be915980..36a4fa671dddb 100644 --- a/cmd/tailscaled/generate.go +++ b/cmd/tailscaled/generate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tailscaled/install_darwin.go b/cmd/tailscaled/install_darwin.go index 05e5eaed8af90..15d9e54621181 100644 --- a/cmd/tailscaled/install_darwin.go +++ b/cmd/tailscaled/install_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/cmd/tailscaled/install_windows.go b/cmd/tailscaled/install_windows.go index 6013660f5aa20..d0f40b37d1156 100644 --- a/cmd/tailscaled/install_windows.go +++ b/cmd/tailscaled/install_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/cmd/tailscaled/netstack.go b/cmd/tailscaled/netstack.go index c0b34ed411c78..d896f384fcc98 100644 --- a/cmd/tailscaled/netstack.go +++ b/cmd/tailscaled/netstack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/cmd/tailscaled/proxy.go b/cmd/tailscaled/proxy.go index 85c3d91f9de96..ea9f54a479dc5 100644 --- a/cmd/tailscaled/proxy.go +++ b/cmd/tailscaled/proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_outboundproxy diff --git a/cmd/tailscaled/required_version.go b/cmd/tailscaled/required_version.go index 3acb3d52e4d8c..bfde77cd8474b 100644 --- a/cmd/tailscaled/required_version.go +++ b/cmd/tailscaled/required_version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !go1.23 diff --git a/cmd/tailscaled/sigpipe.go b/cmd/tailscaled/sigpipe.go index 2fcdab2a4660e..ba69fcd2a0632 100644 --- a/cmd/tailscaled/sigpipe.go +++ b/cmd/tailscaled/sigpipe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.21 && !plan9 diff --git a/cmd/tailscaled/ssh.go b/cmd/tailscaled/ssh.go index 59a1ddd0df461..e69cbd5dce086 100644 --- a/cmd/tailscaled/ssh.go +++ b/cmd/tailscaled/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux || darwin || freebsd || openbsd || plan9) && !ts_omit_ssh diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 410ae00bc0716..df0d68e077b2b 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.23 diff --git a/cmd/tailscaled/tailscaled_bird.go b/cmd/tailscaled/tailscaled_bird.go index c76f77bec6e36..c1c32d2bb493d 100644 --- a/cmd/tailscaled/tailscaled_bird.go +++ b/cmd/tailscaled/tailscaled_bird.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 && (linux || darwin || freebsd || openbsd) && !ts_omit_bird diff --git a/cmd/tailscaled/tailscaled_drive.go b/cmd/tailscaled/tailscaled_drive.go index 49f35a3811404..6a8590bb82217 100644 --- a/cmd/tailscaled/tailscaled_drive.go +++ b/cmd/tailscaled/tailscaled_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go index d5361cf286d3d..735facc37b861 100644 --- a/cmd/tailscaled/tailscaled_notwindows.go +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && go1.19 diff --git a/cmd/tailscaled/tailscaled_test.go b/cmd/tailscaled/tailscaled_test.go index 36327cccc7bc7..7d76e7683a623 100644 --- a/cmd/tailscaled/tailscaled_test.go +++ b/cmd/tailscaled/tailscaled_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main // import "tailscale.com/cmd/tailscaled" diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 3019bbaf9695b..63c8b30c99348 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.19 diff --git a/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go b/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go index 6ea662d39230c..42009d02bf6af 100644 --- a/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go +++ b/cmd/tailscaled/tailscaledhooks/tailscaledhooks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailscaledhooks provides hooks for optional features diff --git a/cmd/tailscaled/webclient.go b/cmd/tailscaled/webclient.go index 672ba7126d2a7..e031277abfc27 100644 --- a/cmd/tailscaled/webclient.go +++ b/cmd/tailscaled/webclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_webclient diff --git a/cmd/tailscaled/with_cli.go b/cmd/tailscaled/with_cli.go index a8554eb8ce9dc..33da1f448e727 100644 --- a/cmd/tailscaled/with_cli.go +++ b/cmd/tailscaled/with_cli.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_include_cli diff --git a/cmd/testcontrol/testcontrol.go b/cmd/testcontrol/testcontrol.go index b05b3128df0ef..49e7e429e63e9 100644 --- a/cmd/testcontrol/testcontrol.go +++ b/cmd/testcontrol/testcontrol.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program testcontrol runs a simple test control server. diff --git a/cmd/testwrapper/args.go b/cmd/testwrapper/args.go index 95157bc34efee..11ed1aeaad0bd 100644 --- a/cmd/testwrapper/args.go +++ b/cmd/testwrapper/args.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/testwrapper/args_test.go b/cmd/testwrapper/args_test.go index 10063d7bcf6e1..25364fb96d6a1 100644 --- a/cmd/testwrapper/args_test.go +++ b/cmd/testwrapper/args_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/testwrapper/flakytest/flakytest.go b/cmd/testwrapper/flakytest/flakytest.go index 856cb28ef275a..b98d739c63620 100644 --- a/cmd/testwrapper/flakytest/flakytest.go +++ b/cmd/testwrapper/flakytest/flakytest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package flakytest contains test helpers for marking a test as flaky. For diff --git a/cmd/testwrapper/flakytest/flakytest_test.go b/cmd/testwrapper/flakytest/flakytest_test.go index 9b744de13d446..54dd2121bd1f3 100644 --- a/cmd/testwrapper/flakytest/flakytest_test.go +++ b/cmd/testwrapper/flakytest/flakytest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package flakytest diff --git a/cmd/testwrapper/testwrapper.go b/cmd/testwrapper/testwrapper.go index 173edee733f04..df10a53bc1a14 100644 --- a/cmd/testwrapper/testwrapper.go +++ b/cmd/testwrapper/testwrapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // testwrapper is a wrapper for retrying flaky tests. It is an alternative to diff --git a/cmd/testwrapper/testwrapper_test.go b/cmd/testwrapper/testwrapper_test.go index ace53ccd0e09a..0ca13e854ff7a 100644 --- a/cmd/testwrapper/testwrapper_test.go +++ b/cmd/testwrapper/testwrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main_test diff --git a/cmd/tl-longchain/tl-longchain.go b/cmd/tl-longchain/tl-longchain.go index 384d24222e6d5..33d0df3011018 100644 --- a/cmd/tl-longchain/tl-longchain.go +++ b/cmd/tl-longchain/tl-longchain.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Program tl-longchain prints commands to re-sign Tailscale nodes that have diff --git a/cmd/tsconnect/build-pkg.go b/cmd/tsconnect/build-pkg.go index 047504858ae0c..53aacc02ec8ea 100644 --- a/cmd/tsconnect/build-pkg.go +++ b/cmd/tsconnect/build-pkg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/build.go b/cmd/tsconnect/build.go index 364ebf5366dfe..64b6b3582bd62 100644 --- a/cmd/tsconnect/build.go +++ b/cmd/tsconnect/build.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go index ff10e4efbb5d3..9daa402692c04 100644 --- a/cmd/tsconnect/common.go +++ b/cmd/tsconnect/common.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/dev-pkg.go b/cmd/tsconnect/dev-pkg.go index de534c3b20625..3e1018c00df0e 100644 --- a/cmd/tsconnect/dev-pkg.go +++ b/cmd/tsconnect/dev-pkg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/dev.go b/cmd/tsconnect/dev.go index 87b10adaf49c8..2d0eb1036a5ad 100644 --- a/cmd/tsconnect/dev.go +++ b/cmd/tsconnect/dev.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/package.json.tmpl b/cmd/tsconnect/package.json.tmpl index 404b896eaf89e..883d794cacb8b 100644 --- a/cmd/tsconnect/package.json.tmpl +++ b/cmd/tsconnect/package.json.tmpl @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Template for the package.json that is generated by the build-pkg command. diff --git a/cmd/tsconnect/serve.go b/cmd/tsconnect/serve.go index d780bdd57c3e3..3e9f097a248a4 100644 --- a/cmd/tsconnect/serve.go +++ b/cmd/tsconnect/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/src/app/app.tsx b/cmd/tsconnect/src/app/app.tsx index ee538eaeac506..8d25b227437dd 100644 --- a/cmd/tsconnect/src/app/app.tsx +++ b/cmd/tsconnect/src/app/app.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { render, Component } from "preact" diff --git a/cmd/tsconnect/src/app/go-panic-display.tsx b/cmd/tsconnect/src/app/go-panic-display.tsx index 5dd7095a27c7d..e15c58cd183ff 100644 --- a/cmd/tsconnect/src/app/go-panic-display.tsx +++ b/cmd/tsconnect/src/app/go-panic-display.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause export function GoPanicDisplay({ diff --git a/cmd/tsconnect/src/app/header.tsx b/cmd/tsconnect/src/app/header.tsx index 099ff2f8c2f7d..640474090e3a4 100644 --- a/cmd/tsconnect/src/app/header.tsx +++ b/cmd/tsconnect/src/app/header.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) { diff --git a/cmd/tsconnect/src/app/index.css b/cmd/tsconnect/src/app/index.css index 751b313d9f362..2c9c4c0d3247b 100644 --- a/cmd/tsconnect/src/app/index.css +++ b/cmd/tsconnect/src/app/index.css @@ -1,4 +1,4 @@ -/* Copyright (c) Tailscale Inc & AUTHORS */ +/* Copyright (c) Tailscale Inc & contributors */ /* SPDX-License-Identifier: BSD-3-Clause */ @import "xterm/css/xterm.css"; diff --git a/cmd/tsconnect/src/app/index.ts b/cmd/tsconnect/src/app/index.ts index 24ca4543921ae..bdbcaf3e52d4a 100644 --- a/cmd/tsconnect/src/app/index.ts +++ b/cmd/tsconnect/src/app/index.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import "../wasm_exec" diff --git a/cmd/tsconnect/src/app/ssh.tsx b/cmd/tsconnect/src/app/ssh.tsx index df81745bd3fd7..1faaad6c68220 100644 --- a/cmd/tsconnect/src/app/ssh.tsx +++ b/cmd/tsconnect/src/app/ssh.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useState, useCallback, useMemo, useEffect, useRef } from "preact/hooks" diff --git a/cmd/tsconnect/src/app/url-display.tsx b/cmd/tsconnect/src/app/url-display.tsx index fc82c7fb91b3c..787989ccae018 100644 --- a/cmd/tsconnect/src/app/url-display.tsx +++ b/cmd/tsconnect/src/app/url-display.tsx @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { useState } from "preact/hooks" diff --git a/cmd/tsconnect/src/lib/js-state-store.ts b/cmd/tsconnect/src/lib/js-state-store.ts index e57dfd98efabd..a17090fa0921d 100644 --- a/cmd/tsconnect/src/lib/js-state-store.ts +++ b/cmd/tsconnect/src/lib/js-state-store.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** @fileoverview Callbacks used by jsStateStore to persist IPN state. */ diff --git a/cmd/tsconnect/src/lib/ssh.ts b/cmd/tsconnect/src/lib/ssh.ts index 9c6f71aee4b41..5fae4a5b7451f 100644 --- a/cmd/tsconnect/src/lib/ssh.ts +++ b/cmd/tsconnect/src/lib/ssh.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import { Terminal, ITerminalOptions } from "xterm" diff --git a/cmd/tsconnect/src/pkg/pkg.css b/cmd/tsconnect/src/pkg/pkg.css index 76ea21f5b53b2..f3b32bb95e808 100644 --- a/cmd/tsconnect/src/pkg/pkg.css +++ b/cmd/tsconnect/src/pkg/pkg.css @@ -1,4 +1,4 @@ -/* Copyright (c) Tailscale Inc & AUTHORS */ +/* Copyright (c) Tailscale Inc & contributors */ /* SPDX-License-Identifier: BSD-3-Clause */ @import "xterm/css/xterm.css"; diff --git a/cmd/tsconnect/src/pkg/pkg.ts b/cmd/tsconnect/src/pkg/pkg.ts index 4d535cb404015..a44c57150ce24 100644 --- a/cmd/tsconnect/src/pkg/pkg.ts +++ b/cmd/tsconnect/src/pkg/pkg.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Type definitions need to be manually imported for dts-bundle-generator to diff --git a/cmd/tsconnect/src/types/esbuild.d.ts b/cmd/tsconnect/src/types/esbuild.d.ts index ef28f7b1cf556..d6bbd9310ead7 100644 --- a/cmd/tsconnect/src/types/esbuild.d.ts +++ b/cmd/tsconnect/src/types/esbuild.d.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** diff --git a/cmd/tsconnect/src/types/wasm_js.d.ts b/cmd/tsconnect/src/types/wasm_js.d.ts index 492197ccb1a9b..938ec759c7615 100644 --- a/cmd/tsconnect/src/types/wasm_js.d.ts +++ b/cmd/tsconnect/src/types/wasm_js.d.ts @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /** diff --git a/cmd/tsconnect/tsconnect.go b/cmd/tsconnect/tsconnect.go index ef55593b49268..6de1f26ad389f 100644 --- a/cmd/tsconnect/tsconnect.go +++ b/cmd/tsconnect/tsconnect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index c7aa00d1d794f..8a0177d1d66f7 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The wasm package builds a WebAssembly module that provides a subset of diff --git a/cmd/tsidp/tsidp.go b/cmd/tsidp/tsidp.go index 7093ab9ee193a..d6dfc79009796 100644 --- a/cmd/tsidp/tsidp.go +++ b/cmd/tsidp/tsidp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tsidp command is an OpenID Connect Identity Provider server. diff --git a/cmd/tsidp/tsidp_test.go b/cmd/tsidp/tsidp_test.go index 4f5af9e598e65..26c906fab216b 100644 --- a/cmd/tsidp/tsidp_test.go +++ b/cmd/tsidp/tsidp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package main tests for tsidp focus on OAuth security boundaries and diff --git a/cmd/tsidp/ui.go b/cmd/tsidp/ui.go index d37b64990cac8..f8717d65e8509 100644 --- a/cmd/tsidp/ui.go +++ b/cmd/tsidp/ui.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tsshd/tsshd.go b/cmd/tsshd/tsshd.go index 950eb661cdb23..51765e2e41526 100644 --- a/cmd/tsshd/tsshd.go +++ b/cmd/tsshd/tsshd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/cmd/tta/fw_linux.go b/cmd/tta/fw_linux.go index a4ceabad8bc05..49d8d41ea4b4d 100644 --- a/cmd/tta/fw_linux.go +++ b/cmd/tta/fw_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/tta/tta.go b/cmd/tta/tta.go index 9f8f002958d61..377d01c9487f7 100644 --- a/cmd/tta/tta.go +++ b/cmd/tta/tta.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tta server is the Tailscale Test Agent. diff --git a/cmd/vet/jsontags/analyzer.go b/cmd/vet/jsontags/analyzer.go index d799b66cbb583..c69634ecd3e8a 100644 --- a/cmd/vet/jsontags/analyzer.go +++ b/cmd/vet/jsontags/analyzer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsontags checks for incompatible usage of JSON struct tags. diff --git a/cmd/vet/jsontags/iszero.go b/cmd/vet/jsontags/iszero.go index 77520d72c66f3..fd25cc120c530 100644 --- a/cmd/vet/jsontags/iszero.go +++ b/cmd/vet/jsontags/iszero.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsontags diff --git a/cmd/vet/jsontags/report.go b/cmd/vet/jsontags/report.go index 8e5869060799c..702de1c4d1c36 100644 --- a/cmd/vet/jsontags/report.go +++ b/cmd/vet/jsontags/report.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsontags diff --git a/cmd/vet/vet.go b/cmd/vet/vet.go index 45473af48f0ee..babc30d254719 100644 --- a/cmd/vet/vet.go +++ b/cmd/vet/vet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vet is a tool to statically check Go source code. diff --git a/cmd/viewer/tests/tests.go b/cmd/viewer/tests/tests.go index d1c753db78710..cbffd38845ec3 100644 --- a/cmd/viewer/tests/tests.go +++ b/cmd/viewer/tests/tests.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tests serves a list of tests for tailscale.com/cmd/viewer. diff --git a/cmd/viewer/tests/tests_clone.go b/cmd/viewer/tests/tests_clone.go index 4602b9d887d2b..cbf5ec2653d98 100644 --- a/cmd/viewer/tests/tests_clone.go +++ b/cmd/viewer/tests/tests_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/cmd/viewer/tests/tests_view.go b/cmd/viewer/tests/tests_view.go index 495281c23b3aa..fe073446ea200 100644 --- a/cmd/viewer/tests/tests_view.go +++ b/cmd/viewer/tests/tests_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go index 3fae737cde692..56b999f5f50fe 100644 --- a/cmd/viewer/viewer.go +++ b/cmd/viewer/viewer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Viewer is a tool to automate the creation of "view" wrapper types that diff --git a/cmd/viewer/viewer_test.go b/cmd/viewer/viewer_test.go index 1e24b705069d7..8bd18d4806ae2 100644 --- a/cmd/viewer/viewer_test.go +++ b/cmd/viewer/viewer_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/cmd/vnet/vnet-main.go b/cmd/vnet/vnet-main.go index 9dd4d8cfafe94..8a3afe2035a95 100644 --- a/cmd/vnet/vnet-main.go +++ b/cmd/vnet/vnet-main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The vnet binary runs a virtual network stack in userspace for qemu instances diff --git a/cmd/xdpderper/xdpderper.go b/cmd/xdpderper/xdpderper.go index c127baf54e340..ea25550bb1189 100644 --- a/cmd/xdpderper/xdpderper.go +++ b/cmd/xdpderper/xdpderper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command xdpderper runs the XDP STUN server. diff --git a/control/controlbase/conn.go b/control/controlbase/conn.go index 78ef73f71000b..8f6e5a7717f79 100644 --- a/control/controlbase/conn.go +++ b/control/controlbase/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlbase implements the base transport of the Tailscale diff --git a/control/controlbase/conn_test.go b/control/controlbase/conn_test.go index ed4642d3b179c..a1e2b313de5b6 100644 --- a/control/controlbase/conn_test.go +++ b/control/controlbase/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/handshake.go b/control/controlbase/handshake.go index 765a4620b876f..919920c344239 100644 --- a/control/controlbase/handshake.go +++ b/control/controlbase/handshake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/handshake_test.go b/control/controlbase/handshake_test.go index 242b1f4d7c658..f6b5409a8904f 100644 --- a/control/controlbase/handshake_test.go +++ b/control/controlbase/handshake_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/interop_test.go b/control/controlbase/interop_test.go index c41fbf4dd4950..87ee7d45876d7 100644 --- a/control/controlbase/interop_test.go +++ b/control/controlbase/interop_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlbase/messages.go b/control/controlbase/messages.go index 59073088f5e81..1357432de7ee5 100644 --- a/control/controlbase/messages.go +++ b/control/controlbase/messages.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlbase diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 336a8d491bc9c..fe227b45e57aa 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/client.go b/control/controlclient/client.go index 41b39622b0199..3bc53ed5a24fc 100644 --- a/control/controlclient/client.go +++ b/control/controlclient/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlclient implements the client for the Tailscale diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index 57d3ca7ca7ae3..c7d61f6b2d13d 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index d5cd6a13e5120..eb49cf4ab44fb 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/direct_test.go b/control/controlclient/direct_test.go index 4329fc878ceb3..d10b346ae39a7 100644 --- a/control/controlclient/direct_test.go +++ b/control/controlclient/direct_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/errors.go b/control/controlclient/errors.go index 9b4dab84467b8..a2397cedeaa5c 100644 --- a/control/controlclient/errors.go +++ b/control/controlclient/errors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/map.go b/control/controlclient/map.go index 9aa8e37107a99..18bd420ebaae3 100644 --- a/control/controlclient/map.go +++ b/control/controlclient/map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/map_test.go b/control/controlclient/map_test.go index 2be4b6ad70b2d..11d4593f03fae 100644 --- a/control/controlclient/map_test.go +++ b/control/controlclient/map_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/sign.go b/control/controlclient/sign.go index e3a479c283c62..6cee1265fe99b 100644 --- a/control/controlclient/sign.go +++ b/control/controlclient/sign.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlclient/sign_supported.go b/control/controlclient/sign_supported.go index 439e6d36b4fe3..ea6fa28e34479 100644 --- a/control/controlclient/sign_supported.go +++ b/control/controlclient/sign_supported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/control/controlclient/sign_supported_test.go b/control/controlclient/sign_supported_test.go index e20349a4e82c3..9d4abafbd12f6 100644 --- a/control/controlclient/sign_supported_test.go +++ b/control/controlclient/sign_supported_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows && cgo diff --git a/control/controlclient/sign_unsupported.go b/control/controlclient/sign_unsupported.go index f6c4ddc6288fb..ff830282e4496 100644 --- a/control/controlclient/sign_unsupported.go +++ b/control/controlclient/sign_unsupported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/control/controlclient/status.go b/control/controlclient/status.go index 65afb7a5011f2..46dc8f773f260 100644 --- a/control/controlclient/status.go +++ b/control/controlclient/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlclient diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go index 06a2131fdcb2b..e812091745ea5 100644 --- a/control/controlhttp/client.go +++ b/control/controlhttp/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/control/controlhttp/client_common.go b/control/controlhttp/client_common.go index dd94e93cdc3cf..5e49b0bfcc295 100644 --- a/control/controlhttp/client_common.go +++ b/control/controlhttp/client_common.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlhttp/client_js.go b/control/controlhttp/client_js.go index cc05b5b192766..a3ce7ffe5c765 100644 --- a/control/controlhttp/client_js.go +++ b/control/controlhttp/client_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlhttp/constants.go b/control/controlhttp/constants.go index 359410ae9d29c..26ace871c1268 100644 --- a/control/controlhttp/constants.go +++ b/control/controlhttp/constants.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlhttp/controlhttpcommon/controlhttpcommon.go b/control/controlhttp/controlhttpcommon/controlhttpcommon.go index a86b7ca04a7f4..21236b09b5574 100644 --- a/control/controlhttp/controlhttpcommon/controlhttpcommon.go +++ b/control/controlhttp/controlhttpcommon/controlhttpcommon.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlhttpcommon contains common constants for used diff --git a/control/controlhttp/controlhttpserver/controlhttpserver.go b/control/controlhttp/controlhttpserver/controlhttpserver.go index af320781069d1..7b413829eff78 100644 --- a/control/controlhttp/controlhttpserver/controlhttpserver.go +++ b/control/controlhttp/controlhttpserver/controlhttpserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios diff --git a/control/controlhttp/http_test.go b/control/controlhttp/http_test.go index 648b9e5ed88d5..c02ac758ebf16 100644 --- a/control/controlhttp/http_test.go +++ b/control/controlhttp/http_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlhttp diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index 09c16b8b12f1e..708840155df45 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package controlknobs contains client options configurable from control which can be turned on diff --git a/control/controlknobs/controlknobs_test.go b/control/controlknobs/controlknobs_test.go index 7618b7121c500..495535b1e2807 100644 --- a/control/controlknobs/controlknobs_test.go +++ b/control/controlknobs/controlknobs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package controlknobs diff --git a/control/ts2021/client.go b/control/ts2021/client.go index ca10b1d1b5bc6..0f0e7598b5591 100644 --- a/control/ts2021/client.go +++ b/control/ts2021/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ts2021 diff --git a/control/ts2021/client_test.go b/control/ts2021/client_test.go index 72fa1f44264c3..da823fc548593 100644 --- a/control/ts2021/client_test.go +++ b/control/ts2021/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ts2021 diff --git a/control/ts2021/conn.go b/control/ts2021/conn.go index 52d663272a8c6..6832f2df12a4f 100644 --- a/control/ts2021/conn.go +++ b/control/ts2021/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ts2021 handles the details of the Tailscale 2021 control protocol diff --git a/derp/client_test.go b/derp/client_test.go index a731ad197f1e7..e1bcaba8bf2c8 100644 --- a/derp/client_test.go +++ b/derp/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp diff --git a/derp/derp.go b/derp/derp.go index e19a99b0025ce..a7d0ea80191a8 100644 --- a/derp/derp.go +++ b/derp/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derp implements the Designated Encrypted Relay for Packets (DERP) diff --git a/derp/derp_client.go b/derp/derp_client.go index d28905cd2c8b2..1e9d48e1456c8 100644 --- a/derp/derp_client.go +++ b/derp/derp_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp diff --git a/derp/derp_test.go b/derp/derp_test.go index 52793f90fa9f5..cff069dd4470c 100644 --- a/derp/derp_test.go +++ b/derp/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp_test diff --git a/derp/derpconst/derpconst.go b/derp/derpconst/derpconst.go index 74ca09ccb734b..03ef249ce1b28 100644 --- a/derp/derpconst/derpconst.go +++ b/derp/derpconst/derpconst.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derpconst contains constants used by the DERP client and server. diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index db56c4a44c682..3c8408e95e1f1 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derphttp implements DERP-over-HTTP. diff --git a/derp/derphttp/derphttp_test.go b/derp/derphttp/derphttp_test.go index 5208481ed7258..ae530c93a31c0 100644 --- a/derp/derphttp/derphttp_test.go +++ b/derp/derphttp/derphttp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derphttp_test diff --git a/derp/derphttp/export_test.go b/derp/derphttp/export_test.go index 59d8324dcba3e..e3f449277fd6a 100644 --- a/derp/derphttp/export_test.go +++ b/derp/derphttp/export_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derphttp diff --git a/derp/derphttp/mesh_client.go b/derp/derphttp/mesh_client.go index c14a9a7e11111..d8fa7cd9aae03 100644 --- a/derp/derphttp/mesh_client.go +++ b/derp/derphttp/mesh_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derphttp diff --git a/derp/derphttp/websocket.go b/derp/derphttp/websocket.go index 9dd640ee37083..295d0a9bd44de 100644 --- a/derp/derphttp/websocket.go +++ b/derp/derphttp/websocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js || ((linux || darwin) && ts_debug_websockets) diff --git a/derp/derphttp/websocket_stub.go b/derp/derphttp/websocket_stub.go index d84bfba571f80..52d5ed15e3c25 100644 --- a/derp/derphttp/websocket_stub.go +++ b/derp/derphttp/websocket_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(js || ((linux || darwin) && ts_debug_websockets)) diff --git a/derp/derpserver/derpserver.go b/derp/derpserver/derpserver.go index 1879e0c536f3d..f311eb25d9817 100644 --- a/derp/derpserver/derpserver.go +++ b/derp/derpserver/derpserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package derpserver implements a DERP server. diff --git a/derp/derpserver/derpserver_default.go b/derp/derpserver/derpserver_default.go index 874e590d3c812..f664e88d1c3dd 100644 --- a/derp/derpserver/derpserver_default.go +++ b/derp/derpserver/derpserver_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || android diff --git a/derp/derpserver/derpserver_linux.go b/derp/derpserver/derpserver_linux.go index 768e6a2ab6ab7..c6154661c5486 100644 --- a/derp/derpserver/derpserver_linux.go +++ b/derp/derpserver/derpserver_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/derp/derpserver/derpserver_test.go b/derp/derpserver/derpserver_test.go index 1dd86f3146c5c..3a778d59fb009 100644 --- a/derp/derpserver/derpserver_test.go +++ b/derp/derpserver/derpserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derpserver diff --git a/derp/derpserver/handler.go b/derp/derpserver/handler.go index 7cd6aa2fd5b95..f639cb7123c73 100644 --- a/derp/derpserver/handler.go +++ b/derp/derpserver/handler.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derpserver diff --git a/derp/export_test.go b/derp/export_test.go index 677a4932d2657..9a73dd13e2798 100644 --- a/derp/export_test.go +++ b/derp/export_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package derp diff --git a/derp/xdp/headers/update.go b/derp/xdp/headers/update.go index c41332d077322..a7680c042ae0f 100644 --- a/derp/xdp/headers/update.go +++ b/derp/xdp/headers/update.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The update program fetches the libbpf headers from the libbpf GitHub repository diff --git a/derp/xdp/xdp.go b/derp/xdp/xdp.go index 5b2dbd1c26bcd..5f95b71e50294 100644 --- a/derp/xdp/xdp.go +++ b/derp/xdp/xdp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package xdp contains the XDP STUN program. diff --git a/derp/xdp/xdp_default.go b/derp/xdp/xdp_default.go index 99bc30d2c2ddc..187a112295c22 100644 --- a/derp/xdp/xdp_default.go +++ b/derp/xdp/xdp_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/derp/xdp/xdp_linux.go b/derp/xdp/xdp_linux.go index 309d9ee9a92b4..5d22716be4f16 100644 --- a/derp/xdp/xdp_linux.go +++ b/derp/xdp/xdp_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package xdp diff --git a/derp/xdp/xdp_linux_test.go b/derp/xdp/xdp_linux_test.go index 07f11eff65b09..5c75a69ff3fbb 100644 --- a/derp/xdp/xdp_linux_test.go +++ b/derp/xdp/xdp_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/disco/disco.go b/disco/disco.go index f58bc1b8c1ba1..2147529d175d4 100644 --- a/disco/disco.go +++ b/disco/disco.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package disco contains the discovery message types. diff --git a/disco/disco_fuzzer.go b/disco/disco_fuzzer.go index b9ffabfb00906..99a96ae85e34f 100644 --- a/disco/disco_fuzzer.go +++ b/disco/disco_fuzzer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build gofuzz diff --git a/disco/disco_test.go b/disco/disco_test.go index 71b68338a8c90..07b653ceeb950 100644 --- a/disco/disco_test.go +++ b/disco/disco_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package disco diff --git a/disco/pcap.go b/disco/pcap.go index 71035424868e8..e4a910163f4aa 100644 --- a/disco/pcap.go +++ b/disco/pcap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package disco diff --git a/docs/k8s/Makefile b/docs/k8s/Makefile index 55804c857c049..6397957808e49 100644 --- a/docs/k8s/Makefile +++ b/docs/k8s/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause TS_ROUTES ?= "" diff --git a/docs/k8s/proxy.yaml b/docs/k8s/proxy.yaml index 048fd7a5bddf9..bd31b7a97bc83 100644 --- a/docs/k8s/proxy.yaml +++ b/docs/k8s/proxy.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/k8s/role.yaml b/docs/k8s/role.yaml index d7d0846ab29a6..869d71b719118 100644 --- a/docs/k8s/role.yaml +++ b/docs/k8s/role.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/docs/k8s/rolebinding.yaml b/docs/k8s/rolebinding.yaml index 3b18ba8d35e57..1bec3df271e8e 100644 --- a/docs/k8s/rolebinding.yaml +++ b/docs/k8s/rolebinding.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/docs/k8s/sa.yaml b/docs/k8s/sa.yaml index edd3944ba8987..e1d61573c5317 100644 --- a/docs/k8s/sa.yaml +++ b/docs/k8s/sa.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: ServiceAccount diff --git a/docs/k8s/sidecar.yaml b/docs/k8s/sidecar.yaml index 520e4379ad9ee..c119c67bbe5f8 100644 --- a/docs/k8s/sidecar.yaml +++ b/docs/k8s/sidecar.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/k8s/subnet.yaml b/docs/k8s/subnet.yaml index ef4e4748c0ceb..556201deb6500 100644 --- a/docs/k8s/subnet.yaml +++ b/docs/k8s/subnet.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/k8s/userspace-sidecar.yaml b/docs/k8s/userspace-sidecar.yaml index ee19b10a5e5dd..32a949593c040 100644 --- a/docs/k8s/userspace-sidecar.yaml +++ b/docs/k8s/userspace-sidecar.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause apiVersion: v1 kind: Pod diff --git a/docs/sysv/tailscale.init b/docs/sysv/tailscale.init index ca21033df7b27..0168adfdb1041 100755 --- a/docs/sysv/tailscale.init +++ b/docs/sysv/tailscale.init @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause ### BEGIN INIT INFO diff --git a/docs/webhooks/example.go b/docs/webhooks/example.go index 712028362c53e..53ec1c8b74b52 100644 --- a/docs/webhooks/example.go +++ b/docs/webhooks/example.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command webhooks provides example consumer code for Tailscale diff --git a/doctor/doctor.go b/doctor/doctor.go index 7c3047e12b62d..437df5e756dea 100644 --- a/doctor/doctor.go +++ b/doctor/doctor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package doctor contains more in-depth healthchecks that can be run to aid in diff --git a/doctor/doctor_test.go b/doctor/doctor_test.go index 87250f10ed00a..cd9d00ae6868e 100644 --- a/doctor/doctor_test.go +++ b/doctor/doctor_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package doctor diff --git a/doctor/ethtool/ethtool.go b/doctor/ethtool/ethtool.go index f80b00a51ff65..40f39cc21b49a 100644 --- a/doctor/ethtool/ethtool.go +++ b/doctor/ethtool/ethtool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ethtool provides a doctor.Check that prints diagnostic information diff --git a/doctor/ethtool/ethtool_linux.go b/doctor/ethtool/ethtool_linux.go index f6eaac1df0542..3914158741724 100644 --- a/doctor/ethtool/ethtool_linux.go +++ b/doctor/ethtool/ethtool_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/doctor/ethtool/ethtool_other.go b/doctor/ethtool/ethtool_other.go index 7af74eec8f872..91b5f6fdb9a6f 100644 --- a/doctor/ethtool/ethtool_other.go +++ b/doctor/ethtool/ethtool_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || android diff --git a/doctor/permissions/permissions.go b/doctor/permissions/permissions.go index 77fe526262f0c..a98ad1e0826a1 100644 --- a/doctor/permissions/permissions.go +++ b/doctor/permissions/permissions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package permissions provides a doctor.Check that prints the process diff --git a/doctor/permissions/permissions_bsd.go b/doctor/permissions/permissions_bsd.go index 8b034cfff1af3..c72e4d5d7a65c 100644 --- a/doctor/permissions/permissions_bsd.go +++ b/doctor/permissions/permissions_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd || openbsd diff --git a/doctor/permissions/permissions_linux.go b/doctor/permissions/permissions_linux.go index 12bb393d53383..8f8f12161e949 100644 --- a/doctor/permissions/permissions_linux.go +++ b/doctor/permissions/permissions_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/doctor/permissions/permissions_other.go b/doctor/permissions/permissions_other.go index 7e6912b4928cf..e96cf4f16277b 100644 --- a/doctor/permissions/permissions_other.go +++ b/doctor/permissions/permissions_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(linux || darwin || freebsd || openbsd) diff --git a/doctor/permissions/permissions_test.go b/doctor/permissions/permissions_test.go index 941d406ef8318..c7a292f39e783 100644 --- a/doctor/permissions/permissions_test.go +++ b/doctor/permissions/permissions_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package permissions diff --git a/doctor/routetable/routetable.go b/doctor/routetable/routetable.go index 76e4ef949b9af..1751d37448411 100644 --- a/doctor/routetable/routetable.go +++ b/doctor/routetable/routetable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package routetable provides a doctor.Check that dumps the current system's diff --git a/drive/drive_clone.go b/drive/drive_clone.go index 927f3b81c4e2c..724ebc386273d 100644 --- a/drive/drive_clone.go +++ b/drive/drive_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/drive/drive_view.go b/drive/drive_view.go index b481751bb3bff..253a2955b2161 100644 --- a/drive/drive_view.go +++ b/drive/drive_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/drive/driveimpl/birthtiming.go b/drive/driveimpl/birthtiming.go index d55ea0b83c322..c71bba5b47c77 100644 --- a/drive/driveimpl/birthtiming.go +++ b/drive/driveimpl/birthtiming.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/birthtiming_test.go b/drive/driveimpl/birthtiming_test.go index a43ffa33db92e..2bb1259224ff2 100644 --- a/drive/driveimpl/birthtiming_test.go +++ b/drive/driveimpl/birthtiming_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // BirthTime is not supported on Linux, so only run the test on windows and Mac. diff --git a/drive/driveimpl/compositedav/compositedav.go b/drive/driveimpl/compositedav/compositedav.go index 7c035912b946d..c6ec797726643 100644 --- a/drive/driveimpl/compositedav/compositedav.go +++ b/drive/driveimpl/compositedav/compositedav.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package compositedav provides an http.Handler that composes multiple WebDAV diff --git a/drive/driveimpl/compositedav/rewriting.go b/drive/driveimpl/compositedav/rewriting.go index 704be93d1bf76..47f020461b77d 100644 --- a/drive/driveimpl/compositedav/rewriting.go +++ b/drive/driveimpl/compositedav/rewriting.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package compositedav diff --git a/drive/driveimpl/compositedav/stat_cache.go b/drive/driveimpl/compositedav/stat_cache.go index 36463fe7e137f..2e53c82419795 100644 --- a/drive/driveimpl/compositedav/stat_cache.go +++ b/drive/driveimpl/compositedav/stat_cache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package compositedav diff --git a/drive/driveimpl/compositedav/stat_cache_test.go b/drive/driveimpl/compositedav/stat_cache_test.go index baa4fdda2c7f7..b982a3aad1d17 100644 --- a/drive/driveimpl/compositedav/stat_cache_test.go +++ b/drive/driveimpl/compositedav/stat_cache_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package compositedav diff --git a/drive/driveimpl/connlistener.go b/drive/driveimpl/connlistener.go index ff60f73404230..8fcc5a6d262d1 100644 --- a/drive/driveimpl/connlistener.go +++ b/drive/driveimpl/connlistener.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/connlistener_test.go b/drive/driveimpl/connlistener_test.go index 6adf15acbd56f..972791c6e530e 100644 --- a/drive/driveimpl/connlistener_test.go +++ b/drive/driveimpl/connlistener_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/dirfs/dirfs.go b/drive/driveimpl/dirfs/dirfs.go index 50a3330a9d751..3c4297264302d 100644 --- a/drive/driveimpl/dirfs/dirfs.go +++ b/drive/driveimpl/dirfs/dirfs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dirfs provides a webdav.FileSystem that looks like a read-only diff --git a/drive/driveimpl/dirfs/dirfs_test.go b/drive/driveimpl/dirfs/dirfs_test.go index 4d83765d9df23..c5f3aed3a99f0 100644 --- a/drive/driveimpl/dirfs/dirfs_test.go +++ b/drive/driveimpl/dirfs/dirfs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/mkdir.go b/drive/driveimpl/dirfs/mkdir.go index 2fb763dd5848a..6ed3ec27ea332 100644 --- a/drive/driveimpl/dirfs/mkdir.go +++ b/drive/driveimpl/dirfs/mkdir.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/openfile.go b/drive/driveimpl/dirfs/openfile.go index 9b678719b5b6c..71b55ab206e24 100644 --- a/drive/driveimpl/dirfs/openfile.go +++ b/drive/driveimpl/dirfs/openfile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/removeall.go b/drive/driveimpl/dirfs/removeall.go index 8fafc8c92bb04..a01d1dd0493d1 100644 --- a/drive/driveimpl/dirfs/removeall.go +++ b/drive/driveimpl/dirfs/removeall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/rename.go b/drive/driveimpl/dirfs/rename.go index 5049acb895e70..eedb1674318c0 100644 --- a/drive/driveimpl/dirfs/rename.go +++ b/drive/driveimpl/dirfs/rename.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/dirfs/stat.go b/drive/driveimpl/dirfs/stat.go index 2e4243bedcd20..dd0aa976afb8e 100644 --- a/drive/driveimpl/dirfs/stat.go +++ b/drive/driveimpl/dirfs/stat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirfs diff --git a/drive/driveimpl/drive_test.go b/drive/driveimpl/drive_test.go index 818e84990baef..db7bfe60bde19 100644 --- a/drive/driveimpl/drive_test.go +++ b/drive/driveimpl/drive_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/fileserver.go b/drive/driveimpl/fileserver.go index d448d83af761d..6aedfef2ce522 100644 --- a/drive/driveimpl/fileserver.go +++ b/drive/driveimpl/fileserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/local_impl.go b/drive/driveimpl/local_impl.go index 871d033431038..ab908c0d31d9c 100644 --- a/drive/driveimpl/local_impl.go +++ b/drive/driveimpl/local_impl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package driveimpl provides an implementation of package drive. diff --git a/drive/driveimpl/remote_impl.go b/drive/driveimpl/remote_impl.go index 2ff98075e3012..df27ba71627df 100644 --- a/drive/driveimpl/remote_impl.go +++ b/drive/driveimpl/remote_impl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package driveimpl diff --git a/drive/driveimpl/shared/pathutil.go b/drive/driveimpl/shared/pathutil.go index fcadcdd5aa0e0..8c0fb179dcc03 100644 --- a/drive/driveimpl/shared/pathutil.go +++ b/drive/driveimpl/shared/pathutil.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/driveimpl/shared/pathutil_test.go b/drive/driveimpl/shared/pathutil_test.go index daee695632ff4..b938f4c1c153f 100644 --- a/drive/driveimpl/shared/pathutil_test.go +++ b/drive/driveimpl/shared/pathutil_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/driveimpl/shared/readonlydir.go b/drive/driveimpl/shared/readonlydir.go index a495a2d5a93d6..b0f958231968f 100644 --- a/drive/driveimpl/shared/readonlydir.go +++ b/drive/driveimpl/shared/readonlydir.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package shared contains types and functions shared by different drive diff --git a/drive/driveimpl/shared/stat.go b/drive/driveimpl/shared/stat.go index d8022894c0888..93aad90abc49d 100644 --- a/drive/driveimpl/shared/stat.go +++ b/drive/driveimpl/shared/stat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/driveimpl/shared/xml.go b/drive/driveimpl/shared/xml.go index 79fd0885dd500..ffaeb031b7636 100644 --- a/drive/driveimpl/shared/xml.go +++ b/drive/driveimpl/shared/xml.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package shared diff --git a/drive/local.go b/drive/local.go index 052efb3f97ecf..300d142d4445b 100644 --- a/drive/local.go +++ b/drive/local.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package drive provides a filesystem that allows sharing folders between diff --git a/drive/remote.go b/drive/remote.go index 2c6fba894dbff..5f34d0023e6f7 100644 --- a/drive/remote.go +++ b/drive/remote.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_nonunix.go b/drive/remote_nonunix.go index d1153c5925419..4186ec0ad46e7 100644 --- a/drive/remote_nonunix.go +++ b/drive/remote_nonunix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !unix diff --git a/drive/remote_permissions.go b/drive/remote_permissions.go index 420eff9a0e743..31ec0caee881d 100644 --- a/drive/remote_permissions.go +++ b/drive/remote_permissions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_permissions_test.go b/drive/remote_permissions_test.go index ff039c80020c8..5d63a503f75d9 100644 --- a/drive/remote_permissions_test.go +++ b/drive/remote_permissions_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_test.go b/drive/remote_test.go index e05b23839bade..c0de1723aee59 100644 --- a/drive/remote_test.go +++ b/drive/remote_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package drive diff --git a/drive/remote_unix.go b/drive/remote_unix.go index 0e41524dbd304..4b367ef5ff79a 100644 --- a/drive/remote_unix.go +++ b/drive/remote_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/envknob/envknob.go b/envknob/envknob.go index 17a21387ecaea..2b1461f11f308 100644 --- a/envknob/envknob.go +++ b/envknob/envknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package envknob provides access to environment-variable tweakable diff --git a/envknob/envknob_nottest.go b/envknob/envknob_nottest.go index 0dd900cc8104e..4693ceebe746a 100644 --- a/envknob/envknob_nottest.go +++ b/envknob/envknob_nottest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_not_in_tests diff --git a/envknob/envknob_testable.go b/envknob/envknob_testable.go index e7f038336c4f3..5f0beea4f962e 100644 --- a/envknob/envknob_testable.go +++ b/envknob/envknob_testable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_not_in_tests diff --git a/envknob/featureknob/featureknob.go b/envknob/featureknob/featureknob.go index 5a54a1c42978d..049366549fcb3 100644 --- a/envknob/featureknob/featureknob.go +++ b/envknob/featureknob/featureknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package featureknob provides a facility to control whether features diff --git a/envknob/logknob/logknob.go b/envknob/logknob/logknob.go index 93302d0d2bd5c..bc6e8c3627077 100644 --- a/envknob/logknob/logknob.go +++ b/envknob/logknob/logknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logknob provides a helpful wrapper that allows enabling logging diff --git a/envknob/logknob/logknob_test.go b/envknob/logknob/logknob_test.go index aa4fb44214e12..9e7ab8aef6368 100644 --- a/envknob/logknob/logknob_test.go +++ b/envknob/logknob/logknob_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logknob diff --git a/feature/ace/ace.go b/feature/ace/ace.go index b6d36543c5281..b99516657ed25 100644 --- a/feature/ace/ace.go +++ b/feature/ace/ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ace registers support for Alternate Connectivity Endpoints (ACE). diff --git a/feature/appconnectors/appconnectors.go b/feature/appconnectors/appconnectors.go index 28f5ccde35acb..82d29ce0e6034 100644 --- a/feature/appconnectors/appconnectors.go +++ b/feature/appconnectors/appconnectors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appconnectors registers support for Tailscale App Connectors. diff --git a/feature/buildfeatures/buildfeatures.go b/feature/buildfeatures/buildfeatures.go index cdb31dc015673..ca4de74344485 100644 --- a/feature/buildfeatures/buildfeatures.go +++ b/feature/buildfeatures/buildfeatures.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run gen.go diff --git a/feature/buildfeatures/feature_ace_disabled.go b/feature/buildfeatures/feature_ace_disabled.go index b4808d4976b02..91a7eeb46da0d 100644 --- a/feature/buildfeatures/feature_ace_disabled.go +++ b/feature/buildfeatures/feature_ace_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_ace_enabled.go b/feature/buildfeatures/feature_ace_enabled.go index 4812f9a61cd4c..0d975ec7ffb35 100644 --- a/feature/buildfeatures/feature_ace_enabled.go +++ b/feature/buildfeatures/feature_ace_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_acme_disabled.go b/feature/buildfeatures/feature_acme_disabled.go index 0a7f25a821cc5..0add296a67f0a 100644 --- a/feature/buildfeatures/feature_acme_disabled.go +++ b/feature/buildfeatures/feature_acme_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_acme_enabled.go b/feature/buildfeatures/feature_acme_enabled.go index f074bfb4e1a7e..78182eaa488c2 100644 --- a/feature/buildfeatures/feature_acme_enabled.go +++ b/feature/buildfeatures/feature_acme_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseexitnode_disabled.go b/feature/buildfeatures/feature_advertiseexitnode_disabled.go index d4fdcec22db3c..aeac607012019 100644 --- a/feature/buildfeatures/feature_advertiseexitnode_disabled.go +++ b/feature/buildfeatures/feature_advertiseexitnode_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseexitnode_enabled.go b/feature/buildfeatures/feature_advertiseexitnode_enabled.go index 28246143ecb3c..0a7451dc3226f 100644 --- a/feature/buildfeatures/feature_advertiseexitnode_enabled.go +++ b/feature/buildfeatures/feature_advertiseexitnode_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseroutes_disabled.go b/feature/buildfeatures/feature_advertiseroutes_disabled.go index 59042720f3870..dbb3bb059eb04 100644 --- a/feature/buildfeatures/feature_advertiseroutes_disabled.go +++ b/feature/buildfeatures/feature_advertiseroutes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_advertiseroutes_enabled.go b/feature/buildfeatures/feature_advertiseroutes_enabled.go index 118fcd55d64e4..3abe33644631d 100644 --- a/feature/buildfeatures/feature_advertiseroutes_enabled.go +++ b/feature/buildfeatures/feature_advertiseroutes_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_appconnectors_disabled.go b/feature/buildfeatures/feature_appconnectors_disabled.go index 64ea8f86b4104..dcb9f24d776e8 100644 --- a/feature/buildfeatures/feature_appconnectors_disabled.go +++ b/feature/buildfeatures/feature_appconnectors_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_appconnectors_enabled.go b/feature/buildfeatures/feature_appconnectors_enabled.go index e00eaffa3e6fc..edbfe5fcf1806 100644 --- a/feature/buildfeatures/feature_appconnectors_enabled.go +++ b/feature/buildfeatures/feature_appconnectors_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_aws_disabled.go b/feature/buildfeatures/feature_aws_disabled.go index 66b670c1fe451..22b611e804a86 100644 --- a/feature/buildfeatures/feature_aws_disabled.go +++ b/feature/buildfeatures/feature_aws_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_aws_enabled.go b/feature/buildfeatures/feature_aws_enabled.go index 30203b2aa6df8..5a640a252f149 100644 --- a/feature/buildfeatures/feature_aws_enabled.go +++ b/feature/buildfeatures/feature_aws_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bakedroots_disabled.go b/feature/buildfeatures/feature_bakedroots_disabled.go index f203bc1b06d44..c06ebd6ff8c02 100644 --- a/feature/buildfeatures/feature_bakedroots_disabled.go +++ b/feature/buildfeatures/feature_bakedroots_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bakedroots_enabled.go b/feature/buildfeatures/feature_bakedroots_enabled.go index 69cf2c34ccf6a..8477e00514d84 100644 --- a/feature/buildfeatures/feature_bakedroots_enabled.go +++ b/feature/buildfeatures/feature_bakedroots_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bird_disabled.go b/feature/buildfeatures/feature_bird_disabled.go index 469aa41f954a9..60ca3eaac61b4 100644 --- a/feature/buildfeatures/feature_bird_disabled.go +++ b/feature/buildfeatures/feature_bird_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_bird_enabled.go b/feature/buildfeatures/feature_bird_enabled.go index 792129f64f567..57203324b2a53 100644 --- a/feature/buildfeatures/feature_bird_enabled.go +++ b/feature/buildfeatures/feature_bird_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_c2n_disabled.go b/feature/buildfeatures/feature_c2n_disabled.go index bc37e9e7bfd23..3fcdd3628cb60 100644 --- a/feature/buildfeatures/feature_c2n_disabled.go +++ b/feature/buildfeatures/feature_c2n_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_c2n_enabled.go b/feature/buildfeatures/feature_c2n_enabled.go index 5950e71571652..41f97157f57f8 100644 --- a/feature/buildfeatures/feature_c2n_enabled.go +++ b/feature/buildfeatures/feature_c2n_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cachenetmap_disabled.go b/feature/buildfeatures/feature_cachenetmap_disabled.go index 22407fe38a57f..d05e9315f2f8d 100644 --- a/feature/buildfeatures/feature_cachenetmap_disabled.go +++ b/feature/buildfeatures/feature_cachenetmap_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cachenetmap_enabled.go b/feature/buildfeatures/feature_cachenetmap_enabled.go index 02663c416bcbb..b1cd51a704152 100644 --- a/feature/buildfeatures/feature_cachenetmap_enabled.go +++ b/feature/buildfeatures/feature_cachenetmap_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_captiveportal_disabled.go b/feature/buildfeatures/feature_captiveportal_disabled.go index 367fef81bdc16..7535da5066ae6 100644 --- a/feature/buildfeatures/feature_captiveportal_disabled.go +++ b/feature/buildfeatures/feature_captiveportal_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_captiveportal_enabled.go b/feature/buildfeatures/feature_captiveportal_enabled.go index bd8e1f6a80ff1..90d70ab1d1556 100644 --- a/feature/buildfeatures/feature_captiveportal_enabled.go +++ b/feature/buildfeatures/feature_captiveportal_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_capture_disabled.go b/feature/buildfeatures/feature_capture_disabled.go index 58535958f26e8..8f46b9c244f1b 100644 --- a/feature/buildfeatures/feature_capture_disabled.go +++ b/feature/buildfeatures/feature_capture_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_capture_enabled.go b/feature/buildfeatures/feature_capture_enabled.go index 7120a3d06fa7d..3e1a2d7aaa70f 100644 --- a/feature/buildfeatures/feature_capture_enabled.go +++ b/feature/buildfeatures/feature_capture_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cliconndiag_disabled.go b/feature/buildfeatures/feature_cliconndiag_disabled.go index 06d8c7935fd4a..d38c4a3d6cf35 100644 --- a/feature/buildfeatures/feature_cliconndiag_disabled.go +++ b/feature/buildfeatures/feature_cliconndiag_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cliconndiag_enabled.go b/feature/buildfeatures/feature_cliconndiag_enabled.go index d6125ef08051c..88775b24de51f 100644 --- a/feature/buildfeatures/feature_cliconndiag_enabled.go +++ b/feature/buildfeatures/feature_cliconndiag_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientmetrics_disabled.go b/feature/buildfeatures/feature_clientmetrics_disabled.go index 721908bb079a2..0345ccc609fdf 100644 --- a/feature/buildfeatures/feature_clientmetrics_disabled.go +++ b/feature/buildfeatures/feature_clientmetrics_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientmetrics_enabled.go b/feature/buildfeatures/feature_clientmetrics_enabled.go index deaeb6e69b1c3..2e58155bd5261 100644 --- a/feature/buildfeatures/feature_clientmetrics_enabled.go +++ b/feature/buildfeatures/feature_clientmetrics_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientupdate_disabled.go b/feature/buildfeatures/feature_clientupdate_disabled.go index 165c9cc9a409d..6662ca2b9cda7 100644 --- a/feature/buildfeatures/feature_clientupdate_disabled.go +++ b/feature/buildfeatures/feature_clientupdate_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_clientupdate_enabled.go b/feature/buildfeatures/feature_clientupdate_enabled.go index 3c3c7878c53a9..041cdf8a53e79 100644 --- a/feature/buildfeatures/feature_clientupdate_enabled.go +++ b/feature/buildfeatures/feature_clientupdate_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cloud_disabled.go b/feature/buildfeatures/feature_cloud_disabled.go index 3b877a9c68d40..b2dc2607ffda2 100644 --- a/feature/buildfeatures/feature_cloud_disabled.go +++ b/feature/buildfeatures/feature_cloud_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_cloud_enabled.go b/feature/buildfeatures/feature_cloud_enabled.go index 8fd748de56c7e..5ee91b9ed7c4c 100644 --- a/feature/buildfeatures/feature_cloud_enabled.go +++ b/feature/buildfeatures/feature_cloud_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_completion_disabled.go b/feature/buildfeatures/feature_completion_disabled.go index ea319beb0af3e..aa46c9c6157e5 100644 --- a/feature/buildfeatures/feature_completion_disabled.go +++ b/feature/buildfeatures/feature_completion_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_completion_enabled.go b/feature/buildfeatures/feature_completion_enabled.go index 6db41c97b3e76..561a377edea15 100644 --- a/feature/buildfeatures/feature_completion_enabled.go +++ b/feature/buildfeatures/feature_completion_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dbus_disabled.go b/feature/buildfeatures/feature_dbus_disabled.go index e6ab896773fd1..c09fa7eeb7a34 100644 --- a/feature/buildfeatures/feature_dbus_disabled.go +++ b/feature/buildfeatures/feature_dbus_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dbus_enabled.go b/feature/buildfeatures/feature_dbus_enabled.go index 374331cdabe0c..f3cc9f003f0ab 100644 --- a/feature/buildfeatures/feature_dbus_enabled.go +++ b/feature/buildfeatures/feature_dbus_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debug_disabled.go b/feature/buildfeatures/feature_debug_disabled.go index eb048c0826eb9..4faafbb756559 100644 --- a/feature/buildfeatures/feature_debug_disabled.go +++ b/feature/buildfeatures/feature_debug_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debug_enabled.go b/feature/buildfeatures/feature_debug_enabled.go index 12a2700a45761..a99dc81044cea 100644 --- a/feature/buildfeatures/feature_debug_enabled.go +++ b/feature/buildfeatures/feature_debug_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugeventbus_disabled.go b/feature/buildfeatures/feature_debugeventbus_disabled.go index 2eb59993444af..a7cf3dd72e8c6 100644 --- a/feature/buildfeatures/feature_debugeventbus_disabled.go +++ b/feature/buildfeatures/feature_debugeventbus_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugeventbus_enabled.go b/feature/buildfeatures/feature_debugeventbus_enabled.go index df13b6fa23167..caa4ca30a5039 100644 --- a/feature/buildfeatures/feature_debugeventbus_enabled.go +++ b/feature/buildfeatures/feature_debugeventbus_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugportmapper_disabled.go b/feature/buildfeatures/feature_debugportmapper_disabled.go index eff85b8baaf50..4b3b03be824f9 100644 --- a/feature/buildfeatures/feature_debugportmapper_disabled.go +++ b/feature/buildfeatures/feature_debugportmapper_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_debugportmapper_enabled.go b/feature/buildfeatures/feature_debugportmapper_enabled.go index 491aa5ed84af1..89250083161e6 100644 --- a/feature/buildfeatures/feature_debugportmapper_enabled.go +++ b/feature/buildfeatures/feature_debugportmapper_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_desktop_sessions_disabled.go b/feature/buildfeatures/feature_desktop_sessions_disabled.go index 1536c886fec25..0df68e9d00704 100644 --- a/feature/buildfeatures/feature_desktop_sessions_disabled.go +++ b/feature/buildfeatures/feature_desktop_sessions_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_desktop_sessions_enabled.go b/feature/buildfeatures/feature_desktop_sessions_enabled.go index 84658de952c86..4f03b9da894fa 100644 --- a/feature/buildfeatures/feature_desktop_sessions_enabled.go +++ b/feature/buildfeatures/feature_desktop_sessions_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dns_disabled.go b/feature/buildfeatures/feature_dns_disabled.go index 30d7379cb9092..e59e4faef0a9a 100644 --- a/feature/buildfeatures/feature_dns_disabled.go +++ b/feature/buildfeatures/feature_dns_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_dns_enabled.go b/feature/buildfeatures/feature_dns_enabled.go index 962f2596bf5c9..f7c7097143aa9 100644 --- a/feature/buildfeatures/feature_dns_enabled.go +++ b/feature/buildfeatures/feature_dns_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_doctor_disabled.go b/feature/buildfeatures/feature_doctor_disabled.go index 8c15e951e311f..a08af9c837771 100644 --- a/feature/buildfeatures/feature_doctor_disabled.go +++ b/feature/buildfeatures/feature_doctor_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_doctor_enabled.go b/feature/buildfeatures/feature_doctor_enabled.go index a8a0bb7d2056b..502950855dd69 100644 --- a/feature/buildfeatures/feature_doctor_enabled.go +++ b/feature/buildfeatures/feature_doctor_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_drive_disabled.go b/feature/buildfeatures/feature_drive_disabled.go index 07202638952e8..90d2eac1b0d15 100644 --- a/feature/buildfeatures/feature_drive_disabled.go +++ b/feature/buildfeatures/feature_drive_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_drive_enabled.go b/feature/buildfeatures/feature_drive_enabled.go index 9f58836a43fc7..8117585c5ef79 100644 --- a/feature/buildfeatures/feature_drive_enabled.go +++ b/feature/buildfeatures/feature_drive_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_gro_disabled.go b/feature/buildfeatures/feature_gro_disabled.go index ffbd0da2e3e4f..9da12c587851f 100644 --- a/feature/buildfeatures/feature_gro_disabled.go +++ b/feature/buildfeatures/feature_gro_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_gro_enabled.go b/feature/buildfeatures/feature_gro_enabled.go index e2c8024e07815..5ca7aeef52b1c 100644 --- a/feature/buildfeatures/feature_gro_enabled.go +++ b/feature/buildfeatures/feature_gro_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_health_disabled.go b/feature/buildfeatures/feature_health_disabled.go index 2f2bcf240a455..59cb53d8c44ba 100644 --- a/feature/buildfeatures/feature_health_disabled.go +++ b/feature/buildfeatures/feature_health_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_health_enabled.go b/feature/buildfeatures/feature_health_enabled.go index 00ce3684eb6db..56b9a97f0b226 100644 --- a/feature/buildfeatures/feature_health_enabled.go +++ b/feature/buildfeatures/feature_health_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_hujsonconf_disabled.go b/feature/buildfeatures/feature_hujsonconf_disabled.go index cee076bc24527..01c82724abc90 100644 --- a/feature/buildfeatures/feature_hujsonconf_disabled.go +++ b/feature/buildfeatures/feature_hujsonconf_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_hujsonconf_enabled.go b/feature/buildfeatures/feature_hujsonconf_enabled.go index aefeeace5f0b9..d321f78ae9d09 100644 --- a/feature/buildfeatures/feature_hujsonconf_enabled.go +++ b/feature/buildfeatures/feature_hujsonconf_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_identityfederation_disabled.go b/feature/buildfeatures/feature_identityfederation_disabled.go index 94488adc8637c..535a478e078f2 100644 --- a/feature/buildfeatures/feature_identityfederation_disabled.go +++ b/feature/buildfeatures/feature_identityfederation_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_identityfederation_enabled.go b/feature/buildfeatures/feature_identityfederation_enabled.go index 892d62d66c37c..85708da513542 100644 --- a/feature/buildfeatures/feature_identityfederation_enabled.go +++ b/feature/buildfeatures/feature_identityfederation_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_iptables_disabled.go b/feature/buildfeatures/feature_iptables_disabled.go index 8cda5be5d6ae6..d444aedbe5aaf 100644 --- a/feature/buildfeatures/feature_iptables_disabled.go +++ b/feature/buildfeatures/feature_iptables_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_iptables_enabled.go b/feature/buildfeatures/feature_iptables_enabled.go index 44d98473f05f2..edc8f110decd0 100644 --- a/feature/buildfeatures/feature_iptables_enabled.go +++ b/feature/buildfeatures/feature_iptables_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_kube_disabled.go b/feature/buildfeatures/feature_kube_disabled.go index 2b76c57e78b94..c16768dabe17d 100644 --- a/feature/buildfeatures/feature_kube_disabled.go +++ b/feature/buildfeatures/feature_kube_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_kube_enabled.go b/feature/buildfeatures/feature_kube_enabled.go index 7abca1759fc49..97fa18e2eed91 100644 --- a/feature/buildfeatures/feature_kube_enabled.go +++ b/feature/buildfeatures/feature_kube_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_lazywg_disabled.go b/feature/buildfeatures/feature_lazywg_disabled.go index ce81d80bab6a1..af1ad388c03a7 100644 --- a/feature/buildfeatures/feature_lazywg_disabled.go +++ b/feature/buildfeatures/feature_lazywg_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_lazywg_enabled.go b/feature/buildfeatures/feature_lazywg_enabled.go index 259357f7f86ef..f2d6a10f81580 100644 --- a/feature/buildfeatures/feature_lazywg_enabled.go +++ b/feature/buildfeatures/feature_lazywg_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linkspeed_disabled.go b/feature/buildfeatures/feature_linkspeed_disabled.go index 19e254a740ff7..c579fdbdcea06 100644 --- a/feature/buildfeatures/feature_linkspeed_disabled.go +++ b/feature/buildfeatures/feature_linkspeed_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linkspeed_enabled.go b/feature/buildfeatures/feature_linkspeed_enabled.go index 939858a162910..a63aabc2a3247 100644 --- a/feature/buildfeatures/feature_linkspeed_enabled.go +++ b/feature/buildfeatures/feature_linkspeed_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linuxdnsfight_disabled.go b/feature/buildfeatures/feature_linuxdnsfight_disabled.go index 2e5b50ea06af0..801696c5f3bc9 100644 --- a/feature/buildfeatures/feature_linuxdnsfight_disabled.go +++ b/feature/buildfeatures/feature_linuxdnsfight_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_linuxdnsfight_enabled.go b/feature/buildfeatures/feature_linuxdnsfight_enabled.go index b9419fccbfc09..9637bdeebcd29 100644 --- a/feature/buildfeatures/feature_linuxdnsfight_enabled.go +++ b/feature/buildfeatures/feature_linuxdnsfight_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_listenrawdisco_disabled.go b/feature/buildfeatures/feature_listenrawdisco_disabled.go index 2911780636cb7..4bad9d002b7ad 100644 --- a/feature/buildfeatures/feature_listenrawdisco_disabled.go +++ b/feature/buildfeatures/feature_listenrawdisco_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_listenrawdisco_enabled.go b/feature/buildfeatures/feature_listenrawdisco_enabled.go index 4a4f85ae37319..e5cfe687f5716 100644 --- a/feature/buildfeatures/feature_listenrawdisco_enabled.go +++ b/feature/buildfeatures/feature_listenrawdisco_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_logtail_disabled.go b/feature/buildfeatures/feature_logtail_disabled.go index 140092a2eba5b..983055d4742a9 100644 --- a/feature/buildfeatures/feature_logtail_disabled.go +++ b/feature/buildfeatures/feature_logtail_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_logtail_enabled.go b/feature/buildfeatures/feature_logtail_enabled.go index 6e777216bf3cb..f9ce154028832 100644 --- a/feature/buildfeatures/feature_logtail_enabled.go +++ b/feature/buildfeatures/feature_logtail_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netlog_disabled.go b/feature/buildfeatures/feature_netlog_disabled.go index 60367a12600f3..a274f6aca61b6 100644 --- a/feature/buildfeatures/feature_netlog_disabled.go +++ b/feature/buildfeatures/feature_netlog_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netlog_enabled.go b/feature/buildfeatures/feature_netlog_enabled.go index f9d2abad30553..1206e7e92b062 100644 --- a/feature/buildfeatures/feature_netlog_enabled.go +++ b/feature/buildfeatures/feature_netlog_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netstack_disabled.go b/feature/buildfeatures/feature_netstack_disabled.go index acb6e8e76396e..45c86c0e362b6 100644 --- a/feature/buildfeatures/feature_netstack_disabled.go +++ b/feature/buildfeatures/feature_netstack_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_netstack_enabled.go b/feature/buildfeatures/feature_netstack_enabled.go index 04f67118523a0..2fc67164ec0b0 100644 --- a/feature/buildfeatures/feature_netstack_enabled.go +++ b/feature/buildfeatures/feature_netstack_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_networkmanager_disabled.go b/feature/buildfeatures/feature_networkmanager_disabled.go index d0ec6f01796ab..9ac3a928b62e0 100644 --- a/feature/buildfeatures/feature_networkmanager_disabled.go +++ b/feature/buildfeatures/feature_networkmanager_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_networkmanager_enabled.go b/feature/buildfeatures/feature_networkmanager_enabled.go index ec284c3109f75..5dd0431e3a892 100644 --- a/feature/buildfeatures/feature_networkmanager_enabled.go +++ b/feature/buildfeatures/feature_networkmanager_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_oauthkey_disabled.go b/feature/buildfeatures/feature_oauthkey_disabled.go index 72ad1723b1d14..8801a90d6db72 100644 --- a/feature/buildfeatures/feature_oauthkey_disabled.go +++ b/feature/buildfeatures/feature_oauthkey_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_oauthkey_enabled.go b/feature/buildfeatures/feature_oauthkey_enabled.go index 39c52a2b0b46d..e03e437957a22 100644 --- a/feature/buildfeatures/feature_oauthkey_enabled.go +++ b/feature/buildfeatures/feature_oauthkey_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_osrouter_disabled.go b/feature/buildfeatures/feature_osrouter_disabled.go index ccd7192bb8899..8004589b8a466 100644 --- a/feature/buildfeatures/feature_osrouter_disabled.go +++ b/feature/buildfeatures/feature_osrouter_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_osrouter_enabled.go b/feature/buildfeatures/feature_osrouter_enabled.go index a5dacc596bfbc..78ed0ca9d96df 100644 --- a/feature/buildfeatures/feature_osrouter_enabled.go +++ b/feature/buildfeatures/feature_osrouter_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_outboundproxy_disabled.go b/feature/buildfeatures/feature_outboundproxy_disabled.go index bf74db0600927..35de2fdbda3b8 100644 --- a/feature/buildfeatures/feature_outboundproxy_disabled.go +++ b/feature/buildfeatures/feature_outboundproxy_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_outboundproxy_enabled.go b/feature/buildfeatures/feature_outboundproxy_enabled.go index 53bb99d5c6a79..5c20b9458375a 100644 --- a/feature/buildfeatures/feature_outboundproxy_enabled.go +++ b/feature/buildfeatures/feature_outboundproxy_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiclient_disabled.go b/feature/buildfeatures/feature_peerapiclient_disabled.go index 83cc2bdfeef5c..bcb45e5fe3278 100644 --- a/feature/buildfeatures/feature_peerapiclient_disabled.go +++ b/feature/buildfeatures/feature_peerapiclient_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiclient_enabled.go b/feature/buildfeatures/feature_peerapiclient_enabled.go index 0bd3f50a869ca..214af9312b68e 100644 --- a/feature/buildfeatures/feature_peerapiclient_enabled.go +++ b/feature/buildfeatures/feature_peerapiclient_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiserver_disabled.go b/feature/buildfeatures/feature_peerapiserver_disabled.go index 4a4f32b8a4065..60b2df96544bb 100644 --- a/feature/buildfeatures/feature_peerapiserver_disabled.go +++ b/feature/buildfeatures/feature_peerapiserver_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_peerapiserver_enabled.go b/feature/buildfeatures/feature_peerapiserver_enabled.go index 17d0547b80946..9c56c5309d483 100644 --- a/feature/buildfeatures/feature_peerapiserver_enabled.go +++ b/feature/buildfeatures/feature_peerapiserver_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portlist_disabled.go b/feature/buildfeatures/feature_portlist_disabled.go index 934061fd8328f..9269a7b5e4ba6 100644 --- a/feature/buildfeatures/feature_portlist_disabled.go +++ b/feature/buildfeatures/feature_portlist_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portlist_enabled.go b/feature/buildfeatures/feature_portlist_enabled.go index c1dc1c163b80e..31a2875363517 100644 --- a/feature/buildfeatures/feature_portlist_enabled.go +++ b/feature/buildfeatures/feature_portlist_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portmapper_disabled.go b/feature/buildfeatures/feature_portmapper_disabled.go index 212b22d40abfb..dea23f2bd6c3b 100644 --- a/feature/buildfeatures/feature_portmapper_disabled.go +++ b/feature/buildfeatures/feature_portmapper_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_portmapper_enabled.go b/feature/buildfeatures/feature_portmapper_enabled.go index 2f915d277a313..495a5bf207d40 100644 --- a/feature/buildfeatures/feature_portmapper_enabled.go +++ b/feature/buildfeatures/feature_portmapper_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_posture_disabled.go b/feature/buildfeatures/feature_posture_disabled.go index a78b1a95720cf..9987819a84980 100644 --- a/feature/buildfeatures/feature_posture_disabled.go +++ b/feature/buildfeatures/feature_posture_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_posture_enabled.go b/feature/buildfeatures/feature_posture_enabled.go index dcd9595f9ca96..4e601d33b578f 100644 --- a/feature/buildfeatures/feature_posture_enabled.go +++ b/feature/buildfeatures/feature_posture_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_qrcodes_disabled.go b/feature/buildfeatures/feature_qrcodes_disabled.go index 4b992501c969e..64d33cfcc731a 100644 --- a/feature/buildfeatures/feature_qrcodes_disabled.go +++ b/feature/buildfeatures/feature_qrcodes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_qrcodes_enabled.go b/feature/buildfeatures/feature_qrcodes_enabled.go index 5b74e2b3e5cbe..35fe9741b0a59 100644 --- a/feature/buildfeatures/feature_qrcodes_enabled.go +++ b/feature/buildfeatures/feature_qrcodes_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_relayserver_disabled.go b/feature/buildfeatures/feature_relayserver_disabled.go index 08ced83101f96..cee2d93fea7e4 100644 --- a/feature/buildfeatures/feature_relayserver_disabled.go +++ b/feature/buildfeatures/feature_relayserver_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_relayserver_enabled.go b/feature/buildfeatures/feature_relayserver_enabled.go index 6a35f8305d68f..3886c853d6e6c 100644 --- a/feature/buildfeatures/feature_relayserver_enabled.go +++ b/feature/buildfeatures/feature_relayserver_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_resolved_disabled.go b/feature/buildfeatures/feature_resolved_disabled.go index 283dd20c76aaa..e19576e2a9131 100644 --- a/feature/buildfeatures/feature_resolved_disabled.go +++ b/feature/buildfeatures/feature_resolved_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_resolved_enabled.go b/feature/buildfeatures/feature_resolved_enabled.go index af1b3b41e9358..46e59411784fb 100644 --- a/feature/buildfeatures/feature_resolved_enabled.go +++ b/feature/buildfeatures/feature_resolved_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_sdnotify_disabled.go b/feature/buildfeatures/feature_sdnotify_disabled.go index 7efa2d22ff587..4ae1cd8b021b0 100644 --- a/feature/buildfeatures/feature_sdnotify_disabled.go +++ b/feature/buildfeatures/feature_sdnotify_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_sdnotify_enabled.go b/feature/buildfeatures/feature_sdnotify_enabled.go index 40fec9755dd16..0f6adcaaea901 100644 --- a/feature/buildfeatures/feature_sdnotify_enabled.go +++ b/feature/buildfeatures/feature_sdnotify_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_serve_disabled.go b/feature/buildfeatures/feature_serve_disabled.go index 6d79713500e29..51aa5e4cd7291 100644 --- a/feature/buildfeatures/feature_serve_disabled.go +++ b/feature/buildfeatures/feature_serve_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_serve_enabled.go b/feature/buildfeatures/feature_serve_enabled.go index 57bf2c6b0fc2b..10638f5b47a8a 100644 --- a/feature/buildfeatures/feature_serve_enabled.go +++ b/feature/buildfeatures/feature_serve_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_ssh_disabled.go b/feature/buildfeatures/feature_ssh_disabled.go index 754f50eb6a816..c51d5425d4e3a 100644 --- a/feature/buildfeatures/feature_ssh_disabled.go +++ b/feature/buildfeatures/feature_ssh_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_ssh_enabled.go b/feature/buildfeatures/feature_ssh_enabled.go index dbdc3a89fa027..539173db4366d 100644 --- a/feature/buildfeatures/feature_ssh_enabled.go +++ b/feature/buildfeatures/feature_ssh_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_synology_disabled.go b/feature/buildfeatures/feature_synology_disabled.go index 0cdf084c32d8e..98613f16c13c9 100644 --- a/feature/buildfeatures/feature_synology_disabled.go +++ b/feature/buildfeatures/feature_synology_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_synology_enabled.go b/feature/buildfeatures/feature_synology_enabled.go index dde4123b61eb0..2090dafb51f43 100644 --- a/feature/buildfeatures/feature_synology_enabled.go +++ b/feature/buildfeatures/feature_synology_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_syspolicy_disabled.go b/feature/buildfeatures/feature_syspolicy_disabled.go index 54d32e32e71d8..e7b2b3dad1889 100644 --- a/feature/buildfeatures/feature_syspolicy_disabled.go +++ b/feature/buildfeatures/feature_syspolicy_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_syspolicy_enabled.go b/feature/buildfeatures/feature_syspolicy_enabled.go index f7c403ae9d68b..5c3b2794adc1a 100644 --- a/feature/buildfeatures/feature_syspolicy_enabled.go +++ b/feature/buildfeatures/feature_syspolicy_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_systray_disabled.go b/feature/buildfeatures/feature_systray_disabled.go index 4ae1edb0ab83f..bfd16f7a4acb0 100644 --- a/feature/buildfeatures/feature_systray_disabled.go +++ b/feature/buildfeatures/feature_systray_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_systray_enabled.go b/feature/buildfeatures/feature_systray_enabled.go index 5fd7fd220325a..602e1223d7c81 100644 --- a/feature/buildfeatures/feature_systray_enabled.go +++ b/feature/buildfeatures/feature_systray_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_taildrop_disabled.go b/feature/buildfeatures/feature_taildrop_disabled.go index 8ffe90617839f..8165a68a848f0 100644 --- a/feature/buildfeatures/feature_taildrop_disabled.go +++ b/feature/buildfeatures/feature_taildrop_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_taildrop_enabled.go b/feature/buildfeatures/feature_taildrop_enabled.go index 4f55d2801c516..c07a2a037ffff 100644 --- a/feature/buildfeatures/feature_taildrop_enabled.go +++ b/feature/buildfeatures/feature_taildrop_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tailnetlock_disabled.go b/feature/buildfeatures/feature_tailnetlock_disabled.go index 6b5a57f24ba4f..5a208babb7797 100644 --- a/feature/buildfeatures/feature_tailnetlock_disabled.go +++ b/feature/buildfeatures/feature_tailnetlock_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tailnetlock_enabled.go b/feature/buildfeatures/feature_tailnetlock_enabled.go index afedb7faad312..c65151152ea83 100644 --- a/feature/buildfeatures/feature_tailnetlock_enabled.go +++ b/feature/buildfeatures/feature_tailnetlock_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tap_disabled.go b/feature/buildfeatures/feature_tap_disabled.go index f0b3eec8d7e6f..07605ff3792dd 100644 --- a/feature/buildfeatures/feature_tap_disabled.go +++ b/feature/buildfeatures/feature_tap_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tap_enabled.go b/feature/buildfeatures/feature_tap_enabled.go index 1363c4b44afb2..0b88a42b6604b 100644 --- a/feature/buildfeatures/feature_tap_enabled.go +++ b/feature/buildfeatures/feature_tap_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tpm_disabled.go b/feature/buildfeatures/feature_tpm_disabled.go index b9d55815ef5df..b0351c80dfd96 100644 --- a/feature/buildfeatures/feature_tpm_disabled.go +++ b/feature/buildfeatures/feature_tpm_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_tpm_enabled.go b/feature/buildfeatures/feature_tpm_enabled.go index dcfc8a30442ad..0af8a10e9b883 100644 --- a/feature/buildfeatures/feature_tpm_enabled.go +++ b/feature/buildfeatures/feature_tpm_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_unixsocketidentity_disabled.go b/feature/buildfeatures/feature_unixsocketidentity_disabled.go index d64e48b825eac..25320544b2282 100644 --- a/feature/buildfeatures/feature_unixsocketidentity_disabled.go +++ b/feature/buildfeatures/feature_unixsocketidentity_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_unixsocketidentity_enabled.go b/feature/buildfeatures/feature_unixsocketidentity_enabled.go index 463ac2ced3636..9511ba00f4094 100644 --- a/feature/buildfeatures/feature_unixsocketidentity_enabled.go +++ b/feature/buildfeatures/feature_unixsocketidentity_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useexitnode_disabled.go b/feature/buildfeatures/feature_useexitnode_disabled.go index 51bec8046cb35..51e95fca084bd 100644 --- a/feature/buildfeatures/feature_useexitnode_disabled.go +++ b/feature/buildfeatures/feature_useexitnode_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useexitnode_enabled.go b/feature/buildfeatures/feature_useexitnode_enabled.go index f7ab414de9477..e6df5c85fd3f4 100644 --- a/feature/buildfeatures/feature_useexitnode_enabled.go +++ b/feature/buildfeatures/feature_useexitnode_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useproxy_disabled.go b/feature/buildfeatures/feature_useproxy_disabled.go index 9f29a9820eb99..604825ba991ca 100644 --- a/feature/buildfeatures/feature_useproxy_disabled.go +++ b/feature/buildfeatures/feature_useproxy_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useproxy_enabled.go b/feature/buildfeatures/feature_useproxy_enabled.go index 9195f2fdce784..fe2ecc9ea538a 100644 --- a/feature/buildfeatures/feature_useproxy_enabled.go +++ b/feature/buildfeatures/feature_useproxy_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_usermetrics_disabled.go b/feature/buildfeatures/feature_usermetrics_disabled.go index 092c89c3b543f..96441b5138497 100644 --- a/feature/buildfeatures/feature_usermetrics_disabled.go +++ b/feature/buildfeatures/feature_usermetrics_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_usermetrics_enabled.go b/feature/buildfeatures/feature_usermetrics_enabled.go index 813e3c3477b66..427c6fd397657 100644 --- a/feature/buildfeatures/feature_usermetrics_enabled.go +++ b/feature/buildfeatures/feature_usermetrics_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useroutes_disabled.go b/feature/buildfeatures/feature_useroutes_disabled.go index ecf9d022bed74..26e2311c6fa17 100644 --- a/feature/buildfeatures/feature_useroutes_disabled.go +++ b/feature/buildfeatures/feature_useroutes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_useroutes_enabled.go b/feature/buildfeatures/feature_useroutes_enabled.go index c0a59322ecdc1..0dc7089d99165 100644 --- a/feature/buildfeatures/feature_useroutes_enabled.go +++ b/feature/buildfeatures/feature_useroutes_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_wakeonlan_disabled.go b/feature/buildfeatures/feature_wakeonlan_disabled.go index 816ac661f78ce..ca76d0b7f00df 100644 --- a/feature/buildfeatures/feature_wakeonlan_disabled.go +++ b/feature/buildfeatures/feature_wakeonlan_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_wakeonlan_enabled.go b/feature/buildfeatures/feature_wakeonlan_enabled.go index 34b3348a10fef..07bb16fba96fc 100644 --- a/feature/buildfeatures/feature_wakeonlan_enabled.go +++ b/feature/buildfeatures/feature_wakeonlan_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_webclient_disabled.go b/feature/buildfeatures/feature_webclient_disabled.go index a7b24f4ac2dda..9792265c6d559 100644 --- a/feature/buildfeatures/feature_webclient_disabled.go +++ b/feature/buildfeatures/feature_webclient_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/feature_webclient_enabled.go b/feature/buildfeatures/feature_webclient_enabled.go index e40dad33c6ebb..cb558a5fe7831 100644 --- a/feature/buildfeatures/feature_webclient_enabled.go +++ b/feature/buildfeatures/feature_webclient_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen.go; DO NOT EDIT. diff --git a/feature/buildfeatures/gen.go b/feature/buildfeatures/gen.go index e967cb8ff1906..cf8e9d49f1f47 100644 --- a/feature/buildfeatures/gen.go +++ b/feature/buildfeatures/gen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore @@ -17,7 +17,7 @@ import ( "tailscale.com/util/must" ) -const header = `// Copyright (c) Tailscale Inc & AUTHORS +const header = `// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code g|e|n|e|r|a|t|e|d by gen.go; D|O N|OT E|D|I|T. diff --git a/feature/c2n/c2n.go b/feature/c2n/c2n.go index ae942e31d0d95..331a9af955a07 100644 --- a/feature/c2n/c2n.go +++ b/feature/c2n/c2n.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package c2n registers support for C2N (Control-to-Node) communications. diff --git a/feature/capture/capture.go b/feature/capture/capture.go index e5e150de8e761..d7145e7c1ebd7 100644 --- a/feature/capture/capture.go +++ b/feature/capture/capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package capture formats packet logging into a debug pcap stream. diff --git a/feature/capture/dissector/dissector.go b/feature/capture/dissector/dissector.go index ab2f6c2ec1607..dec90e28b11b1 100644 --- a/feature/capture/dissector/dissector.go +++ b/feature/capture/dissector/dissector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dissector contains the Lua dissector for Tailscale packets. diff --git a/feature/clientupdate/clientupdate.go b/feature/clientupdate/clientupdate.go index 45fd21129b4e7..d47d048156046 100644 --- a/feature/clientupdate/clientupdate.go +++ b/feature/clientupdate/clientupdate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package clientupdate enables the client update feature. diff --git a/feature/condlite/expvar/expvar.go b/feature/condlite/expvar/expvar.go index edc16ac771b13..68aaf6e2cddf9 100644 --- a/feature/condlite/expvar/expvar.go +++ b/feature/condlite/expvar/expvar.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(ts_omit_debug && ts_omit_clientmetrics && ts_omit_usermetrics) diff --git a/feature/condlite/expvar/omit.go b/feature/condlite/expvar/omit.go index a21d94deb48eb..b5481695c9947 100644 --- a/feature/condlite/expvar/omit.go +++ b/feature/condlite/expvar/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_debug && ts_omit_clientmetrics && ts_omit_usermetrics diff --git a/feature/condregister/condregister.go b/feature/condregister/condregister.go index 654483d1d7745..e0d72b7ac293c 100644 --- a/feature/condregister/condregister.go +++ b/feature/condregister/condregister.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The condregister package registers all conditional features guarded diff --git a/feature/condregister/identityfederation/doc.go b/feature/condregister/identityfederation/doc.go index 503b2c8f127d5..ee811bdec8064 100644 --- a/feature/condregister/identityfederation/doc.go +++ b/feature/condregister/identityfederation/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package identityfederation registers support for authkey resolution diff --git a/feature/condregister/identityfederation/maybe_identityfederation.go b/feature/condregister/identityfederation/maybe_identityfederation.go index b1db42fc3c77a..04c37e36faa47 100644 --- a/feature/condregister/identityfederation/maybe_identityfederation.go +++ b/feature/condregister/identityfederation/maybe_identityfederation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_identityfederation diff --git a/feature/condregister/maybe_ace.go b/feature/condregister/maybe_ace.go index 07023171144a5..a926f5b0d8810 100644 --- a/feature/condregister/maybe_ace.go +++ b/feature/condregister/maybe_ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_ace diff --git a/feature/condregister/maybe_appconnectors.go b/feature/condregister/maybe_appconnectors.go index 70112d7810b10..3b872bc1eb90d 100644 --- a/feature/condregister/maybe_appconnectors.go +++ b/feature/condregister/maybe_appconnectors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_appconnectors diff --git a/feature/condregister/maybe_c2n.go b/feature/condregister/maybe_c2n.go index c222af533a37d..99258956ad8b2 100644 --- a/feature/condregister/maybe_c2n.go +++ b/feature/condregister/maybe_c2n.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_c2n diff --git a/feature/condregister/maybe_capture.go b/feature/condregister/maybe_capture.go index 0c68331f101cd..991843cb58194 100644 --- a/feature/condregister/maybe_capture.go +++ b/feature/condregister/maybe_capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_capture diff --git a/feature/condregister/maybe_clientupdate.go b/feature/condregister/maybe_clientupdate.go index bc694f970c543..df36d8e67b92e 100644 --- a/feature/condregister/maybe_clientupdate.go +++ b/feature/condregister/maybe_clientupdate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_clientupdate diff --git a/feature/condregister/maybe_conn25.go b/feature/condregister/maybe_conn25.go index fb885bfe32fc1..6ce14b2b3f1ce 100644 --- a/feature/condregister/maybe_conn25.go +++ b/feature/condregister/maybe_conn25.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_conn25 diff --git a/feature/condregister/maybe_debugportmapper.go b/feature/condregister/maybe_debugportmapper.go index 4990d09ea5833..443b21e02d331 100644 --- a/feature/condregister/maybe_debugportmapper.go +++ b/feature/condregister/maybe_debugportmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debugportmapper diff --git a/feature/condregister/maybe_doctor.go b/feature/condregister/maybe_doctor.go index 3dc9ffa539312..41d504c5394df 100644 --- a/feature/condregister/maybe_doctor.go +++ b/feature/condregister/maybe_doctor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_doctor diff --git a/feature/condregister/maybe_drive.go b/feature/condregister/maybe_drive.go index cb447ff289a29..4d979e821852b 100644 --- a/feature/condregister/maybe_drive.go +++ b/feature/condregister/maybe_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/feature/condregister/maybe_linkspeed.go b/feature/condregister/maybe_linkspeed.go index 46064b39a5935..5e9e9e4004b19 100644 --- a/feature/condregister/maybe_linkspeed.go +++ b/feature/condregister/maybe_linkspeed.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_linkspeed diff --git a/feature/condregister/maybe_linuxdnsfight.go b/feature/condregister/maybe_linuxdnsfight.go index 0dae62b00ab8a..2866fd0d7a891 100644 --- a/feature/condregister/maybe_linuxdnsfight.go +++ b/feature/condregister/maybe_linuxdnsfight.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_linuxdnsfight diff --git a/feature/condregister/maybe_osrouter.go b/feature/condregister/maybe_osrouter.go index 7ab85add22021..771a86e48bfbf 100644 --- a/feature/condregister/maybe_osrouter.go +++ b/feature/condregister/maybe_osrouter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_osrouter diff --git a/feature/condregister/maybe_portlist.go b/feature/condregister/maybe_portlist.go index 1be56f177daf8..8de98d528ae48 100644 --- a/feature/condregister/maybe_portlist.go +++ b/feature/condregister/maybe_portlist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_portlist diff --git a/feature/condregister/maybe_posture.go b/feature/condregister/maybe_posture.go index 6f14c27137127..ca056eb3612b8 100644 --- a/feature/condregister/maybe_posture.go +++ b/feature/condregister/maybe_posture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_posture diff --git a/feature/condregister/maybe_relayserver.go b/feature/condregister/maybe_relayserver.go index 3360dd0627cc1..49404bef8a08b 100644 --- a/feature/condregister/maybe_relayserver.go +++ b/feature/condregister/maybe_relayserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_relayserver diff --git a/feature/condregister/maybe_sdnotify.go b/feature/condregister/maybe_sdnotify.go index 647996f881d8f..ac8e180cd791f 100644 --- a/feature/condregister/maybe_sdnotify.go +++ b/feature/condregister/maybe_sdnotify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_sdnotify diff --git a/feature/condregister/maybe_store_aws.go b/feature/condregister/maybe_store_aws.go index 8358b49f05843..96de819d1ee39 100644 --- a/feature/condregister/maybe_store_aws.go +++ b/feature/condregister/maybe_store_aws.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (ts_aws || (linux && (arm64 || amd64) && !android)) && !ts_omit_aws diff --git a/feature/condregister/maybe_store_kube.go b/feature/condregister/maybe_store_kube.go index bb795b05e2450..a71ed00e2e248 100644 --- a/feature/condregister/maybe_store_kube.go +++ b/feature/condregister/maybe_store_kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (ts_kube || (linux && (arm64 || amd64) && !android)) && !ts_omit_kube diff --git a/feature/condregister/maybe_syspolicy.go b/feature/condregister/maybe_syspolicy.go index 49ec5c02c63e1..66d44ea3804e4 100644 --- a/feature/condregister/maybe_syspolicy.go +++ b/feature/condregister/maybe_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/feature/condregister/maybe_taildrop.go b/feature/condregister/maybe_taildrop.go index 5fd7b5f8c9a00..264ccff02006f 100644 --- a/feature/condregister/maybe_taildrop.go +++ b/feature/condregister/maybe_taildrop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_taildrop diff --git a/feature/condregister/maybe_tap.go b/feature/condregister/maybe_tap.go index eca4fc3ac84af..fc3997b17dca4 100644 --- a/feature/condregister/maybe_tap.go +++ b/feature/condregister/maybe_tap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_tap diff --git a/feature/condregister/maybe_tpm.go b/feature/condregister/maybe_tpm.go index caa57fef11d73..f46a0996f4feb 100644 --- a/feature/condregister/maybe_tpm.go +++ b/feature/condregister/maybe_tpm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_tpm diff --git a/feature/condregister/maybe_wakeonlan.go b/feature/condregister/maybe_wakeonlan.go index 14cae605d1468..6fc32bb22fe26 100644 --- a/feature/condregister/maybe_wakeonlan.go +++ b/feature/condregister/maybe_wakeonlan.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_wakeonlan diff --git a/feature/condregister/oauthkey/doc.go b/feature/condregister/oauthkey/doc.go index 4c4ea5e4e3078..af1c931480672 100644 --- a/feature/condregister/oauthkey/doc.go +++ b/feature/condregister/oauthkey/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package oauthkey registers support for OAuth key resolution diff --git a/feature/condregister/oauthkey/maybe_oauthkey.go b/feature/condregister/oauthkey/maybe_oauthkey.go index be8d04b8ec035..9e912f149a3db 100644 --- a/feature/condregister/oauthkey/maybe_oauthkey.go +++ b/feature/condregister/oauthkey/maybe_oauthkey.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_oauthkey diff --git a/feature/condregister/portmapper/doc.go b/feature/condregister/portmapper/doc.go index 5c30538c43a11..21e45c4a676be 100644 --- a/feature/condregister/portmapper/doc.go +++ b/feature/condregister/portmapper/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmapper registers support for portmapper diff --git a/feature/condregister/portmapper/maybe_portmapper.go b/feature/condregister/portmapper/maybe_portmapper.go index c306fd3d5a1f0..e1be2b3ced942 100644 --- a/feature/condregister/portmapper/maybe_portmapper.go +++ b/feature/condregister/portmapper/maybe_portmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_portmapper diff --git a/feature/condregister/useproxy/doc.go b/feature/condregister/useproxy/doc.go index 1e8abb358fa83..d5fde367082e2 100644 --- a/feature/condregister/useproxy/doc.go +++ b/feature/condregister/useproxy/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package useproxy registers support for using proxies diff --git a/feature/condregister/useproxy/useproxy.go b/feature/condregister/useproxy/useproxy.go index bda6e49c0bb95..bca17de88f62e 100644 --- a/feature/condregister/useproxy/useproxy.go +++ b/feature/condregister/useproxy/useproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_useproxy diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index e7baca4bd10b7..2a2b75a2d8b19 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package conn25 registers the conn25 feature and implements its associated ipnext.Extension. diff --git a/feature/debugportmapper/debugportmapper.go b/feature/debugportmapper/debugportmapper.go index 2625086c64dcf..45f3c22084fba 100644 --- a/feature/debugportmapper/debugportmapper.go +++ b/feature/debugportmapper/debugportmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package debugportmapper registers support for debugging Tailscale's diff --git a/feature/doctor/doctor.go b/feature/doctor/doctor.go index 875b57d14c4f0..db061311b2e1f 100644 --- a/feature/doctor/doctor.go +++ b/feature/doctor/doctor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The doctor package registers the "doctor" problem diagnosis support into the diff --git a/feature/drive/drive.go b/feature/drive/drive.go index 3660a2b959643..1cf616a143d6e 100644 --- a/feature/drive/drive.go +++ b/feature/drive/drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package drive registers the Taildrive (file server) feature. diff --git a/feature/feature.go b/feature/feature.go index 48a4aff43b84d..5bd79db45c4ed 100644 --- a/feature/feature.go +++ b/feature/feature.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package feature tracks which features are linked into the binary. diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index 99df18b5a3c3b..c0a72a38d1fdd 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The featuretags package is a registry of all the ts_omit-able build tags. diff --git a/feature/featuretags/featuretags_test.go b/feature/featuretags/featuretags_test.go index 893ab0e6a1c71..b970295779591 100644 --- a/feature/featuretags/featuretags_test.go +++ b/feature/featuretags/featuretags_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package featuretags diff --git a/feature/hooks.go b/feature/hooks.go index 7e31061a7eaac..5cd3c0d818ca6 100644 --- a/feature/hooks.go +++ b/feature/hooks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package feature diff --git a/feature/identityfederation/identityfederation.go b/feature/identityfederation/identityfederation.go index f75b096a603a2..4b96fd6a2020c 100644 --- a/feature/identityfederation/identityfederation.go +++ b/feature/identityfederation/identityfederation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package identityfederation registers support for using ID tokens to diff --git a/feature/identityfederation/identityfederation_test.go b/feature/identityfederation/identityfederation_test.go index b050f1a019e38..5e3660dc58725 100644 --- a/feature/identityfederation/identityfederation_test.go +++ b/feature/identityfederation/identityfederation_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package identityfederation diff --git a/feature/linkspeed/doc.go b/feature/linkspeed/doc.go index 2d2fcf0929808..b3adf88ef76e8 100644 --- a/feature/linkspeed/doc.go +++ b/feature/linkspeed/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package linkspeed registers support for setting the TUN link speed on Linux, diff --git a/feature/linkspeed/linkspeed_linux.go b/feature/linkspeed/linkspeed_linux.go index 90e33d4c9fea4..4e36e281ca80c 100644 --- a/feature/linkspeed/linkspeed_linux.go +++ b/feature/linkspeed/linkspeed_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/linuxdnsfight/linuxdnsfight.go b/feature/linuxdnsfight/linuxdnsfight.go index 02d99a3144246..ea37ed7a5b9ef 100644 --- a/feature/linuxdnsfight/linuxdnsfight.go +++ b/feature/linuxdnsfight/linuxdnsfight.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/linuxdnsfight/linuxdnsfight_test.go b/feature/linuxdnsfight/linuxdnsfight_test.go index bd3463666d46b..661ba7f6f3a00 100644 --- a/feature/linuxdnsfight/linuxdnsfight_test.go +++ b/feature/linuxdnsfight/linuxdnsfight_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/oauthkey/oauthkey.go b/feature/oauthkey/oauthkey.go index 336340c85109b..532f6ec73bab9 100644 --- a/feature/oauthkey/oauthkey.go +++ b/feature/oauthkey/oauthkey.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package oauthkey registers support for using OAuth client secrets to diff --git a/feature/oauthkey/oauthkey_test.go b/feature/oauthkey/oauthkey_test.go index b550d8c2ce77a..f8027e45a922e 100644 --- a/feature/oauthkey/oauthkey_test.go +++ b/feature/oauthkey/oauthkey_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package oauthkey diff --git a/feature/portlist/portlist.go b/feature/portlist/portlist.go index 7d69796ffd5d2..b651c64cb6afa 100644 --- a/feature/portlist/portlist.go +++ b/feature/portlist/portlist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portlist contains code to poll the local system for open ports diff --git a/feature/portmapper/portmapper.go b/feature/portmapper/portmapper.go index d1b903cb69c20..3d2004993a510 100644 --- a/feature/portmapper/portmapper.go +++ b/feature/portmapper/portmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmapper registers support for NAT-PMP, PCP, and UPnP port diff --git a/feature/posture/posture.go b/feature/posture/posture.go index 977e7429571a8..d8db1ac1933fb 100644 --- a/feature/posture/posture.go +++ b/feature/posture/posture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package posture registers support for device posture checking, diff --git a/feature/relayserver/relayserver.go b/feature/relayserver/relayserver.go index b29a6abed5336..45d6abcc1d3d6 100644 --- a/feature/relayserver/relayserver.go +++ b/feature/relayserver/relayserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package relayserver registers the relay server feature and implements its diff --git a/feature/relayserver/relayserver_test.go b/feature/relayserver/relayserver_test.go index 807306c707bc1..730e25a00d0d3 100644 --- a/feature/relayserver/relayserver_test.go +++ b/feature/relayserver/relayserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package relayserver diff --git a/feature/sdnotify.go b/feature/sdnotify.go index 7a786dfabd519..45f280c8116f2 100644 --- a/feature/sdnotify.go +++ b/feature/sdnotify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package feature diff --git a/feature/sdnotify/sdnotify.go b/feature/sdnotify/sdnotify.go index d13aa63f23c15..d74eafd52d1d7 100644 --- a/feature/sdnotify/sdnotify.go +++ b/feature/sdnotify/sdnotify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause /* diff --git a/feature/sdnotify/sdnotify_linux.go b/feature/sdnotify/sdnotify_linux.go index 2b13e24bbe509..6a3df879638c9 100644 --- a/feature/sdnotify/sdnotify_linux.go +++ b/feature/sdnotify/sdnotify_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/feature/syspolicy/syspolicy.go b/feature/syspolicy/syspolicy.go index 08c3cf3736b29..dce2eb0b18bbb 100644 --- a/feature/syspolicy/syspolicy.go +++ b/feature/syspolicy/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package syspolicy provides an interface for system-wide policy management. diff --git a/feature/taildrop/delete.go b/feature/taildrop/delete.go index 8b03a125f445e..dc5036006a2f6 100644 --- a/feature/taildrop/delete.go +++ b/feature/taildrop/delete.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/delete_test.go b/feature/taildrop/delete_test.go index 36950f58288cb..2b740a3fb2d6c 100644 --- a/feature/taildrop/delete_test.go +++ b/feature/taildrop/delete_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/doc.go b/feature/taildrop/doc.go index 8980a217096c0..c394ebe82e18a 100644 --- a/feature/taildrop/doc.go +++ b/feature/taildrop/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package taildrop registers the taildrop (file sending) feature. diff --git a/feature/taildrop/ext.go b/feature/taildrop/ext.go index 6bdb375ccfe63..3a4ed456d2269 100644 --- a/feature/taildrop/ext.go +++ b/feature/taildrop/ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/fileops.go b/feature/taildrop/fileops.go index 14f76067a8094..beac0c375c574 100644 --- a/feature/taildrop/fileops.go +++ b/feature/taildrop/fileops.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/fileops_fs.go b/feature/taildrop/fileops_fs.go index 4fecbe4af6bbb..4a5b3e71a0f55 100644 --- a/feature/taildrop/fileops_fs.go +++ b/feature/taildrop/fileops_fs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/feature/taildrop/integration_test.go b/feature/taildrop/integration_test.go index 75896a95b2b54..ad66aa827faa0 100644 --- a/feature/taildrop/integration_test.go +++ b/feature/taildrop/integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop_test diff --git a/feature/taildrop/localapi.go b/feature/taildrop/localapi.go index 8a3904f9f0198..2af057ae8d88c 100644 --- a/feature/taildrop/localapi.go +++ b/feature/taildrop/localapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/paths.go b/feature/taildrop/paths.go index 79dc37d8f0699..76054ef4d58b3 100644 --- a/feature/taildrop/paths.go +++ b/feature/taildrop/paths.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/peerapi.go b/feature/taildrop/peerapi.go index b75ce33b864b4..8b92c8c85b0cd 100644 --- a/feature/taildrop/peerapi.go +++ b/feature/taildrop/peerapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/peerapi_test.go b/feature/taildrop/peerapi_test.go index 254d8794e8273..65f881be906b7 100644 --- a/feature/taildrop/peerapi_test.go +++ b/feature/taildrop/peerapi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/resume.go b/feature/taildrop/resume.go index 20ef527a6da55..208d61de3767a 100644 --- a/feature/taildrop/resume.go +++ b/feature/taildrop/resume.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/resume_test.go b/feature/taildrop/resume_test.go index 4e59d401dcc53..69dd547a123e0 100644 --- a/feature/taildrop/resume_test.go +++ b/feature/taildrop/resume_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/retrieve.go b/feature/taildrop/retrieve.go index e767bac324684..dd1b75b174db7 100644 --- a/feature/taildrop/retrieve.go +++ b/feature/taildrop/retrieve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/send.go b/feature/taildrop/send.go index 32ba5f6f0d644..668166d4409cf 100644 --- a/feature/taildrop/send.go +++ b/feature/taildrop/send.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/send_test.go b/feature/taildrop/send_test.go index 9ffa5fccc0a36..c1def2afdd106 100644 --- a/feature/taildrop/send_test.go +++ b/feature/taildrop/send_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/taildrop.go b/feature/taildrop/taildrop.go index 6c3deaed1b538..7042ca97aa7ef 100644 --- a/feature/taildrop/taildrop.go +++ b/feature/taildrop/taildrop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package taildrop contains the implementation of the Taildrop diff --git a/feature/taildrop/taildrop_test.go b/feature/taildrop/taildrop_test.go index 0d77273f0aab0..5c48412e044ba 100644 --- a/feature/taildrop/taildrop_test.go +++ b/feature/taildrop/taildrop_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/taildrop/target_test.go b/feature/taildrop/target_test.go index 57c96a77a4802..7948c488293bf 100644 --- a/feature/taildrop/target_test.go +++ b/feature/taildrop/target_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package taildrop diff --git a/feature/tap/tap_linux.go b/feature/tap/tap_linux.go index 53dcabc364d6b..e66f74ba4d1e4 100644 --- a/feature/tap/tap_linux.go +++ b/feature/tap/tap_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tap registers Tailscale's experimental (demo) Linux TAP (Layer 2) support. diff --git a/feature/tpm/attestation.go b/feature/tpm/attestation.go index 197a8d6b8798a..8955d5b98f605 100644 --- a/feature/tpm/attestation.go +++ b/feature/tpm/attestation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/attestation_test.go b/feature/tpm/attestation_test.go index e7ff729871230..06518b63a9059 100644 --- a/feature/tpm/attestation_test.go +++ b/feature/tpm/attestation_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/tpm.go b/feature/tpm/tpm.go index 8df269b95bc2e..e257aa7bc2174 100644 --- a/feature/tpm/tpm.go +++ b/feature/tpm/tpm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tpm implements support for TPM 2.0 devices. diff --git a/feature/tpm/tpm_linux.go b/feature/tpm/tpm_linux.go index 3f05c9a8c38ad..e7c214c0be6a5 100644 --- a/feature/tpm/tpm_linux.go +++ b/feature/tpm/tpm_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/tpm_other.go b/feature/tpm/tpm_other.go index 108b2c057e4bd..c34d8edb24617 100644 --- a/feature/tpm/tpm_other.go +++ b/feature/tpm/tpm_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !windows diff --git a/feature/tpm/tpm_test.go b/feature/tpm/tpm_test.go index afce570fc250d..02fb13f58c908 100644 --- a/feature/tpm/tpm_test.go +++ b/feature/tpm/tpm_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/tpm/tpm_windows.go b/feature/tpm/tpm_windows.go index 429d20cb879f7..168c7dca135a4 100644 --- a/feature/tpm/tpm_windows.go +++ b/feature/tpm/tpm_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tpm diff --git a/feature/useproxy/useproxy.go b/feature/useproxy/useproxy.go index a18e60577af85..96be23710caa1 100644 --- a/feature/useproxy/useproxy.go +++ b/feature/useproxy/useproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package useproxy registers support for using system proxies. diff --git a/feature/wakeonlan/wakeonlan.go b/feature/wakeonlan/wakeonlan.go index 96c424084dcc6..5a567ad44237d 100644 --- a/feature/wakeonlan/wakeonlan.go +++ b/feature/wakeonlan/wakeonlan.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wakeonlan registers the Wake-on-LAN feature. diff --git a/gokrazy/build.go b/gokrazy/build.go index c1ee1cbeb1974..ea54cc829d1f1 100644 --- a/gokrazy/build.go +++ b/gokrazy/build.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This program builds the Tailscale Appliance Gokrazy image. diff --git a/gokrazy/tidy-deps.go b/gokrazy/tidy-deps.go index 104156e473642..8f99f333302b2 100644 --- a/gokrazy/tidy-deps.go +++ b/gokrazy/tidy-deps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build for_go_mod_tidy diff --git a/gomod_test.go b/gomod_test.go index f984b5d6f27a5..a9a3c437d9341 100644 --- a/gomod_test.go +++ b/gomod_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot diff --git a/header.txt b/header.txt index 8111cb74e25c7..5a058566ba4b8 100644 --- a/header.txt +++ b/header.txt @@ -1,2 +1,2 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause \ No newline at end of file diff --git a/health/args.go b/health/args.go index 01a75aa2d79f3..e89f7676f0b64 100644 --- a/health/args.go +++ b/health/args.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/health/health.go b/health/health.go index f0f6a6ffbb162..0cfe570c4296a 100644 --- a/health/health.go +++ b/health/health.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package health is a registry for other packages to report & check diff --git a/health/health_test.go b/health/health_test.go index af7d06c8fe258..953c4dca26ea3 100644 --- a/health/health_test.go +++ b/health/health_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/health/healthmsg/healthmsg.go b/health/healthmsg/healthmsg.go index 5ea1c736d8851..3de885d53a61a 100644 --- a/health/healthmsg/healthmsg.go +++ b/health/healthmsg/healthmsg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package healthmsg contains some constants for health messages. diff --git a/health/state.go b/health/state.go index e6d937b6a8f02..91e30b75e796d 100644 --- a/health/state.go +++ b/health/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/health/usermetrics.go b/health/usermetrics.go index 110c57b57971c..b1af0c5852010 100644 --- a/health/usermetrics.go +++ b/health/usermetrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_health && !ts_omit_usermetrics diff --git a/health/usermetrics_omit.go b/health/usermetrics_omit.go index 9d5e35b861681..8a37d8ad8a311 100644 --- a/health/usermetrics_omit.go +++ b/health/usermetrics_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_health || ts_omit_usermetrics diff --git a/health/warnings.go b/health/warnings.go index a9c4b34a0f849..fc9099af2ecc7 100644 --- a/health/warnings.go +++ b/health/warnings.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package health diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 3e8f2f994791e..f91f52ec0c3d8 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package hostinfo answers questions about the host environment that Tailscale is diff --git a/hostinfo/hostinfo_container_linux_test.go b/hostinfo/hostinfo_container_linux_test.go index 594a5f5120a6a..0c14776e712b7 100644 --- a/hostinfo/hostinfo_container_linux_test.go +++ b/hostinfo/hostinfo_container_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && ts_package_container diff --git a/hostinfo/hostinfo_darwin.go b/hostinfo/hostinfo_darwin.go index 0b1774e7712d7..bce99d7003406 100644 --- a/hostinfo/hostinfo_darwin.go +++ b/hostinfo/hostinfo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/hostinfo/hostinfo_freebsd.go b/hostinfo/hostinfo_freebsd.go index 3661b13229ac5..3a214ed2463cb 100644 --- a/hostinfo/hostinfo_freebsd.go +++ b/hostinfo/hostinfo_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build freebsd diff --git a/hostinfo/hostinfo_linux.go b/hostinfo/hostinfo_linux.go index 66484a3588027..bb9a5c58c1bb0 100644 --- a/hostinfo/hostinfo_linux.go +++ b/hostinfo/hostinfo_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/hostinfo/hostinfo_linux_test.go b/hostinfo/hostinfo_linux_test.go index 0286fadf329ab..ebf285e94baeb 100644 --- a/hostinfo/hostinfo_linux_test.go +++ b/hostinfo/hostinfo_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_package_container diff --git a/hostinfo/hostinfo_plan9.go b/hostinfo/hostinfo_plan9.go index f9aa30e51769f..27a2543b38ff0 100644 --- a/hostinfo/hostinfo_plan9.go +++ b/hostinfo/hostinfo_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hostinfo diff --git a/hostinfo/hostinfo_test.go b/hostinfo/hostinfo_test.go index 15b6971b6ccd0..6508d5d995e3b 100644 --- a/hostinfo/hostinfo_test.go +++ b/hostinfo/hostinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hostinfo diff --git a/hostinfo/hostinfo_uname.go b/hostinfo/hostinfo_uname.go index 32b733a03bcb3..b358c0e2cb108 100644 --- a/hostinfo/hostinfo_uname.go +++ b/hostinfo/hostinfo_uname.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || freebsd || openbsd || darwin diff --git a/hostinfo/hostinfo_windows.go b/hostinfo/hostinfo_windows.go index f0422f5a001c5..5e0b340919e34 100644 --- a/hostinfo/hostinfo_windows.go +++ b/hostinfo/hostinfo_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hostinfo diff --git a/hostinfo/packagetype_container.go b/hostinfo/packagetype_container.go index 9bd14493cb34f..8db7df8616e7a 100644 --- a/hostinfo/packagetype_container.go +++ b/hostinfo/packagetype_container.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && ts_package_container diff --git a/internal/client/tailscale/identityfederation.go b/internal/client/tailscale/identityfederation.go index 3bb64b270a017..8c60c1c3c59a5 100644 --- a/internal/client/tailscale/identityfederation.go +++ b/internal/client/tailscale/identityfederation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/internal/client/tailscale/oauthkeys.go b/internal/client/tailscale/oauthkeys.go index 21102ce0b5fc8..43d5b0744fefe 100644 --- a/internal/client/tailscale/oauthkeys.go +++ b/internal/client/tailscale/oauthkeys.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/internal/client/tailscale/tailscale.go b/internal/client/tailscale/tailscale.go index 0e603bf792562..1e6b576fb0cc1 100644 --- a/internal/client/tailscale/tailscale.go +++ b/internal/client/tailscale/tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailscale provides a minimal control plane API client for internal diff --git a/internal/client/tailscale/vip_service.go b/internal/client/tailscale/vip_service.go index 48c59ce4569da..5c35a0c299621 100644 --- a/internal/client/tailscale/vip_service.go +++ b/internal/client/tailscale/vip_service.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscale diff --git a/internal/tooldeps/tooldeps.go b/internal/tooldeps/tooldeps.go index 22940c54d8c33..4ea1cf5084a55 100644 --- a/internal/tooldeps/tooldeps.go +++ b/internal/tooldeps/tooldeps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build for_go_mod_tidy_only diff --git a/ipn/auditlog/auditlog.go b/ipn/auditlog/auditlog.go index 0460bc4e2c655..cc6b43cbdba08 100644 --- a/ipn/auditlog/auditlog.go +++ b/ipn/auditlog/auditlog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package auditlog provides a mechanism for logging audit events. diff --git a/ipn/auditlog/auditlog_test.go b/ipn/auditlog/auditlog_test.go index 041cab3546bd0..5e499cc674fcb 100644 --- a/ipn/auditlog/auditlog_test.go +++ b/ipn/auditlog/auditlog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package auditlog diff --git a/ipn/auditlog/extension.go b/ipn/auditlog/extension.go index ae2a296b2c420..293d3742c97f9 100644 --- a/ipn/auditlog/extension.go +++ b/ipn/auditlog/extension.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package auditlog diff --git a/ipn/auditlog/store.go b/ipn/auditlog/store.go index 3b58ffa9318a2..07c9717726147 100644 --- a/ipn/auditlog/store.go +++ b/ipn/auditlog/store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package auditlog diff --git a/ipn/backend.go b/ipn/backend.go index b4ba958c5dd1e..3183c8b5e7a4e 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/backend_test.go b/ipn/backend_test.go index d72b966152ca3..bfb6d6bc41540 100644 --- a/ipn/backend_test.go +++ b/ipn/backend_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/conf.go b/ipn/conf.go index 2c9fb2fd15f9e..ef753a0b48544 100644 --- a/ipn/conf.go +++ b/ipn/conf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/conffile/cloudconf.go b/ipn/conffile/cloudconf.go index 4475a2d7b799e..ac9f640c34275 100644 --- a/ipn/conffile/cloudconf.go +++ b/ipn/conffile/cloudconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package conffile diff --git a/ipn/conffile/conffile.go b/ipn/conffile/conffile.go index 3a2aeffb3a0c6..c221a3d8c8d94 100644 --- a/ipn/conffile/conffile.go +++ b/ipn/conffile/conffile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package conffile contains code to load, manipulate, and access config file diff --git a/ipn/conffile/conffile_hujson.go b/ipn/conffile/conffile_hujson.go index 1e967f1bdcca2..4f4613dafac4f 100644 --- a/ipn/conffile/conffile_hujson.go +++ b/ipn/conffile/conffile_hujson.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !ts_omit_hujsonconf diff --git a/ipn/conffile/serveconf.go b/ipn/conffile/serveconf.go index bb63c1ac5571a..0d336029246f4 100644 --- a/ipn/conffile/serveconf.go +++ b/ipn/conffile/serveconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/desktop/doc.go b/ipn/desktop/doc.go index 64a332792a5a4..3aa60619d913a 100644 --- a/ipn/desktop/doc.go +++ b/ipn/desktop/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package desktop facilitates interaction with the desktop environment diff --git a/ipn/desktop/extension.go b/ipn/desktop/extension.go index 0277726714512..3e96a9c4c7929 100644 --- a/ipn/desktop/extension.go +++ b/ipn/desktop/extension.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Both the desktop session manager and multi-user support diff --git a/ipn/desktop/mksyscall.go b/ipn/desktop/mksyscall.go index b7af12366b64e..dd4e2acbf554d 100644 --- a/ipn/desktop/mksyscall.go +++ b/ipn/desktop/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/desktop/session.go b/ipn/desktop/session.go index c95378914321d..b5656d99bd4f2 100644 --- a/ipn/desktop/session.go +++ b/ipn/desktop/session.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/desktop/sessions.go b/ipn/desktop/sessions.go index 8bf7a75e2dc3a..e7026c1434f44 100644 --- a/ipn/desktop/sessions.go +++ b/ipn/desktop/sessions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/desktop/sessions_notwindows.go b/ipn/desktop/sessions_notwindows.go index da3230a456480..396d57eff39b2 100644 --- a/ipn/desktop/sessions_notwindows.go +++ b/ipn/desktop/sessions_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/ipn/desktop/sessions_windows.go b/ipn/desktop/sessions_windows.go index 83b884228c1f8..6128548a51216 100644 --- a/ipn/desktop/sessions_windows.go +++ b/ipn/desktop/sessions_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package desktop diff --git a/ipn/doc.go b/ipn/doc.go index c98c7e8b3599f..b39310cf51b4b 100644 --- a/ipn/doc.go +++ b/ipn/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run tailscale.com/cmd/viewer -type=LoginProfile,Prefs,ServeConfig,ServiceConfig,TCPPortHandler,HTTPHandler,WebServerConfig diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index 4bf78b40b022b..94aebefdfd73d 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/ipn/ipn_test.go b/ipn/ipn_test.go index cba70bccd658a..2689faa8e37cd 100644 --- a/ipn/ipn_test.go +++ b/ipn/ipn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index 4157ec76e61a8..90560cec0e195 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/ipn/ipnauth/access.go b/ipn/ipnauth/access.go index 74c66392221b2..3d320585e9637 100644 --- a/ipn/ipnauth/access.go +++ b/ipn/ipnauth/access.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/actor.go b/ipn/ipnauth/actor.go index 108bdd341ae6a..0fa4735f9fe69 100644 --- a/ipn/ipnauth/actor.go +++ b/ipn/ipnauth/actor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/actor_windows.go b/ipn/ipnauth/actor_windows.go index 90d3bdd362bbf..0345bc5fdb2fe 100644 --- a/ipn/ipnauth/actor_windows.go +++ b/ipn/ipnauth/actor_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/ipnauth.go b/ipn/ipnauth/ipnauth.go index 497f30f8c198e..d48ec1f140c58 100644 --- a/ipn/ipnauth/ipnauth.go +++ b/ipn/ipnauth/ipnauth.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnauth controls access to the LocalAPI. diff --git a/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go b/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go index defe7d89c409b..fce0143e43c38 100644 --- a/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go +++ b/ipn/ipnauth/ipnauth_omit_unixsocketidentity.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && ts_omit_unixsocketidentity diff --git a/ipn/ipnauth/ipnauth_unix_creds.go b/ipn/ipnauth/ipnauth_unix_creds.go index 89a9ceaa99388..d031d3b6e09c2 100644 --- a/ipn/ipnauth/ipnauth_unix_creds.go +++ b/ipn/ipnauth/ipnauth_unix_creds.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !ts_omit_unixsocketidentity diff --git a/ipn/ipnauth/ipnauth_windows.go b/ipn/ipnauth/ipnauth_windows.go index e3ea448a855e5..b4591ea3a03f8 100644 --- a/ipn/ipnauth/ipnauth_windows.go +++ b/ipn/ipnauth/ipnauth_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/policy.go b/ipn/ipnauth/policy.go index eeee324352387..692005e5516d0 100644 --- a/ipn/ipnauth/policy.go +++ b/ipn/ipnauth/policy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/self.go b/ipn/ipnauth/self.go index adee0696458d6..0379aede4076c 100644 --- a/ipn/ipnauth/self.go +++ b/ipn/ipnauth/self.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnauth/test_actor.go b/ipn/ipnauth/test_actor.go index 80c5fcc8a6328..667fb84137ca6 100644 --- a/ipn/ipnauth/test_actor.go +++ b/ipn/ipnauth/test_actor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnauth diff --git a/ipn/ipnext/ipnext.go b/ipn/ipnext/ipnext.go index fc93cc8760a0b..275e28c85bddc 100644 --- a/ipn/ipnext/ipnext.go +++ b/ipn/ipnext/ipnext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnext defines types and interfaces used for extending the core LocalBackend diff --git a/ipn/ipnlocal/breaktcp_darwin.go b/ipn/ipnlocal/breaktcp_darwin.go index 13566198ce9fc..732c375f7f5af 100644 --- a/ipn/ipnlocal/breaktcp_darwin.go +++ b/ipn/ipnlocal/breaktcp_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/breaktcp_linux.go b/ipn/ipnlocal/breaktcp_linux.go index b82f6521246f0..0ba9ed6d78f19 100644 --- a/ipn/ipnlocal/breaktcp_linux.go +++ b/ipn/ipnlocal/breaktcp_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/bus.go b/ipn/ipnlocal/bus.go index 910e4e774c958..6061f7223988d 100644 --- a/ipn/ipnlocal/bus.go +++ b/ipn/ipnlocal/bus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/bus_test.go b/ipn/ipnlocal/bus_test.go index 5c75ac54d688d..27ffebcdd570e 100644 --- a/ipn/ipnlocal/bus_test.go +++ b/ipn/ipnlocal/bus_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index b5e722b97c4a4..ccce2a65d99e6 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/c2n_pprof.go b/ipn/ipnlocal/c2n_pprof.go index 13237cc4fad2f..783ba16ff93c5 100644 --- a/ipn/ipnlocal/c2n_pprof.go +++ b/ipn/ipnlocal/c2n_pprof.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !wasm && !ts_omit_debug diff --git a/ipn/ipnlocal/c2n_test.go b/ipn/ipnlocal/c2n_test.go index 86cc6a5490865..810d6765b45e2 100644 --- a/ipn/ipnlocal/c2n_test.go +++ b/ipn/ipnlocal/c2n_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/captiveportal.go b/ipn/ipnlocal/captiveportal.go index 14f8b799eb6dd..ae310c7cc785a 100644 --- a/ipn/ipnlocal/captiveportal.go +++ b/ipn/ipnlocal/captiveportal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_captiveportal diff --git a/ipn/ipnlocal/cert.go b/ipn/ipnlocal/cert.go index b389c93e7e971..764634d30c276 100644 --- a/ipn/ipnlocal/cert.go +++ b/ipn/ipnlocal/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_acme diff --git a/ipn/ipnlocal/cert_disabled.go b/ipn/ipnlocal/cert_disabled.go index 17d446c11af39..0caab6bc32b27 100644 --- a/ipn/ipnlocal/cert_disabled.go +++ b/ipn/ipnlocal/cert_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js || ts_omit_acme diff --git a/ipn/ipnlocal/cert_test.go b/ipn/ipnlocal/cert_test.go index e2398f670b5ad..ec7be570c78f7 100644 --- a/ipn/ipnlocal/cert_test.go +++ b/ipn/ipnlocal/cert_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !js diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index e23d8a057546f..52cc533ff29f6 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/drive.go b/ipn/ipnlocal/drive.go index 456cd45441ba9..485114eae9d27 100644 --- a/ipn/ipnlocal/drive.go +++ b/ipn/ipnlocal/drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/ipnlocal/drive_test.go b/ipn/ipnlocal/drive_test.go index 323c3821499ed..aca05432b8276 100644 --- a/ipn/ipnlocal/drive_test.go +++ b/ipn/ipnlocal/drive_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/ipnlocal/drive_tomove.go b/ipn/ipnlocal/drive_tomove.go index 290fe097022fd..ccea48f7a5106 100644 --- a/ipn/ipnlocal/drive_tomove.go +++ b/ipn/ipnlocal/drive_tomove.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This is the Taildrive stuff that should ideally be registered in init only when diff --git a/ipn/ipnlocal/expiry.go b/ipn/ipnlocal/expiry.go index 8ea63d21a4fb0..461dce2fda055 100644 --- a/ipn/ipnlocal/expiry.go +++ b/ipn/ipnlocal/expiry.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/expiry_test.go b/ipn/ipnlocal/expiry_test.go index 2c646ca724efd..1d85d81d1819d 100644 --- a/ipn/ipnlocal/expiry_test.go +++ b/ipn/ipnlocal/expiry_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/extension_host.go b/ipn/ipnlocal/extension_host.go index ca802ab89f747..125a2329447a3 100644 --- a/ipn/ipnlocal/extension_host.go +++ b/ipn/ipnlocal/extension_host.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/extension_host_test.go b/ipn/ipnlocal/extension_host_test.go index f5c081a5bdb3e..3bd302aeab93d 100644 --- a/ipn/ipnlocal/extension_host_test.go +++ b/ipn/ipnlocal/extension_host_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/hwattest.go b/ipn/ipnlocal/hwattest.go index 2c93cad4c97ff..07c09dc7fe043 100644 --- a/ipn/ipnlocal/hwattest.go +++ b/ipn/ipnlocal/hwattest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tpm diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index bbd2aa2e0e425..0fc26cd041cb6 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnlocal is the heart of the Tailscale node agent that controls diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 23a3161ca47f1..53607cfaaa737 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/loglines_test.go b/ipn/ipnlocal/loglines_test.go index d831aa8b075dc..733c7381b2016 100644 --- a/ipn/ipnlocal/loglines_test.go +++ b/ipn/ipnlocal/loglines_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/netstack.go b/ipn/ipnlocal/netstack.go index f7ffd03058879..b331d93e329de 100644 --- a/ipn/ipnlocal/netstack.go +++ b/ipn/ipnlocal/netstack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/ipn/ipnlocal/network-lock.go b/ipn/ipnlocal/network-lock.go index 246b26409b2b5..242fec0287c65 100644 --- a/ipn/ipnlocal/network-lock.go +++ b/ipn/ipnlocal/network-lock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/ipn/ipnlocal/network-lock_test.go b/ipn/ipnlocal/network-lock_test.go index e5df38bdb6d76..8aa0a877b8dd3 100644 --- a/ipn/ipnlocal/network-lock_test.go +++ b/ipn/ipnlocal/network-lock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index efef57ea492e7..a252f20fe2074 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/node_backend_test.go b/ipn/ipnlocal/node_backend_test.go index f6698bd4bc920..f1f38dae6aee1 100644 --- a/ipn/ipnlocal/node_backend_test.go +++ b/ipn/ipnlocal/node_backend_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 318d9bf6bb72f..aa4c1ef527c6c 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/peerapi_drive.go b/ipn/ipnlocal/peerapi_drive.go index 8dffacd9a2513..d42843577059b 100644 --- a/ipn/ipnlocal/peerapi_drive.go +++ b/ipn/ipnlocal/peerapi_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/ipnlocal/peerapi_macios_ext.go b/ipn/ipnlocal/peerapi_macios_ext.go index f23b877bd663c..70c1fb850e249 100644 --- a/ipn/ipnlocal/peerapi_macios_ext.go +++ b/ipn/ipnlocal/peerapi_macios_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_macext && (darwin || ios) diff --git a/ipn/ipnlocal/peerapi_test.go b/ipn/ipnlocal/peerapi_test.go index 3c9f57f1fcf6a..63abf089c2abc 100644 --- a/ipn/ipnlocal/peerapi_test.go +++ b/ipn/ipnlocal/peerapi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/prefs_metrics.go b/ipn/ipnlocal/prefs_metrics.go index 34c5f5504fac4..7e7a2a5e3a1d0 100644 --- a/ipn/ipnlocal/prefs_metrics.go +++ b/ipn/ipnlocal/prefs_metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/profiles.go b/ipn/ipnlocal/profiles.go index 7080e3c3edd50..430fa63152a77 100644 --- a/ipn/ipnlocal/profiles.go +++ b/ipn/ipnlocal/profiles.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/profiles_notwindows.go b/ipn/ipnlocal/profiles_notwindows.go index 0ca8f439cf9f4..389dedc9e7015 100644 --- a/ipn/ipnlocal/profiles_notwindows.go +++ b/ipn/ipnlocal/profiles_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/ipn/ipnlocal/profiles_test.go b/ipn/ipnlocal/profiles_test.go index 6be7f0e53f59e..ec92673e50b29 100644 --- a/ipn/ipnlocal/profiles_test.go +++ b/ipn/ipnlocal/profiles_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/profiles_windows.go b/ipn/ipnlocal/profiles_windows.go index c4beb22f9d42f..a0b5bbfdd49fb 100644 --- a/ipn/ipnlocal/profiles_windows.go +++ b/ipn/ipnlocal/profiles_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index a857147e1adab..d25251accd797 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/ipnlocal/serve_disabled.go b/ipn/ipnlocal/serve_disabled.go index a97112941d844..e9d2678a80d8b 100644 --- a/ipn/ipnlocal/serve_disabled.go +++ b/ipn/ipnlocal/serve_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_serve diff --git a/ipn/ipnlocal/serve_test.go b/ipn/ipnlocal/serve_test.go index 0892545cceec8..b3f48b105c8f7 100644 --- a/ipn/ipnlocal/serve_test.go +++ b/ipn/ipnlocal/serve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/ipnlocal/serve_unix_test.go b/ipn/ipnlocal/serve_unix_test.go index e57aafab212ae..2d1f0a1e34af8 100644 --- a/ipn/ipnlocal/serve_unix_test.go +++ b/ipn/ipnlocal/serve_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/ipn/ipnlocal/ssh.go b/ipn/ipnlocal/ssh.go index e2c2f50671386..52b3066584e08 100644 --- a/ipn/ipnlocal/ssh.go +++ b/ipn/ipnlocal/ssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ((linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9) && !ts_omit_ssh diff --git a/ipn/ipnlocal/ssh_stub.go b/ipn/ipnlocal/ssh_stub.go index 6b2e36015c2d7..9a997c9143f7b 100644 --- a/ipn/ipnlocal/ssh_stub.go +++ b/ipn/ipnlocal/ssh_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_ssh || ios || android || (!linux && !darwin && !freebsd && !openbsd && !plan9) diff --git a/ipn/ipnlocal/ssh_test.go b/ipn/ipnlocal/ssh_test.go index b24cd6732f605..bb293d10ac4d6 100644 --- a/ipn/ipnlocal/ssh_test.go +++ b/ipn/ipnlocal/ssh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || (darwin && !ios) diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 27d53fe01b599..97c2c4d8f9daf 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnlocal diff --git a/ipn/ipnlocal/tailnetlock_disabled.go b/ipn/ipnlocal/tailnetlock_disabled.go index 85cf4bd3f4ea5..0668437b163c6 100644 --- a/ipn/ipnlocal/tailnetlock_disabled.go +++ b/ipn/ipnlocal/tailnetlock_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_tailnetlock diff --git a/ipn/ipnlocal/web_client.go b/ipn/ipnlocal/web_client.go index a3c9387e46fce..37dba93d0a49b 100644 --- a/ipn/ipnlocal/web_client.go +++ b/ipn/ipnlocal/web_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !ts_omit_webclient diff --git a/ipn/ipnlocal/web_client_stub.go b/ipn/ipnlocal/web_client_stub.go index 787867b4f450e..02798b4f709e9 100644 --- a/ipn/ipnlocal/web_client_stub.go +++ b/ipn/ipnlocal/web_client_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || ts_omit_webclient diff --git a/ipn/ipnserver/actor.go b/ipn/ipnserver/actor.go index 628e3c37cfc0b..c9a4c6e891f86 100644 --- a/ipn/ipnserver/actor.go +++ b/ipn/ipnserver/actor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnserver/proxyconnect.go b/ipn/ipnserver/proxyconnect.go index 7d41273bdc52a..c8348a76c2b6a 100644 --- a/ipn/ipnserver/proxyconnect.go +++ b/ipn/ipnserver/proxyconnect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/ipn/ipnserver/proxyconnect_js.go b/ipn/ipnserver/proxyconnect_js.go index 368221e2269c8..b4a6aef3a43bd 100644 --- a/ipn/ipnserver/proxyconnect_js.go +++ b/ipn/ipnserver/proxyconnect_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index d473252e134a8..1f8abf0e20128 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnserver runs the LocalAPI HTTP server that communicates diff --git a/ipn/ipnserver/server_fortest.go b/ipn/ipnserver/server_fortest.go index 9aab3b276d31f..70148f030e6b0 100644 --- a/ipn/ipnserver/server_fortest.go +++ b/ipn/ipnserver/server_fortest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnserver/server_test.go b/ipn/ipnserver/server_test.go index 713db9e50085e..9aa9c4c015f23 100644 --- a/ipn/ipnserver/server_test.go +++ b/ipn/ipnserver/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver_test diff --git a/ipn/ipnserver/waiterset_test.go b/ipn/ipnserver/waiterset_test.go index b7d5ea144c408..b8a143212c1a3 100644 --- a/ipn/ipnserver/waiterset_test.go +++ b/ipn/ipnserver/waiterset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipnserver diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 213090b559692..4d219d131d528 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipnstate captures the entire state of the Tailscale network. diff --git a/ipn/ipnstate/ipnstate_clone.go b/ipn/ipnstate/ipnstate_clone.go index 20ae43c5fb73e..9af066832b27f 100644 --- a/ipn/ipnstate/ipnstate_clone.go +++ b/ipn/ipnstate/ipnstate_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/ipn/lapitest/backend.go b/ipn/lapitest/backend.go index 7a1c276a7b229..b622d098f4f55 100644 --- a/ipn/lapitest/backend.go +++ b/ipn/lapitest/backend.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/client.go b/ipn/lapitest/client.go index 6d22e938b210e..c2c07dfbaf689 100644 --- a/ipn/lapitest/client.go +++ b/ipn/lapitest/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/example_test.go b/ipn/lapitest/example_test.go index 57479199a8123..648c97880cdb8 100644 --- a/ipn/lapitest/example_test.go +++ b/ipn/lapitest/example_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/opts.go b/ipn/lapitest/opts.go index 6eb1594da2607..5ed2f97573b90 100644 --- a/ipn/lapitest/opts.go +++ b/ipn/lapitest/opts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lapitest diff --git a/ipn/lapitest/server.go b/ipn/lapitest/server.go index 457a338ab9f5a..8fd3c8cdd361f 100644 --- a/ipn/lapitest/server.go +++ b/ipn/lapitest/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lapitest provides utilities for black-box testing of LocalAPI ([ipnserver]). diff --git a/ipn/localapi/cert.go b/ipn/localapi/cert.go index 2313631cc3229..cd8afa03bf599 100644 --- a/ipn/localapi/cert.go +++ b/ipn/localapi/cert.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !js && !ts_omit_acme diff --git a/ipn/localapi/debug.go b/ipn/localapi/debug.go index ae9cb01e02fe9..fe936db6ab78d 100644 --- a/ipn/localapi/debug.go +++ b/ipn/localapi/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/ipn/localapi/debugderp.go b/ipn/localapi/debugderp.go index 3edbc0856c8a3..52987ee0a18e6 100644 --- a/ipn/localapi/debugderp.go +++ b/ipn/localapi/debugderp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/ipn/localapi/disabled_stubs.go b/ipn/localapi/disabled_stubs.go index c744f34d5f5c5..0d16de880cf41 100644 --- a/ipn/localapi/disabled_stubs.go +++ b/ipn/localapi/disabled_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || js diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 4648b2c49e849..248c0377ec968 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package localapi contains the HTTP server handlers for tailscaled's API server. diff --git a/ipn/localapi/localapi_drive.go b/ipn/localapi/localapi_drive.go index eb765ec2eabba..e1dee441e0fde 100644 --- a/ipn/localapi/localapi_drive.go +++ b/ipn/localapi/localapi_drive.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_drive diff --git a/ipn/localapi/localapi_test.go b/ipn/localapi/localapi_test.go index 5d228ffd69343..47e33457188ab 100644 --- a/ipn/localapi/localapi_test.go +++ b/ipn/localapi/localapi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package localapi diff --git a/ipn/localapi/pprof.go b/ipn/localapi/pprof.go index 9476f721fb1ce..fabdb18e24d87 100644 --- a/ipn/localapi/pprof.go +++ b/ipn/localapi/pprof.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !js && !ts_omit_debug diff --git a/ipn/localapi/serve.go b/ipn/localapi/serve.go index efbbde06ff954..1f677f7ab3a05 100644 --- a/ipn/localapi/serve.go +++ b/ipn/localapi/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_serve diff --git a/ipn/localapi/syspolicy_api.go b/ipn/localapi/syspolicy_api.go index edb82e042f2ce..9962f342bd884 100644 --- a/ipn/localapi/syspolicy_api.go +++ b/ipn/localapi/syspolicy_api.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/ipn/localapi/tailnetlock.go b/ipn/localapi/tailnetlock.go index e5f999bb8847e..445f705056cf7 100644 --- a/ipn/localapi/tailnetlock.go +++ b/ipn/localapi/tailnetlock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/ipn/policy/policy.go b/ipn/policy/policy.go index 494a0dc408819..bbc78a254e141 100644 --- a/ipn/policy/policy.go +++ b/ipn/policy/policy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policy contains various policy decisions that need to be diff --git a/ipn/prefs.go b/ipn/prefs.go index 9f98465d2d883..72e0cf8b78424 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index aa152843a5af9..347a91e50739c 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/serve.go b/ipn/serve.go index 240308f290edc..911b408b65026 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/serve_expand_test.go b/ipn/serve_expand_test.go index b977238fe32ff..33f808d62ec05 100644 --- a/ipn/serve_expand_test.go +++ b/ipn/serve_expand_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/serve_test.go b/ipn/serve_test.go index 5e0f4a43a38e7..8be39a1ed81ce 100644 --- a/ipn/serve_test.go +++ b/ipn/serve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/store.go b/ipn/store.go index 2034ae09a92f9..1bd3e5a3b4e6b 100644 --- a/ipn/store.go +++ b/ipn/store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/ipn/store/awsstore/store_aws.go b/ipn/store/awsstore/store_aws.go index 78b72d0bc8f45..e06e00eb3d3dd 100644 --- a/ipn/store/awsstore/store_aws.go +++ b/ipn/store/awsstore/store_aws.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_aws diff --git a/ipn/store/awsstore/store_aws_test.go b/ipn/store/awsstore/store_aws_test.go index 3cc23e48d4b12..ba2274bf1c09e 100644 --- a/ipn/store/awsstore/store_aws_test.go +++ b/ipn/store/awsstore/store_aws_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_aws diff --git a/ipn/store/kubestore/store_kube.go b/ipn/store/kubestore/store_kube.go index 5fbd795c2174d..f7d1b90cd1e2c 100644 --- a/ipn/store/kubestore/store_kube.go +++ b/ipn/store/kubestore/store_kube.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubestore contains an ipn.StateStore implementation using Kubernetes Secrets. diff --git a/ipn/store/kubestore/store_kube_test.go b/ipn/store/kubestore/store_kube_test.go index aea39d3bb51f8..1e6f711d686e2 100644 --- a/ipn/store/kubestore/store_kube_test.go +++ b/ipn/store/kubestore/store_kube_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubestore diff --git a/ipn/store/mem/store_mem.go b/ipn/store/mem/store_mem.go index 6f474ce993b43..247714c9a2b47 100644 --- a/ipn/store/mem/store_mem.go +++ b/ipn/store/mem/store_mem.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mem provides an in-memory ipn.StateStore implementation. diff --git a/ipn/store/stores.go b/ipn/store/stores.go index bf175da41d8aa..fd51f8c38540d 100644 --- a/ipn/store/stores.go +++ b/ipn/store/stores.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package store provides various implementation of ipn.StateStore. diff --git a/ipn/store/stores_test.go b/ipn/store/stores_test.go index 1f0fc0fef1bff..345b1c10376d9 100644 --- a/ipn/store/stores_test.go +++ b/ipn/store/stores_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package store diff --git a/ipn/store_test.go b/ipn/store_test.go index 4dd7321b9048d..fc42fdbec3610 100644 --- a/ipn/store_test.go +++ b/ipn/store_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipn diff --git a/jsondb/db.go b/jsondb/db.go index 68bb05af45e8e..c45ab4cd39913 100644 --- a/jsondb/db.go +++ b/jsondb/db.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsondb provides a trivial "database": a Go object saved to diff --git a/jsondb/db_test.go b/jsondb/db_test.go index 655754f38e1a9..18797ebd174e6 100644 --- a/jsondb/db_test.go +++ b/jsondb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsondb diff --git a/k8s-operator/api-docs-config.yaml b/k8s-operator/api-docs-config.yaml index 214171ca35c0d..0bfb32be92f27 100644 --- a/k8s-operator/api-docs-config.yaml +++ b/k8s-operator/api-docs-config.yaml @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause processor: {} diff --git a/k8s-operator/api-proxy/doc.go b/k8s-operator/api-proxy/doc.go index 89d8909595fd3..a685b9907d710 100644 --- a/k8s-operator/api-proxy/doc.go +++ b/k8s-operator/api-proxy/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/api-proxy/proxy.go b/k8s-operator/api-proxy/proxy.go index f5f1da80f1a05..c4c651b1fb029 100644 --- a/k8s-operator/api-proxy/proxy.go +++ b/k8s-operator/api-proxy/proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/api-proxy/proxy_events_test.go b/k8s-operator/api-proxy/proxy_events_test.go index e35be33a0e734..1426f170c5207 100644 --- a/k8s-operator/api-proxy/proxy_events_test.go +++ b/k8s-operator/api-proxy/proxy_events_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/api-proxy/proxy_test.go b/k8s-operator/api-proxy/proxy_test.go index 14e6554236234..5d1606d764e45 100644 --- a/k8s-operator/api-proxy/proxy_test.go +++ b/k8s-operator/api-proxy/proxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/doc.go b/k8s-operator/apis/doc.go index 0a1145ca8a0dc..3fea3d6e80e12 100644 --- a/k8s-operator/apis/doc.go +++ b/k8s-operator/apis/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/doc.go b/k8s-operator/apis/v1alpha1/doc.go index 467e73e17cb21..0fc5bb8ece622 100644 --- a/k8s-operator/apis/v1alpha1/doc.go +++ b/k8s-operator/apis/v1alpha1/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/register.go b/k8s-operator/apis/v1alpha1/register.go index 993a119fad2eb..ebdd2bae1f3ea 100644 --- a/k8s-operator/apis/v1alpha1/register.go +++ b/k8s-operator/apis/v1alpha1/register.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_connector.go b/k8s-operator/apis/v1alpha1/types_connector.go index ebedea18f0e98..af2df58af2fd9 100644 --- a/k8s-operator/apis/v1alpha1/types_connector.go +++ b/k8s-operator/apis/v1alpha1/types_connector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_proxyclass.go b/k8s-operator/apis/v1alpha1/types_proxyclass.go index 670df3b95097e..3c2fe76868ae3 100644 --- a/k8s-operator/apis/v1alpha1/types_proxyclass.go +++ b/k8s-operator/apis/v1alpha1/types_proxyclass.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_proxygroup.go b/k8s-operator/apis/v1alpha1/types_proxygroup.go index 8cbcc2d196e51..00c196628ab86 100644 --- a/k8s-operator/apis/v1alpha1/types_proxygroup.go +++ b/k8s-operator/apis/v1alpha1/types_proxygroup.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_recorder.go b/k8s-operator/apis/v1alpha1/types_recorder.go index d5a22e82c2dbc..6cc5e3dd572c9 100644 --- a/k8s-operator/apis/v1alpha1/types_recorder.go +++ b/k8s-operator/apis/v1alpha1/types_recorder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_tailnet.go b/k8s-operator/apis/v1alpha1/types_tailnet.go index a3a17374be5cd..b11b2cf17658c 100644 --- a/k8s-operator/apis/v1alpha1/types_tailnet.go +++ b/k8s-operator/apis/v1alpha1/types_tailnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go b/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go index 7991003b82dff..c1a2e7906fcd8 100644 --- a/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go +++ b/k8s-operator/apis/v1alpha1/types_tsdnsconfig.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go index 4743a5156c16b..1081c162c81bc 100644 --- a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go +++ b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go @@ -1,6 +1,6 @@ //go:build !ignore_autogenerated && !plan9 -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by controller-gen. DO NOT EDIT. diff --git a/k8s-operator/conditions.go b/k8s-operator/conditions.go index bce6e39bdb142..89b83dd5f83cc 100644 --- a/k8s-operator/conditions.go +++ b/k8s-operator/conditions.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/conditions_test.go b/k8s-operator/conditions_test.go index 7eb65257d3414..940a300d88ba8 100644 --- a/k8s-operator/conditions_test.go +++ b/k8s-operator/conditions_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/reconciler/reconciler.go b/k8s-operator/reconciler/reconciler.go index 2751790964577..fcad7201e31e4 100644 --- a/k8s-operator/reconciler/reconciler.go +++ b/k8s-operator/reconciler/reconciler.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/reconciler/reconciler_test.go b/k8s-operator/reconciler/reconciler_test.go index 573cd4d9db8da..2db77e7aad419 100644 --- a/k8s-operator/reconciler/reconciler_test.go +++ b/k8s-operator/reconciler/reconciler_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/reconciler/tailnet/mocks_test.go b/k8s-operator/reconciler/tailnet/mocks_test.go index 7f3f2ddb91085..4342556885013 100644 --- a/k8s-operator/reconciler/tailnet/mocks_test.go +++ b/k8s-operator/reconciler/tailnet/mocks_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/reconciler/tailnet/tailnet.go b/k8s-operator/reconciler/tailnet/tailnet.go index fe445a36323be..2e7004b698c93 100644 --- a/k8s-operator/reconciler/tailnet/tailnet.go +++ b/k8s-operator/reconciler/tailnet/tailnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/reconciler/tailnet/tailnet_test.go b/k8s-operator/reconciler/tailnet/tailnet_test.go index 471752b86080d..0ed2ca598d720 100644 --- a/k8s-operator/reconciler/tailnet/tailnet_test.go +++ b/k8s-operator/reconciler/tailnet/tailnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/fakes/fakes.go b/k8s-operator/sessionrecording/fakes/fakes.go index 94853df195f7c..26f57e4eb4b69 100644 --- a/k8s-operator/sessionrecording/fakes/fakes.go +++ b/k8s-operator/sessionrecording/fakes/fakes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/hijacker.go b/k8s-operator/sessionrecording/hijacker.go index 7345a407c8faa..cdbeeddb43ac8 100644 --- a/k8s-operator/sessionrecording/hijacker.go +++ b/k8s-operator/sessionrecording/hijacker.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/hijacker_test.go b/k8s-operator/sessionrecording/hijacker_test.go index fb45820a71b86..ac243c2e8bc82 100644 --- a/k8s-operator/sessionrecording/hijacker_test.go +++ b/k8s-operator/sessionrecording/hijacker_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/conn.go b/k8s-operator/sessionrecording/spdy/conn.go index 9fefca11fc2b8..682003055acb8 100644 --- a/k8s-operator/sessionrecording/spdy/conn.go +++ b/k8s-operator/sessionrecording/spdy/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/conn_test.go b/k8s-operator/sessionrecording/spdy/conn_test.go index 3c1cb8427d822..232fa8e2c2227 100644 --- a/k8s-operator/sessionrecording/spdy/conn_test.go +++ b/k8s-operator/sessionrecording/spdy/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/frame.go b/k8s-operator/sessionrecording/spdy/frame.go index 54b29d33a9622..7087db3c32166 100644 --- a/k8s-operator/sessionrecording/spdy/frame.go +++ b/k8s-operator/sessionrecording/spdy/frame.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/frame_test.go b/k8s-operator/sessionrecording/spdy/frame_test.go index 4896cdcbf78a5..1b7e54f4cc1fb 100644 --- a/k8s-operator/sessionrecording/spdy/frame_test.go +++ b/k8s-operator/sessionrecording/spdy/frame_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/spdy/zlib-reader.go b/k8s-operator/sessionrecording/spdy/zlib-reader.go index 1eb654be35632..2b63f7e2b592b 100644 --- a/k8s-operator/sessionrecording/spdy/zlib-reader.go +++ b/k8s-operator/sessionrecording/spdy/zlib-reader.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go b/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go index a5bdf7ddddeeb..40a96d6d29ac7 100644 --- a/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go +++ b/k8s-operator/sessionrecording/tsrecorder/tsrecorder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/conn.go b/k8s-operator/sessionrecording/ws/conn.go index a618f85fb7822..4762630ca7522 100644 --- a/k8s-operator/sessionrecording/ws/conn.go +++ b/k8s-operator/sessionrecording/ws/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/conn_test.go b/k8s-operator/sessionrecording/ws/conn_test.go index 87205c4e6f610..0b4353698cd9f 100644 --- a/k8s-operator/sessionrecording/ws/conn_test.go +++ b/k8s-operator/sessionrecording/ws/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/message.go b/k8s-operator/sessionrecording/ws/message.go index 35667ae21a5d0..36359996a7c12 100644 --- a/k8s-operator/sessionrecording/ws/message.go +++ b/k8s-operator/sessionrecording/ws/message.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/sessionrecording/ws/message_test.go b/k8s-operator/sessionrecording/ws/message_test.go index f634f86dc55c2..07d55ce4dcbd5 100644 --- a/k8s-operator/sessionrecording/ws/message_test.go +++ b/k8s-operator/sessionrecording/ws/message_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/k8s-operator/utils.go b/k8s-operator/utils.go index 2acbf338dbdd3..043a9d7b54c7a 100644 --- a/k8s-operator/utils.go +++ b/k8s-operator/utils.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/certs/certs.go b/kube/certs/certs.go index 8e2e5fb43a8ac..dd8fd7d799ac6 100644 --- a/kube/certs/certs.go +++ b/kube/certs/certs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package certs implements logic to help multiple Kubernetes replicas share TLS diff --git a/kube/certs/certs_test.go b/kube/certs/certs_test.go index 8434f21ae6976..91196f5760f72 100644 --- a/kube/certs/certs_test.go +++ b/kube/certs/certs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package certs diff --git a/kube/egressservices/egressservices.go b/kube/egressservices/egressservices.go index 56c874f31dbb1..3828760afccf4 100644 --- a/kube/egressservices/egressservices.go +++ b/kube/egressservices/egressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package egressservices contains shared types for exposing tailnet services to diff --git a/kube/egressservices/egressservices_test.go b/kube/egressservices/egressservices_test.go index 806ad91be61cd..27d818cab970a 100644 --- a/kube/egressservices/egressservices_test.go +++ b/kube/egressservices/egressservices_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package egressservices diff --git a/kube/health/healthz.go b/kube/health/healthz.go index c8cfcc7ec01b4..53888922bb940 100644 --- a/kube/health/healthz.go +++ b/kube/health/healthz.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/ingressservices/ingressservices.go b/kube/ingressservices/ingressservices.go index f79410761af02..440582666a0a1 100644 --- a/kube/ingressservices/ingressservices.go +++ b/kube/ingressservices/ingressservices.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ingressservices contains shared types for exposing Kubernetes Services to tailnet. diff --git a/kube/k8s-proxy/conf/conf.go b/kube/k8s-proxy/conf/conf.go index 5294952438896..62ef67feecb16 100644 --- a/kube/k8s-proxy/conf/conf.go +++ b/kube/k8s-proxy/conf/conf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/k8s-proxy/conf/conf_test.go b/kube/k8s-proxy/conf/conf_test.go index 3082be1ba9dcd..4034bf3cb7752 100644 --- a/kube/k8s-proxy/conf/conf_test.go +++ b/kube/k8s-proxy/conf/conf_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/kubeapi/api.go b/kube/kubeapi/api.go index e62bd6e2b2eb1..c3ed1a3b7e917 100644 --- a/kube/kubeapi/api.go +++ b/kube/kubeapi/api.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubeapi contains Kubernetes API types for internal consumption. diff --git a/kube/kubeclient/client.go b/kube/kubeclient/client.go index 0ed960f4ddcd4..5f5ab138eb65e 100644 --- a/kube/kubeclient/client.go +++ b/kube/kubeclient/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubeclient provides a client to interact with Kubernetes. diff --git a/kube/kubeclient/client_test.go b/kube/kubeclient/client_test.go index 8599e7e3c19e2..9778c7e6fa1ad 100644 --- a/kube/kubeclient/client_test.go +++ b/kube/kubeclient/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubeclient diff --git a/kube/kubeclient/fake_client.go b/kube/kubeclient/fake_client.go index 15ebb5f443f2a..7fb102764a939 100644 --- a/kube/kubeclient/fake_client.go +++ b/kube/kubeclient/fake_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubeclient diff --git a/kube/kubetypes/grants.go b/kube/kubetypes/grants.go index 50d7d760ff5a7..8f17a28546d94 100644 --- a/kube/kubetypes/grants.go +++ b/kube/kubetypes/grants.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package kubetypes contains types and constants related to the Tailscale diff --git a/kube/kubetypes/types.go b/kube/kubetypes/types.go index b8b94a4b21a5d..187f54f3481f8 100644 --- a/kube/kubetypes/types.go +++ b/kube/kubetypes/types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubetypes diff --git a/kube/kubetypes/types_test.go b/kube/kubetypes/types_test.go index ea1846b3253e8..86b3962ef1b01 100644 --- a/kube/kubetypes/types_test.go +++ b/kube/kubetypes/types_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package kubetypes diff --git a/kube/localclient/fake-client.go b/kube/localclient/fake-client.go index 7f0a08316634e..1bce4bef00d6f 100644 --- a/kube/localclient/fake-client.go +++ b/kube/localclient/fake-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package localclient diff --git a/kube/localclient/local-client.go b/kube/localclient/local-client.go index 550b3ae742c34..8cc0d41ffe473 100644 --- a/kube/localclient/local-client.go +++ b/kube/localclient/local-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package localclient provides an interface for all the local.Client methods diff --git a/kube/metrics/metrics.go b/kube/metrics/metrics.go index 0db683008f91e..062f18b8b95b5 100644 --- a/kube/metrics/metrics.go +++ b/kube/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/services/services.go b/kube/services/services.go index a9e50975ca9f1..36566c2855a9f 100644 --- a/kube/services/services.go +++ b/kube/services/services.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package services manages graceful shutdown of Tailscale Services advertised diff --git a/kube/state/state.go b/kube/state/state.go index 2605f0952f708..ebedb2f725b3d 100644 --- a/kube/state/state.go +++ b/kube/state/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/kube/state/state_test.go b/kube/state/state_test.go index 8701aa1b7fa65..9b2ce69be5599 100644 --- a/kube/state/state_test.go +++ b/kube/state/state_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/license_test.go b/license_test.go index 9b62c48ed218e..cac195c49c5a4 100644 --- a/license_test.go +++ b/license_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot @@ -23,7 +23,7 @@ func normalizeLineEndings(b []byte) []byte { // directory tree have a correct-looking Tailscale license header. func TestLicenseHeaders(t *testing.T) { want := normalizeLineEndings([]byte(strings.TrimLeft(` -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause `, "\n"))) diff --git a/licenses/licenses.go b/licenses/licenses.go index 5e59edb9f7b75..a4bf51befe773 100644 --- a/licenses/licenses.go +++ b/licenses/licenses.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package licenses provides utilities for working with open source licenses. diff --git a/log/filelogger/log.go b/log/filelogger/log.go index 599e5237b3e22..268cf1bba7583 100644 --- a/log/filelogger/log.go +++ b/log/filelogger/log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package filelogger provides localdisk log writing & rotation, primarily for Windows diff --git a/log/filelogger/log_test.go b/log/filelogger/log_test.go index dfa489637f720..32c3d0e90bf1b 100644 --- a/log/filelogger/log_test.go +++ b/log/filelogger/log_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filelogger diff --git a/log/sockstatlog/logger.go b/log/sockstatlog/logger.go index 8ddfabb866745..30d16fbcc8c6c 100644 --- a/log/sockstatlog/logger.go +++ b/log/sockstatlog/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sockstatlog provides a logger for capturing network socket stats for debugging. diff --git a/log/sockstatlog/logger_test.go b/log/sockstatlog/logger_test.go index e5c2feb2986d8..66228731e368e 100644 --- a/log/sockstatlog/logger_test.go +++ b/log/sockstatlog/logger_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sockstatlog diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index f7491783ad781..7a0027dad74ea 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logpolicy manages the creation or reuse of logtail loggers, diff --git a/logpolicy/logpolicy_test.go b/logpolicy/logpolicy_test.go index c09e590bb8399..64e54467c496b 100644 --- a/logpolicy/logpolicy_test.go +++ b/logpolicy/logpolicy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logpolicy diff --git a/logpolicy/maybe_syspolicy.go b/logpolicy/maybe_syspolicy.go index 8b2836c97411c..7cdaabcc7c77a 100644 --- a/logpolicy/maybe_syspolicy.go +++ b/logpolicy/maybe_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_syspolicy diff --git a/logtail/buffer.go b/logtail/buffer.go index 6efdbda63ac8e..bc39783ea768a 100644 --- a/logtail/buffer.go +++ b/logtail/buffer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail diff --git a/logtail/config.go b/logtail/config.go index bf47dd8aa7b52..c504047a3f2bf 100644 --- a/logtail/config.go +++ b/logtail/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logtail diff --git a/logtail/example/logadopt/logadopt.go b/logtail/example/logadopt/logadopt.go index eba3f93112d62..f9104231644b4 100644 --- a/logtail/example/logadopt/logadopt.go +++ b/logtail/example/logadopt/logadopt.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Command logadopt is a CLI tool to adopt a machine into a logtail collection. diff --git a/logtail/example/logreprocess/demo.sh b/logtail/example/logreprocess/demo.sh index 583929c12b4fe..89ff476c248cb 100755 --- a/logtail/example/logreprocess/demo.sh +++ b/logtail/example/logreprocess/demo.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # diff --git a/logtail/example/logreprocess/logreprocess.go b/logtail/example/logreprocess/logreprocess.go index aae65df9f1321..d434da1b187db 100644 --- a/logtail/example/logreprocess/logreprocess.go +++ b/logtail/example/logreprocess/logreprocess.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The logreprocess program tails a log and reprocesses it. diff --git a/logtail/example/logtail/logtail.go b/logtail/example/logtail/logtail.go index 0c9e442584410..24f98090f57c8 100644 --- a/logtail/example/logtail/logtail.go +++ b/logtail/example/logtail/logtail.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The logtail program logs stdin. diff --git a/logtail/filch/filch.go b/logtail/filch/filch.go index 88c72f233daab..32b0b88b15990 100644 --- a/logtail/filch/filch.go +++ b/logtail/filch/filch.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail diff --git a/logtail/filch/filch_omit.go b/logtail/filch/filch_omit.go index 898978e2152ea..c4edc1bcd392f 100644 --- a/logtail/filch/filch_omit.go +++ b/logtail/filch/filch_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_logtail diff --git a/logtail/filch/filch_stub.go b/logtail/filch/filch_stub.go index f2aeeb9b9f819..0bb2c306c05dc 100644 --- a/logtail/filch/filch_stub.go +++ b/logtail/filch/filch_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail && (wasm || plan9 || tamago) diff --git a/logtail/filch/filch_test.go b/logtail/filch/filch_test.go index 1e33471809dbb..0975a2d11f8a3 100644 --- a/logtail/filch/filch_test.go +++ b/logtail/filch/filch_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filch diff --git a/logtail/filch/filch_unix.go b/logtail/filch/filch_unix.go index 27f1d02ee86aa..0817e131190bb 100644 --- a/logtail/filch/filch_unix.go +++ b/logtail/filch/filch_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail && !windows && !wasm && !plan9 && !tamago diff --git a/logtail/filch/filch_windows.go b/logtail/filch/filch_windows.go index b08b64db39f61..3bffe8662396d 100644 --- a/logtail/filch/filch_windows.go +++ b/logtail/filch/filch_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail && windows diff --git a/logtail/logtail.go b/logtail/logtail.go index ce50c1c0a7f52..ef296568da957 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_logtail diff --git a/logtail/logtail_omit.go b/logtail/logtail_omit.go index 814fd3be90d8e..21f18c980cce4 100644 --- a/logtail/logtail_omit.go +++ b/logtail/logtail_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_logtail diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index b618fc0d7bc65..67250ae0db03f 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logtail diff --git a/maths/ewma.go b/maths/ewma.go index 0897b73e4727f..1946081cf6d08 100644 --- a/maths/ewma.go +++ b/maths/ewma.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package maths contains additional mathematical functions or structures not diff --git a/maths/ewma_test.go b/maths/ewma_test.go index 307078a38ebdf..9fddf34e17193 100644 --- a/maths/ewma_test.go +++ b/maths/ewma_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package maths diff --git a/metrics/fds_linux.go b/metrics/fds_linux.go index 34740c2bb1c74..b0abf946b7ef6 100644 --- a/metrics/fds_linux.go +++ b/metrics/fds_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/metrics/fds_notlinux.go b/metrics/fds_notlinux.go index 2dae97cad86b9..1877830134ec4 100644 --- a/metrics/fds_notlinux.go +++ b/metrics/fds_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/metrics/metrics.go b/metrics/metrics.go index 092b56c41b6dc..6540dcc13d66d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package metrics contains expvar & Prometheus types and code used by diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index a808d5a73eb3e..8478cdedc8de8 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/metrics/multilabelmap.go b/metrics/multilabelmap.go index 223a55a75bf1b..54d41bbae9ef2 100644 --- a/metrics/multilabelmap.go +++ b/metrics/multilabelmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/metrics/multilabelmap_test.go b/metrics/multilabelmap_test.go index 195696234e545..70554c63e50a0 100644 --- a/metrics/multilabelmap_test.go +++ b/metrics/multilabelmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/net/ace/ace.go b/net/ace/ace.go index 47e780313cadd..9b1264dc0927b 100644 --- a/net/ace/ace.go +++ b/net/ace/ace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ace implements a Dialer that dials via a Tailscale ACE (CONNECT) diff --git a/net/art/art_test.go b/net/art/art_test.go index daf8553ca020d..004f31b8ba3b5 100644 --- a/net/art/art_test.go +++ b/net/art/art_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/art/stride_table.go b/net/art/stride_table.go index 5050df24500ce..6cdb0b5a0a1e9 100644 --- a/net/art/stride_table.go +++ b/net/art/stride_table.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/art/stride_table_test.go b/net/art/stride_table_test.go index 4ccef1fe083cb..e797f40ee0ddc 100644 --- a/net/art/stride_table_test.go +++ b/net/art/stride_table_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/art/table.go b/net/art/table.go index fa397577868a8..447a56b394182 100644 --- a/net/art/table.go +++ b/net/art/table.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package art provides a routing table that implements the Allotment Routing diff --git a/net/art/table_test.go b/net/art/table_test.go index a129c8484ddcd..5c35ac7dafd77 100644 --- a/net/art/table_test.go +++ b/net/art/table_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package art diff --git a/net/bakedroots/bakedroots.go b/net/bakedroots/bakedroots.go index b268b1546caac..70d947c6b725d 100644 --- a/net/bakedroots/bakedroots.go +++ b/net/bakedroots/bakedroots.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package bakedroots contains WebPKI CA roots we bake into the tailscaled binary, diff --git a/net/bakedroots/bakedroots_test.go b/net/bakedroots/bakedroots_test.go index 8ba502a7827e0..12a656d4edae9 100644 --- a/net/bakedroots/bakedroots_test.go +++ b/net/bakedroots/bakedroots_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package bakedroots diff --git a/net/batching/conn.go b/net/batching/conn.go index 77cdf8c849ca4..1631c33cfe448 100644 --- a/net/batching/conn.go +++ b/net/batching/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package batching implements a socket optimized for increased throughput. diff --git a/net/batching/conn_default.go b/net/batching/conn_default.go index 37d644f50624c..0d208578b6d06 100644 --- a/net/batching/conn_default.go +++ b/net/batching/conn_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/batching/conn_linux.go b/net/batching/conn_linux.go index bd7ac25be2a4d..373625b772738 100644 --- a/net/batching/conn_linux.go +++ b/net/batching/conn_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package batching diff --git a/net/batching/conn_linux_test.go b/net/batching/conn_linux_test.go index c2cc463ebc6ad..a15de4f671ec6 100644 --- a/net/batching/conn_linux_test.go +++ b/net/batching/conn_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package batching diff --git a/net/captivedetection/captivedetection.go b/net/captivedetection/captivedetection.go index 3ec820b794400..dfd4bbd875608 100644 --- a/net/captivedetection/captivedetection.go +++ b/net/captivedetection/captivedetection.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package captivedetection provides a way to detect if the system is connected to a network that has diff --git a/net/captivedetection/captivedetection_test.go b/net/captivedetection/captivedetection_test.go index 0778e07df393a..2aa660d88d0a4 100644 --- a/net/captivedetection/captivedetection_test.go +++ b/net/captivedetection/captivedetection_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package captivedetection diff --git a/net/captivedetection/endpoints.go b/net/captivedetection/endpoints.go index 57b3e53351a1a..5c1d31d0c35a4 100644 --- a/net/captivedetection/endpoints.go +++ b/net/captivedetection/endpoints.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package captivedetection diff --git a/net/captivedetection/rawconn.go b/net/captivedetection/rawconn.go index a7197d9df2577..3c6f65f84692e 100644 --- a/net/captivedetection/rawconn.go +++ b/net/captivedetection/rawconn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(ios || darwin) diff --git a/net/captivedetection/rawconn_apple.go b/net/captivedetection/rawconn_apple.go index 12b4446e62eb8..ee8e7c4c3f6b9 100644 --- a/net/captivedetection/rawconn_apple.go +++ b/net/captivedetection/rawconn_apple.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || darwin diff --git a/net/connectproxy/connectproxy.go b/net/connectproxy/connectproxy.go index 4bf6875029554..a63c6acf7b7c8 100644 --- a/net/connectproxy/connectproxy.go +++ b/net/connectproxy/connectproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package connectproxy contains some CONNECT proxy code. diff --git a/net/dns/config.go b/net/dns/config.go index 2425b304dffd8..2b5505fc9734c 100644 --- a/net/dns/config.go +++ b/net/dns/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:generate go run tailscale.com/cmd/viewer --type=Config --clonefunc diff --git a/net/dns/dbus.go b/net/dns/dbus.go index c53e8b7205949..f80136196376a 100644 --- a/net/dns/dbus.go +++ b/net/dns/dbus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_dbus diff --git a/net/dns/debian_resolvconf.go b/net/dns/debian_resolvconf.go index 63fd80c1274e8..128b26f2aceca 100644 --- a/net/dns/debian_resolvconf.go +++ b/net/dns/debian_resolvconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || freebsd || openbsd diff --git a/net/dns/direct.go b/net/dns/direct.go index 78495d4737d1d..ec2e42e75176f 100644 --- a/net/dns/direct.go +++ b/net/dns/direct.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android && !ios diff --git a/net/dns/direct_linux_test.go b/net/dns/direct_linux_test.go index 035763a45f30d..8199b41f3b973 100644 --- a/net/dns/direct_linux_test.go +++ b/net/dns/direct_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/dns/direct_test.go b/net/dns/direct_test.go index 07202502e231e..c96323571c1f9 100644 --- a/net/dns/direct_test.go +++ b/net/dns/direct_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/direct_unix_test.go b/net/dns/direct_unix_test.go index bffa6ade943c8..068c5633645a5 100644 --- a/net/dns/direct_unix_test.go +++ b/net/dns/direct_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/net/dns/dns_clone.go b/net/dns/dns_clone.go index 807bfce23df8b..de08be8a27b8e 100644 --- a/net/dns/dns_clone.go +++ b/net/dns/dns_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/net/dns/dns_view.go b/net/dns/dns_view.go index c7ce376cba8db..b10861cca8821 100644 --- a/net/dns/dns_view.go +++ b/net/dns/dns_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/net/dns/flush_default.go b/net/dns/flush_default.go index eb6d9da417104..006373a513a76 100644 --- a/net/dns/flush_default.go +++ b/net/dns/flush_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/dns/flush_windows.go b/net/dns/flush_windows.go index d7c7b7fce515d..a3b1f6b3116af 100644 --- a/net/dns/flush_windows.go +++ b/net/dns/flush_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/ini.go b/net/dns/ini.go index 1e47d606e970f..623362bf56fe2 100644 --- a/net/dns/ini.go +++ b/net/dns/ini.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/net/dns/ini_test.go b/net/dns/ini_test.go index 3afe7009caa27..0e5b966a8674f 100644 --- a/net/dns/ini_test.go +++ b/net/dns/ini_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/net/dns/manager.go b/net/dns/manager.go index 4441c4f69ef70..0b7ae465f59eb 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_darwin.go b/net/dns/manager_darwin.go index 01c920626e466..bb590aa4e7c14 100644 --- a/net/dns/manager_darwin.go +++ b/net/dns/manager_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_default.go b/net/dns/manager_default.go index 42e7d295d713f..b514a741799a4 100644 --- a/net/dns/manager_default.go +++ b/net/dns/manager_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (!linux || android) && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris && !plan9 diff --git a/net/dns/manager_freebsd.go b/net/dns/manager_freebsd.go index da3a821ce3cc4..deea462ed049d 100644 --- a/net/dns/manager_freebsd.go +++ b/net/dns/manager_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_linux.go b/net/dns/manager_linux.go index 4fbf6a8dbffa2..e68b2e7f9e266 100644 --- a/net/dns/manager_linux.go +++ b/net/dns/manager_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/dns/manager_linux_test.go b/net/dns/manager_linux_test.go index 605344c062de9..d48fe23e70a8b 100644 --- a/net/dns/manager_linux_test.go +++ b/net/dns/manager_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_openbsd.go b/net/dns/manager_openbsd.go index 766c82f981218..fe4641bbd1ee1 100644 --- a/net/dns/manager_openbsd.go +++ b/net/dns/manager_openbsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_plan9.go b/net/dns/manager_plan9.go index 47c996dad7cda..d7619f414d45f 100644 --- a/net/dns/manager_plan9.go +++ b/net/dns/manager_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO: man 6 ndb | grep -e 'suffix.*same line' diff --git a/net/dns/manager_plan9_test.go b/net/dns/manager_plan9_test.go index 806fdb68ed6ba..cc09b360e0c09 100644 --- a/net/dns/manager_plan9_test.go +++ b/net/dns/manager_plan9_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build plan9 diff --git a/net/dns/manager_solaris.go b/net/dns/manager_solaris.go index dcd8b1fd3951c..3324e2b07af23 100644 --- a/net/dns/manager_solaris.go +++ b/net/dns/manager_solaris.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_tcp_test.go b/net/dns/manager_tcp_test.go index 420efe40405df..bdd5cc7bb314b 100644 --- a/net/dns/manager_tcp_test.go +++ b/net/dns/manager_tcp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_test.go b/net/dns/manager_test.go index 18c88df9125c3..679f81cd5d8a2 100644 --- a/net/dns/manager_test.go +++ b/net/dns/manager_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go index 1eccb9a16ff1d..118dd18dde14b 100644 --- a/net/dns/manager_windows.go +++ b/net/dns/manager_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/manager_windows_test.go b/net/dns/manager_windows_test.go index 5525096b35c55..d1c65ed2bffd6 100644 --- a/net/dns/manager_windows_test.go +++ b/net/dns/manager_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/nm.go b/net/dns/nm.go index a88d29b374ebb..99f032431ce57 100644 --- a/net/dns/nm.go +++ b/net/dns/nm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_networkmanager diff --git a/net/dns/noop.go b/net/dns/noop.go index 9466b57a0f477..70dd93ed22220 100644 --- a/net/dns/noop.go +++ b/net/dns/noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/nrpt_windows.go b/net/dns/nrpt_windows.go index 261ca337558ef..1e1462e9ef908 100644 --- a/net/dns/nrpt_windows.go +++ b/net/dns/nrpt_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/openresolv.go b/net/dns/openresolv.go index c9562b6a91d13..c3aaf3a6948c8 100644 --- a/net/dns/openresolv.go +++ b/net/dns/openresolv.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || freebsd || openbsd diff --git a/net/dns/osconfig.go b/net/dns/osconfig.go index af4c0f01fc75b..f871335ade38c 100644 --- a/net/dns/osconfig.go +++ b/net/dns/osconfig.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/osconfig_test.go b/net/dns/osconfig_test.go index c19db299f4b54..93b13c57d482b 100644 --- a/net/dns/osconfig_test.go +++ b/net/dns/osconfig_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/publicdns/publicdns.go b/net/dns/publicdns/publicdns.go index b8a7f88091617..e3148a5ae8a98 100644 --- a/net/dns/publicdns/publicdns.go +++ b/net/dns/publicdns/publicdns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package publicdns contains mapping and helpers for working with diff --git a/net/dns/publicdns/publicdns_test.go b/net/dns/publicdns/publicdns_test.go index 6efeb2c6f96c8..4f494930b21e9 100644 --- a/net/dns/publicdns/publicdns_test.go +++ b/net/dns/publicdns/publicdns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package publicdns diff --git a/net/dns/resolvconf-workaround.sh b/net/dns/resolvconf-workaround.sh index aec6708a06da1..e0bb250207952 100644 --- a/net/dns/resolvconf-workaround.sh +++ b/net/dns/resolvconf-workaround.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # # This script is a workaround for a vpn-unfriendly behavior of the diff --git a/net/dns/resolvconf.go b/net/dns/resolvconf.go index ca584ffcc5f1f..990a25f2909ac 100644 --- a/net/dns/resolvconf.go +++ b/net/dns/resolvconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || freebsd || openbsd diff --git a/net/dns/resolvconffile/resolvconffile.go b/net/dns/resolvconffile/resolvconffile.go index 753000f6d33da..7a3b90474b5d0 100644 --- a/net/dns/resolvconffile/resolvconffile.go +++ b/net/dns/resolvconffile/resolvconffile.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package resolvconffile parses & serializes /etc/resolv.conf-style files. diff --git a/net/dns/resolvconffile/resolvconffile_test.go b/net/dns/resolvconffile/resolvconffile_test.go index 4f5ddd599899a..21d9e493d56df 100644 --- a/net/dns/resolvconffile/resolvconffile_test.go +++ b/net/dns/resolvconffile/resolvconffile_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolvconffile diff --git a/net/dns/resolvconfpath_default.go b/net/dns/resolvconfpath_default.go index 57e82c4c773ea..00caf30b24a64 100644 --- a/net/dns/resolvconfpath_default.go +++ b/net/dns/resolvconfpath_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !gokrazy diff --git a/net/dns/resolvconfpath_gokrazy.go b/net/dns/resolvconfpath_gokrazy.go index f0759b0e31a0f..ec382ae82e966 100644 --- a/net/dns/resolvconfpath_gokrazy.go +++ b/net/dns/resolvconfpath_gokrazy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build gokrazy diff --git a/net/dns/resolvd.go b/net/dns/resolvd.go index ad1a99c111997..56fedfef94daf 100644 --- a/net/dns/resolvd.go +++ b/net/dns/resolvd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build openbsd diff --git a/net/dns/resolved.go b/net/dns/resolved.go index d8f63c9d66006..754570fdc1779 100644 --- a/net/dns/resolved.go +++ b/net/dns/resolved.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android && !ts_omit_resolved diff --git a/net/dns/resolver/debug.go b/net/dns/resolver/debug.go index a41462e185e24..8d700ce54a143 100644 --- a/net/dns/resolver/debug.go +++ b/net/dns/resolver/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/doh_test.go b/net/dns/resolver/doh_test.go index a9c28476166fc..b7045c3211e6b 100644 --- a/net/dns/resolver/doh_test.go +++ b/net/dns/resolver/doh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 797c5272ad651..0a3daa3bc46ca 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 0b38008c8a9c2..3165bb9783faa 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/macios_ext.go b/net/dns/resolver/macios_ext.go index e3f979c194d91..c9b6626523d84 100644 --- a/net/dns/resolver/macios_ext.go +++ b/net/dns/resolver/macios_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_macext && (darwin || ios) diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 3185cbe2b35ff..a6f05c4702550 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package resolver implements a stub DNS resolver that can also serve diff --git a/net/dns/resolver/tsdns_server_test.go b/net/dns/resolver/tsdns_server_test.go index 82fd3bebf232c..9e18918b9d6e4 100644 --- a/net/dns/resolver/tsdns_server_test.go +++ b/net/dns/resolver/tsdns_server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index f0dbb48b33f6e..5597c2cf2d921 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package resolver diff --git a/net/dns/utf.go b/net/dns/utf.go index 0c1db69acb33b..b18cdebb4eb56 100644 --- a/net/dns/utf.go +++ b/net/dns/utf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/utf_test.go b/net/dns/utf_test.go index b5fd372622519..7ae5a6854d34c 100644 --- a/net/dns/utf_test.go +++ b/net/dns/utf_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dns/wsl_windows.go b/net/dns/wsl_windows.go index 81e8593160c02..c2400746b8a2d 100644 --- a/net/dns/wsl_windows.go +++ b/net/dns/wsl_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dns diff --git a/net/dnscache/dnscache.go b/net/dnscache/dnscache.go index e222b983f0287..8300917248773 100644 --- a/net/dnscache/dnscache.go +++ b/net/dnscache/dnscache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnscache contains a minimal DNS cache that makes a bunch of diff --git a/net/dnscache/dnscache_test.go b/net/dnscache/dnscache_test.go index 58bb6cd7f594c..9306c62cc90ee 100644 --- a/net/dnscache/dnscache_test.go +++ b/net/dnscache/dnscache_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnscache diff --git a/net/dnscache/messagecache.go b/net/dnscache/messagecache.go index 040706b9c7746..9bdedf19e6308 100644 --- a/net/dnscache/messagecache.go +++ b/net/dnscache/messagecache.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnscache diff --git a/net/dnscache/messagecache_test.go b/net/dnscache/messagecache_test.go index 0bedfa5ad78e7..79fa49360a281 100644 --- a/net/dnscache/messagecache_test.go +++ b/net/dnscache/messagecache_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnscache diff --git a/net/dnsfallback/dnsfallback.go b/net/dnsfallback/dnsfallback.go index 74b625970302b..5467127762ecd 100644 --- a/net/dnsfallback/dnsfallback.go +++ b/net/dnsfallback/dnsfallback.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnsfallback contains a DNS fallback mechanism diff --git a/net/dnsfallback/dnsfallback_test.go b/net/dnsfallback/dnsfallback_test.go index 7f881057450e7..4e816c3405e3a 100644 --- a/net/dnsfallback/dnsfallback_test.go +++ b/net/dnsfallback/dnsfallback_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnsfallback diff --git a/net/dnsfallback/update-dns-fallbacks.go b/net/dnsfallback/update-dns-fallbacks.go index 384e77e104cdc..173b464582257 100644 --- a/net/dnsfallback/update-dns-fallbacks.go +++ b/net/dnsfallback/update-dns-fallbacks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/net/flowtrack/flowtrack.go b/net/flowtrack/flowtrack.go index 8b3d799f7bbf4..5df34a5095219 100644 --- a/net/flowtrack/flowtrack.go +++ b/net/flowtrack/flowtrack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // // Original implementation (from same author) from which this was derived was: diff --git a/net/flowtrack/flowtrack_test.go b/net/flowtrack/flowtrack_test.go index 1a13f7753a547..21e2021e1216f 100644 --- a/net/flowtrack/flowtrack_test.go +++ b/net/flowtrack/flowtrack_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package flowtrack diff --git a/net/ipset/ipset.go b/net/ipset/ipset.go index 27c1e27ed4180..92cec9d0be854 100644 --- a/net/ipset/ipset.go +++ b/net/ipset/ipset.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipset provides code for creating efficient IP-in-set lookup functions diff --git a/net/ipset/ipset_test.go b/net/ipset/ipset_test.go index 2df4939cb99ad..291416f380ef6 100644 --- a/net/ipset/ipset_test.go +++ b/net/ipset/ipset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipset diff --git a/net/ktimeout/ktimeout.go b/net/ktimeout/ktimeout.go index 7cd4391435ed0..abe049b06380a 100644 --- a/net/ktimeout/ktimeout.go +++ b/net/ktimeout/ktimeout.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ktimeout configures kernel TCP stack timeouts via the provided diff --git a/net/ktimeout/ktimeout_default.go b/net/ktimeout/ktimeout_default.go index f1b11661b1335..2304245b3fe21 100644 --- a/net/ktimeout/ktimeout_default.go +++ b/net/ktimeout/ktimeout_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/ktimeout/ktimeout_linux.go b/net/ktimeout/ktimeout_linux.go index 84286b647bcba..634c32119b569 100644 --- a/net/ktimeout/ktimeout_linux.go +++ b/net/ktimeout/ktimeout_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ktimeout diff --git a/net/ktimeout/ktimeout_linux_test.go b/net/ktimeout/ktimeout_linux_test.go index 0330923a96c13..dc3dbe12b9363 100644 --- a/net/ktimeout/ktimeout_linux_test.go +++ b/net/ktimeout/ktimeout_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ktimeout diff --git a/net/ktimeout/ktimeout_test.go b/net/ktimeout/ktimeout_test.go index b534f046caddb..f361d35cbbbe5 100644 --- a/net/ktimeout/ktimeout_test.go +++ b/net/ktimeout/ktimeout_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ktimeout diff --git a/net/memnet/conn.go b/net/memnet/conn.go index a9e1fd39901a0..8cab63403bc5e 100644 --- a/net/memnet/conn.go +++ b/net/memnet/conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/conn_test.go b/net/memnet/conn_test.go index 743ce5248cb9d..340c4c6ee00eb 100644 --- a/net/memnet/conn_test.go +++ b/net/memnet/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/listener.go b/net/memnet/listener.go index dded97995bbc1..5d751b12e7f1a 100644 --- a/net/memnet/listener.go +++ b/net/memnet/listener.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/listener_test.go b/net/memnet/listener_test.go index b6ceb3dfa94cf..6c767ed57be7a 100644 --- a/net/memnet/listener_test.go +++ b/net/memnet/listener_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/memnet.go b/net/memnet/memnet.go index db9e3872f6f26..25b1062a19cec 100644 --- a/net/memnet/memnet.go +++ b/net/memnet/memnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package memnet implements an in-memory network implementation. diff --git a/net/memnet/memnet_test.go b/net/memnet/memnet_test.go index 38086cec05f3c..d5a53ba81cb9f 100644 --- a/net/memnet/memnet_test.go +++ b/net/memnet/memnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/pipe.go b/net/memnet/pipe.go index 47163508353a6..8caca57df2f81 100644 --- a/net/memnet/pipe.go +++ b/net/memnet/pipe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/memnet/pipe_test.go b/net/memnet/pipe_test.go index a86d65388e27d..ebd9dd8c2323f 100644 --- a/net/memnet/pipe_test.go +++ b/net/memnet/pipe_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package memnet diff --git a/net/netaddr/netaddr.go b/net/netaddr/netaddr.go index a04acd57aa670..7057a8eec58e8 100644 --- a/net/netaddr/netaddr.go +++ b/net/netaddr/netaddr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netaddr is a transitional package while we finish migrating from inet.af/netaddr diff --git a/net/netcheck/captiveportal.go b/net/netcheck/captiveportal.go index ad11f19a05b6b..310e98ce73a79 100644 --- a/net/netcheck/captiveportal.go +++ b/net/netcheck/captiveportal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_captiveportal diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index c5a3d2392007e..ebcdc4eaca4e3 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netcheck checks the network conditions from the current host. diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 6830e7f27075c..ab7f58febcb3b 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netcheck diff --git a/net/netcheck/standalone.go b/net/netcheck/standalone.go index b4523a832d463..88d5b4cc5a7f1 100644 --- a/net/netcheck/standalone.go +++ b/net/netcheck/standalone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netcheck diff --git a/net/neterror/neterror.go b/net/neterror/neterror.go index e2387440d33d5..43b96999841a1 100644 --- a/net/neterror/neterror.go +++ b/net/neterror/neterror.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package neterror classifies network errors. diff --git a/net/neterror/neterror_linux.go b/net/neterror/neterror_linux.go index 857367fe8ebb5..9add4fd1d213c 100644 --- a/net/neterror/neterror_linux.go +++ b/net/neterror/neterror_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package neterror diff --git a/net/neterror/neterror_linux_test.go b/net/neterror/neterror_linux_test.go index 5b99060741351..d8846219afad2 100644 --- a/net/neterror/neterror_linux_test.go +++ b/net/neterror/neterror_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package neterror diff --git a/net/neterror/neterror_windows.go b/net/neterror/neterror_windows.go index bf112f5ed7ab7..4b0b2c8024c31 100644 --- a/net/neterror/neterror_windows.go +++ b/net/neterror/neterror_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package neterror diff --git a/net/netkernelconf/netkernelconf.go b/net/netkernelconf/netkernelconf.go index 3ea502b377fdf..7840074c9fbd2 100644 --- a/net/netkernelconf/netkernelconf.go +++ b/net/netkernelconf/netkernelconf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netkernelconf contains code for checking kernel netdev config. diff --git a/net/netkernelconf/netkernelconf_default.go b/net/netkernelconf/netkernelconf_default.go index 3e160e5edf5b0..8e3f2061d999c 100644 --- a/net/netkernelconf/netkernelconf_default.go +++ b/net/netkernelconf/netkernelconf_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || android diff --git a/net/netkernelconf/netkernelconf_linux.go b/net/netkernelconf/netkernelconf_linux.go index 2a4f0a049f56d..b8c165ac558cf 100644 --- a/net/netkernelconf/netkernelconf_linux.go +++ b/net/netkernelconf/netkernelconf_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/netknob/netknob.go b/net/netknob/netknob.go index 53171f4243f8d..a870af824a3ed 100644 --- a/net/netknob/netknob.go +++ b/net/netknob/netknob.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netknob has Tailscale network knobs. diff --git a/net/netmon/defaultroute_bsd.go b/net/netmon/defaultroute_bsd.go index 9195ae0730ebc..88f2c8ea54be1 100644 --- a/net/netmon/defaultroute_bsd.go +++ b/net/netmon/defaultroute_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Common code for FreeBSD. This might also work on other diff --git a/net/netmon/defaultroute_darwin.go b/net/netmon/defaultroute_darwin.go index 57f7e22b7ddce..121535937bc22 100644 --- a/net/netmon/defaultroute_darwin.go +++ b/net/netmon/defaultroute_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || ios diff --git a/net/netmon/interfaces.go b/net/netmon/interfaces.go index 4cf93973c6473..c7a2cb213e893 100644 --- a/net/netmon/interfaces.go +++ b/net/netmon/interfaces.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_android.go b/net/netmon/interfaces_android.go index 26104e879a393..2cd7f23f6f164 100644 --- a/net/netmon/interfaces_android.go +++ b/net/netmon/interfaces_android.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_bsd.go b/net/netmon/interfaces_bsd.go index 86bc5615d2321..d53e2cfc18f99 100644 --- a/net/netmon/interfaces_bsd.go +++ b/net/netmon/interfaces_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Common code for FreeBSD and Darwin. This might also work on other diff --git a/net/netmon/interfaces_darwin.go b/net/netmon/interfaces_darwin.go index 126040350bdb2..c0f588fd20c1b 100644 --- a/net/netmon/interfaces_darwin.go +++ b/net/netmon/interfaces_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_darwin_test.go b/net/netmon/interfaces_darwin_test.go index c3d40a6f0e34e..e4b84a144a432 100644 --- a/net/netmon/interfaces_darwin_test.go +++ b/net/netmon/interfaces_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_default_route_test.go b/net/netmon/interfaces_default_route_test.go index e231eea9ac794..76424aef7af2f 100644 --- a/net/netmon/interfaces_default_route_test.go +++ b/net/netmon/interfaces_default_route_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || (darwin && !ts_macext) diff --git a/net/netmon/interfaces_defaultrouteif_todo.go b/net/netmon/interfaces_defaultrouteif_todo.go index df0820fa94eb4..e428f16a1f946 100644 --- a/net/netmon/interfaces_defaultrouteif_todo.go +++ b/net/netmon/interfaces_defaultrouteif_todo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !windows && !darwin && !freebsd && !android diff --git a/net/netmon/interfaces_freebsd.go b/net/netmon/interfaces_freebsd.go index 654eb5316384a..5573643ca7370 100644 --- a/net/netmon/interfaces_freebsd.go +++ b/net/netmon/interfaces_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This might work on other BSDs, but only tested on FreeBSD. diff --git a/net/netmon/interfaces_linux.go b/net/netmon/interfaces_linux.go index a9b93c0a1ff49..64cb0b9af2ce6 100644 --- a/net/netmon/interfaces_linux.go +++ b/net/netmon/interfaces_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/net/netmon/interfaces_linux_test.go b/net/netmon/interfaces_linux_test.go index 4f740ac28ba08..5a29c4b8b3ada 100644 --- a/net/netmon/interfaces_linux_test.go +++ b/net/netmon/interfaces_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_test.go b/net/netmon/interfaces_test.go index e4274819f90df..bd81eb96a42bf 100644 --- a/net/netmon/interfaces_test.go +++ b/net/netmon/interfaces_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_windows.go b/net/netmon/interfaces_windows.go index d6625ead3cd05..070b08ba658e2 100644 --- a/net/netmon/interfaces_windows.go +++ b/net/netmon/interfaces_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/interfaces_windows_test.go b/net/netmon/interfaces_windows_test.go index 91db7bcc5266c..13526612eb477 100644 --- a/net/netmon/interfaces_windows_test.go +++ b/net/netmon/interfaces_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/loghelper.go b/net/netmon/loghelper.go index 2876e9b12481c..bddbd4d616462 100644 --- a/net/netmon/loghelper.go +++ b/net/netmon/loghelper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/loghelper_test.go b/net/netmon/loghelper_test.go index 468a12505f322..aec5206443aa4 100644 --- a/net/netmon/loghelper_test.go +++ b/net/netmon/loghelper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon.go b/net/netmon/netmon.go index e18bc392dd196..c30010ee407da 100644 --- a/net/netmon/netmon.go +++ b/net/netmon/netmon.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package monitor provides facilities for monitoring network diff --git a/net/netmon/netmon_darwin.go b/net/netmon/netmon_darwin.go index 042f9a3b750c2..588cbf6161845 100644 --- a/net/netmon/netmon_darwin.go +++ b/net/netmon/netmon_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_darwin_test.go b/net/netmon/netmon_darwin_test.go index 84c67cf6fa3e2..e57b5ca84c146 100644 --- a/net/netmon/netmon_darwin_test.go +++ b/net/netmon/netmon_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_freebsd.go b/net/netmon/netmon_freebsd.go index 3a4fb44d8f0a0..8e99532c589b6 100644 --- a/net/netmon/netmon_freebsd.go +++ b/net/netmon/netmon_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_linux.go b/net/netmon/netmon_linux.go index aa5253f9be28b..b7d87f995634f 100644 --- a/net/netmon/netmon_linux.go +++ b/net/netmon/netmon_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/net/netmon/netmon_linux_test.go b/net/netmon/netmon_linux_test.go index 75d7c646559f1..c6c12e850f3fe 100644 --- a/net/netmon/netmon_linux_test.go +++ b/net/netmon/netmon_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/netmon/netmon_polling.go b/net/netmon/netmon_polling.go index 3b5ef6fe9206f..bdeb43005782b 100644 --- a/net/netmon/netmon_polling.go +++ b/net/netmon/netmon_polling.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (!linux && !freebsd && !windows && !darwin) || android diff --git a/net/netmon/netmon_test.go b/net/netmon/netmon_test.go index 50519b4a9c531..97c203274cd8f 100644 --- a/net/netmon/netmon_test.go +++ b/net/netmon/netmon_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/netmon_windows.go b/net/netmon/netmon_windows.go index e8966faf00f46..91c137de0e328 100644 --- a/net/netmon/netmon_windows.go +++ b/net/netmon/netmon_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netmon/polling.go b/net/netmon/polling.go index 2a3e44cba0b9d..806f0b0451fe1 100644 --- a/net/netmon/polling.go +++ b/net/netmon/polling.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !darwin diff --git a/net/netmon/state.go b/net/netmon/state.go index 79dd8a01ba9e1..10d68ab785edc 100644 --- a/net/netmon/state.go +++ b/net/netmon/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmon diff --git a/net/netns/mksyscall.go b/net/netns/mksyscall.go index ff2c0b8610657..2a8a2176b9c84 100644 --- a/net/netns/mksyscall.go +++ b/net/netns/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns.go b/net/netns/netns.go index 81ab5e2a212a6..5d692c787eae8 100644 --- a/net/netns/netns.go +++ b/net/netns/netns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netns contains the common code for using the Go net package diff --git a/net/netns/netns_android.go b/net/netns/netns_android.go index 162e5c79a62fa..e747f61f40e50 100644 --- a/net/netns/netns_android.go +++ b/net/netns/netns_android.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build android diff --git a/net/netns/netns_darwin.go b/net/netns/netns_darwin.go index ff05a3f3139c3..e5d01542edfb4 100644 --- a/net/netns/netns_darwin.go +++ b/net/netns/netns_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/net/netns/netns_darwin_test.go b/net/netns/netns_darwin_test.go index 2030c169ef68b..768b095b82739 100644 --- a/net/netns/netns_darwin_test.go +++ b/net/netns/netns_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go index 58c5936640e4f..4087e40488e60 100644 --- a/net/netns/netns_default.go +++ b/net/netns/netns_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !windows && !darwin diff --git a/net/netns/netns_dw.go b/net/netns/netns_dw.go index b9f750e8a6657..82494737130b6 100644 --- a/net/netns/netns_dw.go +++ b/net/netns/netns_dw.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || windows diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go index 609f524b5cc01..02b2dd89b197f 100644 --- a/net/netns/netns_linux.go +++ b/net/netns/netns_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/netns/netns_linux_test.go b/net/netns/netns_linux_test.go index a5000f37f0a44..e467ee41405d6 100644 --- a/net/netns/netns_linux_test.go +++ b/net/netns/netns_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns_test.go b/net/netns/netns_test.go index 82f919b946d4a..9ecc19b424f95 100644 --- a/net/netns/netns_test.go +++ b/net/netns/netns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netns contains the common code for using the Go net package diff --git a/net/netns/netns_windows.go b/net/netns/netns_windows.go index afbda0f47ece6..686c813f6b1d1 100644 --- a/net/netns/netns_windows.go +++ b/net/netns/netns_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/netns_windows_test.go b/net/netns/netns_windows_test.go index 390604f465041..67e7b3de86c09 100644 --- a/net/netns/netns_windows_test.go +++ b/net/netns/netns_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netns diff --git a/net/netns/socks.go b/net/netns/socks.go index 9a137db7f5b18..7746e91778353 100644 --- a/net/netns/socks.go +++ b/net/netns/socks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !js && !android && !ts_omit_useproxy diff --git a/net/netstat/netstat.go b/net/netstat/netstat.go index 53c7d7757eac6..44b421d5d15b5 100644 --- a/net/netstat/netstat.go +++ b/net/netstat/netstat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netstat returns the local machine's network connection table. diff --git a/net/netstat/netstat_noimpl.go b/net/netstat/netstat_noimpl.go index e455c8ce931de..78bb018f213ad 100644 --- a/net/netstat/netstat_noimpl.go +++ b/net/netstat/netstat_noimpl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/netstat/netstat_test.go b/net/netstat/netstat_test.go index 38827df5ef65a..8407db778f001 100644 --- a/net/netstat/netstat_test.go +++ b/net/netstat/netstat_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstat diff --git a/net/netstat/netstat_windows.go b/net/netstat/netstat_windows.go index 24191a50eadab..4b3edbdf8134b 100644 --- a/net/netstat/netstat_windows.go +++ b/net/netstat/netstat_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstat diff --git a/net/netutil/default_interface_portable.go b/net/netutil/default_interface_portable.go index d75cefb7aec74..2a80553715aec 100644 --- a/net/netutil/default_interface_portable.go +++ b/net/netutil/default_interface_portable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netutil/default_interface_portable_test.go b/net/netutil/default_interface_portable_test.go index 03dce340505a5..b54733747524f 100644 --- a/net/netutil/default_interface_portable_test.go +++ b/net/netutil/default_interface_portable_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netutil/ip_forward.go b/net/netutil/ip_forward.go index c64a9e4269ae0..0711953f52e68 100644 --- a/net/netutil/ip_forward.go +++ b/net/netutil/ip_forward.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netutil/netutil.go b/net/netutil/netutil.go index 5c42f51c64837..13882988594d1 100644 --- a/net/netutil/netutil.go +++ b/net/netutil/netutil.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netutil contains misc shared networking code & types. diff --git a/net/netutil/netutil_test.go b/net/netutil/netutil_test.go index 0523946e63c9b..a512238d5f5ee 100644 --- a/net/netutil/netutil_test.go +++ b/net/netutil/netutil_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netutil/routes.go b/net/netutil/routes.go index 7d67d3695e10d..c8212b9af66dd 100644 --- a/net/netutil/routes.go +++ b/net/netutil/routes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netutil diff --git a/net/netx/netx.go b/net/netx/netx.go index 014daa9a795cb..fba6567c4c312 100644 --- a/net/netx/netx.go +++ b/net/netx/netx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netx contains types to describe and abstract over how dialing and diff --git a/net/packet/capture.go b/net/packet/capture.go index dd0ca411f2051..630a4b1610c2b 100644 --- a/net/packet/capture.go +++ b/net/packet/capture.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/checksum/checksum.go b/net/packet/checksum/checksum.go index 4b5b82174a22f..e6918e7ae1c9f 100644 --- a/net/packet/checksum/checksum.go +++ b/net/packet/checksum/checksum.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package checksum provides functions for updating checksums in parsed packets. diff --git a/net/packet/checksum/checksum_test.go b/net/packet/checksum/checksum_test.go index bf818743d3dbf..ab7c783b3e96a 100644 --- a/net/packet/checksum/checksum_test.go +++ b/net/packet/checksum/checksum_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package checksum diff --git a/net/packet/doc.go b/net/packet/doc.go index ce6c0c30716c6..4a62b8aa77727 100644 --- a/net/packet/doc.go +++ b/net/packet/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package packet contains packet parsing and marshaling utilities. diff --git a/net/packet/geneve.go b/net/packet/geneve.go index 71b365ae89414..bed54f641425f 100644 --- a/net/packet/geneve.go +++ b/net/packet/geneve.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/geneve_test.go b/net/packet/geneve_test.go index be9784998adf2..bd673cd0d963a 100644 --- a/net/packet/geneve_test.go +++ b/net/packet/geneve_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/header.go b/net/packet/header.go index fa66a8641c6c4..44b99e520d717 100644 --- a/net/packet/header.go +++ b/net/packet/header.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp.go b/net/packet/icmp.go index 89a7aaa32bec4..8f9cd0e2bb4a1 100644 --- a/net/packet/icmp.go +++ b/net/packet/icmp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp4.go b/net/packet/icmp4.go index 06780e0bb48ff..492a0e9dfee98 100644 --- a/net/packet/icmp4.go +++ b/net/packet/icmp4.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp6.go b/net/packet/icmp6.go index f78db1f4a8c3c..a91db53c9e50c 100644 --- a/net/packet/icmp6.go +++ b/net/packet/icmp6.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/icmp6_test.go b/net/packet/icmp6_test.go index f34883ca41e7e..0348824b62296 100644 --- a/net/packet/icmp6_test.go +++ b/net/packet/icmp6_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/ip4.go b/net/packet/ip4.go index 967a8dba7f57b..1964acf1b7900 100644 --- a/net/packet/ip4.go +++ b/net/packet/ip4.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/ip6.go b/net/packet/ip6.go index d26b9a1619b31..eb92f1450f523 100644 --- a/net/packet/ip6.go +++ b/net/packet/ip6.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/packet.go b/net/packet/packet.go index 34b63aadd2c2e..b41e0dcd93301 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/packet_test.go b/net/packet/packet_test.go index 09c2c101d66d9..4dbf88009b20a 100644 --- a/net/packet/packet_test.go +++ b/net/packet/packet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index 9881299b7d13e..ad1db311a64c2 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TSMP is our ICMP-like "Tailscale Message Protocol" for signaling diff --git a/net/packet/tsmp_test.go b/net/packet/tsmp_test.go index d8f1d38d57180..01bb836d76971 100644 --- a/net/packet/tsmp_test.go +++ b/net/packet/tsmp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/udp4.go b/net/packet/udp4.go index 0d5bca73e8c89..a42222f785292 100644 --- a/net/packet/udp4.go +++ b/net/packet/udp4.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/packet/udp6.go b/net/packet/udp6.go index 10fdcb99e525c..8d7f380884cbb 100644 --- a/net/packet/udp6.go +++ b/net/packet/udp6.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package packet diff --git a/net/ping/ping.go b/net/ping/ping.go index 8e16a692a8136..de79da51c5c48 100644 --- a/net/ping/ping.go +++ b/net/ping/ping.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ping allows sending ICMP echo requests to a host in order to diff --git a/net/ping/ping_test.go b/net/ping/ping_test.go index bbedbcad80e44..9fe12de7e9a54 100644 --- a/net/ping/ping_test.go +++ b/net/ping/ping_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ping diff --git a/net/portmapper/disabled_stubs.go b/net/portmapper/disabled_stubs.go index a1324c20be9e1..dea4ef0d3e630 100644 --- a/net/portmapper/disabled_stubs.go +++ b/net/portmapper/disabled_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js diff --git a/net/portmapper/igd_test.go b/net/portmapper/igd_test.go index 77015f5bfb189..9426790639563 100644 --- a/net/portmapper/igd_test.go +++ b/net/portmapper/igd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/legacy_upnp.go b/net/portmapper/legacy_upnp.go index 2ce92dc65d6b3..ed2c23a04a975 100644 --- a/net/portmapper/legacy_upnp.go +++ b/net/portmapper/legacy_upnp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/net/portmapper/pcp.go b/net/portmapper/pcp.go index d0752734e8752..0332295b8cfa0 100644 --- a/net/portmapper/pcp.go +++ b/net/portmapper/pcp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/pcp_test.go b/net/portmapper/pcp_test.go index 8f8eef3ef8399..ef2621f7dc401 100644 --- a/net/portmapper/pcp_test.go +++ b/net/portmapper/pcp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/pcpresultcode_string.go b/net/portmapper/pcpresultcode_string.go index 45eb70d39fa06..8ffd5beae0604 100644 --- a/net/portmapper/pcpresultcode_string.go +++ b/net/portmapper/pcpresultcode_string.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by "stringer -type=pcpResultCode -trimprefix=pcpCode"; DO NOT EDIT. diff --git a/net/portmapper/pmpresultcode_string.go b/net/portmapper/pmpresultcode_string.go index 18d911d944126..f32626328fdec 100644 --- a/net/portmapper/pmpresultcode_string.go +++ b/net/portmapper/pmpresultcode_string.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by "stringer -type=pmpResultCode -trimprefix=pmpCode"; DO NOT EDIT. diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index 16a981d1d8336..37d7730c51f0d 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmapper is a UDP port mapping client. It currently allows for mapping over diff --git a/net/portmapper/portmapper_test.go b/net/portmapper/portmapper_test.go index a697a39089635..beb14cb8074eb 100644 --- a/net/portmapper/portmapper_test.go +++ b/net/portmapper/portmapper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/portmappertype/portmappertype.go b/net/portmapper/portmappertype/portmappertype.go index cc8358a4aed12..3b756e0ed04b2 100644 --- a/net/portmapper/portmappertype/portmappertype.go +++ b/net/portmapper/portmappertype/portmappertype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package portmappertype defines the net/portmapper interface, which may or may not be diff --git a/net/portmapper/select_test.go b/net/portmapper/select_test.go index cc685bc253d3d..b7370c24139f1 100644 --- a/net/portmapper/select_test.go +++ b/net/portmapper/select_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/portmapper/upnp.go b/net/portmapper/upnp.go index 46d7ff70215fd..e3971a2ae0f97 100644 --- a/net/portmapper/upnp.go +++ b/net/portmapper/upnp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js diff --git a/net/portmapper/upnp_test.go b/net/portmapper/upnp_test.go index a954b2beac094..15b03517708e4 100644 --- a/net/portmapper/upnp_test.go +++ b/net/portmapper/upnp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portmapper diff --git a/net/proxymux/mux.go b/net/proxymux/mux.go index ff5aaff3b975f..d9c57cd76ecf5 100644 --- a/net/proxymux/mux.go +++ b/net/proxymux/mux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package proxymux splits a net.Listener in two, routing SOCKS5 diff --git a/net/proxymux/mux_test.go b/net/proxymux/mux_test.go index 29166f9966bbc..6e84e57d8ef80 100644 --- a/net/proxymux/mux_test.go +++ b/net/proxymux/mux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package proxymux diff --git a/net/routetable/routetable.go b/net/routetable/routetable.go index 2884706f109a1..bfa62af7b3ce3 100644 --- a/net/routetable/routetable.go +++ b/net/routetable/routetable.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package routetable provides functions that operate on the system's route diff --git a/net/routetable/routetable_bsd.go b/net/routetable/routetable_bsd.go index 1de1a2734ce6c..7a6bf48cc96e8 100644 --- a/net/routetable/routetable_bsd.go +++ b/net/routetable/routetable_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd diff --git a/net/routetable/routetable_bsd_test.go b/net/routetable/routetable_bsd_test.go index 29493d59bdc36..df515c5788681 100644 --- a/net/routetable/routetable_bsd_test.go +++ b/net/routetable/routetable_bsd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd diff --git a/net/routetable/routetable_darwin.go b/net/routetable/routetable_darwin.go index 7f525ae32807a..5c143f0c1d7eb 100644 --- a/net/routetable/routetable_darwin.go +++ b/net/routetable/routetable_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/net/routetable/routetable_freebsd.go b/net/routetable/routetable_freebsd.go index 8e57a330246ed..313febf3ca94d 100644 --- a/net/routetable/routetable_freebsd.go +++ b/net/routetable/routetable_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build freebsd diff --git a/net/routetable/routetable_linux.go b/net/routetable/routetable_linux.go index 0b2cb305d7154..479aa8fd8f0af 100644 --- a/net/routetable/routetable_linux.go +++ b/net/routetable/routetable_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/net/routetable/routetable_linux_test.go b/net/routetable/routetable_linux_test.go index bbf7790e787ca..4d03b7f9d5466 100644 --- a/net/routetable/routetable_linux_test.go +++ b/net/routetable/routetable_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/routetable/routetable_other.go b/net/routetable/routetable_other.go index e547ab0ac769a..da162c3f8e191 100644 --- a/net/routetable/routetable_other.go +++ b/net/routetable/routetable_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build android || (!linux && !darwin && !freebsd) diff --git a/net/sockopts/sockopts.go b/net/sockopts/sockopts.go index 0c0ee7692cf6a..aa10d977f6468 100644 --- a/net/sockopts/sockopts.go +++ b/net/sockopts/sockopts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sockopts contains logic for applying socket options. diff --git a/net/sockopts/sockopts_default.go b/net/sockopts/sockopts_default.go index 3cc8679b512c1..6b728d34c6a42 100644 --- a/net/sockopts/sockopts_default.go +++ b/net/sockopts/sockopts_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/sockopts/sockopts_linux.go b/net/sockopts/sockopts_linux.go index 5d778d380f5c9..216c589225d39 100644 --- a/net/sockopts/sockopts_linux.go +++ b/net/sockopts/sockopts_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/sockopts/sockopts_notwindows.go b/net/sockopts/sockopts_notwindows.go index f1bc7fd442ee1..880860a58036f 100644 --- a/net/sockopts/sockopts_notwindows.go +++ b/net/sockopts/sockopts_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/sockopts/sockopts_unix_test.go b/net/sockopts/sockopts_unix_test.go index ebb4354ac1385..d474326a14df8 100644 --- a/net/sockopts/sockopts_unix_test.go +++ b/net/sockopts/sockopts_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/net/sockopts/sockopts_windows.go b/net/sockopts/sockopts_windows.go index 1e6c3f69d3af5..9533fd2a4ca9f 100644 --- a/net/sockopts/sockopts_windows.go +++ b/net/sockopts/sockopts_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/net/socks5/socks5.go b/net/socks5/socks5.go index 2e277147bc50d..729fc8e882cf1 100644 --- a/net/socks5/socks5.go +++ b/net/socks5/socks5.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package socks5 is a SOCKS5 server implementation. diff --git a/net/socks5/socks5_test.go b/net/socks5/socks5_test.go index bc6fac79fdcf9..9fbc11f8c0dfb 100644 --- a/net/socks5/socks5_test.go +++ b/net/socks5/socks5_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package socks5 diff --git a/net/sockstats/sockstats.go b/net/sockstats/sockstats.go index 715c1ee06e9a9..14a58d19d800d 100644 --- a/net/sockstats/sockstats.go +++ b/net/sockstats/sockstats.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sockstats collects statistics about network sockets used by diff --git a/net/sockstats/sockstats_noop.go b/net/sockstats/sockstats_noop.go index 96723111ade7a..b586a04cbee29 100644 --- a/net/sockstats/sockstats_noop.go +++ b/net/sockstats/sockstats_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !tailscale_go || !(darwin || ios || android || ts_enable_sockstats) diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go index 4e9f4a9666308..46ac75c990d48 100644 --- a/net/sockstats/sockstats_tsgo.go +++ b/net/sockstats/sockstats_tsgo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && (darwin || ios || android || ts_enable_sockstats) diff --git a/net/sockstats/sockstats_tsgo_darwin.go b/net/sockstats/sockstats_tsgo_darwin.go index 321d32e04e5f0..e79ff9ee3a02c 100644 --- a/net/sockstats/sockstats_tsgo_darwin.go +++ b/net/sockstats/sockstats_tsgo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && (darwin || ios) diff --git a/net/sockstats/sockstats_tsgo_test.go b/net/sockstats/sockstats_tsgo_test.go index c467c8a70ff79..b06ffa8946c44 100644 --- a/net/sockstats/sockstats_tsgo_test.go +++ b/net/sockstats/sockstats_tsgo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && (darwin || ios || android || ts_enable_sockstats) diff --git a/net/speedtest/speedtest.go b/net/speedtest/speedtest.go index a462dbeece42b..8b887a8ef8b0b 100644 --- a/net/speedtest/speedtest.go +++ b/net/speedtest/speedtest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package speedtest contains both server and client code for diff --git a/net/speedtest/speedtest_client.go b/net/speedtest/speedtest_client.go index 299a12a8dfaec..099eb48549975 100644 --- a/net/speedtest/speedtest_client.go +++ b/net/speedtest/speedtest_client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package speedtest diff --git a/net/speedtest/speedtest_server.go b/net/speedtest/speedtest_server.go index 72f85fa15b019..6b6f53b7da5a9 100644 --- a/net/speedtest/speedtest_server.go +++ b/net/speedtest/speedtest_server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package speedtest diff --git a/net/speedtest/speedtest_test.go b/net/speedtest/speedtest_test.go index bb8f2676af8c3..1fbd0915b219f 100644 --- a/net/speedtest/speedtest_test.go +++ b/net/speedtest/speedtest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package speedtest diff --git a/net/stun/stun.go b/net/stun/stun.go index eeac23cbbd45d..7d75e79b8e732 100644 --- a/net/stun/stun.go +++ b/net/stun/stun.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package STUN generates STUN request packets and parses response packets. diff --git a/net/stun/stun_fuzzer.go b/net/stun/stun_fuzzer.go index 6f0c9e3b0beae..b7e3198df873d 100644 --- a/net/stun/stun_fuzzer.go +++ b/net/stun/stun_fuzzer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build gofuzz diff --git a/net/stun/stun_test.go b/net/stun/stun_test.go index 05fc4d2ba727f..7f754324e7597 100644 --- a/net/stun/stun_test.go +++ b/net/stun/stun_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package stun_test diff --git a/net/stun/stuntest/stuntest.go b/net/stun/stuntest/stuntest.go index 09684160055fb..0d3988ce800a9 100644 --- a/net/stun/stuntest/stuntest.go +++ b/net/stun/stuntest/stuntest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package stuntest provides a STUN test server. diff --git a/net/stunserver/stunserver.go b/net/stunserver/stunserver.go index 7397675ca8dc3..97df8cb4d79e9 100644 --- a/net/stunserver/stunserver.go +++ b/net/stunserver/stunserver.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package stunserver implements a STUN server. The package publishes a number of stats diff --git a/net/stunserver/stunserver_test.go b/net/stunserver/stunserver_test.go index 24a7bb570b6bd..c96aea4d15973 100644 --- a/net/stunserver/stunserver_test.go +++ b/net/stunserver/stunserver_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package stunserver diff --git a/net/tcpinfo/tcpinfo.go b/net/tcpinfo/tcpinfo.go index a757add9f8f46..3e2d76a9529fa 100644 --- a/net/tcpinfo/tcpinfo.go +++ b/net/tcpinfo/tcpinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tcpinfo provides platform-agnostic accessors to information about a diff --git a/net/tcpinfo/tcpinfo_darwin.go b/net/tcpinfo/tcpinfo_darwin.go index 53fa22fbf5bed..3e53cd4ed0c8d 100644 --- a/net/tcpinfo/tcpinfo_darwin.go +++ b/net/tcpinfo/tcpinfo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tcpinfo diff --git a/net/tcpinfo/tcpinfo_linux.go b/net/tcpinfo/tcpinfo_linux.go index 885d462c95e35..1ff0c1bc4f539 100644 --- a/net/tcpinfo/tcpinfo_linux.go +++ b/net/tcpinfo/tcpinfo_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tcpinfo diff --git a/net/tcpinfo/tcpinfo_other.go b/net/tcpinfo/tcpinfo_other.go index be45523aeb00d..c7d0f9177af2f 100644 --- a/net/tcpinfo/tcpinfo_other.go +++ b/net/tcpinfo/tcpinfo_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !darwin diff --git a/net/tcpinfo/tcpinfo_test.go b/net/tcpinfo/tcpinfo_test.go index bb3d224ec1beb..6baac934a00f0 100644 --- a/net/tcpinfo/tcpinfo_test.go +++ b/net/tcpinfo/tcpinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tcpinfo diff --git a/net/tlsdial/blockblame/blockblame.go b/net/tlsdial/blockblame/blockblame.go index 5b48dc009b980..f2d7db27c1a5e 100644 --- a/net/tlsdial/blockblame/blockblame.go +++ b/net/tlsdial/blockblame/blockblame.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package blockblame blames specific firewall manufacturers for blocking Tailscale, diff --git a/net/tlsdial/blockblame/blockblame_test.go b/net/tlsdial/blockblame/blockblame_test.go index 6d3592c60a3de..3d08bf811601c 100644 --- a/net/tlsdial/blockblame/blockblame_test.go +++ b/net/tlsdial/blockblame/blockblame_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package blockblame diff --git a/net/tlsdial/deps_test.go b/net/tlsdial/deps_test.go index 7a93899c2f126..3600af537cd85 100644 --- a/net/tlsdial/deps_test.go +++ b/net/tlsdial/deps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build for_go_mod_tidy_only diff --git a/net/tlsdial/tlsdial.go b/net/tlsdial/tlsdial.go index ee4771d8db613..ffc8c90a80f96 100644 --- a/net/tlsdial/tlsdial.go +++ b/net/tlsdial/tlsdial.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tlsdial generates tls.Config values and does x509 validation of diff --git a/net/tlsdial/tlsdial_test.go b/net/tlsdial/tlsdial_test.go index a288d765306e1..9ef0f76884c53 100644 --- a/net/tlsdial/tlsdial_test.go +++ b/net/tlsdial/tlsdial_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tlsdial diff --git a/net/tsaddr/tsaddr.go b/net/tsaddr/tsaddr.go index 06e6a26ddb721..1eac9eb77cfde 100644 --- a/net/tsaddr/tsaddr.go +++ b/net/tsaddr/tsaddr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsaddr handles Tailscale-specific IPs and ranges. diff --git a/net/tsaddr/tsaddr_test.go b/net/tsaddr/tsaddr_test.go index 9ac1ce3036299..ac5a07fff94f5 100644 --- a/net/tsaddr/tsaddr_test.go +++ b/net/tsaddr/tsaddr_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsaddr diff --git a/net/tsdial/dnsmap.go b/net/tsdial/dnsmap.go index 37fedd14c899d..d7204463f66ed 100644 --- a/net/tsdial/dnsmap.go +++ b/net/tsdial/dnsmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/dnsmap_test.go b/net/tsdial/dnsmap_test.go index 41a957f186f4a..b2a50fa0c4549 100644 --- a/net/tsdial/dnsmap_test.go +++ b/net/tsdial/dnsmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/dohclient.go b/net/tsdial/dohclient.go index d830398cdfb9c..59b0da04d25f4 100644 --- a/net/tsdial/dohclient.go +++ b/net/tsdial/dohclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/dohclient_test.go b/net/tsdial/dohclient_test.go index 23255769f4847..63e5dbd997826 100644 --- a/net/tsdial/dohclient_test.go +++ b/net/tsdial/dohclient_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsdial diff --git a/net/tsdial/peerapi_macios_ext.go b/net/tsdial/peerapi_macios_ext.go index 3ebead3db439f..fa40feef04524 100644 --- a/net/tsdial/peerapi_macios_ext.go +++ b/net/tsdial/peerapi_macios_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file's built on iOS and on two of three macOS build variants: diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go index df2d80a619752..ebbafa52b01e9 100644 --- a/net/tsdial/tsdial.go +++ b/net/tsdial/tsdial.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsdial provides a Dialer type that can dial out of tailscaled. diff --git a/net/tshttpproxy/mksyscall.go b/net/tshttpproxy/mksyscall.go index f8fdae89b55f0..37824c84653de 100644 --- a/net/tshttpproxy/mksyscall.go +++ b/net/tshttpproxy/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tshttpproxy diff --git a/net/tshttpproxy/tshttpproxy.go b/net/tshttpproxy/tshttpproxy.go index 0456009ed9a81..1ea444c8f5e99 100644 --- a/net/tshttpproxy/tshttpproxy.go +++ b/net/tshttpproxy/tshttpproxy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tshttpproxy contains Tailscale additions to httpproxy not available diff --git a/net/tshttpproxy/tshttpproxy_linux.go b/net/tshttpproxy/tshttpproxy_linux.go index 7e086e4929bc7..30096e214a982 100644 --- a/net/tshttpproxy/tshttpproxy_linux.go +++ b/net/tshttpproxy/tshttpproxy_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/tshttpproxy/tshttpproxy_synology.go b/net/tshttpproxy/tshttpproxy_synology.go index e28844f7dbf67..a632753f7bc1b 100644 --- a/net/tshttpproxy/tshttpproxy_synology.go +++ b/net/tshttpproxy/tshttpproxy_synology.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/tshttpproxy/tshttpproxy_synology_test.go b/net/tshttpproxy/tshttpproxy_synology_test.go index b6e8b948c3ae9..a57ac1558d4f4 100644 --- a/net/tshttpproxy/tshttpproxy_synology_test.go +++ b/net/tshttpproxy/tshttpproxy_synology_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/tshttpproxy/tshttpproxy_test.go b/net/tshttpproxy/tshttpproxy_test.go index 97f8c1f8b049a..da847429d4bd4 100644 --- a/net/tshttpproxy/tshttpproxy_test.go +++ b/net/tshttpproxy/tshttpproxy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tshttpproxy diff --git a/net/tshttpproxy/tshttpproxy_windows.go b/net/tshttpproxy/tshttpproxy_windows.go index 7163c786307ac..1a80be3ff370f 100644 --- a/net/tshttpproxy/tshttpproxy_windows.go +++ b/net/tshttpproxy/tshttpproxy_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tshttpproxy diff --git a/net/tstun/fake.go b/net/tstun/fake.go index 3d86bb3df4ca9..f7925116e80bd 100644 --- a/net/tstun/fake.go +++ b/net/tstun/fake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/ifstatus_noop.go b/net/tstun/ifstatus_noop.go index 8cf569f982010..420326c2fda38 100644 --- a/net/tstun/ifstatus_noop.go +++ b/net/tstun/ifstatus_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/tstun/ifstatus_windows.go b/net/tstun/ifstatus_windows.go index fd9fc2112524c..64c898fd3aef2 100644 --- a/net/tstun/ifstatus_windows.go +++ b/net/tstun/ifstatus_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/mtu.go b/net/tstun/mtu.go index 004529c205f9e..6eceb6833b964 100644 --- a/net/tstun/mtu.go +++ b/net/tstun/mtu.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/mtu_test.go b/net/tstun/mtu_test.go index ec31e45ce73f5..6129e0c140a85 100644 --- a/net/tstun/mtu_test.go +++ b/net/tstun/mtu_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/netstack_disabled.go b/net/tstun/netstack_disabled.go index c1266b30559d4..6425668a36c87 100644 --- a/net/tstun/netstack_disabled.go +++ b/net/tstun/netstack_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_netstack diff --git a/net/tstun/netstack_enabled.go b/net/tstun/netstack_enabled.go index 8fc1a2e20e35a..440013c7e9510 100644 --- a/net/tstun/netstack_enabled.go +++ b/net/tstun/netstack_enabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/net/tstun/tstun_stub.go b/net/tstun/tstun_stub.go index d21eda6b07a57..27d530bc8b95c 100644 --- a/net/tstun/tstun_stub.go +++ b/net/tstun/tstun_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build aix || solaris || illumos diff --git a/net/tstun/tun.go b/net/tstun/tun.go index 19b0a53f5be6c..42b0d239c39d4 100644 --- a/net/tstun/tun.go +++ b/net/tstun/tun.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !wasm && !tamago && !aix && !solaris && !illumos diff --git a/net/tstun/tun_linux.go b/net/tstun/tun_linux.go index 05cf58c17df8a..028e0a14b5bd8 100644 --- a/net/tstun/tun_linux.go +++ b/net/tstun/tun_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/tun_macos.go b/net/tstun/tun_macos.go index 3506f05b1e4c9..fb8eb9450fb7e 100644 --- a/net/tstun/tun_macos.go +++ b/net/tstun/tun_macos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/net/tstun/tun_notwindows.go b/net/tstun/tun_notwindows.go index 087fcd4eec784..73f80fea12ce8 100644 --- a/net/tstun/tun_notwindows.go +++ b/net/tstun/tun_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/net/tstun/tun_windows.go b/net/tstun/tun_windows.go index 2b1d3054e5ecb..96721021b2022 100644 --- a/net/tstun/tun_windows.go +++ b/net/tstun/tun_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index fe1bc31b812b4..d463948a208fa 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/tstun/wrap_linux.go b/net/tstun/wrap_linux.go index 7498f107b5fda..a4e76de5a9d20 100644 --- a/net/tstun/wrap_linux.go +++ b/net/tstun/wrap_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_gro diff --git a/net/tstun/wrap_noop.go b/net/tstun/wrap_noop.go index 8ad04bafe94c1..8f5b62d0cbcfe 100644 --- a/net/tstun/wrap_noop.go +++ b/net/tstun/wrap_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || ts_omit_gro diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 3bc2ff447422d..8515cb8f0a4c0 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstun diff --git a/net/udprelay/endpoint/endpoint.go b/net/udprelay/endpoint/endpoint.go index 0d2a14e965a4a..7b8368b615e52 100644 --- a/net/udprelay/endpoint/endpoint.go +++ b/net/udprelay/endpoint/endpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package endpoint contains types relating to UDP relay server endpoints. It diff --git a/net/udprelay/endpoint/endpoint_test.go b/net/udprelay/endpoint/endpoint_test.go index f12a6e2f62240..eaef289de6725 100644 --- a/net/udprelay/endpoint/endpoint_test.go +++ b/net/udprelay/endpoint/endpoint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package endpoint diff --git a/net/udprelay/metrics.go b/net/udprelay/metrics.go index 235029bf425ce..6e22acd03ce70 100644 --- a/net/udprelay/metrics.go +++ b/net/udprelay/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package udprelay diff --git a/net/udprelay/metrics_test.go b/net/udprelay/metrics_test.go index 0b7650534f884..da90550b88920 100644 --- a/net/udprelay/metrics_test.go +++ b/net/udprelay/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package udprelay diff --git a/net/udprelay/server.go b/net/udprelay/server.go index 38ee04df9e1ca..3d870904493ec 100644 --- a/net/udprelay/server.go +++ b/net/udprelay/server.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package udprelay contains constructs for relaying Disco and WireGuard packets diff --git a/net/udprelay/server_linux.go b/net/udprelay/server_linux.go index d4cf2a2b16ee9..3a734f9c75505 100644 --- a/net/udprelay/server_linux.go +++ b/net/udprelay/server_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/net/udprelay/server_notlinux.go b/net/udprelay/server_notlinux.go index f21020631f76e..027ffb7658aa2 100644 --- a/net/udprelay/server_notlinux.go +++ b/net/udprelay/server_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/net/udprelay/server_test.go b/net/udprelay/server_test.go index cb6b05eea2108..66de0d88a7d0d 100644 --- a/net/udprelay/server_test.go +++ b/net/udprelay/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package udprelay diff --git a/net/udprelay/status/status.go b/net/udprelay/status/status.go index 9ed9a0d2a8def..d64792ab6032e 100644 --- a/net/udprelay/status/status.go +++ b/net/udprelay/status/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package status contains types relating to the status of peer relay sessions diff --git a/net/wsconn/wsconn.go b/net/wsconn/wsconn.go index 9e44da59ca1d7..fed734cf5ffd8 100644 --- a/net/wsconn/wsconn.go +++ b/net/wsconn/wsconn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wsconn contains an adapter type that turns diff --git a/omit/aws_def.go b/omit/aws_def.go index 8ae539736b28c..7f48881c10bcf 100644 --- a/omit/aws_def.go +++ b/omit/aws_def.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_aws diff --git a/omit/aws_omit.go b/omit/aws_omit.go index 5b6957d5b639b..f077041158f0d 100644 --- a/omit/aws_omit.go +++ b/omit/aws_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_aws diff --git a/omit/omit.go b/omit/omit.go index 018cfba94545c..e59d4fc3c61f7 100644 --- a/omit/omit.go +++ b/omit/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package omit provides consts to access Tailscale ts_omit_FOO build tags. diff --git a/packages/deb/deb.go b/packages/deb/deb.go index cab0fea075e74..63f30fc9d7d4f 100644 --- a/packages/deb/deb.go +++ b/packages/deb/deb.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package deb extracts metadata from Debian packages. diff --git a/packages/deb/deb_test.go b/packages/deb/deb_test.go index 1a25f67ad4875..fb8a6454c3ab7 100644 --- a/packages/deb/deb_test.go +++ b/packages/deb/deb_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deb diff --git a/paths/migrate.go b/paths/migrate.go index 3a23ecca34fdc..22f947611f4cd 100644 --- a/paths/migrate.go +++ b/paths/migrate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package paths diff --git a/paths/paths.go b/paths/paths.go index 6c9c3fa6c9dea..398d8b23d8988 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package paths returns platform and user-specific default paths to diff --git a/paths/paths_unix.go b/paths/paths_unix.go index d317921d59cd9..b1556b233104f 100644 --- a/paths/paths_unix.go +++ b/paths/paths_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !wasm && !plan9 && !tamago diff --git a/paths/paths_windows.go b/paths/paths_windows.go index 4705400655212..850a1c97b52a0 100644 --- a/paths/paths_windows.go +++ b/paths/paths_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package paths diff --git a/pkgdoc_test.go b/pkgdoc_test.go index 0f4a455288950..b3a902bf41f4b 100644 --- a/pkgdoc_test.go +++ b/pkgdoc_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot diff --git a/portlist/clean.go b/portlist/clean.go index 7e137de948e99..f6c3f4a6b3587 100644 --- a/portlist/clean.go +++ b/portlist/clean.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/clean_test.go b/portlist/clean_test.go index 5a1e34405eed0..e7a5f6a0ca4ac 100644 --- a/portlist/clean_test.go +++ b/portlist/clean_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/netstat.go b/portlist/netstat.go index 5fdef675d0e2a..de625afb52170 100644 --- a/portlist/netstat.go +++ b/portlist/netstat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/portlist/netstat_test.go b/portlist/netstat_test.go index 023b75b794426..7048e90b2ffea 100644 --- a/portlist/netstat_test.go +++ b/portlist/netstat_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/portlist/poller.go b/portlist/poller.go index 423bad3be33ba..a8e611054eb1b 100644 --- a/portlist/poller.go +++ b/portlist/poller.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains the code related to the Poller type and its methods. diff --git a/portlist/portlist.go b/portlist/portlist.go index 9f7af40d08dc1..9430e2562268b 100644 --- a/portlist/portlist.go +++ b/portlist/portlist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file is just the types. The bulk of the code is in poller.go. diff --git a/portlist/portlist_linux.go b/portlist/portlist_linux.go index 94f843746c29d..159c4beb3a74a 100644 --- a/portlist/portlist_linux.go +++ b/portlist/portlist_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_linux_test.go b/portlist/portlist_linux_test.go index 24635fae26577..4b541f8e7dd70 100644 --- a/portlist/portlist_linux_test.go +++ b/portlist/portlist_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_macos.go b/portlist/portlist_macos.go index e67b2c9b8c064..d210fdd946de1 100644 --- a/portlist/portlist_macos.go +++ b/portlist/portlist_macos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/portlist/portlist_plan9.go b/portlist/portlist_plan9.go index 77f8619f97ffa..62ed61fb3ddea 100644 --- a/portlist/portlist_plan9.go +++ b/portlist/portlist_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_test.go b/portlist/portlist_test.go index 8503b0fefdf50..5e0964b248882 100644 --- a/portlist/portlist_test.go +++ b/portlist/portlist_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/portlist/portlist_windows.go b/portlist/portlist_windows.go index f449973599247..bd603dbfd8f91 100644 --- a/portlist/portlist_windows.go +++ b/portlist/portlist_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package portlist diff --git a/posture/doc.go b/posture/doc.go index d061065235b99..14fd21998647e 100644 --- a/posture/doc.go +++ b/posture/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package posture contains functions to query the local system diff --git a/posture/hwaddr.go b/posture/hwaddr.go index dd0b6d8be77ce..2075331f16727 100644 --- a/posture/hwaddr.go +++ b/posture/hwaddr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package posture diff --git a/posture/serialnumber_macos.go b/posture/serialnumber_macos.go index 18c929107a768..fed0d4111fb83 100644 --- a/posture/serialnumber_macos.go +++ b/posture/serialnumber_macos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo && darwin && !ios diff --git a/posture/serialnumber_macos_test.go b/posture/serialnumber_macos_test.go index 9d9b9f578da55..5f1aec5cd790b 100644 --- a/posture/serialnumber_macos_test.go +++ b/posture/serialnumber_macos_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo && darwin && !ios diff --git a/posture/serialnumber_notmacos.go b/posture/serialnumber_notmacos.go index 132fa08f6a56e..e076b8f3dcfdf 100644 --- a/posture/serialnumber_notmacos.go +++ b/posture/serialnumber_notmacos.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Build on Windows, Linux and *BSD diff --git a/posture/serialnumber_notmacos_test.go b/posture/serialnumber_notmacos_test.go index da5aada8509e3..1009ea6b4a208 100644 --- a/posture/serialnumber_notmacos_test.go +++ b/posture/serialnumber_notmacos_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Build on Windows, Linux and *BSD diff --git a/posture/serialnumber_stub.go b/posture/serialnumber_stub.go index 854a0014bd1bf..e040aacfb30e2 100644 --- a/posture/serialnumber_stub.go +++ b/posture/serialnumber_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // js: not implemented diff --git a/posture/serialnumber_syspolicy.go b/posture/serialnumber_syspolicy.go index 64a154a2cae0b..448fdb677abef 100644 --- a/posture/serialnumber_syspolicy.go +++ b/posture/serialnumber_syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build android || ios diff --git a/posture/serialnumber_test.go b/posture/serialnumber_test.go index 6db3651e21cd7..20e726d9ff530 100644 --- a/posture/serialnumber_test.go +++ b/posture/serialnumber_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package posture diff --git a/prober/derp.go b/prober/derp.go index 22843b53a4049..73ea02cf5ad4f 100644 --- a/prober/derp.go +++ b/prober/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/derp_test.go b/prober/derp_test.go index 08a65d6978f13..364d57481ae20 100644 --- a/prober/derp_test.go +++ b/prober/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/dns.go b/prober/dns.go index 77e22ea3f89ba..cfef252716ae9 100644 --- a/prober/dns.go +++ b/prober/dns.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/dns_example_test.go b/prober/dns_example_test.go index 089816919489a..625ecec0c1411 100644 --- a/prober/dns_example_test.go +++ b/prober/dns_example_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober_test diff --git a/prober/dns_test.go b/prober/dns_test.go index 1b6c31b554877..4eaea199ae289 100644 --- a/prober/dns_test.go +++ b/prober/dns_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/histogram.go b/prober/histogram.go index c544a5f79bb17..5c52894f9eb02 100644 --- a/prober/histogram.go +++ b/prober/histogram.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/histogram_test.go b/prober/histogram_test.go index dbb5eda6741a5..2c7deea354a89 100644 --- a/prober/histogram_test.go +++ b/prober/histogram_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/http.go b/prober/http.go index e4b0b26fd3e7d..144ed3fb55195 100644 --- a/prober/http.go +++ b/prober/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/prober.go b/prober/prober.go index 6b904dd97d231..16c262bc81c0d 100644 --- a/prober/prober.go +++ b/prober/prober.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prober implements a simple blackbox prober. Each probe runs diff --git a/prober/prober_test.go b/prober/prober_test.go index 1e045fa8971b0..c945f617a6633 100644 --- a/prober/prober_test.go +++ b/prober/prober_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/status.go b/prober/status.go index 20fbeec58a77e..a06d3d55c4e6c 100644 --- a/prober/status.go +++ b/prober/status.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tcp.go b/prober/tcp.go index 22d05461652a4..f932be44553ab 100644 --- a/prober/tcp.go +++ b/prober/tcp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tls.go b/prober/tls.go index 3ce5354357d71..1247f9502e8f6 100644 --- a/prober/tls.go +++ b/prober/tls.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tls_test.go b/prober/tls_test.go index 86fba91b98836..a32693762f291 100644 --- a/prober/tls_test.go +++ b/prober/tls_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prober diff --git a/prober/tun_darwin.go b/prober/tun_darwin.go index 0ef22e41e4076..45c5415acd8d0 100644 --- a/prober/tun_darwin.go +++ b/prober/tun_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/prober/tun_default.go b/prober/tun_default.go index 93a5b07fd442a..2094e19933c06 100644 --- a/prober/tun_default.go +++ b/prober/tun_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !darwin diff --git a/prober/tun_linux.go b/prober/tun_linux.go index 52a31efbbf66a..7a28a4b3f829e 100644 --- a/prober/tun_linux.go +++ b/prober/tun_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/proxymap/proxymap.go b/proxymap/proxymap.go index 20dc96c848307..2407371513d89 100644 --- a/proxymap/proxymap.go +++ b/proxymap/proxymap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package proxymap contains a mapping table for ephemeral localhost ports used diff --git a/release/dist/cli/cli.go b/release/dist/cli/cli.go index f4480cbdbdfa4..ca4977f5d2cbe 100644 --- a/release/dist/cli/cli.go +++ b/release/dist/cli/cli.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cli provides the skeleton of a CLI for building release packages. diff --git a/release/dist/dist.go b/release/dist/dist.go index 6fb0102993cbd..094d0a0e04c46 100644 --- a/release/dist/dist.go +++ b/release/dist/dist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dist is a release artifact builder library. diff --git a/release/dist/memoize.go b/release/dist/memoize.go index 0927ac0a81540..bdf0f68ff9fd6 100644 --- a/release/dist/memoize.go +++ b/release/dist/memoize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dist diff --git a/release/dist/qnap/pkgs.go b/release/dist/qnap/pkgs.go index 5062011f06ea6..1d69b3eaf3500 100644 --- a/release/dist/qnap/pkgs.go +++ b/release/dist/qnap/pkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package qnap contains dist Targets for building QNAP Tailscale packages. diff --git a/release/dist/qnap/targets.go b/release/dist/qnap/targets.go index 0a02139548b17..3eef3cbbe693d 100644 --- a/release/dist/qnap/targets.go +++ b/release/dist/qnap/targets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package qnap diff --git a/release/dist/synology/pkgs.go b/release/dist/synology/pkgs.go index ab89dbee3e19f..c2fe6528e4dfe 100644 --- a/release/dist/synology/pkgs.go +++ b/release/dist/synology/pkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package synology contains dist Targets for building Synology Tailscale packages. diff --git a/release/dist/synology/targets.go b/release/dist/synology/targets.go index bc7b20afca5d3..2f08510557d8d 100644 --- a/release/dist/synology/targets.go +++ b/release/dist/synology/targets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package synology diff --git a/release/dist/unixpkgs/pkgs.go b/release/dist/unixpkgs/pkgs.go index bad6ce572e675..d251ff621f98a 100644 --- a/release/dist/unixpkgs/pkgs.go +++ b/release/dist/unixpkgs/pkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package unixpkgs contains dist Targets for building unix Tailscale packages. diff --git a/release/dist/unixpkgs/targets.go b/release/dist/unixpkgs/targets.go index 42bab6d3b2685..b5f96fc38b3f0 100644 --- a/release/dist/unixpkgs/targets.go +++ b/release/dist/unixpkgs/targets.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package unixpkgs diff --git a/release/release.go b/release/release.go index a8d0e6b62e8d7..314bb0d8e7473 100644 --- a/release/release.go +++ b/release/release.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package release provides functionality for building client releases. diff --git a/safesocket/basic_test.go b/safesocket/basic_test.go index 292a3438a0e75..9cef300497422 100644 --- a/safesocket/basic_test.go +++ b/safesocket/basic_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/pipe_windows.go b/safesocket/pipe_windows.go index 2968542f2ccf4..0ffee762f8840 100644 --- a/safesocket/pipe_windows.go +++ b/safesocket/pipe_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/pipe_windows_test.go b/safesocket/pipe_windows_test.go index 8d9cbd19b5e43..5d4e68cc251c9 100644 --- a/safesocket/pipe_windows_test.go +++ b/safesocket/pipe_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket.go b/safesocket/safesocket.go index 287cdca599f77..6be8ae5b8fac3 100644 --- a/safesocket/safesocket.go +++ b/safesocket/safesocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package safesocket creates either a Unix socket, if possible, or diff --git a/safesocket/safesocket_darwin.go b/safesocket/safesocket_darwin.go index e2b3ea4581059..8cbabff63364e 100644 --- a/safesocket/safesocket_darwin.go +++ b/safesocket/safesocket_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket_darwin_test.go b/safesocket/safesocket_darwin_test.go index e52959ad58dcf..d828a80f5dfe2 100644 --- a/safesocket/safesocket_darwin_test.go +++ b/safesocket/safesocket_darwin_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket_js.go b/safesocket/safesocket_js.go index 38e615da43535..746fea51115c4 100644 --- a/safesocket/safesocket_js.go +++ b/safesocket/safesocket_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/safesocket_plan9.go b/safesocket/safesocket_plan9.go index c8a5e3b05bbef..921e758748d5c 100644 --- a/safesocket/safesocket_plan9.go +++ b/safesocket/safesocket_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build plan9 diff --git a/safesocket/safesocket_ps.go b/safesocket/safesocket_ps.go index d3f409df58d15..6130ca5b0d574 100644 --- a/safesocket/safesocket_ps.go +++ b/safesocket/safesocket_ps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ((linux && !android) || windows || (darwin && !ios) || freebsd) && !ts_omit_cliconndiag diff --git a/safesocket/safesocket_test.go b/safesocket/safesocket_test.go index 3f36a1cf6ca1f..be2dd193d8e32 100644 --- a/safesocket/safesocket_test.go +++ b/safesocket/safesocket_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safesocket diff --git a/safesocket/unixsocket.go b/safesocket/unixsocket.go index ec8635bbbf0d7..6fe3883c32c9d 100644 --- a/safesocket/unixsocket.go +++ b/safesocket/unixsocket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !js && !plan9 diff --git a/safeweb/http.go b/safeweb/http.go index d085fcb8819d8..f76591cbd0e16 100644 --- a/safeweb/http.go +++ b/safeweb/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package safeweb provides a wrapper around an http.Server that applies diff --git a/safeweb/http_test.go b/safeweb/http_test.go index 852ce326ba374..cbac7210a4807 100644 --- a/safeweb/http_test.go +++ b/safeweb/http_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safeweb diff --git a/scripts/installer.sh b/scripts/installer.sh index 76e8943e9931f..8ffd3f5720a2d 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # # This script detects the current operating system, and installs diff --git a/sessionrecording/connect.go b/sessionrecording/connect.go index 9d20b41f9b31a..6135688ca0153 100644 --- a/sessionrecording/connect.go +++ b/sessionrecording/connect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sessionrecording contains session recording utils shared amongst diff --git a/sessionrecording/connect_test.go b/sessionrecording/connect_test.go index e834828f5a6cc..64bcb1c3185d3 100644 --- a/sessionrecording/connect_test.go +++ b/sessionrecording/connect_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sessionrecording diff --git a/sessionrecording/event.go b/sessionrecording/event.go index 8f8172cc4b303..0597048a2113f 100644 --- a/sessionrecording/event.go +++ b/sessionrecording/event.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sessionrecording diff --git a/sessionrecording/header.go b/sessionrecording/header.go index 2208522168dec..95b70962cefd8 100644 --- a/sessionrecording/header.go +++ b/sessionrecording/header.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sessionrecording diff --git a/ssh/tailssh/accept_env.go b/ssh/tailssh/accept_env.go index 6461a79a3408b..6354d41d76a6b 100644 --- a/ssh/tailssh/accept_env.go +++ b/ssh/tailssh/accept_env.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailssh diff --git a/ssh/tailssh/accept_env_test.go b/ssh/tailssh/accept_env_test.go index b54c980978ece..25787db302357 100644 --- a/ssh/tailssh/accept_env_test.go +++ b/ssh/tailssh/accept_env_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailssh diff --git a/ssh/tailssh/auditd_linux.go b/ssh/tailssh/auditd_linux.go index e9f551d9e7991..bddb901d5cebe 100644 --- a/ssh/tailssh/auditd_linux.go +++ b/ssh/tailssh/auditd_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/ssh/tailssh/auditd_linux_test.go b/ssh/tailssh/auditd_linux_test.go index 93f5442918a98..c3c2302fe9e66 100644 --- a/ssh/tailssh/auditd_linux_test.go +++ b/ssh/tailssh/auditd_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/ssh/tailssh/incubator.go b/ssh/tailssh/incubator.go index f75646771057a..b414ce3fbf42a 100644 --- a/ssh/tailssh/incubator.go +++ b/ssh/tailssh/incubator.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains the code for the incubator process. Tailscaled diff --git a/ssh/tailssh/incubator_linux.go b/ssh/tailssh/incubator_linux.go index 4dfb9f27cc097..cff46160758f3 100644 --- a/ssh/tailssh/incubator_linux.go +++ b/ssh/tailssh/incubator_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/ssh/tailssh/incubator_plan9.go b/ssh/tailssh/incubator_plan9.go index 61b6a54ebdc94..69112635f5c11 100644 --- a/ssh/tailssh/incubator_plan9.go +++ b/ssh/tailssh/incubator_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains the plan9-specific version of the incubator. Tailscaled diff --git a/ssh/tailssh/privs_test.go b/ssh/tailssh/privs_test.go index 32b219a7798ca..f0ec66c64e581 100644 --- a/ssh/tailssh/privs_test.go +++ b/ssh/tailssh/privs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin || freebsd || openbsd || netbsd || dragonfly diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 91e1779bfd543..9d5a7d2a880db 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9 diff --git a/ssh/tailssh/tailssh_integration_test.go b/ssh/tailssh/tailssh_integration_test.go index 9ab26e169665b..1135bebbc2a5b 100644 --- a/ssh/tailssh/tailssh_integration_test.go +++ b/ssh/tailssh/tailssh_integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build integrationtest diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index 3b6d3c52c391c..f91cbafe72213 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin diff --git a/ssh/tailssh/user.go b/ssh/tailssh/user.go index ac92c762a875e..7da6bb4eb387f 100644 --- a/ssh/tailssh/user.go +++ b/ssh/tailssh/user.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9 diff --git a/syncs/locked.go b/syncs/locked.go index d2e9edef7a9dd..5c94e6336fb7a 100644 --- a/syncs/locked.go +++ b/syncs/locked.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/locked_test.go b/syncs/locked_test.go index 90b36e8321d82..94481f9cb2205 100644 --- a/syncs/locked_test.go +++ b/syncs/locked_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build go1.13 && !go1.19 diff --git a/syncs/mutex.go b/syncs/mutex.go index 8034e17121717..cb60c3432b5cc 100644 --- a/syncs/mutex.go +++ b/syncs/mutex.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_mutex_debug diff --git a/syncs/mutex_debug.go b/syncs/mutex_debug.go index 55a9b1231092f..7af1e9abfe12d 100644 --- a/syncs/mutex_debug.go +++ b/syncs/mutex_debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_mutex_debug diff --git a/syncs/pool.go b/syncs/pool.go index 46ffd2e521783..9a13dd526f55d 100644 --- a/syncs/pool.go +++ b/syncs/pool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/pool_test.go b/syncs/pool_test.go index 798b18cbabfd8..34ca9973ff334 100644 --- a/syncs/pool_test.go +++ b/syncs/pool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardedint.go b/syncs/shardedint.go index 28c4168d54c79..c0fda341fb564 100644 --- a/syncs/shardedint.go +++ b/syncs/shardedint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardedint_test.go b/syncs/shardedint_test.go index 815a739d13842..8c3f7ef7bd915 100644 --- a/syncs/shardedint_test.go +++ b/syncs/shardedint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs_test diff --git a/syncs/shardedmap.go b/syncs/shardedmap.go index 12edf5bfce475..6f53522360464 100644 --- a/syncs/shardedmap.go +++ b/syncs/shardedmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardedmap_test.go b/syncs/shardedmap_test.go index 993ffdff875c2..0491bf3dd1fbf 100644 --- a/syncs/shardedmap_test.go +++ b/syncs/shardedmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardvalue.go b/syncs/shardvalue.go index b1474477c7082..fcb5d3c73207e 100644 --- a/syncs/shardvalue.go +++ b/syncs/shardvalue.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/shardvalue_go.go b/syncs/shardvalue_go.go index 9b9d252a796d4..8531993319d1e 100644 --- a/syncs/shardvalue_go.go +++ b/syncs/shardvalue_go.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !tailscale_go diff --git a/syncs/shardvalue_tailscale.go b/syncs/shardvalue_tailscale.go index 8ef778ff3e669..6b03d7d0dd58e 100644 --- a/syncs/shardvalue_tailscale.go +++ b/syncs/shardvalue_tailscale.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO(raggi): update build tag after toolchain update diff --git a/syncs/shardvalue_test.go b/syncs/shardvalue_test.go index 8f6ac6414dee7..1dd0a542e60c2 100644 --- a/syncs/shardvalue_test.go +++ b/syncs/shardvalue_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/syncs/syncs.go b/syncs/syncs.go index 3b37bca085c89..d447b2e7bc4e0 100644 --- a/syncs/syncs.go +++ b/syncs/syncs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package syncs contains additional sync types and functionality. diff --git a/syncs/syncs_test.go b/syncs/syncs_test.go index a546b8d0a2343..81fcccbf63aca 100644 --- a/syncs/syncs_test.go +++ b/syncs/syncs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syncs diff --git a/tailcfg/c2ntypes.go b/tailcfg/c2ntypes.go index d78baef1c29a4..d3f5755e81ba1 100644 --- a/tailcfg/c2ntypes.go +++ b/tailcfg/c2ntypes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // c2n (control-to-node) API types. diff --git a/tailcfg/derpmap.go b/tailcfg/derpmap.go index e05559f3ed7f1..c18b04ea11342 100644 --- a/tailcfg/derpmap.go +++ b/tailcfg/derpmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tailcfg/proto_port_range.go b/tailcfg/proto_port_range.go index 03505dbd131e7..63012e93b2b8e 100644 --- a/tailcfg/proto_port_range.go +++ b/tailcfg/proto_port_range.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tailcfg/proto_port_range_test.go b/tailcfg/proto_port_range_test.go index 59ccc9be4a1a8..c0c5ff5d5cb76 100644 --- a/tailcfg/proto_port_range_test.go +++ b/tailcfg/proto_port_range_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 8468aa09efb3e..535c42b212b2e 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailcfg contains types used by the Tailscale protocol with between diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 751b7c288f274..483746145b6e1 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 6691263eb997a..4e9909db09f89 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg_test diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index dbd29a87a354e..b2734d8af36c9 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/tailcfg/tka.go b/tailcfg/tka.go index 97fdcc0db687a..29c17b7567198 100644 --- a/tailcfg/tka.go +++ b/tailcfg/tka.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailcfg diff --git a/tka/aum.go b/tka/aum.go index b8c4b6c9e14d4..44d289906566c 100644 --- a/tka/aum.go +++ b/tka/aum.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/aum_test.go b/tka/aum_test.go index 833a026544f54..4f32e91a1964f 100644 --- a/tka/aum_test.go +++ b/tka/aum_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/builder.go b/tka/builder.go index ab2364d856ee2..1e7b130151876 100644 --- a/tka/builder.go +++ b/tka/builder.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/builder_test.go b/tka/builder_test.go index 3fd32f64eac12..edca1e95a516e 100644 --- a/tka/builder_test.go +++ b/tka/builder_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/chaintest_test.go b/tka/chaintest_test.go index a3122b5d19da8..c370bf60a2e4c 100644 --- a/tka/chaintest_test.go +++ b/tka/chaintest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/deeplink.go b/tka/deeplink.go index 5570a19d7371b..34f80be034bb0 100644 --- a/tka/deeplink.go +++ b/tka/deeplink.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/deeplink_test.go b/tka/deeplink_test.go index 03523202fed8b..6d85b158589ac 100644 --- a/tka/deeplink_test.go +++ b/tka/deeplink_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/disabled_stub.go b/tka/disabled_stub.go index 4c4afa3706d98..d14473e5ec1ac 100644 --- a/tka/disabled_stub.go +++ b/tka/disabled_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_tailnetlock diff --git a/tka/key.go b/tka/key.go index dca1b4416560b..bc946156eb9be 100644 --- a/tka/key.go +++ b/tka/key.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/key_test.go b/tka/key_test.go index 327de1a0e2851..799accc857e1c 100644 --- a/tka/key_test.go +++ b/tka/key_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/scenario_test.go b/tka/scenario_test.go index a0361a130dcc6..cf4ee2d5b2582 100644 --- a/tka/scenario_test.go +++ b/tka/scenario_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/sig.go b/tka/sig.go index 46d598ad97b47..9d107c98ff64c 100644 --- a/tka/sig.go +++ b/tka/sig.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/sig_test.go b/tka/sig_test.go index c5c03ef2e0055..efec62b7d791f 100644 --- a/tka/sig_test.go +++ b/tka/sig_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/state.go b/tka/state.go index 95a319bd9bd7d..06fdc65048b59 100644 --- a/tka/state.go +++ b/tka/state.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/state_test.go b/tka/state_test.go index 32b6563145ee7..337e3c3ceff85 100644 --- a/tka/state_test.go +++ b/tka/state_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/sync.go b/tka/sync.go index 2dbfb7ac435b2..27e1c0e633329 100644 --- a/tka/sync.go +++ b/tka/sync.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/sync_test.go b/tka/sync_test.go index ea14a37e57e9b..158f73c46cb01 100644 --- a/tka/sync_test.go +++ b/tka/sync_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/tailchonk.go b/tka/tailchonk.go index 13bdf6aac86d4..256faaea2b8b9 100644 --- a/tka/tailchonk.go +++ b/tka/tailchonk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/tailchonk_test.go b/tka/tailchonk_test.go index eeb6edfff3018..d40e4b09da769 100644 --- a/tka/tailchonk_test.go +++ b/tka/tailchonk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/tka.go b/tka/tka.go index ed029c82e0592..e3862c29d3264 100644 --- a/tka/tka.go +++ b/tka/tka.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/tka_clone.go b/tka/tka_clone.go index 323a824fe5a63..9c7a6eeb3350d 100644 --- a/tka/tka_clone.go +++ b/tka/tka_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/tka/tka_test.go b/tka/tka_test.go index cc9ea57ee2f6a..f2ce73d357343 100644 --- a/tka/tka_test.go +++ b/tka/tka_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tka diff --git a/tka/verify.go b/tka/verify.go index ed0ecea669817..1ef4fbbb19308 100644 --- a/tka/verify.go +++ b/tka/verify.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/tka/verify_disabled.go b/tka/verify_disabled.go index ba72f93e27d8f..a4b3136d2ffea 100644 --- a/tka/verify_disabled.go +++ b/tka/verify_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_tailnetlock diff --git a/tool/gocross/autoflags.go b/tool/gocross/autoflags.go index b28d3bc5dd26e..405cad8b3b68e 100644 --- a/tool/gocross/autoflags.go +++ b/tool/gocross/autoflags.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/autoflags_test.go b/tool/gocross/autoflags_test.go index a0f3edfd2bb68..7363e452ed635 100644 --- a/tool/gocross/autoflags_test.go +++ b/tool/gocross/autoflags_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/env.go b/tool/gocross/env.go index 9d8a4f1b390b4..6b22f9365d255 100644 --- a/tool/gocross/env.go +++ b/tool/gocross/env.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/env_test.go b/tool/gocross/env_test.go index 001487bb8e1a6..39af579eb15f1 100644 --- a/tool/gocross/env_test.go +++ b/tool/gocross/env_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/exec_other.go b/tool/gocross/exec_other.go index 4dd74f84d7d2b..20e52aa8f9496 100644 --- a/tool/gocross/exec_other.go +++ b/tool/gocross/exec_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !unix diff --git a/tool/gocross/exec_unix.go b/tool/gocross/exec_unix.go index 79cbf764ad2f6..2d9fd72ba046b 100644 --- a/tool/gocross/exec_unix.go +++ b/tool/gocross/exec_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build unix diff --git a/tool/gocross/gocross-wrapper.ps1 b/tool/gocross/gocross-wrapper.ps1 index 324b220c8319d..df00d36641ad7 100644 --- a/tool/gocross/gocross-wrapper.ps1 +++ b/tool/gocross/gocross-wrapper.ps1 @@ -1,4 +1,4 @@ -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause #Requires -Version 7.4 diff --git a/tool/gocross/gocross-wrapper.sh b/tool/gocross/gocross-wrapper.sh index d93b137aab6f5..352d639b75530 100755 --- a/tool/gocross/gocross-wrapper.sh +++ b/tool/gocross/gocross-wrapper.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright (c) Tailscale Inc & AUTHORS +# Copyright (c) Tailscale Inc & contributors # SPDX-License-Identifier: BSD-3-Clause # # gocross-wrapper.sh is a wrapper that can be aliased to 'go', which diff --git a/tool/gocross/gocross.go b/tool/gocross/gocross.go index 41fab3d584260..67d4bfceee74f 100644 --- a/tool/gocross/gocross.go +++ b/tool/gocross/gocross.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // gocross is a wrapper around the `go` tool that invokes `go` from Tailscale's diff --git a/tool/gocross/gocross_test.go b/tool/gocross/gocross_test.go index 82afd268c6d8f..2737432e2d0dc 100644 --- a/tool/gocross/gocross_test.go +++ b/tool/gocross/gocross_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/gocross_wrapper_test.go b/tool/gocross/gocross_wrapper_test.go index 6937ccec7188f..7fc81207f6379 100644 --- a/tool/gocross/gocross_wrapper_test.go +++ b/tool/gocross/gocross_wrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin diff --git a/tool/gocross/gocross_wrapper_windows_test.go b/tool/gocross/gocross_wrapper_windows_test.go index aa4277425d442..ed565e15ad677 100644 --- a/tool/gocross/gocross_wrapper_windows_test.go +++ b/tool/gocross/gocross_wrapper_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/goroot.go b/tool/gocross/goroot.go index 00e629fdeba63..8ff771889747a 100644 --- a/tool/gocross/goroot.go +++ b/tool/gocross/goroot.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/gocross/toolchain.go b/tool/gocross/toolchain.go index 9cf7f892b9b17..2eb675861bbce 100644 --- a/tool/gocross/toolchain.go +++ b/tool/gocross/toolchain.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/tool/listpkgs/listpkgs.go b/tool/listpkgs/listpkgs.go index 400bf90c18315..e2c286efc0f7d 100644 --- a/tool/listpkgs/listpkgs.go +++ b/tool/listpkgs/listpkgs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // listpkgs prints the import paths that match the Go package patterns diff --git a/tsconsensus/authorization.go b/tsconsensus/authorization.go index bd8e2f39a014b..6261a8f1debb6 100644 --- a/tsconsensus/authorization.go +++ b/tsconsensus/authorization.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/authorization_test.go b/tsconsensus/authorization_test.go index e0023f4ff24d2..0f7a4e5958595 100644 --- a/tsconsensus/authorization_test.go +++ b/tsconsensus/authorization_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/bolt_store.go b/tsconsensus/bolt_store.go index ca347cfc049b2..e8dbb5a227505 100644 --- a/tsconsensus/bolt_store.go +++ b/tsconsensus/bolt_store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !loong64 diff --git a/tsconsensus/bolt_store_no_bolt.go b/tsconsensus/bolt_store_no_bolt.go index 33b3bd6c7a29f..f799cc5938d10 100644 --- a/tsconsensus/bolt_store_no_bolt.go +++ b/tsconsensus/bolt_store_no_bolt.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build loong64 diff --git a/tsconsensus/http.go b/tsconsensus/http.go index d2a44015f8f68..a7e3af35d94f6 100644 --- a/tsconsensus/http.go +++ b/tsconsensus/http.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/monitor.go b/tsconsensus/monitor.go index c84e83454f3f7..cc5ac812c49d9 100644 --- a/tsconsensus/monitor.go +++ b/tsconsensus/monitor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconsensus/tsconsensus.go b/tsconsensus/tsconsensus.go index 1f7dc1b7b6a5e..27cbf964e7207 100644 --- a/tsconsensus/tsconsensus.go +++ b/tsconsensus/tsconsensus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsconsensus implements a consensus algorithm for a group of tsnet.Servers diff --git a/tsconsensus/tsconsensus_test.go b/tsconsensus/tsconsensus_test.go index 796c8f51b76a9..2199a0c6b9441 100644 --- a/tsconsensus/tsconsensus_test.go +++ b/tsconsensus/tsconsensus_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconsensus diff --git a/tsconst/health.go b/tsconst/health.go index 5db9b1fc286ec..93c6550efaba4 100644 --- a/tsconst/health.go +++ b/tsconst/health.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconst diff --git a/tsconst/linuxfw.go b/tsconst/linuxfw.go index ce571e40239ed..3a7a4cf2e0b5e 100644 --- a/tsconst/linuxfw.go +++ b/tsconst/linuxfw.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconst diff --git a/tsconst/tsconst.go b/tsconst/tsconst.go index d17aa356d25fe..85f05e54905f9 100644 --- a/tsconst/tsconst.go +++ b/tsconst/tsconst.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsconst exports some constants used elsewhere in the diff --git a/tsconst/webclient.go b/tsconst/webclient.go index d4b3c8db51b2a..705931159d24f 100644 --- a/tsconst/webclient.go +++ b/tsconst/webclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsconst diff --git a/tsd/tsd.go b/tsd/tsd.go index 8dc0c14278864..4284a8cd3bade 100644 --- a/tsd/tsd.go +++ b/tsd/tsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsd (short for "Tailscale Daemon") contains a System type that diff --git a/tsnet/example/tshello/tshello.go b/tsnet/example/tshello/tshello.go index 0cadcdd837d99..d45d209dc7abc 100644 --- a/tsnet/example/tshello/tshello.go +++ b/tsnet/example/tshello/tshello.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tshello server demonstrates how to use Tailscale as a library. diff --git a/tsnet/example/tsnet-funnel/tsnet-funnel.go b/tsnet/example/tsnet-funnel/tsnet-funnel.go index 1dac57a1ebf86..27c3e1e5cdf2e 100644 --- a/tsnet/example/tsnet-funnel/tsnet-funnel.go +++ b/tsnet/example/tsnet-funnel/tsnet-funnel.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tsnet-funnel server demonstrates how to use tsnet with Funnel. diff --git a/tsnet/example/tsnet-http-client/tsnet-http-client.go b/tsnet/example/tsnet-http-client/tsnet-http-client.go index 9666fe9992745..e61c512a0b085 100644 --- a/tsnet/example/tsnet-http-client/tsnet-http-client.go +++ b/tsnet/example/tsnet-http-client/tsnet-http-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tshello server demonstrates how to use Tailscale as a library. diff --git a/tsnet/example/tsnet-services/tsnet-services.go b/tsnet/example/tsnet-services/tsnet-services.go index 6eb1a76ab5f5c..d72fd68fd412a 100644 --- a/tsnet/example/tsnet-services/tsnet-services.go +++ b/tsnet/example/tsnet-services/tsnet-services.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The tsnet-services example demonstrates how to use tsnet with Services. diff --git a/tsnet/example/web-client/web-client.go b/tsnet/example/web-client/web-client.go index 541efbaedf3d3..e64eb47e6e14f 100644 --- a/tsnet/example/web-client/web-client.go +++ b/tsnet/example/web-client/web-client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The web-client command demonstrates serving the Tailscale web client over tsnet. diff --git a/tsnet/example_tshello_test.go b/tsnet/example_tshello_test.go index d534bcfd1f1d4..62b6737fd5245 100644 --- a/tsnet/example_tshello_test.go +++ b/tsnet/example_tshello_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet_test diff --git a/tsnet/example_tsnet_listen_service_multiple_ports_test.go b/tsnet/example_tsnet_listen_service_multiple_ports_test.go index 04781c2b20d16..0c7b3899955e1 100644 --- a/tsnet/example_tsnet_listen_service_multiple_ports_test.go +++ b/tsnet/example_tsnet_listen_service_multiple_ports_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet_test diff --git a/tsnet/example_tsnet_test.go b/tsnet/example_tsnet_test.go index 2a3236b3b6501..dbaa8111fb623 100644 --- a/tsnet/example_tsnet_test.go +++ b/tsnet/example_tsnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet_test diff --git a/tsnet/packet_filter_test.go b/tsnet/packet_filter_test.go index 455400eaa0c8a..ca776436e7085 100644 --- a/tsnet/packet_filter_test.go +++ b/tsnet/packet_filter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index d627d55b37314..ccea22d1619f1 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsnet provides Tailscale as a library. diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 2c6970fa3b723..aeee43646cb0a 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsnet diff --git a/tstest/allocs.go b/tstest/allocs.go index f15a00508d87f..6c2a1a22bec6a 100644 --- a/tstest/allocs.go +++ b/tstest/allocs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/archtest/archtest_test.go b/tstest/archtest/archtest_test.go index 1aeca5c109073..1523baf7b2044 100644 --- a/tstest/archtest/archtest_test.go +++ b/tstest/archtest/archtest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package archtest diff --git a/tstest/archtest/qemu_test.go b/tstest/archtest/qemu_test.go index 68ec38851069e..400f8bc4f9ea0 100644 --- a/tstest/archtest/qemu_test.go +++ b/tstest/archtest/qemu_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && amd64 && !race diff --git a/tstest/chonktest/chonktest.go b/tstest/chonktest/chonktest.go index 404f1ec47f16c..b0b32e6151c82 100644 --- a/tstest/chonktest/chonktest.go +++ b/tstest/chonktest/chonktest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package chonktest contains a shared set of tests for the Chonk diff --git a/tstest/chonktest/tailchonk_test.go b/tstest/chonktest/tailchonk_test.go index d9343e9160ea9..99b57f54f5900 100644 --- a/tstest/chonktest/tailchonk_test.go +++ b/tstest/chonktest/tailchonk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package chonktest diff --git a/tstest/clock.go b/tstest/clock.go index ee7523430ff54..f11187a4a69e5 100644 --- a/tstest/clock.go +++ b/tstest/clock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/clock_test.go b/tstest/clock_test.go index 2ebaf752a1963..cdfc2319ac115 100644 --- a/tstest/clock_test.go +++ b/tstest/clock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/deptest/deptest.go b/tstest/deptest/deptest.go index c0b6d8b8cffb5..3117af2fffa01 100644 --- a/tstest/deptest/deptest.go +++ b/tstest/deptest/deptest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The deptest package contains a shared implementation of negative diff --git a/tstest/deptest/deptest_test.go b/tstest/deptest/deptest_test.go index ebafa56849efb..1b83d46d3cc31 100644 --- a/tstest/deptest/deptest_test.go +++ b/tstest/deptest/deptest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deptest diff --git a/tstest/integration/capmap_test.go b/tstest/integration/capmap_test.go index 0ee05be2f57d7..aea4a210b44e1 100644 --- a/tstest/integration/capmap_test.go +++ b/tstest/integration/capmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package integration diff --git a/tstest/integration/gen_deps.go b/tstest/integration/gen_deps.go index 23bb95ee56a9f..7e668266bbb78 100644 --- a/tstest/integration/gen_deps.go +++ b/tstest/integration/gen_deps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore @@ -35,7 +35,7 @@ func generate(goos string) { log.Fatal(err) } var out bytes.Buffer - out.WriteString(`// Copyright (c) Tailscale Inc & AUTHORS + out.WriteString(`// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/integration.go b/tstest/integration/integration.go index a62173ae3e353..a98df81808097 100644 --- a/tstest/integration/integration.go +++ b/tstest/integration/integration.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package integration contains Tailscale integration tests. diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go index fc891ad722b28..779cba6290cfe 100644 --- a/tstest/integration/integration_test.go +++ b/tstest/integration/integration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package integration diff --git a/tstest/integration/nat/nat_test.go b/tstest/integration/nat/nat_test.go index 15f1269858947..2aea7c296701b 100644 --- a/tstest/integration/nat/nat_test.go +++ b/tstest/integration/nat/nat_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package nat diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index 9f92839d8cde7..112f04767c89d 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index 82f8097c8bc36..cabac744a5c6c 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by gen_deps.go; DO NOT EDIT. diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go index 447efb0c1b15d..4607665924c45 100644 --- a/tstest/integration/testcontrol/testcontrol.go +++ b/tstest/integration/testcontrol/testcontrol.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package testcontrol contains a minimal control plane server for testing purposes. diff --git a/tstest/integration/vms/derive_bindhost_test.go b/tstest/integration/vms/derive_bindhost_test.go index 728f60c01e465..079308055da3a 100644 --- a/tstest/integration/vms/derive_bindhost_test.go +++ b/tstest/integration/vms/derive_bindhost_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vms diff --git a/tstest/integration/vms/distros.go b/tstest/integration/vms/distros.go index ca2bf53ba66a7..94f11c77aac5d 100644 --- a/tstest/integration/vms/distros.go +++ b/tstest/integration/vms/distros.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vms diff --git a/tstest/integration/vms/distros_test.go b/tstest/integration/vms/distros_test.go index 462aa2a6bc825..8cc15aa7297fc 100644 --- a/tstest/integration/vms/distros_test.go +++ b/tstest/integration/vms/distros_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vms diff --git a/tstest/integration/vms/dns_tester.go b/tstest/integration/vms/dns_tester.go index 50b39bb5f1fa1..8a0ca5afaf366 100644 --- a/tstest/integration/vms/dns_tester.go +++ b/tstest/integration/vms/dns_tester.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/tstest/integration/vms/doc.go b/tstest/integration/vms/doc.go index 6093b53ac8ed5..0c9eced92d686 100644 --- a/tstest/integration/vms/doc.go +++ b/tstest/integration/vms/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vms does VM-based integration/functional tests by using diff --git a/tstest/integration/vms/harness_test.go b/tstest/integration/vms/harness_test.go index 256227d6c64cc..ccff6e81e0f33 100644 --- a/tstest/integration/vms/harness_test.go +++ b/tstest/integration/vms/harness_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/nixos_test.go b/tstest/integration/vms/nixos_test.go index 02b040fedfaff..7d7a104363761 100644 --- a/tstest/integration/vms/nixos_test.go +++ b/tstest/integration/vms/nixos_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/top_level_test.go b/tstest/integration/vms/top_level_test.go index 5db237b6e33b7..849abfd2469ed 100644 --- a/tstest/integration/vms/top_level_test.go +++ b/tstest/integration/vms/top_level_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/udp_tester.go b/tstest/integration/vms/udp_tester.go index be44aa9636103..46bc1261f1765 100644 --- a/tstest/integration/vms/udp_tester.go +++ b/tstest/integration/vms/udp_tester.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/tstest/integration/vms/vm_setup_test.go b/tstest/integration/vms/vm_setup_test.go index 0c6901014bb74..690c89dcf487b 100644 --- a/tstest/integration/vms/vm_setup_test.go +++ b/tstest/integration/vms/vm_setup_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/vms_steps_test.go b/tstest/integration/vms/vms_steps_test.go index 94e4114f01e78..940c92ddac63c 100644 --- a/tstest/integration/vms/vms_steps_test.go +++ b/tstest/integration/vms/vms_steps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/integration/vms/vms_test.go b/tstest/integration/vms/vms_test.go index c3a3775de9407..5ebb12b71032b 100644 --- a/tstest/integration/vms/vms_test.go +++ b/tstest/integration/vms/vms_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !plan9 diff --git a/tstest/iosdeps/iosdeps.go b/tstest/iosdeps/iosdeps.go index f414f53dfd0b6..f6290af676e97 100644 --- a/tstest/iosdeps/iosdeps.go +++ b/tstest/iosdeps/iosdeps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package iosdeps is a just a list of the packages we import on iOS, to let us diff --git a/tstest/iosdeps/iosdeps_test.go b/tstest/iosdeps/iosdeps_test.go index b533724eb4b3d..870088e38db9a 100644 --- a/tstest/iosdeps/iosdeps_test.go +++ b/tstest/iosdeps/iosdeps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package iosdeps diff --git a/tstest/jsdeps/jsdeps.go b/tstest/jsdeps/jsdeps.go index 1d188152f73b1..964ca51e10fb6 100644 --- a/tstest/jsdeps/jsdeps.go +++ b/tstest/jsdeps/jsdeps.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsdeps is a just a list of the packages we import in the diff --git a/tstest/jsdeps/jsdeps_test.go b/tstest/jsdeps/jsdeps_test.go index 27570fc2676b0..ba6dad6badf3d 100644 --- a/tstest/jsdeps/jsdeps_test.go +++ b/tstest/jsdeps/jsdeps_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsdeps diff --git a/tstest/kernel_linux.go b/tstest/kernel_linux.go index 664fe9bdd7b9f..ab7c0d529fc13 100644 --- a/tstest/kernel_linux.go +++ b/tstest/kernel_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/tstest/kernel_other.go b/tstest/kernel_other.go index bf69be6df4b27..3dfc3c239576f 100644 --- a/tstest/kernel_other.go +++ b/tstest/kernel_other.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux diff --git a/tstest/log.go b/tstest/log.go index d081c819d8ce2..73e973d238d66 100644 --- a/tstest/log.go +++ b/tstest/log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/log_test.go b/tstest/log_test.go index 51a5743c2c7f2..34aab000d4ad3 100644 --- a/tstest/log_test.go +++ b/tstest/log_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/mts/mts.go b/tstest/mts/mts.go index c10d69d8daca4..c91e0ce996f44 100644 --- a/tstest/mts/mts.go +++ b/tstest/mts/mts.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux || darwin diff --git a/tstest/natlab/firewall.go b/tstest/natlab/firewall.go index c427d6692a29c..e9192cfbd8ab7 100644 --- a/tstest/natlab/firewall.go +++ b/tstest/natlab/firewall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package natlab diff --git a/tstest/natlab/nat.go b/tstest/natlab/nat.go index d756c5bf11833..67e84f44e7b7a 100644 --- a/tstest/natlab/nat.go +++ b/tstest/natlab/nat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package natlab diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go index ffa02eee46e06..add812d8fe6e3 100644 --- a/tstest/natlab/natlab.go +++ b/tstest/natlab/natlab.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package natlab lets us simulate different types of networks all diff --git a/tstest/natlab/natlab_test.go b/tstest/natlab/natlab_test.go index 84388373236be..d604907017a84 100644 --- a/tstest/natlab/natlab_test.go +++ b/tstest/natlab/natlab_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package natlab diff --git a/tstest/natlab/vnet/conf.go b/tstest/natlab/vnet/conf.go index 07b181540838c..3f83e35c09ba3 100644 --- a/tstest/natlab/vnet/conf.go +++ b/tstest/natlab/vnet/conf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/conf_test.go b/tstest/natlab/vnet/conf_test.go index 6566ac8cf4610..5716a503e4007 100644 --- a/tstest/natlab/vnet/conf_test.go +++ b/tstest/natlab/vnet/conf_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/easyaf.go b/tstest/natlab/vnet/easyaf.go index 0901bbdffdd7d..1edc9b3cd16b2 100644 --- a/tstest/natlab/vnet/easyaf.go +++ b/tstest/natlab/vnet/easyaf.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/nat.go b/tstest/natlab/vnet/nat.go index ad6f29b3adb58..172e19767b179 100644 --- a/tstest/natlab/vnet/nat.go +++ b/tstest/natlab/vnet/nat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/pcap.go b/tstest/natlab/vnet/pcap.go index 41a443e30b6c5..3a766b3759a52 100644 --- a/tstest/natlab/vnet/pcap.go +++ b/tstest/natlab/vnet/pcap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/vip.go b/tstest/natlab/vnet/vip.go index 190c9e75f1a62..9d7aa56a3d2a0 100644 --- a/tstest/natlab/vnet/vip.go +++ b/tstest/natlab/vnet/vip.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/natlab/vnet/vnet.go b/tstest/natlab/vnet/vnet.go index 49d47f02937ae..357fe213c8c28 100644 --- a/tstest/natlab/vnet/vnet.go +++ b/tstest/natlab/vnet/vnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vnet simulates a virtual Internet containing a set of networks with various diff --git a/tstest/natlab/vnet/vnet_test.go b/tstest/natlab/vnet/vnet_test.go index 5ffa2b1049c88..93f208c29ca0a 100644 --- a/tstest/natlab/vnet/vnet_test.go +++ b/tstest/natlab/vnet/vnet_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vnet diff --git a/tstest/nettest/nettest.go b/tstest/nettest/nettest.go index c78677dd45c59..0ceef463d8160 100644 --- a/tstest/nettest/nettest.go +++ b/tstest/nettest/nettest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package nettest contains additional test helpers related to network state diff --git a/tstest/reflect.go b/tstest/reflect.go index 125391349a941..22903e7e9fca2 100644 --- a/tstest/reflect.go +++ b/tstest/reflect.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/resource.go b/tstest/resource.go index f50bb3330e846..867925b7ddeb1 100644 --- a/tstest/resource.go +++ b/tstest/resource.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/resource_test.go b/tstest/resource_test.go index 7199ac5d11cbf..ecef91cf60b08 100644 --- a/tstest/resource_test.go +++ b/tstest/resource_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/tailmac/Swift/Common/Config.swift b/tstest/tailmac/Swift/Common/Config.swift index 18b68ae9b9d14..53d7680205a00 100644 --- a/tstest/tailmac/Swift/Common/Config.swift +++ b/tstest/tailmac/Swift/Common/Config.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/Common/Notifications.swift b/tstest/tailmac/Swift/Common/Notifications.swift index de2216e227eb7..b91741a463c8e 100644 --- a/tstest/tailmac/Swift/Common/Notifications.swift +++ b/tstest/tailmac/Swift/Common/Notifications.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift b/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift index c0961c883fdbb..fc7f2d89dc0e2 100644 --- a/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift +++ b/tstest/tailmac/Swift/Common/TailMacConfigHelper.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/Host/AppDelegate.swift b/tstest/tailmac/Swift/Host/AppDelegate.swift index 63c0192da236e..378a524d13c37 100644 --- a/tstest/tailmac/Swift/Host/AppDelegate.swift +++ b/tstest/tailmac/Swift/Host/AppDelegate.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Cocoa diff --git a/tstest/tailmac/Swift/Host/HostCli.swift b/tstest/tailmac/Swift/Host/HostCli.swift index c31478cc39d45..9c9ae6fa0476e 100644 --- a/tstest/tailmac/Swift/Host/HostCli.swift +++ b/tstest/tailmac/Swift/Host/HostCli.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Cocoa diff --git a/tstest/tailmac/Swift/Host/VMController.swift b/tstest/tailmac/Swift/Host/VMController.swift index fe4a3828b18fe..a19d7222e1e9e 100644 --- a/tstest/tailmac/Swift/Host/VMController.swift +++ b/tstest/tailmac/Swift/Host/VMController.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Cocoa diff --git a/tstest/tailmac/Swift/TailMac/RestoreImage.swift b/tstest/tailmac/Swift/TailMac/RestoreImage.swift index c2b8b3dd6a878..8346cbe26c408 100644 --- a/tstest/tailmac/Swift/TailMac/RestoreImage.swift +++ b/tstest/tailmac/Swift/TailMac/RestoreImage.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/TailMac/TailMac.swift b/tstest/tailmac/Swift/TailMac/TailMac.swift index 84aa5e498a008..3859b9b0b0aeb 100644 --- a/tstest/tailmac/Swift/TailMac/TailMac.swift +++ b/tstest/tailmac/Swift/TailMac/TailMac.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tailmac/Swift/TailMac/VMInstaller.swift b/tstest/tailmac/Swift/TailMac/VMInstaller.swift index 568b6efc4bfe0..7e90079b596c2 100644 --- a/tstest/tailmac/Swift/TailMac/VMInstaller.swift +++ b/tstest/tailmac/Swift/TailMac/VMInstaller.swift @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause import Foundation diff --git a/tstest/tkatest/tkatest.go b/tstest/tkatest/tkatest.go index fb157a1a19315..2726b4deca249 100644 --- a/tstest/tkatest/tkatest.go +++ b/tstest/tkatest/tkatest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // tkatest has functions for creating a mock control server that responds diff --git a/tstest/tlstest/tlstest.go b/tstest/tlstest/tlstest.go index 76ec0e7e2dfad..3ab08c61fb211 100644 --- a/tstest/tlstest/tlstest.go +++ b/tstest/tlstest/tlstest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tlstest contains code to help test Tailscale's TLS support without diff --git a/tstest/tlstest/tlstest_test.go b/tstest/tlstest/tlstest_test.go index 8497b872ec7c5..7f3583c8af15a 100644 --- a/tstest/tlstest/tlstest_test.go +++ b/tstest/tlstest/tlstest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tlstest diff --git a/tstest/tools/tools.go b/tstest/tools/tools.go index 4d810483b78b5..439acc053250a 100644 --- a/tstest/tools/tools.go +++ b/tstest/tools/tools.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tools diff --git a/tstest/tstest.go b/tstest/tstest.go index d0828f508a46c..4e00fbaa38ae8 100644 --- a/tstest/tstest.go +++ b/tstest/tstest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tstest provides utilities for use in unit tests. diff --git a/tstest/tstest_test.go b/tstest/tstest_test.go index ce59bde538b9a..0c281d2352f7b 100644 --- a/tstest/tstest_test.go +++ b/tstest/tstest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstest diff --git a/tstest/typewalk/typewalk.go b/tstest/typewalk/typewalk.go index b22505351b1a2..f989b4c180394 100644 --- a/tstest/typewalk/typewalk.go +++ b/tstest/typewalk/typewalk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package typewalk provides utilities to walk Go types using reflection. diff --git a/tstime/jitter.go b/tstime/jitter.go index c5095c15d87ae..987680f3c0ba7 100644 --- a/tstime/jitter.go +++ b/tstime/jitter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstime diff --git a/tstime/jitter_test.go b/tstime/jitter_test.go index 579287bda0bc2..149ed3fa5d6d8 100644 --- a/tstime/jitter_test.go +++ b/tstime/jitter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstime diff --git a/tstime/mono/mono.go b/tstime/mono/mono.go index 260e02b0fb0f3..8975c2480c748 100644 --- a/tstime/mono/mono.go +++ b/tstime/mono/mono.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mono provides fast monotonic time. diff --git a/tstime/mono/mono_test.go b/tstime/mono/mono_test.go index 67a8614baf2ef..dfa6fe1f078a3 100644 --- a/tstime/mono/mono_test.go +++ b/tstime/mono/mono_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package mono diff --git a/tstime/rate/rate.go b/tstime/rate/rate.go index f0473862a2890..3f2f5c9be55f5 100644 --- a/tstime/rate/rate.go +++ b/tstime/rate/rate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This is a modified, simplified version of code from golang.org/x/time/rate. diff --git a/tstime/rate/rate_test.go b/tstime/rate/rate_test.go index dc3f9e84bb851..3486371be565a 100644 --- a/tstime/rate/rate_test.go +++ b/tstime/rate/rate_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This is a modified, simplified version of code from golang.org/x/time/rate. diff --git a/tstime/rate/value.go b/tstime/rate/value.go index 610f06bbd7991..8a627ff36119e 100644 --- a/tstime/rate/value.go +++ b/tstime/rate/value.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rate diff --git a/tstime/rate/value_test.go b/tstime/rate/value_test.go index a26442650cf94..e6d60798407f1 100644 --- a/tstime/rate/value_test.go +++ b/tstime/rate/value_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rate diff --git a/tstime/tstime.go b/tstime/tstime.go index 6e5b7f9f47146..8c52a4652beca 100644 --- a/tstime/tstime.go +++ b/tstime/tstime.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tstime defines Tailscale-specific time utilities. diff --git a/tstime/tstime_test.go b/tstime/tstime_test.go index 556ad4e8bb1d0..80d4e318e66bc 100644 --- a/tstime/tstime_test.go +++ b/tstime/tstime_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tstime diff --git a/tsweb/debug.go b/tsweb/debug.go index 4c0fabaff4aea..e4ac7a55909dd 100644 --- a/tsweb/debug.go +++ b/tsweb/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/debug_test.go b/tsweb/debug_test.go index 2a68ab6fb27b9..b46a3a3f37c32 100644 --- a/tsweb/debug_test.go +++ b/tsweb/debug_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/log.go b/tsweb/log.go index 51f95e95f5d07..1cb5f28eff29f 100644 --- a/tsweb/log.go +++ b/tsweb/log.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/pprof_default.go b/tsweb/pprof_default.go index 7d22a61619855..a4ac86cdba161 100644 --- a/tsweb/pprof_default.go +++ b/tsweb/pprof_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !wasm diff --git a/tsweb/pprof_js.go b/tsweb/pprof_js.go index 1212b37e86f5a..5635fbb2ca7e9 100644 --- a/tsweb/pprof_js.go +++ b/tsweb/pprof_js.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js && wasm diff --git a/tsweb/promvarz/promvarz.go b/tsweb/promvarz/promvarz.go index 1d978c7677328..4fdf394d0891b 100644 --- a/tsweb/promvarz/promvarz.go +++ b/tsweb/promvarz/promvarz.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package promvarz combines Prometheus metrics exported by our expvar converter diff --git a/tsweb/promvarz/promvarz_test.go b/tsweb/promvarz/promvarz_test.go index cffbbec2273c8..123330d6e5831 100644 --- a/tsweb/promvarz/promvarz_test.go +++ b/tsweb/promvarz/promvarz_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package promvarz diff --git a/tsweb/request_id.go b/tsweb/request_id.go index 46e52385240ca..351ed1710802b 100644 --- a/tsweb/request_id.go +++ b/tsweb/request_id.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index f6196174b38b2..f464e7af2141e 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tsweb contains code used in various Tailscale webservers. diff --git a/tsweb/tsweb_test.go b/tsweb/tsweb_test.go index d4c9721e97215..af8e52420bd50 100644 --- a/tsweb/tsweb_test.go +++ b/tsweb/tsweb_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tsweb diff --git a/tsweb/varz/varz.go b/tsweb/varz/varz.go index b1c66b859e8cf..d6100672c6c56 100644 --- a/tsweb/varz/varz.go +++ b/tsweb/varz/varz.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package varz contains code to export metrics in Prometheus format. diff --git a/tsweb/varz/varz_test.go b/tsweb/varz/varz_test.go index 5bbacbe356940..6505ba985160e 100644 --- a/tsweb/varz/varz_test.go +++ b/tsweb/varz/varz_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package varz diff --git a/types/appctype/appconnector.go b/types/appctype/appconnector.go index 567ab755f0598..5442e8290cb8a 100644 --- a/types/appctype/appconnector.go +++ b/types/appctype/appconnector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package appcfg contains an experimental configuration structure for diff --git a/types/appctype/appconnector_test.go b/types/appctype/appconnector_test.go index 390d1776a3280..f411faec5bef0 100644 --- a/types/appctype/appconnector_test.go +++ b/types/appctype/appconnector_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package appctype diff --git a/types/bools/bools.go b/types/bools/bools.go index e64068746ed9e..d271b8c28e19a 100644 --- a/types/bools/bools.go +++ b/types/bools/bools.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package bools contains the [Int], [Compare], and [IfElse] functions. diff --git a/types/bools/bools_test.go b/types/bools/bools_test.go index 67faf3bcc92d8..70fcd0fbcb1a2 100644 --- a/types/bools/bools_test.go +++ b/types/bools/bools_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package bools diff --git a/types/dnstype/dnstype.go b/types/dnstype/dnstype.go index a3ba1b0a981e2..1cd38d38385ba 100644 --- a/types/dnstype/dnstype.go +++ b/types/dnstype/dnstype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnstype defines types for working with DNS. diff --git a/types/dnstype/dnstype_clone.go b/types/dnstype/dnstype_clone.go index 3985704aa0638..e690ebaec3865 100644 --- a/types/dnstype/dnstype_clone.go +++ b/types/dnstype/dnstype_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/dnstype/dnstype_test.go b/types/dnstype/dnstype_test.go index ada5f687def9f..cf20f4f7f6618 100644 --- a/types/dnstype/dnstype_test.go +++ b/types/dnstype/dnstype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnstype diff --git a/types/dnstype/dnstype_view.go b/types/dnstype/dnstype_view.go index a983864d0ce42..c91feb6b8aa38 100644 --- a/types/dnstype/dnstype_view.go +++ b/types/dnstype/dnstype_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/empty/message.go b/types/empty/message.go index dc8eb4cc2dc37..bee653038be33 100644 --- a/types/empty/message.go +++ b/types/empty/message.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package empty defines an empty struct type. diff --git a/types/flagtype/flagtype.go b/types/flagtype/flagtype.go index be160dee82a21..1e45b04f453ed 100644 --- a/types/flagtype/flagtype.go +++ b/types/flagtype/flagtype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package flagtype defines flag.Value types. diff --git a/types/geo/doc.go b/types/geo/doc.go index 749c6308093f6..61c78f78cb653 100644 --- a/types/geo/doc.go +++ b/types/geo/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package geo provides functionality to represent and process geographical diff --git a/types/geo/point.go b/types/geo/point.go index d7160ac593338..820582b0ff6b3 100644 --- a/types/geo/point.go +++ b/types/geo/point.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo diff --git a/types/geo/point_test.go b/types/geo/point_test.go index 308c1a1834377..f0d0cb3abba3e 100644 --- a/types/geo/point_test.go +++ b/types/geo/point_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo_test diff --git a/types/geo/quantize.go b/types/geo/quantize.go index 18ec11f9f119c..f07562424f96e 100644 --- a/types/geo/quantize.go +++ b/types/geo/quantize.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo diff --git a/types/geo/quantize_test.go b/types/geo/quantize_test.go index bc1f62c9be32f..59d5587e565dc 100644 --- a/types/geo/quantize_test.go +++ b/types/geo/quantize_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo_test diff --git a/types/geo/units.go b/types/geo/units.go index 76a4c02f79f34..74df9624d6bd9 100644 --- a/types/geo/units.go +++ b/types/geo/units.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo diff --git a/types/geo/units_test.go b/types/geo/units_test.go index b6f724ce0d9b3..cfbb7ae6a6685 100644 --- a/types/geo/units_test.go +++ b/types/geo/units_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package geo_test diff --git a/types/iox/io.go b/types/iox/io.go index a5ca1be43f737..f78328a10a969 100644 --- a/types/iox/io.go +++ b/types/iox/io.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package iox provides types to implement [io] functionality. diff --git a/types/iox/io_test.go b/types/iox/io_test.go index 9fba39605d28d..7a902841b75fe 100644 --- a/types/iox/io_test.go +++ b/types/iox/io_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package iox diff --git a/types/ipproto/ipproto.go b/types/ipproto/ipproto.go index b5333eb56ace0..a08985b3aba26 100644 --- a/types/ipproto/ipproto.go +++ b/types/ipproto/ipproto.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ipproto contains IP Protocol constants. diff --git a/types/ipproto/ipproto_test.go b/types/ipproto/ipproto_test.go index 102b79cffae5b..8bfeb13fa4246 100644 --- a/types/ipproto/ipproto_test.go +++ b/types/ipproto/ipproto_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ipproto diff --git a/types/jsonx/json.go b/types/jsonx/json.go index 3f01ea358df30..36516f495380b 100644 --- a/types/jsonx/json.go +++ b/types/jsonx/json.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package jsonx contains helper types and functionality to use with diff --git a/types/jsonx/json_test.go b/types/jsonx/json_test.go index 0f2a646c40d6d..5c302d9746c3e 100644 --- a/types/jsonx/json_test.go +++ b/types/jsonx/json_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package jsonx diff --git a/types/key/chal.go b/types/key/chal.go index 742ac5479e4a1..50827d28ed0fe 100644 --- a/types/key/chal.go +++ b/types/key/chal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/control.go b/types/key/control.go index 96021249ba047..384be160265fa 100644 --- a/types/key/control.go +++ b/types/key/control.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/control_test.go b/types/key/control_test.go index a98a586f3ba5a..928be4283bc3a 100644 --- a/types/key/control_test.go +++ b/types/key/control_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/derp.go b/types/key/derp.go index 1466b85bc5288..a85611d241765 100644 --- a/types/key/derp.go +++ b/types/key/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/derp_test.go b/types/key/derp_test.go index b91cbbf8c4e01..ab98671e5bd29 100644 --- a/types/key/derp_test.go +++ b/types/key/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/disco.go b/types/key/disco.go index 52b40c766fbbf..f46347c919ebb 100644 --- a/types/key/disco.go +++ b/types/key/disco.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/disco_test.go b/types/key/disco_test.go index 131fe350f508a..fb22fa82f5400 100644 --- a/types/key/disco_test.go +++ b/types/key/disco_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/doc.go b/types/key/doc.go index b2aad72d612bb..cbee21e5f5732 100644 --- a/types/key/doc.go +++ b/types/key/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package key contains types for different types of public and private keys diff --git a/types/key/hardware_attestation.go b/types/key/hardware_attestation.go index 9d4a21ee42706..5ca7e936b3d72 100644 --- a/types/key/hardware_attestation.go +++ b/types/key/hardware_attestation.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/machine.go b/types/key/machine.go index a05f3cc1f5735..9ad73bec1a434 100644 --- a/types/key/machine.go +++ b/types/key/machine.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/machine_test.go b/types/key/machine_test.go index 157df9e4356b1..3db92ed406bd6 100644 --- a/types/key/machine_test.go +++ b/types/key/machine_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/nl.go b/types/key/nl.go index 50caed98c2d0b..fc11d5b20ff64 100644 --- a/types/key/nl.go +++ b/types/key/nl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/nl_test.go b/types/key/nl_test.go index 75b7765a19ea1..84fa920569f08 100644 --- a/types/key/nl_test.go +++ b/types/key/nl_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/node.go b/types/key/node.go index 11ee1fa3cfd41..1402aad361870 100644 --- a/types/key/node.go +++ b/types/key/node.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/node_test.go b/types/key/node_test.go index 80a2dadf90f5f..77eef2b28d2f5 100644 --- a/types/key/node_test.go +++ b/types/key/node_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/util.go b/types/key/util.go index 50fac827556aa..c336d38792a25 100644 --- a/types/key/util.go +++ b/types/key/util.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/key/util_test.go b/types/key/util_test.go index 4d6f8242280ad..3323e0e574684 100644 --- a/types/key/util_test.go +++ b/types/key/util_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package key diff --git a/types/lazy/deferred.go b/types/lazy/deferred.go index 973082914c48c..582090ab93112 100644 --- a/types/lazy/deferred.go +++ b/types/lazy/deferred.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/deferred_test.go b/types/lazy/deferred_test.go index 98cacbfce7088..61cc8f8ac6c27 100644 --- a/types/lazy/deferred_test.go +++ b/types/lazy/deferred_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/lazy.go b/types/lazy/lazy.go index f537758fa6415..915ae2002c135 100644 --- a/types/lazy/lazy.go +++ b/types/lazy/lazy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lazy provides types for lazily initialized values. diff --git a/types/lazy/map.go b/types/lazy/map.go index 75a1dd739d3bc..4718c5b873c4b 100644 --- a/types/lazy/map.go +++ b/types/lazy/map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/map_test.go b/types/lazy/map_test.go index ec1152b0b802c..5f09da5aea1f1 100644 --- a/types/lazy/map_test.go +++ b/types/lazy/map_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/sync_test.go b/types/lazy/sync_test.go index 4d1278253955b..b517594d0a8e3 100644 --- a/types/lazy/sync_test.go +++ b/types/lazy/sync_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/unsync.go b/types/lazy/unsync.go index 0f89ce4f6935a..75d7be23f1e04 100644 --- a/types/lazy/unsync.go +++ b/types/lazy/unsync.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/lazy/unsync_test.go b/types/lazy/unsync_test.go index f0d2494d12b6e..c3fcf27acad65 100644 --- a/types/lazy/unsync_test.go +++ b/types/lazy/unsync_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lazy diff --git a/types/logger/logger.go b/types/logger/logger.go index 6c4edf6336005..71086e87dbd83 100644 --- a/types/logger/logger.go +++ b/types/logger/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logger defines a type for writing to logs. It's just a diff --git a/types/logger/logger_test.go b/types/logger/logger_test.go index 52c1d3900e1c5..f55a9484d344e 100644 --- a/types/logger/logger_test.go +++ b/types/logger/logger_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logger diff --git a/types/logger/rusage.go b/types/logger/rusage.go index 3943636d6e255..c1bbbaa5378f7 100644 --- a/types/logger/rusage.go +++ b/types/logger/rusage.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logger diff --git a/types/logger/rusage_stub.go b/types/logger/rusage_stub.go index f646f1e1eee7f..e94478ef7c59b 100644 --- a/types/logger/rusage_stub.go +++ b/types/logger/rusage_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows || wasm || plan9 || tamago diff --git a/types/logger/rusage_syscall.go b/types/logger/rusage_syscall.go index 2871b66c6bb24..25b026994afe8 100644 --- a/types/logger/rusage_syscall.go +++ b/types/logger/rusage_syscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !wasm && !plan9 && !tamago diff --git a/types/logger/tokenbucket.go b/types/logger/tokenbucket.go index 83d4059c2af00..fdee56237757e 100644 --- a/types/logger/tokenbucket.go +++ b/types/logger/tokenbucket.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logger diff --git a/types/logid/id.go b/types/logid/id.go index fd46a7bef735c..94e363879d324 100644 --- a/types/logid/id.go +++ b/types/logid/id.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package logid contains ID types for interacting with the log service. diff --git a/types/logid/id_test.go b/types/logid/id_test.go index c93d1f1c1adc0..86a736bd877e6 100644 --- a/types/logid/id_test.go +++ b/types/logid/id_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package logid diff --git a/types/mapx/ordered.go b/types/mapx/ordered.go index 1991f039d7726..caaa4d098f8fb 100644 --- a/types/mapx/ordered.go +++ b/types/mapx/ordered.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mapx contains extra map types and functions. diff --git a/types/mapx/ordered_test.go b/types/mapx/ordered_test.go index 7dcb7e40558c3..9bf0be6410e80 100644 --- a/types/mapx/ordered_test.go +++ b/types/mapx/ordered_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package mapx diff --git a/types/netlogfunc/netlogfunc.go b/types/netlogfunc/netlogfunc.go index 6185fcb715c65..db856f0cf49f1 100644 --- a/types/netlogfunc/netlogfunc.go +++ b/types/netlogfunc/netlogfunc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netlogfunc defines types for network logging. diff --git a/types/netlogtype/netlogtype.go b/types/netlogtype/netlogtype.go index cc38684a30dbf..24fb32ab0bb5f 100644 --- a/types/netlogtype/netlogtype.go +++ b/types/netlogtype/netlogtype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netlogtype defines types for network logging. diff --git a/types/netlogtype/netlogtype_test.go b/types/netlogtype/netlogtype_test.go index 00f89b228aa96..8271f0ae04144 100644 --- a/types/netlogtype/netlogtype_test.go +++ b/types/netlogtype/netlogtype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_tailnetlock diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index 18abd1c195024..d809cbab4ad5d 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netmap contains the netmap.NetworkMap type. diff --git a/types/netmap/netmap_test.go b/types/netmap/netmap_test.go index ee4fecdb4ff4e..e68b243b37f42 100644 --- a/types/netmap/netmap_test.go +++ b/types/netmap/netmap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmap diff --git a/types/netmap/nodemut.go b/types/netmap/nodemut.go index 4f93be21c6d68..5c9000d56ef38 100644 --- a/types/netmap/nodemut.go +++ b/types/netmap/nodemut.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmap diff --git a/types/netmap/nodemut_test.go b/types/netmap/nodemut_test.go index 374f8623ad564..f7302d48df097 100644 --- a/types/netmap/nodemut_test.go +++ b/types/netmap/nodemut_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netmap diff --git a/types/nettype/nettype.go b/types/nettype/nettype.go index 5d3d303c38a0d..e44daa0c709f3 100644 --- a/types/nettype/nettype.go +++ b/types/nettype/nettype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package nettype defines an interface that doesn't exist in the Go net package. diff --git a/types/opt/bool.go b/types/opt/bool.go index fbc39e1dc3754..cecbf5eac1c68 100644 --- a/types/opt/bool.go +++ b/types/opt/bool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package opt defines optional types. diff --git a/types/opt/bool_test.go b/types/opt/bool_test.go index e61d66dbe9e96..de4da3788d1e7 100644 --- a/types/opt/bool_test.go +++ b/types/opt/bool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package opt diff --git a/types/opt/value.go b/types/opt/value.go index c71c53e511aca..1ccdd75a47c72 100644 --- a/types/opt/value.go +++ b/types/opt/value.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package opt diff --git a/types/opt/value_test.go b/types/opt/value_test.go index 890f9a5795cb3..0b73182996ad2 100644 --- a/types/opt/value_test.go +++ b/types/opt/value_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package opt diff --git a/types/persist/persist.go b/types/persist/persist.go index 80bac9b5e2741..2a8c2fb824d36 100644 --- a/types/persist/persist.go +++ b/types/persist/persist.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package persist contains the Persist type. diff --git a/types/persist/persist_clone.go b/types/persist/persist_clone.go index 9dbe7e0f6fa6d..f5fa36b6da0fc 100644 --- a/types/persist/persist_clone.go +++ b/types/persist/persist_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/persist/persist_test.go b/types/persist/persist_test.go index 713114b74dcd5..b25af5a0b2066 100644 --- a/types/persist/persist_test.go +++ b/types/persist/persist_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package persist diff --git a/types/persist/persist_view.go b/types/persist/persist_view.go index dbf8294ef5a7a..b18634917c651 100644 --- a/types/persist/persist_view.go +++ b/types/persist/persist_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/prefs/item.go b/types/prefs/item.go index 717a0c76cf291..fdb9301f9fdf8 100644 --- a/types/prefs/item.go +++ b/types/prefs/item.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/list.go b/types/prefs/list.go index ae6b2fae335db..20e4dad463135 100644 --- a/types/prefs/list.go +++ b/types/prefs/list.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/map.go b/types/prefs/map.go index 4b64690ed1351..6bf1948b87ab4 100644 --- a/types/prefs/map.go +++ b/types/prefs/map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/options.go b/types/prefs/options.go index 3769b784b731a..bc0123a526084 100644 --- a/types/prefs/options.go +++ b/types/prefs/options.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/prefs.go b/types/prefs/prefs.go index a6caf12838b79..3f18886a724bc 100644 --- a/types/prefs/prefs.go +++ b/types/prefs/prefs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prefs contains types and functions to work with arbitrary diff --git a/types/prefs/prefs_clone_test.go b/types/prefs/prefs_clone_test.go index 2a03fba8b092c..07dc24fdc7361 100644 --- a/types/prefs/prefs_clone_test.go +++ b/types/prefs/prefs_clone_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/prefs/prefs_example/prefs_example_clone.go b/types/prefs/prefs_example/prefs_example_clone.go index 5c707b46343e1..c5fdc49fc3b9b 100644 --- a/types/prefs/prefs_example/prefs_example_clone.go +++ b/types/prefs/prefs_example/prefs_example_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/types/prefs/prefs_example/prefs_example_view.go b/types/prefs/prefs_example/prefs_example_view.go index 6a1a36865fe00..67a284bb5b4bb 100644 --- a/types/prefs/prefs_example/prefs_example_view.go +++ b/types/prefs/prefs_example/prefs_example_view.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/prefs/prefs_example/prefs_test.go b/types/prefs/prefs_example/prefs_test.go index aefbae9f2873a..93ed5b4fea27b 100644 --- a/types/prefs/prefs_example/prefs_test.go +++ b/types/prefs/prefs_example/prefs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs_example diff --git a/types/prefs/prefs_example/prefs_types.go b/types/prefs/prefs_example/prefs_types.go index c35f1f62fde3d..d0764c64b6efc 100644 --- a/types/prefs/prefs_example/prefs_types.go +++ b/types/prefs/prefs_example/prefs_types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prefs_example contains a [Prefs] type, which is like [tailscale.com/ipn.Prefs], diff --git a/types/prefs/prefs_test.go b/types/prefs/prefs_test.go index dc1213adb27ab..ccc37b0a74df7 100644 --- a/types/prefs/prefs_test.go +++ b/types/prefs/prefs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/prefs_view_test.go b/types/prefs/prefs_view_test.go index 8993cb535bd67..ce4dee726badc 100644 --- a/types/prefs/prefs_view_test.go +++ b/types/prefs/prefs_view_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale/cmd/viewer; DO NOT EDIT. diff --git a/types/prefs/struct_list.go b/types/prefs/struct_list.go index ba145e2cf7086..09aa808ccc37e 100644 --- a/types/prefs/struct_list.go +++ b/types/prefs/struct_list.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/prefs/struct_map.go b/types/prefs/struct_map.go index 83cc7447baedd..2f2715a62a94a 100644 --- a/types/prefs/struct_map.go +++ b/types/prefs/struct_map.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package prefs diff --git a/types/preftype/netfiltermode.go b/types/preftype/netfiltermode.go index 273e173444365..f108bebc35688 100644 --- a/types/preftype/netfiltermode.go +++ b/types/preftype/netfiltermode.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package preftype is a leaf package containing types for various diff --git a/types/ptr/ptr.go b/types/ptr/ptr.go index beb17bee8ee0e..5b65a0e1c13e7 100644 --- a/types/ptr/ptr.go +++ b/types/ptr/ptr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ptr contains the ptr.To function. diff --git a/types/result/result.go b/types/result/result.go index 6bd1c2ea62004..4d537b084ea54 100644 --- a/types/result/result.go +++ b/types/result/result.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package result contains the Of result type, which is diff --git a/types/structs/structs.go b/types/structs/structs.go index 47c359f0caa0f..dd0cd809b8928 100644 --- a/types/structs/structs.go +++ b/types/structs/structs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package structs contains the Incomparable type. diff --git a/types/tkatype/tkatype.go b/types/tkatype/tkatype.go index 6ad51f6a90240..e315f4422ae98 100644 --- a/types/tkatype/tkatype.go +++ b/types/tkatype/tkatype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tkatype defines types for working with the tka package. diff --git a/types/tkatype/tkatype_test.go b/types/tkatype/tkatype_test.go index c81891b9ce103..337167a7d3bd8 100644 --- a/types/tkatype/tkatype_test.go +++ b/types/tkatype/tkatype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tkatype diff --git a/types/views/views.go b/types/views/views.go index 252f126a79f57..9260311edc29a 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package views provides read-only accessors for commonly used diff --git a/types/views/views_test.go b/types/views/views_test.go index 5a30c11a13c86..7cdd1ab020312 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package views diff --git a/util/backoff/backoff.go b/util/backoff/backoff.go index 95089fc2479ff..2edb1e7712e65 100644 --- a/util/backoff/backoff.go +++ b/util/backoff/backoff.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package backoff provides a back-off timer type. diff --git a/util/checkchange/checkchange.go b/util/checkchange/checkchange.go index 8ba64720d7e14..45e3c0bf54660 100644 --- a/util/checkchange/checkchange.go +++ b/util/checkchange/checkchange.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package checkchange defines a utility for determining whether a value diff --git a/util/cibuild/cibuild.go b/util/cibuild/cibuild.go index c1e337f9a142a..4a4e241ac2cf0 100644 --- a/util/cibuild/cibuild.go +++ b/util/cibuild/cibuild.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cibuild reports runtime CI information. diff --git a/util/clientmetric/clientmetric.go b/util/clientmetric/clientmetric.go index 50cf3b2960499..b67cbbd39aa1e 100644 --- a/util/clientmetric/clientmetric.go +++ b/util/clientmetric/clientmetric.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_clientmetrics diff --git a/util/clientmetric/clientmetric_test.go b/util/clientmetric/clientmetric_test.go index 555d7a71170a4..db1cfe1893512 100644 --- a/util/clientmetric/clientmetric_test.go +++ b/util/clientmetric/clientmetric_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package clientmetric diff --git a/util/clientmetric/omit.go b/util/clientmetric/omit.go index 6d678cf20d1ae..725b18fe48d3c 100644 --- a/util/clientmetric/omit.go +++ b/util/clientmetric/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_clientmetrics diff --git a/util/cloudenv/cloudenv.go b/util/cloudenv/cloudenv.go index f55f7dfb0794a..aee4bac723f2d 100644 --- a/util/cloudenv/cloudenv.go +++ b/util/cloudenv/cloudenv.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cloudenv reports which known cloud environment we're running in. diff --git a/util/cloudenv/cloudenv_test.go b/util/cloudenv/cloudenv_test.go index c4486b2841ec1..c928fe660ee72 100644 --- a/util/cloudenv/cloudenv_test.go +++ b/util/cloudenv/cloudenv_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cloudenv diff --git a/util/cloudinfo/cloudinfo.go b/util/cloudinfo/cloudinfo.go index 2c4a32c031d2c..5f6a54ebdcb95 100644 --- a/util/cloudinfo/cloudinfo.go +++ b/util/cloudinfo/cloudinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(ios || android || js) diff --git a/util/cloudinfo/cloudinfo_nocloud.go b/util/cloudinfo/cloudinfo_nocloud.go index 6a525cd2a5725..b7ea210c15c6f 100644 --- a/util/cloudinfo/cloudinfo_nocloud.go +++ b/util/cloudinfo/cloudinfo_nocloud.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || js diff --git a/util/cloudinfo/cloudinfo_test.go b/util/cloudinfo/cloudinfo_test.go index 38817f47a6e56..721eca25f960d 100644 --- a/util/cloudinfo/cloudinfo_test.go +++ b/util/cloudinfo/cloudinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cloudinfo diff --git a/util/cmpver/version.go b/util/cmpver/version.go index 972c7b95f9a5e..69b01c48b1d4b 100644 --- a/util/cmpver/version.go +++ b/util/cmpver/version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cmpver implements a variant of debian version number diff --git a/util/cmpver/version_test.go b/util/cmpver/version_test.go index 8a3e470d1d37f..5688aa037927b 100644 --- a/util/cmpver/version_test.go +++ b/util/cmpver/version_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cmpver_test diff --git a/util/codegen/codegen.go b/util/codegen/codegen.go index ec02d652b8760..2023c8d9b1e08 100644 --- a/util/codegen/codegen.go +++ b/util/codegen/codegen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package codegen contains shared utilities for generating code. @@ -69,7 +69,7 @@ func HasNoClone(structTag string) bool { return false } -const copyrightHeader = `// Copyright (c) Tailscale Inc & AUTHORS +const copyrightHeader = `// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause ` diff --git a/util/codegen/codegen_test.go b/util/codegen/codegen_test.go index 74715eecae6ef..49656401a9b18 100644 --- a/util/codegen/codegen_test.go +++ b/util/codegen/codegen_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package codegen diff --git a/util/cstruct/cstruct.go b/util/cstruct/cstruct.go index 4d1d0a98b8032..afb0150bb1e77 100644 --- a/util/cstruct/cstruct.go +++ b/util/cstruct/cstruct.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package cstruct provides a helper for decoding binary data that is in the diff --git a/util/cstruct/cstruct_example_test.go b/util/cstruct/cstruct_example_test.go index 17032267b9dc6..a665abe355f6a 100644 --- a/util/cstruct/cstruct_example_test.go +++ b/util/cstruct/cstruct_example_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Only built on 64-bit platforms to avoid complexity diff --git a/util/cstruct/cstruct_test.go b/util/cstruct/cstruct_test.go index 5a75f338502bc..95d4876ca9256 100644 --- a/util/cstruct/cstruct_test.go +++ b/util/cstruct/cstruct_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package cstruct diff --git a/util/ctxkey/key.go b/util/ctxkey/key.go index e2b0e9d4ce0bf..982c65f04c8d7 100644 --- a/util/ctxkey/key.go +++ b/util/ctxkey/key.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // ctxkey provides type-safe key-value pairs for use with [context.Context]. diff --git a/util/ctxkey/key_test.go b/util/ctxkey/key_test.go index 20d85a3c0d2ae..413c3eacdd847 100644 --- a/util/ctxkey/key_test.go +++ b/util/ctxkey/key_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ctxkey diff --git a/util/deephash/debug.go b/util/deephash/debug.go index 50b3d5605f327..70c7a965551a4 100644 --- a/util/deephash/debug.go +++ b/util/deephash/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build deephash_debug diff --git a/util/deephash/deephash.go b/util/deephash/deephash.go index 29f47e3386ebd..ae082ef35e019 100644 --- a/util/deephash/deephash.go +++ b/util/deephash/deephash.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package deephash hashes a Go value recursively, in a predictable order, diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go index 413893ff967d2..c50d70bc6ed7f 100644 --- a/util/deephash/deephash_test.go +++ b/util/deephash/deephash_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/deephash/pointer.go b/util/deephash/pointer.go index aafae47a23673..448f12108eeee 100644 --- a/util/deephash/pointer.go +++ b/util/deephash/pointer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/deephash/pointer_norace.go b/util/deephash/pointer_norace.go index f98a70f6a18e5..dc77bbeaaf4a6 100644 --- a/util/deephash/pointer_norace.go +++ b/util/deephash/pointer_norace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !race diff --git a/util/deephash/pointer_race.go b/util/deephash/pointer_race.go index c638c7d39f393..15fe45b9113b3 100644 --- a/util/deephash/pointer_race.go +++ b/util/deephash/pointer_race.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build race diff --git a/util/deephash/tailscale_types_test.go b/util/deephash/tailscale_types_test.go index eeb7fdf84d11f..7e803c841c1f5 100644 --- a/util/deephash/tailscale_types_test.go +++ b/util/deephash/tailscale_types_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains tests and benchmarks that use types from other packages diff --git a/util/deephash/testtype/testtype.go b/util/deephash/testtype/testtype.go index 3c90053d6dfd5..b5775c62a0af1 100644 --- a/util/deephash/testtype/testtype.go +++ b/util/deephash/testtype/testtype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package testtype contains types for testing deephash. diff --git a/util/deephash/types.go b/util/deephash/types.go index 54edcbffc3fe8..ef19207bf68b8 100644 --- a/util/deephash/types.go +++ b/util/deephash/types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/deephash/types_test.go b/util/deephash/types_test.go index 78b40d88e5094..7a0a43b27e6d3 100644 --- a/util/deephash/types_test.go +++ b/util/deephash/types_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package deephash diff --git a/util/dirwalk/dirwalk.go b/util/dirwalk/dirwalk.go index 811766892896a..38f58d517df77 100644 --- a/util/dirwalk/dirwalk.go +++ b/util/dirwalk/dirwalk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dirwalk contains code to walk a directory. diff --git a/util/dirwalk/dirwalk_linux.go b/util/dirwalk/dirwalk_linux.go index 256467ebd8ac5..4a12f8ebe075b 100644 --- a/util/dirwalk/dirwalk_linux.go +++ b/util/dirwalk/dirwalk_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirwalk diff --git a/util/dirwalk/dirwalk_test.go b/util/dirwalk/dirwalk_test.go index 15ebc13dd404d..f9ba842977d6b 100644 --- a/util/dirwalk/dirwalk_test.go +++ b/util/dirwalk/dirwalk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dirwalk diff --git a/util/dnsname/dnsname.go b/util/dnsname/dnsname.go index ef898ebbd842f..09b44e73e2faa 100644 --- a/util/dnsname/dnsname.go +++ b/util/dnsname/dnsname.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package dnsname contains string functions for working with DNS names. diff --git a/util/dnsname/dnsname_test.go b/util/dnsname/dnsname_test.go index b038bb1bd10e1..35e04de2ebb35 100644 --- a/util/dnsname/dnsname_test.go +++ b/util/dnsname/dnsname_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package dnsname diff --git a/util/eventbus/bench_test.go b/util/eventbus/bench_test.go index 25f5b80020880..7cd7a424184d2 100644 --- a/util/eventbus/bench_test.go +++ b/util/eventbus/bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus_test diff --git a/util/eventbus/bus.go b/util/eventbus/bus.go index 880e075ccaf3c..1bc8aaed63dfc 100644 --- a/util/eventbus/bus.go +++ b/util/eventbus/bus.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/bus_test.go b/util/eventbus/bus_test.go index 88e11e7199aee..e7fa7577f2bdd 100644 --- a/util/eventbus/bus_test.go +++ b/util/eventbus/bus_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus_test diff --git a/util/eventbus/client.go b/util/eventbus/client.go index a7a5ab673bdfd..f405146ce4a82 100644 --- a/util/eventbus/client.go +++ b/util/eventbus/client.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/debug-demo/main.go b/util/eventbus/debug-demo/main.go index 71894d2eab94e..64b51a0fa4fac 100644 --- a/util/eventbus/debug-demo/main.go +++ b/util/eventbus/debug-demo/main.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // debug-demo is a program that serves a bus's debug interface over diff --git a/util/eventbus/debug.go b/util/eventbus/debug.go index 0453defb1a77e..7a37aeac8b6f3 100644 --- a/util/eventbus/debug.go +++ b/util/eventbus/debug.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/debughttp.go b/util/eventbus/debughttp.go index 9e03676d07128..1c5a64074e441 100644 --- a/util/eventbus/debughttp.go +++ b/util/eventbus/debughttp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !android && !ts_omit_debugeventbus diff --git a/util/eventbus/debughttp_off.go b/util/eventbus/debughttp_off.go index 332525262aa29..4b31bd6b78a79 100644 --- a/util/eventbus/debughttp_off.go +++ b/util/eventbus/debughttp_off.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || android || ts_omit_debugeventbus diff --git a/util/eventbus/doc.go b/util/eventbus/doc.go index f95f9398c8de9..89af076f9b637 100644 --- a/util/eventbus/doc.go +++ b/util/eventbus/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package eventbus provides an in-process event bus. diff --git a/util/eventbus/eventbustest/doc.go b/util/eventbus/eventbustest/doc.go index 1e9928b9d7cf9..504d40d9546fe 100644 --- a/util/eventbus/eventbustest/doc.go +++ b/util/eventbus/eventbustest/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package eventbustest provides helper methods for testing an [eventbus.Bus]. diff --git a/util/eventbus/eventbustest/eventbustest.go b/util/eventbus/eventbustest/eventbustest.go index fd8a150812e0d..b3ef6c884d89f 100644 --- a/util/eventbus/eventbustest/eventbustest.go +++ b/util/eventbus/eventbustest/eventbustest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbustest diff --git a/util/eventbus/eventbustest/eventbustest_test.go b/util/eventbus/eventbustest/eventbustest_test.go index ac454023c9c47..810312fcb411a 100644 --- a/util/eventbus/eventbustest/eventbustest_test.go +++ b/util/eventbus/eventbustest/eventbustest_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbustest_test diff --git a/util/eventbus/eventbustest/examples_test.go b/util/eventbus/eventbustest/examples_test.go index c848113173bc6..87a0efe31c6e9 100644 --- a/util/eventbus/eventbustest/examples_test.go +++ b/util/eventbus/eventbustest/examples_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbustest_test diff --git a/util/eventbus/fetch-htmx.go b/util/eventbus/fetch-htmx.go index f80d5025727fd..6a780d3025681 100644 --- a/util/eventbus/fetch-htmx.go +++ b/util/eventbus/fetch-htmx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ignore diff --git a/util/eventbus/monitor.go b/util/eventbus/monitor.go index db6fe1be44737..0d3056e206664 100644 --- a/util/eventbus/monitor.go +++ b/util/eventbus/monitor.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/publish.go b/util/eventbus/publish.go index 348bb9dff950c..f6fd029b7902e 100644 --- a/util/eventbus/publish.go +++ b/util/eventbus/publish.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/queue.go b/util/eventbus/queue.go index 2589b75cef999..0483a05a68d5c 100644 --- a/util/eventbus/queue.go +++ b/util/eventbus/queue.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/eventbus/subscribe.go b/util/eventbus/subscribe.go index b0348e125c393..3edf6deb44bb2 100644 --- a/util/eventbus/subscribe.go +++ b/util/eventbus/subscribe.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package eventbus diff --git a/util/execqueue/execqueue.go b/util/execqueue/execqueue.go index 87616a6b50a45..b2c7014377e13 100644 --- a/util/execqueue/execqueue.go +++ b/util/execqueue/execqueue.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package execqueue implements an ordered asynchronous queue for executing functions. diff --git a/util/execqueue/execqueue_test.go b/util/execqueue/execqueue_test.go index 1bce69556e1f7..c9f3a449ef67e 100644 --- a/util/execqueue/execqueue_test.go +++ b/util/execqueue/execqueue_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package execqueue diff --git a/util/expvarx/expvarx.go b/util/expvarx/expvarx.go index bcdc4a91a7982..6dc2379b961a5 100644 --- a/util/expvarx/expvarx.go +++ b/util/expvarx/expvarx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package expvarx provides some extensions to the [expvar] package. diff --git a/util/expvarx/expvarx_test.go b/util/expvarx/expvarx_test.go index 9ed2e8f209115..f8d2139d3ecb1 100644 --- a/util/expvarx/expvarx_test.go +++ b/util/expvarx/expvarx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package expvarx diff --git a/util/goroutines/goroutines.go b/util/goroutines/goroutines.go index d40cbecb10876..fd0a4dd7eb321 100644 --- a/util/goroutines/goroutines.go +++ b/util/goroutines/goroutines.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The goroutines package contains utilities for tracking and getting active goroutines. diff --git a/util/goroutines/goroutines_test.go b/util/goroutines/goroutines_test.go index ae17c399ca274..97adccf1c2ab1 100644 --- a/util/goroutines/goroutines_test.go +++ b/util/goroutines/goroutines_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package goroutines diff --git a/util/goroutines/tracker.go b/util/goroutines/tracker.go index c2a0cb8c3a3ed..b0513ef4efa3f 100644 --- a/util/goroutines/tracker.go +++ b/util/goroutines/tracker.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package goroutines diff --git a/util/groupmember/groupmember.go b/util/groupmember/groupmember.go index d604168169022..090e1561dcded 100644 --- a/util/groupmember/groupmember.go +++ b/util/groupmember/groupmember.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package groupmember verifies group membership of the provided user on the diff --git a/util/hashx/block512.go b/util/hashx/block512.go index e637c0c030653..5f32f33a6c8d2 100644 --- a/util/hashx/block512.go +++ b/util/hashx/block512.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package hashx provides a concrete implementation of [hash.Hash] diff --git a/util/hashx/block512_test.go b/util/hashx/block512_test.go index ca3ee0d784514..91d5d9ee67749 100644 --- a/util/hashx/block512_test.go +++ b/util/hashx/block512_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package hashx diff --git a/util/httphdr/httphdr.go b/util/httphdr/httphdr.go index 852e28b8fae03..01e8eddc67ac1 100644 --- a/util/httphdr/httphdr.go +++ b/util/httphdr/httphdr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package httphdr implements functionality for parsing and formatting diff --git a/util/httphdr/httphdr_test.go b/util/httphdr/httphdr_test.go index 81feeaca080d8..37906a5bf6603 100644 --- a/util/httphdr/httphdr_test.go +++ b/util/httphdr/httphdr_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package httphdr diff --git a/util/httpm/httpm.go b/util/httpm/httpm.go index a9a691b8a69e2..f15912ecb772a 100644 --- a/util/httpm/httpm.go +++ b/util/httpm/httpm.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package httpm has shorter names for HTTP method constants. diff --git a/util/httpm/httpm_test.go b/util/httpm/httpm_test.go index 0c71edc2f3c42..4e7f7b5ab277c 100644 --- a/util/httpm/httpm_test.go +++ b/util/httpm/httpm_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package httpm diff --git a/util/limiter/limiter.go b/util/limiter/limiter.go index b5fbb6fa6b2f7..f48114d531fa4 100644 --- a/util/limiter/limiter.go +++ b/util/limiter/limiter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package limiter provides a keyed token bucket rate limiter. diff --git a/util/limiter/limiter_test.go b/util/limiter/limiter_test.go index d3f3e307a2b82..5210322bbd3b0 100644 --- a/util/limiter/limiter_test.go +++ b/util/limiter/limiter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package limiter diff --git a/util/lineiter/lineiter.go b/util/lineiter/lineiter.go index 5cb1eeef3ee1d..06d35909022b8 100644 --- a/util/lineiter/lineiter.go +++ b/util/lineiter/lineiter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lineiter iterates over lines in things. diff --git a/util/lineiter/lineiter_test.go b/util/lineiter/lineiter_test.go index 3373d5fe7b122..6e9e285501fab 100644 --- a/util/lineiter/lineiter_test.go +++ b/util/lineiter/lineiter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lineiter diff --git a/util/lineread/lineread.go b/util/lineread/lineread.go index 6b01d2b69ffd7..25cc63247e953 100644 --- a/util/lineread/lineread.go +++ b/util/lineread/lineread.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lineread reads lines from files. It's not fancy, but it got repetitive. diff --git a/util/linuxfw/detector.go b/util/linuxfw/detector.go index 149e0c96049c8..a3a1c1ddaa547 100644 --- a/util/linuxfw/detector.go +++ b/util/linuxfw/detector.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/fake.go b/util/linuxfw/fake.go index d01849a2e5c9d..1886e25429537 100644 --- a/util/linuxfw/fake.go +++ b/util/linuxfw/fake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/fake_netfilter.go b/util/linuxfw/fake_netfilter.go index a998ed765fd63..d760edfcf757e 100644 --- a/util/linuxfw/fake_netfilter.go +++ b/util/linuxfw/fake_netfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/helpers.go b/util/linuxfw/helpers.go index a4b9fdf402558..a369b6a8841b3 100644 --- a/util/linuxfw/helpers.go +++ b/util/linuxfw/helpers.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables.go b/util/linuxfw/iptables.go index 76c5400becff8..f054e7abe1718 100644 --- a/util/linuxfw/iptables.go +++ b/util/linuxfw/iptables.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_iptables diff --git a/util/linuxfw/iptables_disabled.go b/util/linuxfw/iptables_disabled.go index 538e33647381a..c986fe7c206ea 100644 --- a/util/linuxfw/iptables_disabled.go +++ b/util/linuxfw/iptables_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && ts_omit_iptables diff --git a/util/linuxfw/iptables_for_svcs.go b/util/linuxfw/iptables_for_svcs.go index 2cd8716e4622b..acc2baf6c6fcf 100644 --- a/util/linuxfw/iptables_for_svcs.go +++ b/util/linuxfw/iptables_for_svcs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables_for_svcs_test.go b/util/linuxfw/iptables_for_svcs_test.go index 0e56d70ba7078..b4dfe19c84c14 100644 --- a/util/linuxfw/iptables_for_svcs_test.go +++ b/util/linuxfw/iptables_for_svcs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables_runner.go b/util/linuxfw/iptables_runner.go index 4443a907107d6..ed55960b36d7c 100644 --- a/util/linuxfw/iptables_runner.go +++ b/util/linuxfw/iptables_runner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/iptables_runner_test.go b/util/linuxfw/iptables_runner_test.go index ce905aef3f75b..0dcade35188fc 100644 --- a/util/linuxfw/iptables_runner_test.go +++ b/util/linuxfw/iptables_runner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/linuxfw.go b/util/linuxfw/linuxfw.go index ec73aaceea03a..325a5809f8586 100644 --- a/util/linuxfw/linuxfw.go +++ b/util/linuxfw/linuxfw.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/linuxfwtest/linuxfwtest.go b/util/linuxfw/linuxfwtest/linuxfwtest.go index ee2cbd1b227f4..bf1477ad9b994 100644 --- a/util/linuxfw/linuxfwtest/linuxfwtest.go +++ b/util/linuxfw/linuxfwtest/linuxfwtest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build cgo && linux diff --git a/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go b/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go index 6e95699001d4b..ec2d24d3521c9 100644 --- a/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go +++ b/util/linuxfw/linuxfwtest/linuxfwtest_unsupported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !cgo || !linux diff --git a/util/linuxfw/nftables.go b/util/linuxfw/nftables.go index 94ce51a1405a4..6059128a97c2f 100644 --- a/util/linuxfw/nftables.go +++ b/util/linuxfw/nftables.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO(#8502): add support for more architectures diff --git a/util/linuxfw/nftables_for_svcs.go b/util/linuxfw/nftables_for_svcs.go index 474b980869691..c2425e2ff285b 100644 --- a/util/linuxfw/nftables_for_svcs.go +++ b/util/linuxfw/nftables_for_svcs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/nftables_for_svcs_test.go b/util/linuxfw/nftables_for_svcs_test.go index 73472ce20cbe5..c3be3fc3b7bee 100644 --- a/util/linuxfw/nftables_for_svcs_test.go +++ b/util/linuxfw/nftables_for_svcs_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/nftables_runner.go b/util/linuxfw/nftables_runner.go index faa02f7c75956..2c44a6218e76e 100644 --- a/util/linuxfw/nftables_runner.go +++ b/util/linuxfw/nftables_runner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/nftables_runner_test.go b/util/linuxfw/nftables_runner_test.go index 6fb180ed67ce6..dc4d3194a23ba 100644 --- a/util/linuxfw/nftables_runner_test.go +++ b/util/linuxfw/nftables_runner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/linuxfw/nftables_types.go b/util/linuxfw/nftables_types.go index b6e24d2a67b5b..27c5ee5981e13 100644 --- a/util/linuxfw/nftables_types.go +++ b/util/linuxfw/nftables_types.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // TODO(#8502): add support for more architectures diff --git a/util/lru/lru.go b/util/lru/lru.go index 8e4dd417b98d2..7fb191535dcce 100644 --- a/util/lru/lru.go +++ b/util/lru/lru.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package lru contains a typed Least-Recently-Used cache. diff --git a/util/lru/lru_test.go b/util/lru/lru_test.go index 04de2e5070c87..5fbc718b1decd 100644 --- a/util/lru/lru_test.go +++ b/util/lru/lru_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package lru diff --git a/util/mak/mak.go b/util/mak/mak.go index fbdb40b0afd21..97daab98a7650 100644 --- a/util/mak/mak.go +++ b/util/mak/mak.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mak helps make maps. It contains generic helpers to make/assign diff --git a/util/mak/mak_test.go b/util/mak/mak_test.go index e47839a3c8fe9..7a4090c20292c 100644 --- a/util/mak/mak_test.go +++ b/util/mak/mak_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mak contains code to help make things. diff --git a/util/multierr/multierr.go b/util/multierr/multierr.go index 93ca068f56532..3acdb7d773222 100644 --- a/util/multierr/multierr.go +++ b/util/multierr/multierr.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package multierr provides a simple multiple-error type. diff --git a/util/multierr/multierr_test.go b/util/multierr/multierr_test.go index de7721a665f40..35195b3770db1 100644 --- a/util/multierr/multierr_test.go +++ b/util/multierr/multierr_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package multierr_test diff --git a/util/must/must.go b/util/must/must.go index a292da2268c27..6a4b519361f2f 100644 --- a/util/must/must.go +++ b/util/must/must.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package must assists in calling functions that must succeed. diff --git a/util/nocasemaps/nocase.go b/util/nocasemaps/nocase.go index 2d91d8fe96a7a..737ab5de7c3bb 100644 --- a/util/nocasemaps/nocase.go +++ b/util/nocasemaps/nocase.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // nocasemaps provides efficient functions to set and get entries in Go maps diff --git a/util/nocasemaps/nocase_test.go b/util/nocasemaps/nocase_test.go index 5275b3ee6ef23..cae36242c3040 100644 --- a/util/nocasemaps/nocase_test.go +++ b/util/nocasemaps/nocase_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package nocasemaps diff --git a/util/osdiag/internal/wsc/wsc_windows.go b/util/osdiag/internal/wsc/wsc_windows.go index b402946eda4d2..8bc43ac54bbb9 100644 --- a/util/osdiag/internal/wsc/wsc_windows.go +++ b/util/osdiag/internal/wsc/wsc_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by 'go generate'; DO NOT EDIT. diff --git a/util/osdiag/mksyscall.go b/util/osdiag/mksyscall.go index bcbe113b051cd..688e0a31a7cc7 100644 --- a/util/osdiag/mksyscall.go +++ b/util/osdiag/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osdiag diff --git a/util/osdiag/osdiag.go b/util/osdiag/osdiag.go index 2ebecbdbf74a2..9845bd3f8be46 100644 --- a/util/osdiag/osdiag.go +++ b/util/osdiag/osdiag.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osdiag provides loggers for OS-specific diagnostic information. diff --git a/util/osdiag/osdiag_notwindows.go b/util/osdiag/osdiag_notwindows.go index 0e46c97e50803..72237438b480b 100644 --- a/util/osdiag/osdiag_notwindows.go +++ b/util/osdiag/osdiag_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/util/osdiag/osdiag_windows.go b/util/osdiag/osdiag_windows.go index 5dcce3beaf76e..d6ba1d30bb674 100644 --- a/util/osdiag/osdiag_windows.go +++ b/util/osdiag/osdiag_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osdiag diff --git a/util/osdiag/osdiag_windows_test.go b/util/osdiag/osdiag_windows_test.go index b29b602ccb73c..f285f80feac43 100644 --- a/util/osdiag/osdiag_windows_test.go +++ b/util/osdiag/osdiag_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osdiag diff --git a/util/osshare/filesharingstatus_noop.go b/util/osshare/filesharingstatus_noop.go index 7f2b131904ea9..22f0a33785131 100644 --- a/util/osshare/filesharingstatus_noop.go +++ b/util/osshare/filesharingstatus_noop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/util/osshare/filesharingstatus_windows.go b/util/osshare/filesharingstatus_windows.go index c125de15990c3..d21c394d0a27c 100644 --- a/util/osshare/filesharingstatus_windows.go +++ b/util/osshare/filesharingstatus_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osshare provides utilities for enabling/disabling Taildrop file diff --git a/util/osuser/group_ids.go b/util/osuser/group_ids.go index 7c2b5b090cbcc..2a1f147d87b00 100644 --- a/util/osuser/group_ids.go +++ b/util/osuser/group_ids.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osuser diff --git a/util/osuser/group_ids_test.go b/util/osuser/group_ids_test.go index 69e8336ea6872..79e189ed8c866 100644 --- a/util/osuser/group_ids_test.go +++ b/util/osuser/group_ids_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osuser diff --git a/util/osuser/user.go b/util/osuser/user.go index 8b96194d716ce..2de3da762739d 100644 --- a/util/osuser/user.go +++ b/util/osuser/user.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osuser implements OS user lookup. It's a wrapper around os/user that diff --git a/util/pidowner/pidowner.go b/util/pidowner/pidowner.go index 56bb640b785dd..cec92ba367e49 100644 --- a/util/pidowner/pidowner.go +++ b/util/pidowner/pidowner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package pidowner handles lookups from process ID to its owning user. diff --git a/util/pidowner/pidowner_linux.go b/util/pidowner/pidowner_linux.go index a07f512427062..f3f5cd97ddcb2 100644 --- a/util/pidowner/pidowner_linux.go +++ b/util/pidowner/pidowner_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pidowner diff --git a/util/pidowner/pidowner_noimpl.go b/util/pidowner/pidowner_noimpl.go index 50add492fda76..4bc665d61071e 100644 --- a/util/pidowner/pidowner_noimpl.go +++ b/util/pidowner/pidowner_noimpl.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows && !linux diff --git a/util/pidowner/pidowner_test.go b/util/pidowner/pidowner_test.go index 19c9ab46dff01..2774a8ab0fe36 100644 --- a/util/pidowner/pidowner_test.go +++ b/util/pidowner/pidowner_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pidowner diff --git a/util/pidowner/pidowner_windows.go b/util/pidowner/pidowner_windows.go index dbf13ac8135f1..8edd7698d4207 100644 --- a/util/pidowner/pidowner_windows.go +++ b/util/pidowner/pidowner_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pidowner diff --git a/util/pool/pool.go b/util/pool/pool.go index 7014751e7ab77..7042fb893a59e 100644 --- a/util/pool/pool.go +++ b/util/pool/pool.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package pool contains a generic type for managing a pool of resources; for diff --git a/util/pool/pool_test.go b/util/pool/pool_test.go index 9d8eacbcb9d0b..ac7cf86be3ef7 100644 --- a/util/pool/pool_test.go +++ b/util/pool/pool_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package pool diff --git a/util/precompress/precompress.go b/util/precompress/precompress.go index 6d1a26efdd767..80aed36821b2e 100644 --- a/util/precompress/precompress.go +++ b/util/precompress/precompress.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package precompress provides build- and serving-time support for diff --git a/util/progresstracking/progresstracking.go b/util/progresstracking/progresstracking.go index a9411fb46f7fd..21cbfa52ba3ff 100644 --- a/util/progresstracking/progresstracking.go +++ b/util/progresstracking/progresstracking.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package progresstracking provides wrappers around io.Reader and io.Writer diff --git a/util/prompt/prompt.go b/util/prompt/prompt.go index a6d86fb481769..b84993a0aeed9 100644 --- a/util/prompt/prompt.go +++ b/util/prompt/prompt.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package prompt provides a simple way to prompt the user for input. diff --git a/util/qrcodes/format.go b/util/qrcodes/format.go index dbd565b2ec9d3..99b58ff747fde 100644 --- a/util/qrcodes/format.go +++ b/util/qrcodes/format.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package qrcodes diff --git a/util/qrcodes/qrcodes.go b/util/qrcodes/qrcodes.go index 02e06e59b4be3..dc16fee8cfd2a 100644 --- a/util/qrcodes/qrcodes.go +++ b/util/qrcodes/qrcodes.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_qrcodes diff --git a/util/qrcodes/qrcodes_disabled.go b/util/qrcodes/qrcodes_disabled.go index fa1b89cf437ef..bda8957573780 100644 --- a/util/qrcodes/qrcodes_disabled.go +++ b/util/qrcodes/qrcodes_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_qrcodes diff --git a/util/qrcodes/qrcodes_linux.go b/util/qrcodes/qrcodes_linux.go index 8f0d40f0a5e4a..474e231e23aff 100644 --- a/util/qrcodes/qrcodes_linux.go +++ b/util/qrcodes/qrcodes_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_qrcodes diff --git a/util/qrcodes/qrcodes_notlinux.go b/util/qrcodes/qrcodes_notlinux.go index 3149a60605bf3..4a7b493ff55a9 100644 --- a/util/qrcodes/qrcodes_notlinux.go +++ b/util/qrcodes/qrcodes_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux && !ts_omit_qrcodes diff --git a/util/quarantine/quarantine.go b/util/quarantine/quarantine.go index 7ad65a81d69ee..48c032d06cfcb 100644 --- a/util/quarantine/quarantine.go +++ b/util/quarantine/quarantine.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package quarantine sets platform specific "quarantine" attributes on files diff --git a/util/quarantine/quarantine_darwin.go b/util/quarantine/quarantine_darwin.go index 35405d9cc7a87..de1bbf70df985 100644 --- a/util/quarantine/quarantine_darwin.go +++ b/util/quarantine/quarantine_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package quarantine diff --git a/util/quarantine/quarantine_default.go b/util/quarantine/quarantine_default.go index 65954a4d25415..5158bda54314b 100644 --- a/util/quarantine/quarantine_default.go +++ b/util/quarantine/quarantine_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !darwin && !windows diff --git a/util/quarantine/quarantine_windows.go b/util/quarantine/quarantine_windows.go index 6fdf4e699b75b..886b2202a4beb 100644 --- a/util/quarantine/quarantine_windows.go +++ b/util/quarantine/quarantine_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package quarantine diff --git a/util/race/race.go b/util/race/race.go index 26c8e13eb468e..8e339dad2fd03 100644 --- a/util/race/race.go +++ b/util/race/race.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package race contains a helper to "race" two functions, returning the first diff --git a/util/race/race_test.go b/util/race/race_test.go index d3838271226ac..90b049909ce3c 100644 --- a/util/race/race_test.go +++ b/util/race/race_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package race diff --git a/util/racebuild/off.go b/util/racebuild/off.go index 8f4fe998fb4bb..2ffe9fd5370e5 100644 --- a/util/racebuild/off.go +++ b/util/racebuild/off.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !race diff --git a/util/racebuild/on.go b/util/racebuild/on.go index 69ae2bcae4239..794171c55a792 100644 --- a/util/racebuild/on.go +++ b/util/racebuild/on.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build race diff --git a/util/racebuild/racebuild.go b/util/racebuild/racebuild.go index d061276cb8a0a..9dc0fb9f77ce9 100644 --- a/util/racebuild/racebuild.go +++ b/util/racebuild/racebuild.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package racebuild exports a constant about whether the current binary diff --git a/util/rands/cheap.go b/util/rands/cheap.go index 69785e086e664..f3b931d34662b 100644 --- a/util/rands/cheap.go +++ b/util/rands/cheap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Copyright 2009 The Go Authors. All rights reserved. diff --git a/util/rands/cheap_test.go b/util/rands/cheap_test.go index 756b55b4e0ddc..874592a1b647e 100644 --- a/util/rands/cheap_test.go +++ b/util/rands/cheap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rands diff --git a/util/rands/rands.go b/util/rands/rands.go index d83e1e55898dc..94c6e6f4a1c29 100644 --- a/util/rands/rands.go +++ b/util/rands/rands.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package rands contains utility functions for randomness. diff --git a/util/rands/rands_test.go b/util/rands/rands_test.go index 5813f2bb46763..81cdf3bec02e0 100644 --- a/util/rands/rands_test.go +++ b/util/rands/rands_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rands diff --git a/util/reload/reload.go b/util/reload/reload.go index f18f9ebd1028c..edcb90c12a3f2 100644 --- a/util/reload/reload.go +++ b/util/reload/reload.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package reload contains functions that allow periodically reloading a value diff --git a/util/reload/reload_test.go b/util/reload/reload_test.go index f6a38168659cd..7e7963c3f7a9e 100644 --- a/util/reload/reload_test.go +++ b/util/reload/reload_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package reload diff --git a/util/ringlog/ringlog.go b/util/ringlog/ringlog.go index 62dfbae5bd5c3..d8197dda84deb 100644 --- a/util/ringlog/ringlog.go +++ b/util/ringlog/ringlog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ringlog contains a limited-size concurrency-safe generic ring log. diff --git a/util/ringlog/ringlog_test.go b/util/ringlog/ringlog_test.go index d6776e181a4f8..8ecf99cd0f3f1 100644 --- a/util/ringlog/ringlog_test.go +++ b/util/ringlog/ringlog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ringlog diff --git a/util/safediff/diff.go b/util/safediff/diff.go index cf8add94b21dd..c9a2c60bea1a8 100644 --- a/util/safediff/diff.go +++ b/util/safediff/diff.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package safediff computes the difference between two lists. diff --git a/util/safediff/diff_test.go b/util/safediff/diff_test.go index e580bd9222dd9..4251d788b10fe 100644 --- a/util/safediff/diff_test.go +++ b/util/safediff/diff_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package safediff diff --git a/util/set/handle.go b/util/set/handle.go index 9c6b6dab0549b..1ad86d1fd307e 100644 --- a/util/set/handle.go +++ b/util/set/handle.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/intset.go b/util/set/intset.go index d325246914488..04f614742e796 100644 --- a/util/set/intset.go +++ b/util/set/intset.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/intset_test.go b/util/set/intset_test.go index d838215c97848..6cbf5a0bb472b 100644 --- a/util/set/intset_test.go +++ b/util/set/intset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/set.go b/util/set/set.go index eb0697536f73b..df4b1fa3a24ac 100644 --- a/util/set/set.go +++ b/util/set/set.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package set contains set types. diff --git a/util/set/set_test.go b/util/set/set_test.go index 85913ad24a216..4afaeea5747fc 100644 --- a/util/set/set_test.go +++ b/util/set/set_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/slice.go b/util/set/slice.go index 2fc65b82d1c6e..921da4fa21622 100644 --- a/util/set/slice.go +++ b/util/set/slice.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/slice_test.go b/util/set/slice_test.go index 9134c296292d3..468ba686c5772 100644 --- a/util/set/slice_test.go +++ b/util/set/slice_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/smallset.go b/util/set/smallset.go index 1b77419d27dc9..da52dd265a939 100644 --- a/util/set/smallset.go +++ b/util/set/smallset.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/set/smallset_test.go b/util/set/smallset_test.go index d6f446df08e81..019a9d24d1f5c 100644 --- a/util/set/smallset_test.go +++ b/util/set/smallset_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package set diff --git a/util/singleflight/singleflight.go b/util/singleflight/singleflight.go index 9df47448b70ab..23cf7e21fec15 100644 --- a/util/singleflight/singleflight.go +++ b/util/singleflight/singleflight.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Copyright 2013 The Go Authors. All rights reserved. diff --git a/util/singleflight/singleflight_test.go b/util/singleflight/singleflight_test.go index 031922736fab6..9f0ca7f1de853 100644 --- a/util/singleflight/singleflight_test.go +++ b/util/singleflight/singleflight_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Copyright 2013 The Go Authors. All rights reserved. diff --git a/util/slicesx/slicesx.go b/util/slicesx/slicesx.go index ff9d473759fb0..660110a3c4f28 100644 --- a/util/slicesx/slicesx.go +++ b/util/slicesx/slicesx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package slicesx contains some helpful generic slice functions. diff --git a/util/slicesx/slicesx_test.go b/util/slicesx/slicesx_test.go index 34644928465d8..d5c87a3727748 100644 --- a/util/slicesx/slicesx_test.go +++ b/util/slicesx/slicesx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package slicesx diff --git a/util/stringsx/stringsx.go b/util/stringsx/stringsx.go index 6c7a8d20d4221..5afea98a6a7c6 100644 --- a/util/stringsx/stringsx.go +++ b/util/stringsx/stringsx.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package stringsx provides additional string manipulation functions diff --git a/util/stringsx/stringsx_test.go b/util/stringsx/stringsx_test.go index 8575c0b278fca..afce987c08a53 100644 --- a/util/stringsx/stringsx_test.go +++ b/util/stringsx/stringsx_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package stringsx diff --git a/util/syspolicy/internal/internal.go b/util/syspolicy/internal/internal.go index 6ab147de6d096..4179f26c82cb2 100644 --- a/util/syspolicy/internal/internal.go +++ b/util/syspolicy/internal/internal.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package internal contains miscellaneous functions and types diff --git a/util/syspolicy/internal/loggerx/logger.go b/util/syspolicy/internal/loggerx/logger.go index d1f48cbb428fe..412616cb132cd 100644 --- a/util/syspolicy/internal/loggerx/logger.go +++ b/util/syspolicy/internal/loggerx/logger.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package loggerx provides logging functions to the rest of the syspolicy packages. diff --git a/util/syspolicy/internal/loggerx/logger_test.go b/util/syspolicy/internal/loggerx/logger_test.go index 9735b5d30c20b..5c8fb7e2860d5 100644 --- a/util/syspolicy/internal/loggerx/logger_test.go +++ b/util/syspolicy/internal/loggerx/logger_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package loggerx diff --git a/util/syspolicy/internal/metrics/metrics.go b/util/syspolicy/internal/metrics/metrics.go index 8f27456735ca6..8a3b5327fbab8 100644 --- a/util/syspolicy/internal/metrics/metrics.go +++ b/util/syspolicy/internal/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package metrics provides logging and reporting for policy settings and scopes. diff --git a/util/syspolicy/internal/metrics/metrics_test.go b/util/syspolicy/internal/metrics/metrics_test.go index a99938769712f..ce9dea98b86d7 100644 --- a/util/syspolicy/internal/metrics/metrics_test.go +++ b/util/syspolicy/internal/metrics/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/util/syspolicy/internal/metrics/test_handler.go b/util/syspolicy/internal/metrics/test_handler.go index 36c3f2cad876a..1ec0c9f4c3015 100644 --- a/util/syspolicy/internal/metrics/test_handler.go +++ b/util/syspolicy/internal/metrics/test_handler.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package metrics diff --git a/util/syspolicy/pkey/pkey.go b/util/syspolicy/pkey/pkey.go index e450625cd1710..9ed1d5b210b8d 100644 --- a/util/syspolicy/pkey/pkey.go +++ b/util/syspolicy/pkey/pkey.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package pkey defines the keys used to store system policies in the registry. diff --git a/util/syspolicy/policy_keys.go b/util/syspolicy/policy_keys.go index 3a54f9dde5dd7..2a4599cb85869 100644 --- a/util/syspolicy/policy_keys.go +++ b/util/syspolicy/policy_keys.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/syspolicy/policy_keys_test.go b/util/syspolicy/policy_keys_test.go index c2b8d5741831d..17e2e7a9b8f92 100644 --- a/util/syspolicy/policy_keys_test.go +++ b/util/syspolicy/policy_keys_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/syspolicy/policyclient/policyclient.go b/util/syspolicy/policyclient/policyclient.go index 728a16718e8e4..e6ad208b02f65 100644 --- a/util/syspolicy/policyclient/policyclient.go +++ b/util/syspolicy/policyclient/policyclient.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policyclient contains the minimal syspolicy interface as needed by diff --git a/util/syspolicy/policytest/policytest.go b/util/syspolicy/policytest/policytest.go index e5c1c7856d0a3..ef5ce889dd2de 100644 --- a/util/syspolicy/policytest/policytest.go +++ b/util/syspolicy/policytest/policytest.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policytest contains test helpers for the syspolicy packages. diff --git a/util/syspolicy/ptype/ptype.go b/util/syspolicy/ptype/ptype.go index 65ca9e63108eb..ea8b03ad7db22 100644 --- a/util/syspolicy/ptype/ptype.go +++ b/util/syspolicy/ptype/ptype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package ptype contains types used by syspolicy. diff --git a/util/syspolicy/ptype/ptype_test.go b/util/syspolicy/ptype/ptype_test.go index 7c963398b41b1..ba7eab471b5ea 100644 --- a/util/syspolicy/ptype/ptype_test.go +++ b/util/syspolicy/ptype/ptype_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package ptype diff --git a/util/syspolicy/rsop/change_callbacks.go b/util/syspolicy/rsop/change_callbacks.go index 71135bb2ac788..0b6cd10c4cc1a 100644 --- a/util/syspolicy/rsop/change_callbacks.go +++ b/util/syspolicy/rsop/change_callbacks.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/rsop/resultant_policy.go b/util/syspolicy/rsop/resultant_policy.go index bdda909763008..5f8081a677a79 100644 --- a/util/syspolicy/rsop/resultant_policy.go +++ b/util/syspolicy/rsop/resultant_policy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/rsop/resultant_policy_test.go b/util/syspolicy/rsop/resultant_policy_test.go index 3ff1421197b1f..60132eae7a1d8 100644 --- a/util/syspolicy/rsop/resultant_policy_test.go +++ b/util/syspolicy/rsop/resultant_policy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/rsop/rsop.go b/util/syspolicy/rsop/rsop.go index 333dca64343c1..a57a4b34825ac 100644 --- a/util/syspolicy/rsop/rsop.go +++ b/util/syspolicy/rsop/rsop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package rsop facilitates [source.Store] registration via [RegisterStore] diff --git a/util/syspolicy/rsop/store_registration.go b/util/syspolicy/rsop/store_registration.go index a7c354b6d5678..99dbc7096fc56 100644 --- a/util/syspolicy/rsop/store_registration.go +++ b/util/syspolicy/rsop/store_registration.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package rsop diff --git a/util/syspolicy/setting/errors.go b/util/syspolicy/setting/errors.go index 38dc6a88c7f1d..655018d4b5aff 100644 --- a/util/syspolicy/setting/errors.go +++ b/util/syspolicy/setting/errors.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/origin.go b/util/syspolicy/setting/origin.go index 4c7cc7025cc48..8ed629e72a322 100644 --- a/util/syspolicy/setting/origin.go +++ b/util/syspolicy/setting/origin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/policy_scope.go b/util/syspolicy/setting/policy_scope.go index c2039fdda15b8..4162614929dd2 100644 --- a/util/syspolicy/setting/policy_scope.go +++ b/util/syspolicy/setting/policy_scope.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/policy_scope_test.go b/util/syspolicy/setting/policy_scope_test.go index e1b6cf7ea0a78..a2f6328151d05 100644 --- a/util/syspolicy/setting/policy_scope_test.go +++ b/util/syspolicy/setting/policy_scope_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/raw_item.go b/util/syspolicy/setting/raw_item.go index ea97865f5a396..4bfb50faa1b62 100644 --- a/util/syspolicy/setting/raw_item.go +++ b/util/syspolicy/setting/raw_item.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/raw_item_test.go b/util/syspolicy/setting/raw_item_test.go index 05562d78c41f3..1a40bc829c351 100644 --- a/util/syspolicy/setting/raw_item_test.go +++ b/util/syspolicy/setting/raw_item_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/setting.go b/util/syspolicy/setting/setting.go index 97362b1dca8e0..4384e64c234f9 100644 --- a/util/syspolicy/setting/setting.go +++ b/util/syspolicy/setting/setting.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package setting contains types for defining and representing policy settings. diff --git a/util/syspolicy/setting/setting_test.go b/util/syspolicy/setting/setting_test.go index 9d99884f6436f..3ccd2ef606c50 100644 --- a/util/syspolicy/setting/setting_test.go +++ b/util/syspolicy/setting/setting_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/snapshot.go b/util/syspolicy/setting/snapshot.go index 94c7ecadb2533..74cadd0be7296 100644 --- a/util/syspolicy/setting/snapshot.go +++ b/util/syspolicy/setting/snapshot.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/snapshot_test.go b/util/syspolicy/setting/snapshot_test.go index 762a9681c6d7e..0385e4aefc8b4 100644 --- a/util/syspolicy/setting/snapshot_test.go +++ b/util/syspolicy/setting/snapshot_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/setting/summary.go b/util/syspolicy/setting/summary.go index 9864822f7a235..4cb15c7c4d078 100644 --- a/util/syspolicy/setting/summary.go +++ b/util/syspolicy/setting/summary.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package setting diff --git a/util/syspolicy/source/env_policy_store.go b/util/syspolicy/source/env_policy_store.go index be363b79a84eb..9b7cebfbf19e5 100644 --- a/util/syspolicy/source/env_policy_store.go +++ b/util/syspolicy/source/env_policy_store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/env_policy_store_test.go b/util/syspolicy/source/env_policy_store_test.go index 3255095b2d286..5cda0f32a2984 100644 --- a/util/syspolicy/source/env_policy_store_test.go +++ b/util/syspolicy/source/env_policy_store_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_reader.go b/util/syspolicy/source/policy_reader.go index 33ef22912f172..177985322d318 100644 --- a/util/syspolicy/source/policy_reader.go +++ b/util/syspolicy/source/policy_reader.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_reader_test.go b/util/syspolicy/source/policy_reader_test.go index 32e8c51a6d3c9..e5a893f56877a 100644 --- a/util/syspolicy/source/policy_reader_test.go +++ b/util/syspolicy/source/policy_reader_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_source.go b/util/syspolicy/source/policy_source.go index c4774217c09ac..3dfa83fd17a30 100644 --- a/util/syspolicy/source/policy_source.go +++ b/util/syspolicy/source/policy_source.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package source defines interfaces for policy stores, diff --git a/util/syspolicy/source/policy_store_windows.go b/util/syspolicy/source/policy_store_windows.go index f97b17f3afee6..edcdcae69b408 100644 --- a/util/syspolicy/source/policy_store_windows.go +++ b/util/syspolicy/source/policy_store_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/policy_store_windows_test.go b/util/syspolicy/source/policy_store_windows_test.go index 4ab1da805d6c8..b3ca5083d2a05 100644 --- a/util/syspolicy/source/policy_store_windows_test.go +++ b/util/syspolicy/source/policy_store_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/source/test_store.go b/util/syspolicy/source/test_store.go index ddec9efbb2d01..1baa138319337 100644 --- a/util/syspolicy/source/test_store.go +++ b/util/syspolicy/source/test_store.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package source diff --git a/util/syspolicy/syspolicy.go b/util/syspolicy/syspolicy.go index 48e430b674e35..7451bde758d4f 100644 --- a/util/syspolicy/syspolicy.go +++ b/util/syspolicy/syspolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package syspolicy contains the implementation of system policy management. diff --git a/util/syspolicy/syspolicy_test.go b/util/syspolicy/syspolicy_test.go index 10f8da48657d3..532cd03b8b9a7 100644 --- a/util/syspolicy/syspolicy_test.go +++ b/util/syspolicy/syspolicy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/syspolicy/syspolicy_windows.go b/util/syspolicy/syspolicy_windows.go index ca0fd329aca04..80c84b4570b9f 100644 --- a/util/syspolicy/syspolicy_windows.go +++ b/util/syspolicy/syspolicy_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package syspolicy diff --git a/util/sysresources/memory.go b/util/sysresources/memory.go index 7363155cdb2ae..3c6b9ae852e47 100644 --- a/util/sysresources/memory.go +++ b/util/sysresources/memory.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sysresources diff --git a/util/sysresources/memory_bsd.go b/util/sysresources/memory_bsd.go index 26850dce652ff..945f86ea35ec9 100644 --- a/util/sysresources/memory_bsd.go +++ b/util/sysresources/memory_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build freebsd || openbsd || dragonfly || netbsd diff --git a/util/sysresources/memory_darwin.go b/util/sysresources/memory_darwin.go index e07bac0cd7f9b..165f12eb3b808 100644 --- a/util/sysresources/memory_darwin.go +++ b/util/sysresources/memory_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin diff --git a/util/sysresources/memory_linux.go b/util/sysresources/memory_linux.go index 0239b0e80d62a..3885a8aa6c66e 100644 --- a/util/sysresources/memory_linux.go +++ b/util/sysresources/memory_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/util/sysresources/memory_unsupported.go b/util/sysresources/memory_unsupported.go index 0fde256e0543d..c88e9ed5201e9 100644 --- a/util/sysresources/memory_unsupported.go +++ b/util/sysresources/memory_unsupported.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !(linux || darwin || freebsd || openbsd || dragonfly || netbsd) diff --git a/util/sysresources/sysresources.go b/util/sysresources/sysresources.go index 32d972ab15513..33d0d5d96a96e 100644 --- a/util/sysresources/sysresources.go +++ b/util/sysresources/sysresources.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package sysresources provides OS-independent methods of determining the diff --git a/util/sysresources/sysresources_test.go b/util/sysresources/sysresources_test.go index 331ad913bfba1..7fea1bf0f5b32 100644 --- a/util/sysresources/sysresources_test.go +++ b/util/sysresources/sysresources_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package sysresources diff --git a/util/testenv/testenv.go b/util/testenv/testenv.go index aa6660411c91b..1ae1fe8a8a0f1 100644 --- a/util/testenv/testenv.go +++ b/util/testenv/testenv.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package testenv provides utility functions for tests. It does not depend on diff --git a/util/testenv/testenv_test.go b/util/testenv/testenv_test.go index c647d9aec1ea4..3001d19eb2722 100644 --- a/util/testenv/testenv_test.go +++ b/util/testenv/testenv_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package testenv diff --git a/util/topk/topk.go b/util/topk/topk.go index d3bbb2c6d1055..95ebd895d05aa 100644 --- a/util/topk/topk.go +++ b/util/topk/topk.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package topk defines a count-min sketch and a cheap probabilistic top-K data diff --git a/util/topk/topk_test.go b/util/topk/topk_test.go index d30342e90de7b..06656c4204fe6 100644 --- a/util/topk/topk_test.go +++ b/util/topk/topk_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package topk diff --git a/util/truncate/truncate.go b/util/truncate/truncate.go index 310b81dd07100..7b98013f0bd59 100644 --- a/util/truncate/truncate.go +++ b/util/truncate/truncate.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package truncate provides a utility function for safely truncating UTF-8 diff --git a/util/truncate/truncate_test.go b/util/truncate/truncate_test.go index c0d9e6e14df99..6a99a0efc4706 100644 --- a/util/truncate/truncate_test.go +++ b/util/truncate/truncate_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package truncate_test diff --git a/util/usermetric/metrics.go b/util/usermetric/metrics.go index be425fb87fd6c..14c2fabbec1f5 100644 --- a/util/usermetric/metrics.go +++ b/util/usermetric/metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // This file contains user-facing metrics that are used by multiple packages. diff --git a/util/usermetric/omit.go b/util/usermetric/omit.go index 0611990abe89e..c2681ebdaa3b4 100644 --- a/util/usermetric/omit.go +++ b/util/usermetric/omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_usermetrics diff --git a/util/usermetric/usermetric.go b/util/usermetric/usermetric.go index 1805a5dbee626..f435f3ec23da3 100644 --- a/util/usermetric/usermetric.go +++ b/util/usermetric/usermetric.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_usermetrics diff --git a/util/usermetric/usermetric_test.go b/util/usermetric/usermetric_test.go index e92db5bfce130..cdbb44ec057bc 100644 --- a/util/usermetric/usermetric_test.go +++ b/util/usermetric/usermetric_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package usermetric diff --git a/util/vizerror/vizerror.go b/util/vizerror/vizerror.go index 919d765d0ef2d..479bd2de9e7c8 100644 --- a/util/vizerror/vizerror.go +++ b/util/vizerror/vizerror.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package vizerror provides types and utility funcs for handling visible errors diff --git a/util/vizerror/vizerror_test.go b/util/vizerror/vizerror_test.go index 242ca6462f37b..10e8376030beb 100644 --- a/util/vizerror/vizerror_test.go +++ b/util/vizerror/vizerror_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package vizerror diff --git a/util/winutil/authenticode/authenticode_windows.go b/util/winutil/authenticode/authenticode_windows.go index 27c09b8cbb758..46f60caf76f79 100644 --- a/util/winutil/authenticode/authenticode_windows.go +++ b/util/winutil/authenticode/authenticode_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package authenticode contains Windows Authenticode signature verification code. diff --git a/util/winutil/authenticode/mksyscall.go b/util/winutil/authenticode/mksyscall.go index 8b7cabe6e4d7f..198081fce185a 100644 --- a/util/winutil/authenticode/mksyscall.go +++ b/util/winutil/authenticode/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package authenticode diff --git a/util/winutil/conpty/conpty_windows.go b/util/winutil/conpty/conpty_windows.go index 0a35759b49136..1071493f529b0 100644 --- a/util/winutil/conpty/conpty_windows.go +++ b/util/winutil/conpty/conpty_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package conpty implements support for Windows pseudo-consoles. diff --git a/util/winutil/gp/gp_windows.go b/util/winutil/gp/gp_windows.go index dd0e695eb08f2..dd13a27012d18 100644 --- a/util/winutil/gp/gp_windows.go +++ b/util/winutil/gp/gp_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package gp contains [Group Policy]-related functions and types. diff --git a/util/winutil/gp/gp_windows_test.go b/util/winutil/gp/gp_windows_test.go index f892068835bce..dfad029302c47 100644 --- a/util/winutil/gp/gp_windows_test.go +++ b/util/winutil/gp/gp_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/gp/mksyscall.go b/util/winutil/gp/mksyscall.go index 3f3682d64d07e..22fd0c137894f 100644 --- a/util/winutil/gp/mksyscall.go +++ b/util/winutil/gp/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/gp/policylock_windows.go b/util/winutil/gp/policylock_windows.go index 6c3ca0baf6d21..6e6f63f820489 100644 --- a/util/winutil/gp/policylock_windows.go +++ b/util/winutil/gp/policylock_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/gp/watcher_windows.go b/util/winutil/gp/watcher_windows.go index ae66c391ff595..8a6538e8bb619 100644 --- a/util/winutil/gp/watcher_windows.go +++ b/util/winutil/gp/watcher_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gp diff --git a/util/winutil/mksyscall.go b/util/winutil/mksyscall.go index afee739986cda..f3b61f2dda4ad 100644 --- a/util/winutil/mksyscall.go +++ b/util/winutil/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/policy/policy_windows.go b/util/winutil/policy/policy_windows.go index 89142951f8bd5..c831066034608 100644 --- a/util/winutil/policy/policy_windows.go +++ b/util/winutil/policy/policy_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package policy contains higher-level abstractions for accessing Windows enterprise policies. diff --git a/util/winutil/policy/policy_windows_test.go b/util/winutil/policy/policy_windows_test.go index cf2390c568cce..881c08356b99a 100644 --- a/util/winutil/policy/policy_windows_test.go +++ b/util/winutil/policy/policy_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package policy diff --git a/util/winutil/restartmgr_windows.go b/util/winutil/restartmgr_windows.go index 6f549de557653..3ef8a0383b2ba 100644 --- a/util/winutil/restartmgr_windows.go +++ b/util/winutil/restartmgr_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/restartmgr_windows_test.go b/util/winutil/restartmgr_windows_test.go index 6b2d75c3c5459..eb11ffc9ce51f 100644 --- a/util/winutil/restartmgr_windows_test.go +++ b/util/winutil/restartmgr_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/s4u/lsa_windows.go b/util/winutil/s4u/lsa_windows.go index 3276b26766c08..a26a7bcf094fa 100644 --- a/util/winutil/s4u/lsa_windows.go +++ b/util/winutil/s4u/lsa_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package s4u diff --git a/util/winutil/s4u/mksyscall.go b/util/winutil/s4u/mksyscall.go index 8925c0209b124..b8ab33672c563 100644 --- a/util/winutil/s4u/mksyscall.go +++ b/util/winutil/s4u/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package s4u diff --git a/util/winutil/s4u/s4u_windows.go b/util/winutil/s4u/s4u_windows.go index 8c8e02dbe83bc..a5b543cab5ea1 100644 --- a/util/winutil/s4u/s4u_windows.go +++ b/util/winutil/s4u/s4u_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package s4u is an API for accessing Service-For-User (S4U) functionality on Windows. diff --git a/util/winutil/startupinfo_windows.go b/util/winutil/startupinfo_windows.go index edf48fa651cb5..5ded67c7c78e1 100644 --- a/util/winutil/startupinfo_windows.go +++ b/util/winutil/startupinfo_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/svcdiag_windows.go b/util/winutil/svcdiag_windows.go index 372377cf93217..e28f9b6af58d6 100644 --- a/util/winutil/svcdiag_windows.go +++ b/util/winutil/svcdiag_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go b/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go index 8a4e1b7f72c79..3ef834fd54546 100644 --- a/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go +++ b/util/winutil/testdata/testrestartableprocesses/restartableprocess_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // The testrestartableprocesses is a program for a test. diff --git a/util/winutil/userprofile_windows.go b/util/winutil/userprofile_windows.go index d2e6067c7a93f..c7fb028966ba6 100644 --- a/util/winutil/userprofile_windows.go +++ b/util/winutil/userprofile_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/userprofile_windows_test.go b/util/winutil/userprofile_windows_test.go index 09dcfd59627aa..0a21cea6ac10f 100644 --- a/util/winutil/userprofile_windows_test.go +++ b/util/winutil/userprofile_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/winenv/mksyscall.go b/util/winutil/winenv/mksyscall.go index 9737c40c470bb..77d8ec66ab2a2 100644 --- a/util/winutil/winenv/mksyscall.go +++ b/util/winutil/winenv/mksyscall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winenv diff --git a/util/winutil/winenv/winenv_windows.go b/util/winutil/winenv/winenv_windows.go index 81fe4202633fb..eb7e87cedc320 100644 --- a/util/winutil/winenv/winenv_windows.go +++ b/util/winutil/winenv/winenv_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package winenv provides information about the current Windows environment. diff --git a/util/winutil/winutil.go b/util/winutil/winutil.go index ca231363acf1b..84ac2b1e341ef 100644 --- a/util/winutil/winutil.go +++ b/util/winutil/winutil.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package winutil contains misc Windows/Win32 helper functions. diff --git a/util/winutil/winutil_notwindows.go b/util/winutil/winutil_notwindows.go index caa415e08a513..774a0dad7af08 100644 --- a/util/winutil/winutil_notwindows.go +++ b/util/winutil/winutil_notwindows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !windows diff --git a/util/winutil/winutil_windows.go b/util/winutil/winutil_windows.go index c935b210e9e6a..cab0dabdf3694 100644 --- a/util/winutil/winutil_windows.go +++ b/util/winutil/winutil_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/winutil/winutil_windows_test.go b/util/winutil/winutil_windows_test.go index ead10a45d7ee8..955006789bc43 100644 --- a/util/winutil/winutil_windows_test.go +++ b/util/winutil/winutil_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winutil diff --git a/util/zstdframe/options.go b/util/zstdframe/options.go index b4b0f2b85304c..67ab27169166d 100644 --- a/util/zstdframe/options.go +++ b/util/zstdframe/options.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package zstdframe diff --git a/util/zstdframe/zstd.go b/util/zstdframe/zstd.go index b207984182b15..69fe3ee4017df 100644 --- a/util/zstdframe/zstd.go +++ b/util/zstdframe/zstd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package zstdframe provides functionality for encoding and decoding diff --git a/util/zstdframe/zstd_test.go b/util/zstdframe/zstd_test.go index 120fd3508460f..302090b9951b8 100644 --- a/util/zstdframe/zstd_test.go +++ b/util/zstdframe/zstd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package zstdframe diff --git a/version-embed.go b/version-embed.go index 17bf578dd33f1..9f48d1384ff67 100644 --- a/version-embed.go +++ b/version-embed.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package tailscaleroot embeds VERSION.txt into the binary. diff --git a/version/cmdname.go b/version/cmdname.go index c38544ce1642c..8a4040f9718b9 100644 --- a/version/cmdname.go +++ b/version/cmdname.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios diff --git a/version/cmdname_ios.go b/version/cmdname_ios.go index 6bfed38b64226..1e6ec9dec4b23 100644 --- a/version/cmdname_ios.go +++ b/version/cmdname_ios.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios diff --git a/version/cmp.go b/version/cmp.go index 494a7ea72947f..4af0aec69ea6e 100644 --- a/version/cmp.go +++ b/version/cmp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/cmp_test.go b/version/cmp_test.go index e244d5e16fe22..10fc130b768eb 100644 --- a/version/cmp_test.go +++ b/version/cmp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version_test diff --git a/version/distro/distro.go b/version/distro/distro.go index 0e88bdd2fa297..03c02ccab91cf 100644 --- a/version/distro/distro.go +++ b/version/distro/distro.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package distro reports which distro we're running on. diff --git a/version/distro/distro_test.go b/version/distro/distro_test.go index 4d61c720581c7..f3460a180edc2 100644 --- a/version/distro/distro_test.go +++ b/version/distro/distro_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package distro diff --git a/version/exename.go b/version/exename.go index d5047c2038ffe..adb5236177e31 100644 --- a/version/exename.go +++ b/version/exename.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/export_test.go b/version/export_test.go index 8e8ce5ecb2129..ec43ad33248a7 100644 --- a/version/export_test.go +++ b/version/export_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/mkversion/mkversion.go b/version/mkversion/mkversion.go index 2fa84480dd144..f42b3ad036de3 100644 --- a/version/mkversion/mkversion.go +++ b/version/mkversion/mkversion.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package mkversion gets version info from git and provides a bunch of diff --git a/version/mkversion/mkversion_test.go b/version/mkversion/mkversion_test.go index 210d3053a14a3..2f1a922c98924 100644 --- a/version/mkversion/mkversion_test.go +++ b/version/mkversion/mkversion_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package mkversion diff --git a/version/modinfo_test.go b/version/modinfo_test.go index 746e6296de795..ef75ce0771a47 100644 --- a/version/modinfo_test.go +++ b/version/modinfo_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version_test diff --git a/version/print.go b/version/print.go index 43ee2b5591410..ca62226ee2b6d 100644 --- a/version/print.go +++ b/version/print.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/prop.go b/version/prop.go index 795f3a9127be0..36d7699176f1e 100644 --- a/version/prop.go +++ b/version/prop.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/race.go b/version/race.go index e1dc76591ebf4..1cea65e7111e4 100644 --- a/version/race.go +++ b/version/race.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build race diff --git a/version/race_off.go b/version/race_off.go index 6db901974bb77..cbe7c198b2754 100644 --- a/version/race_off.go +++ b/version/race_off.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !race diff --git a/version/version.go b/version/version.go index 2add25689e1dd..1171ed2ffe722 100644 --- a/version/version.go +++ b/version/version.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package version provides the version that the binary was built at. diff --git a/version/version_checkformat.go b/version/version_checkformat.go index 05a97d1912dbe..970010ddf21d6 100644 --- a/version/version_checkformat.go +++ b/version/version_checkformat.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go && android diff --git a/version/version_internal_test.go b/version/version_internal_test.go index b3b848276e820..c78df4ff81a70 100644 --- a/version/version_internal_test.go +++ b/version/version_internal_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version diff --git a/version/version_test.go b/version/version_test.go index a515650586cc4..ebae7f177613a 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package version_test diff --git a/version_tailscale_test.go b/version_tailscale_test.go index 0a690e312202f..60a8d54f48093 100644 --- a/version_tailscale_test.go +++ b/version_tailscale_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build tailscale_go diff --git a/version_test.go b/version_test.go index 3d983a19d51db..6fb3ddef9d52b 100644 --- a/version_test.go +++ b/version_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package tailscaleroot diff --git a/wf/firewall.go b/wf/firewall.go index 07e160eb36071..5209c2293b10c 100644 --- a/wf/firewall.go +++ b/wf/firewall.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/wgengine/bench/bench.go b/wgengine/bench/bench.go index 8695f18d15899..7ce673b488e4a 100644 --- a/wgengine/bench/bench.go +++ b/wgengine/bench/bench.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Create two wgengine instances and pass data through them, measuring diff --git a/wgengine/bench/bench_test.go b/wgengine/bench/bench_test.go index 4fae86c0580ba..8788f4721a0e0 100644 --- a/wgengine/bench/bench_test.go +++ b/wgengine/bench/bench_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Create two wgengine instances and pass data through them, measuring diff --git a/wgengine/bench/trafficgen.go b/wgengine/bench/trafficgen.go index ce79c616f86ed..3be398d5348d1 100644 --- a/wgengine/bench/trafficgen.go +++ b/wgengine/bench/trafficgen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/wgengine/bench/wg.go b/wgengine/bench/wg.go index ce6add866f9e8..7b35a089aebcc 100644 --- a/wgengine/bench/wg.go +++ b/wgengine/bench/wg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package main diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index 987fcee0153a6..63a7aee1e461f 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package filter is a stateful packet filter. diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index ae39eeb08692f..4b364d30e85cb 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filter diff --git a/wgengine/filter/filtertype/filtertype.go b/wgengine/filter/filtertype/filtertype.go index 212eda43f1404..aab5fe8eef046 100644 --- a/wgengine/filter/filtertype/filtertype.go +++ b/wgengine/filter/filtertype/filtertype.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package filtertype defines the types used by wgengine/filter. diff --git a/wgengine/filter/filtertype/filtertype_clone.go b/wgengine/filter/filtertype/filtertype_clone.go index 63709188ea5c1..094063a5d1305 100644 --- a/wgengine/filter/filtertype/filtertype_clone.go +++ b/wgengine/filter/filtertype/filtertype_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/wgengine/filter/match.go b/wgengine/filter/match.go index 6292c49714a49..eee6ddf258fa1 100644 --- a/wgengine/filter/match.go +++ b/wgengine/filter/match.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filter diff --git a/wgengine/filter/tailcfg.go b/wgengine/filter/tailcfg.go index ff81077f727b7..e7e71526a43bc 100644 --- a/wgengine/filter/tailcfg.go +++ b/wgengine/filter/tailcfg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package filter diff --git a/wgengine/magicsock/blockforever_conn.go b/wgengine/magicsock/blockforever_conn.go index 272a12513b353..a215826b751f7 100644 --- a/wgengine/magicsock/blockforever_conn.go +++ b/wgengine/magicsock/blockforever_conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/debughttp.go b/wgengine/magicsock/debughttp.go index 9aecab74b4278..68019d0a76cbb 100644 --- a/wgengine/magicsock/debughttp.go +++ b/wgengine/magicsock/debughttp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/debugknobs.go b/wgengine/magicsock/debugknobs.go index b0a47ff87f31b..39cec25e64885 100644 --- a/wgengine/magicsock/debugknobs.go +++ b/wgengine/magicsock/debugknobs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !js diff --git a/wgengine/magicsock/debugknobs_stubs.go b/wgengine/magicsock/debugknobs_stubs.go index 7dee1d6b0b91c..c156ff8a7d92b 100644 --- a/wgengine/magicsock/debugknobs_stubs.go +++ b/wgengine/magicsock/debugknobs_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || js diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go index 1c5225e2249b5..b3cc5c2ce4927 100644 --- a/wgengine/magicsock/derp.go +++ b/wgengine/magicsock/derp.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/derp_test.go b/wgengine/magicsock/derp_test.go index ffb230789e4c8..084f710d8526d 100644 --- a/wgengine/magicsock/derp_test.go +++ b/wgengine/magicsock/derp_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/disco_atomic.go b/wgengine/magicsock/disco_atomic.go index 5b765fbc2c9a0..e17ce2f97eb30 100644 --- a/wgengine/magicsock/disco_atomic.go +++ b/wgengine/magicsock/disco_atomic.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/disco_atomic_test.go b/wgengine/magicsock/disco_atomic_test.go index a1de9b843379f..cec4b1133b274 100644 --- a/wgengine/magicsock/disco_atomic_test.go +++ b/wgengine/magicsock/disco_atomic_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/discopingpurpose_string.go b/wgengine/magicsock/discopingpurpose_string.go index 8eebf97a2dbd9..4cfbc751cf81c 100644 --- a/wgengine/magicsock/discopingpurpose_string.go +++ b/wgengine/magicsock/discopingpurpose_string.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by "stringer -type=discoPingPurpose -trimprefix=ping"; DO NOT EDIT. diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go index 586a2dc75c5cc..1f99f57ec2d16 100644 --- a/wgengine/magicsock/endpoint.go +++ b/wgengine/magicsock/endpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/endpoint_default.go b/wgengine/magicsock/endpoint_default.go index 1ed6e5e0e2399..59a47a98602bc 100644 --- a/wgengine/magicsock/endpoint_default.go +++ b/wgengine/magicsock/endpoint_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !wasm && !plan9 diff --git a/wgengine/magicsock/endpoint_stub.go b/wgengine/magicsock/endpoint_stub.go index a209c352bfe5e..da153abe57152 100644 --- a/wgengine/magicsock/endpoint_stub.go +++ b/wgengine/magicsock/endpoint_stub.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build wasm || plan9 diff --git a/wgengine/magicsock/endpoint_test.go b/wgengine/magicsock/endpoint_test.go index f1dab924f5d3b..43ff012c73d61 100644 --- a/wgengine/magicsock/endpoint_test.go +++ b/wgengine/magicsock/endpoint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/endpoint_tracker.go b/wgengine/magicsock/endpoint_tracker.go index e95852d2491b7..372f346853723 100644 --- a/wgengine/magicsock/endpoint_tracker.go +++ b/wgengine/magicsock/endpoint_tracker.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/endpoint_tracker_test.go b/wgengine/magicsock/endpoint_tracker_test.go index 6fccdfd576878..b3b1a63d94e29 100644 --- a/wgengine/magicsock/endpoint_tracker_test.go +++ b/wgengine/magicsock/endpoint_tracker_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 1c13093478a2e..7c5442d0b996c 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package magicsock implements a socket that can change its communication path while diff --git a/wgengine/magicsock/magicsock_default.go b/wgengine/magicsock/magicsock_default.go index 88759d3acc2e3..fc3e656b245f3 100644 --- a/wgengine/magicsock/magicsock_default.go +++ b/wgengine/magicsock/magicsock_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !linux || ts_omit_listenrawdisco diff --git a/wgengine/magicsock/magicsock_linux.go b/wgengine/magicsock/magicsock_linux.go index f37e19165141f..522341e721317 100644 --- a/wgengine/magicsock/magicsock_linux.go +++ b/wgengine/magicsock/magicsock_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !ts_omit_listenrawdisco diff --git a/wgengine/magicsock/magicsock_linux_test.go b/wgengine/magicsock/magicsock_linux_test.go index 28ccd220ee784..b670fa6bab601 100644 --- a/wgengine/magicsock/magicsock_linux_test.go +++ b/wgengine/magicsock/magicsock_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/magicsock_notplan9.go b/wgengine/magicsock/magicsock_notplan9.go index 86d099ee7f48c..db2c5fca052b9 100644 --- a/wgengine/magicsock/magicsock_notplan9.go +++ b/wgengine/magicsock/magicsock_notplan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !plan9 diff --git a/wgengine/magicsock/magicsock_plan9.go b/wgengine/magicsock/magicsock_plan9.go index 65714c3e13c33..a234beecf6f8a 100644 --- a/wgengine/magicsock/magicsock_plan9.go +++ b/wgengine/magicsock/magicsock_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build plan9 diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 68ab4dfa012a7..3b7ceeaa23323 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/peermap.go b/wgengine/magicsock/peermap.go index 136353563e2bd..b6e9b08a360ed 100644 --- a/wgengine/magicsock/peermap.go +++ b/wgengine/magicsock/peermap.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/peermap_test.go b/wgengine/magicsock/peermap_test.go index 171e22a6d5795..7fcd09384e540 100644 --- a/wgengine/magicsock/peermap_test.go +++ b/wgengine/magicsock/peermap_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/peermtu.go b/wgengine/magicsock/peermtu.go index b675bf409cfa4..6f3df50a3c435 100644 --- a/wgengine/magicsock/peermtu.go +++ b/wgengine/magicsock/peermtu.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (darwin && !ios) || (linux && !android) diff --git a/wgengine/magicsock/peermtu_darwin.go b/wgengine/magicsock/peermtu_darwin.go index a0a1aacb55f5f..007c413f5efc5 100644 --- a/wgengine/magicsock/peermtu_darwin.go +++ b/wgengine/magicsock/peermtu_darwin.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin && !ios diff --git a/wgengine/magicsock/peermtu_linux.go b/wgengine/magicsock/peermtu_linux.go index b76f30f081042..5a6b5a64e9bc9 100644 --- a/wgengine/magicsock/peermtu_linux.go +++ b/wgengine/magicsock/peermtu_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux && !android diff --git a/wgengine/magicsock/peermtu_stubs.go b/wgengine/magicsock/peermtu_stubs.go index e4f8038a42f21..a7fc5c99ba869 100644 --- a/wgengine/magicsock/peermtu_stubs.go +++ b/wgengine/magicsock/peermtu_stubs.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (!linux && !darwin) || android || ios diff --git a/wgengine/magicsock/peermtu_unix.go b/wgengine/magicsock/peermtu_unix.go index eec3d744f3ded..7c394e98e0fa5 100644 --- a/wgengine/magicsock/peermtu_unix.go +++ b/wgengine/magicsock/peermtu_unix.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build (darwin && !ios) || (linux && !android) diff --git a/wgengine/magicsock/rebinding_conn.go b/wgengine/magicsock/rebinding_conn.go index c98e645705b46..e00eed1f5c88c 100644 --- a/wgengine/magicsock/rebinding_conn.go +++ b/wgengine/magicsock/rebinding_conn.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/relaymanager.go b/wgengine/magicsock/relaymanager.go index 69831a4df19f8..e4cd5eb9ff537 100644 --- a/wgengine/magicsock/relaymanager.go +++ b/wgengine/magicsock/relaymanager.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/magicsock/relaymanager_test.go b/wgengine/magicsock/relaymanager_test.go index e8fddfd91b46e..7d773e381a7c4 100644 --- a/wgengine/magicsock/relaymanager_test.go +++ b/wgengine/magicsock/relaymanager_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package magicsock diff --git a/wgengine/mem_ios.go b/wgengine/mem_ios.go index cc266ea3aadc8..f278359a8809b 100644 --- a/wgengine/mem_ios.go +++ b/wgengine/mem_ios.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine diff --git a/wgengine/netlog/netlog.go b/wgengine/netlog/netlog.go index 12fe9c797641a..e03a520f29bce 100644 --- a/wgengine/netlog/netlog.go +++ b/wgengine/netlog/netlog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netlog/netlog_omit.go b/wgengine/netlog/netlog_omit.go index 03610a1ef017a..041a183769554 100644 --- a/wgengine/netlog/netlog_omit.go +++ b/wgengine/netlog/netlog_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_netlog || ts_omit_logtail diff --git a/wgengine/netlog/netlog_test.go b/wgengine/netlog/netlog_test.go index b4758c7ec7beb..cc6968547cdbf 100644 --- a/wgengine/netlog/netlog_test.go +++ b/wgengine/netlog/netlog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netlog/record.go b/wgengine/netlog/record.go index 25b6b1148793a..62c3a6866da2a 100644 --- a/wgengine/netlog/record.go +++ b/wgengine/netlog/record.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netlog/record_test.go b/wgengine/netlog/record_test.go index ec0229534f244..1edae7450c842 100644 --- a/wgengine/netlog/record_test.go +++ b/wgengine/netlog/record_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail diff --git a/wgengine/netstack/gro/gro.go b/wgengine/netstack/gro/gro.go index c8e5e56e1acb5..152b252951c80 100644 --- a/wgengine/netstack/gro/gro.go +++ b/wgengine/netstack/gro/gro.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netstack diff --git a/wgengine/netstack/gro/gro_default.go b/wgengine/netstack/gro/gro_default.go index c70e19f7c5861..ac9d672ab8a6e 100644 --- a/wgengine/netstack/gro/gro_default.go +++ b/wgengine/netstack/gro/gro_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios && !ts_omit_gro diff --git a/wgengine/netstack/gro/gro_disabled.go b/wgengine/netstack/gro/gro_disabled.go index d7ffbd9139d99..9b2ae69955c97 100644 --- a/wgengine/netstack/gro/gro_disabled.go +++ b/wgengine/netstack/gro/gro_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios || ts_omit_gro diff --git a/wgengine/netstack/gro/gro_test.go b/wgengine/netstack/gro/gro_test.go index 1eb200a05134c..49171b78c97aa 100644 --- a/wgengine/netstack/gro/gro_test.go +++ b/wgengine/netstack/gro/gro_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package gro diff --git a/wgengine/netstack/gro/netstack_disabled.go b/wgengine/netstack/gro/netstack_disabled.go index a0f56fa4499cf..a61b90b48ed91 100644 --- a/wgengine/netstack/gro/netstack_disabled.go +++ b/wgengine/netstack/gro/netstack_disabled.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_netstack diff --git a/wgengine/netstack/link_endpoint.go b/wgengine/netstack/link_endpoint.go index c5a9dbcbca538..4800ed1673d20 100644 --- a/wgengine/netstack/link_endpoint.go +++ b/wgengine/netstack/link_endpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index e05846e150a27..59fc0e0694bcc 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package netstack wires up gVisor's netstack into Tailscale. diff --git a/wgengine/netstack/netstack_linux.go b/wgengine/netstack/netstack_linux.go index a0bfb44567da7..a0f431cf6fdb4 100644 --- a/wgengine/netstack/netstack_linux.go +++ b/wgengine/netstack/netstack_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack diff --git a/wgengine/netstack/netstack_tcpbuf_default.go b/wgengine/netstack/netstack_tcpbuf_default.go index 3640964ffe399..ed93175c4fed1 100644 --- a/wgengine/netstack/netstack_tcpbuf_default.go +++ b/wgengine/netstack/netstack_tcpbuf_default.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ios diff --git a/wgengine/netstack/netstack_tcpbuf_ios.go b/wgengine/netstack/netstack_tcpbuf_ios.go index a4210c9ac7517..a5368da8633d7 100644 --- a/wgengine/netstack/netstack_tcpbuf_ios.go +++ b/wgengine/netstack/netstack_tcpbuf_ios.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ios diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index 93022811ce409..f9903c0c210d5 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack diff --git a/wgengine/netstack/netstack_userping.go b/wgengine/netstack/netstack_userping.go index b35a6eca9e11b..d42c8fbe7c271 100644 --- a/wgengine/netstack/netstack_userping.go +++ b/wgengine/netstack/netstack_userping.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !darwin && !ios diff --git a/wgengine/netstack/netstack_userping_apple.go b/wgengine/netstack/netstack_userping_apple.go index 52fb7a24a4c41..a82b81e99e827 100644 --- a/wgengine/netstack/netstack_userping_apple.go +++ b/wgengine/netstack/netstack_userping_apple.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || ios diff --git a/wgengine/netstack/netstack_userping_test.go b/wgengine/netstack/netstack_userping_test.go index a179f74673469..cba298d453698 100644 --- a/wgengine/netstack/netstack_userping_test.go +++ b/wgengine/netstack/netstack_userping_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package netstack diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index 7eaf43e52a816..77cb4a7b9b451 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_debug diff --git a/wgengine/pendopen_omit.go b/wgengine/pendopen_omit.go index 013425d357f26..01d33306b291e 100644 --- a/wgengine/pendopen_omit.go +++ b/wgengine/pendopen_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build ts_omit_debug diff --git a/wgengine/router/callback.go b/wgengine/router/callback.go index c1838539ba2a3..11ce832f4f5e4 100644 --- a/wgengine/router/callback.go +++ b/wgengine/router/callback.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/consolidating_router.go b/wgengine/router/consolidating_router.go index 10c4825d8856a..14283330b124c 100644 --- a/wgengine/router/consolidating_router.go +++ b/wgengine/router/consolidating_router.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/consolidating_router_test.go b/wgengine/router/consolidating_router_test.go index ba2e4d07a746a..1bf79a29d614d 100644 --- a/wgengine/router/consolidating_router_test.go +++ b/wgengine/router/consolidating_router_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/osrouter/ifconfig_windows_test.go b/wgengine/router/osrouter/ifconfig_windows_test.go index b858ef4f60d19..f272a59f899f6 100644 --- a/wgengine/router/osrouter/ifconfig_windows_test.go +++ b/wgengine/router/osrouter/ifconfig_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/osrouter.go b/wgengine/router/osrouter/osrouter.go index 281454b069984..ac4e48c7268c7 100644 --- a/wgengine/router/osrouter/osrouter.go +++ b/wgengine/router/osrouter/osrouter.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package osrouter contains OS-specific router implementations. diff --git a/wgengine/router/osrouter/osrouter_test.go b/wgengine/router/osrouter/osrouter_test.go index d0cb3db6968c1..5e81d6297d035 100644 --- a/wgengine/router/osrouter/osrouter_test.go +++ b/wgengine/router/osrouter/osrouter_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_freebsd.go b/wgengine/router/osrouter/router_freebsd.go index a142e7a84e14a..c1e1a389b8537 100644 --- a/wgengine/router/osrouter/router_freebsd.go +++ b/wgengine/router/osrouter/router_freebsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_linux.go b/wgengine/router/osrouter/router_linux.go index 7442c045ee079..8ca38f9ecd15d 100644 --- a/wgengine/router/osrouter/router_linux.go +++ b/wgengine/router/osrouter/router_linux.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !android diff --git a/wgengine/router/osrouter/router_linux_test.go b/wgengine/router/osrouter/router_linux_test.go index 68ed8dbb2bb64..bce0ea09275e3 100644 --- a/wgengine/router/osrouter/router_linux_test.go +++ b/wgengine/router/osrouter/router_linux_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_openbsd.go b/wgengine/router/osrouter/router_openbsd.go index 55b485f0e7a9e..8807a32d5b860 100644 --- a/wgengine/router/osrouter/router_openbsd.go +++ b/wgengine/router/osrouter/router_openbsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_plan9.go b/wgengine/router/osrouter/router_plan9.go index a5b461a6fff67..1436ee8a2191a 100644 --- a/wgengine/router/osrouter/router_plan9.go +++ b/wgengine/router/osrouter/router_plan9.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_userspace_bsd.go b/wgengine/router/osrouter/router_userspace_bsd.go index 70ef2b6bf3ca9..272594d7c427b 100644 --- a/wgengine/router/osrouter/router_userspace_bsd.go +++ b/wgengine/router/osrouter/router_userspace_bsd.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build darwin || freebsd diff --git a/wgengine/router/osrouter/router_windows.go b/wgengine/router/osrouter/router_windows.go index a1acbe3b67287..ef9eb04a147a7 100644 --- a/wgengine/router/osrouter/router_windows.go +++ b/wgengine/router/osrouter/router_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/router_windows_test.go b/wgengine/router/osrouter/router_windows_test.go index 119b6a77867f9..abbbdf93b1fcb 100644 --- a/wgengine/router/osrouter/router_windows_test.go +++ b/wgengine/router/osrouter/router_windows_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package osrouter diff --git a/wgengine/router/osrouter/runner.go b/wgengine/router/osrouter/runner.go index 7afb7fdc2088f..bdc710a8d369a 100644 --- a/wgengine/router/osrouter/runner.go +++ b/wgengine/router/osrouter/runner.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build linux diff --git a/wgengine/router/router.go b/wgengine/router/router.go index 04cc898876557..6868acb43ee2b 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package router presents an interface to manipulate the host network diff --git a/wgengine/router/router_fake.go b/wgengine/router/router_fake.go index db35fc9eebe15..6b3bc044aea6d 100644 --- a/wgengine/router/router_fake.go +++ b/wgengine/router/router_fake.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/router/router_test.go b/wgengine/router/router_test.go index fd17b8c5d5297..28750e115a9e3 100644 --- a/wgengine/router/router_test.go +++ b/wgengine/router/router_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package router diff --git a/wgengine/userspace.go b/wgengine/userspace.go index dbc8e8b573c49..e69712061f5c9 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine diff --git a/wgengine/userspace_ext_test.go b/wgengine/userspace_ext_test.go index 8e7bbb7a9c5c9..2d41a2df08dd2 100644 --- a/wgengine/userspace_ext_test.go +++ b/wgengine/userspace_ext_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine_test diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index 0a1d2924d593b..b06ea527b27ba 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 9cc4ed3b594c3..18b36e0039d6d 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build !js && !ts_omit_debug diff --git a/wgengine/watchdog_omit.go b/wgengine/watchdog_omit.go index 1d175b41a87eb..b4ed4344292e6 100644 --- a/wgengine/watchdog_omit.go +++ b/wgengine/watchdog_omit.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build js || ts_omit_debug diff --git a/wgengine/watchdog_test.go b/wgengine/watchdog_test.go index 35fd8f33105e6..47f133373c445 100644 --- a/wgengine/watchdog_test.go +++ b/wgengine/watchdog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgengine diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index 2734f6c6ea969..7828121390fba 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wgcfg has types and a parser for representing WireGuard config. diff --git a/wgengine/wgcfg/config_test.go b/wgengine/wgcfg/config_test.go index 5ac3b7cd56376..b15b8cbf56f8b 100644 --- a/wgengine/wgcfg/config_test.go +++ b/wgengine/wgcfg/config_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/device.go b/wgengine/wgcfg/device.go index ee7eb91c93b66..ba29cfbdca8c0 100644 --- a/wgengine/wgcfg/device.go +++ b/wgengine/wgcfg/device.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/device_test.go b/wgengine/wgcfg/device_test.go index 9138d6e5a0f47..a0443147db80d 100644 --- a/wgengine/wgcfg/device_test.go +++ b/wgengine/wgcfg/device_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go index a42827337d5c6..f99b7b007a564 100644 --- a/wgengine/wgcfg/nmcfg/nmcfg.go +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package nmcfg converts a controlclient.NetMap into a wgcfg config. diff --git a/wgengine/wgcfg/parser.go b/wgengine/wgcfg/parser.go index ec3d008f7de97..8fb9214091a42 100644 --- a/wgengine/wgcfg/parser.go +++ b/wgengine/wgcfg/parser.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/parser_test.go b/wgengine/wgcfg/parser_test.go index a5d7ad44f2e39..8c38ec0251b21 100644 --- a/wgengine/wgcfg/parser_test.go +++ b/wgengine/wgcfg/parser_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgcfg/wgcfg_clone.go b/wgengine/wgcfg/wgcfg_clone.go index 9f3cabde182f9..5c771a2288fce 100644 --- a/wgengine/wgcfg/wgcfg_clone.go +++ b/wgengine/wgcfg/wgcfg_clone.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Code generated by tailscale.com/cmd/cloner; DO NOT EDIT. diff --git a/wgengine/wgcfg/writer.go b/wgengine/wgcfg/writer.go index 9cdd31df2e38c..f4981e3e9185b 100644 --- a/wgengine/wgcfg/writer.go +++ b/wgengine/wgcfg/writer.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgcfg diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index be78731474bc9..9dd782e4ab44f 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wgengine provides the Tailscale WireGuard engine interface. diff --git a/wgengine/wgint/wgint.go b/wgengine/wgint/wgint.go index 309113df71d41..88c48486e3fc6 100644 --- a/wgengine/wgint/wgint.go +++ b/wgengine/wgint/wgint.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wgint provides somewhat shady access to wireguard-go diff --git a/wgengine/wgint/wgint_test.go b/wgengine/wgint/wgint_test.go index 714d2044b1806..3409a7fde2d15 100644 --- a/wgengine/wgint/wgint_test.go +++ b/wgengine/wgint/wgint_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wgint diff --git a/wgengine/wglog/wglog.go b/wgengine/wglog/wglog.go index dabd4562ad704..174babb91a933 100644 --- a/wgengine/wglog/wglog.go +++ b/wgengine/wglog/wglog.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wglog contains logging helpers for wireguard-go. diff --git a/wgengine/wglog/wglog_test.go b/wgengine/wglog/wglog_test.go index 9e9850f39ef59..2e82f9312a5dc 100644 --- a/wgengine/wglog/wglog_test.go +++ b/wgengine/wglog/wglog_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package wglog_test diff --git a/wgengine/winnet/winnet.go b/wgengine/winnet/winnet.go index e04e6f5c5b1a0..a5a84b04bd25c 100644 --- a/wgengine/winnet/winnet.go +++ b/wgengine/winnet/winnet.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause //go:build windows diff --git a/wgengine/winnet/winnet_windows.go b/wgengine/winnet/winnet_windows.go index 283ce5ad17b68..6ce298f8165ab 100644 --- a/wgengine/winnet/winnet_windows.go +++ b/wgengine/winnet/winnet_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package winnet diff --git a/wif/wif.go b/wif/wif.go index 557685c448c0b..bb2e760f2c7b7 100644 --- a/wif/wif.go +++ b/wif/wif.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package wif deals with obtaining ID tokens from provider VMs diff --git a/words/words.go b/words/words.go index b373ffef6541f..ebac1cc0a571e 100644 --- a/words/words.go +++ b/words/words.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause // Package words contains accessors for some nice words. diff --git a/words/words_test.go b/words/words_test.go index a9691792a5c00..3547411a1e160 100644 --- a/words/words_test.go +++ b/words/words_test.go @@ -1,4 +1,4 @@ -// Copyright (c) Tailscale Inc & AUTHORS +// Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause package words From 2a69f48541e0ed7fdf81fc88b079474331eeee76 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Fri, 23 Jan 2026 17:53:00 -0600 Subject: [PATCH 021/202] wf: allow limited broadcast to/from permitted interfaces when using an exit node on Windows Similarly to allowing link-local multicast in #13661, we should also allow broadcast traffic on permitted interfaces when the killswitch is enabled due to exit node usage on Windows. This always includes internal interfaces, such as Hyper-V/WSL2, and also the LAN when "Allow local network access" is enabled in the client. Updates #18504 Signed-off-by: Nick Khyl --- tstest/test-wishlist.md | 3 ++ wf/firewall.go | 82 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/tstest/test-wishlist.md b/tstest/test-wishlist.md index eb4601b929650..39b4da6c05453 100644 --- a/tstest/test-wishlist.md +++ b/tstest/test-wishlist.md @@ -18,3 +18,6 @@ reference to an issue or PR about the feature. When the option is disabled, we should still permit it for internal interfaces, such as Hyper-V/WSL2 on Windows. +- Inbound and outbound broadcasts when an exit node is used, both with and without + the "Allow local network access" option enabled. When the option is disabled, + we should still permit traffic on internal interfaces, such as Hyper-V/WSL2 on Windows. \ No newline at end of file diff --git a/wf/firewall.go b/wf/firewall.go index 5209c2293b10c..995a60c3e3356 100644 --- a/wf/firewall.go +++ b/wf/firewall.go @@ -25,6 +25,8 @@ var ( linkLocalMulticastIPv4Range = netip.MustParsePrefix("224.0.0.0/24") linkLocalMulticastIPv6Range = netip.MustParsePrefix("ff02::/16") + + limitedBroadcast = netip.MustParsePrefix("255.255.255.255/32") ) type direction int @@ -233,26 +235,41 @@ func (f *Firewall) UpdatePermittedRoutes(newRoutes []netip.Prefix) error { return err } - name = "link-local multicast - " + r.String() - conditions = matchLinkLocalMulticast(r, false) - multicastRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + multicastRules, err := f.addLinkLocalMulticastRules(p, r) if err != nil { return err } rules = append(rules, multicastRules...) - conditions = matchLinkLocalMulticast(r, true) - multicastRules, err = f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + broadcastRules, err := f.addLimitedBroadcastRules(p, r) if err != nil { return err } - rules = append(rules, multicastRules...) + rules = append(rules, broadcastRules...) f.permittedRoutes[r] = rules } return nil } +// addLinkLocalMulticastRules adds rules to allow inbound and outbound +// link-local multicast traffic to or from the specified network. +// It returns the added rules, or an error. +func (f *Firewall) addLinkLocalMulticastRules(p protocol, r netip.Prefix) ([]*wf.Rule, error) { + name := "link-local multicast - " + r.String() + conditions := matchLinkLocalMulticast(r, false) + outboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return nil, err + } + conditions = matchLinkLocalMulticast(r, true) + inboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + if err != nil { + return nil, err + } + return append(outboundRules, inboundRules...), nil +} + // matchLinkLocalMulticast returns a list of conditions that match // outbound or inbound link-local multicast traffic to or from the // specified network. @@ -288,6 +305,59 @@ func matchLinkLocalMulticast(pfx netip.Prefix, inbound bool) []*wf.Match { } } +// addLimitedBroadcastRules adds rules to allow inbound and outbound +// limited broadcast traffic to or from the specified network, +// if the network is IPv4. It returns the added rules, or an error. +func (f *Firewall) addLimitedBroadcastRules(p protocol, r netip.Prefix) ([]*wf.Rule, error) { + if !r.Addr().Is4() { + return nil, nil + } + name := "broadcast - " + r.String() + conditions := matchLimitedBroadcast(r, false) + outboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionOutbound) + if err != nil { + return nil, err + } + conditions = matchLimitedBroadcast(r, true) + inboundRules, err := f.addRules(name, weightKnownTraffic, conditions, wf.ActionPermit, p, directionInbound) + if err != nil { + return nil, err + } + return append(outboundRules, inboundRules...), nil +} + +// matchLimitedBroadcast returns a list of conditions that match +// outbound or inbound limited broadcast traffic to or from the +// specified network. It panics if the pfx is not IPv4. +func matchLimitedBroadcast(pfx netip.Prefix, inbound bool) []*wf.Match { + if !pfx.Addr().Is4() { + panic("limited broadcast is only applicable to IPv4") + } + var localAddr, remoteAddr netip.Prefix + if inbound { + localAddr, remoteAddr = limitedBroadcast, pfx + } else { + localAddr, remoteAddr = pfx, limitedBroadcast + } + return []*wf.Match{ + { + Field: wf.FieldIPProtocol, + Op: wf.MatchTypeEqual, + Value: wf.IPProtoUDP, + }, + { + Field: wf.FieldIPLocalAddress, + Op: wf.MatchTypeEqual, + Value: localAddr, + }, + { + Field: wf.FieldIPRemoteAddress, + Op: wf.MatchTypeEqual, + Value: remoteAddr, + }, + } +} + func (f *Firewall) newRule(name string, w weight, layer wf.LayerID, conditions []*wf.Match, action wf.Action) (*wf.Rule, error) { id, err := windows.GenerateGUID() if err != nil { From bfa90ea9b38e1b20c9944abade6258db6e3d4157 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Fri, 23 Jan 2026 17:08:46 -0800 Subject: [PATCH 022/202] go.toolchain.rev: update to Go 1.25.6 (#18507) Updates #18506 Signed-off-by: Andrew Lytvynov --- go.mod | 2 +- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- go.toolchain.version | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index a8ec79e6e014f..7fd487382e574 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module tailscale.com -go 1.25.5 +go 1.25.6 require ( filippo.io/mkcert v1.4.4 diff --git a/go.toolchain.rev b/go.toolchain.rev index 16058a407c704..dbf37cef1af47 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -0bab982699fa5903259ba9b4cba3e5fd6cb3baf2 +0c028efa1dac96fbb046b793877061645d01ed74 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 310dcf87fcf1c..26fe3501b5d26 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-fBezkBGRHCnfJiOUmMMqBCPCqjlGC4F6KEt5h1JhsCg= +sha256-1AG7yXAbDsBdKUNe5FQ45YXWJ3eLekD4t9mwKrqxiOY= diff --git a/go.toolchain.version b/go.toolchain.version index b45fe310644f7..198ec23ccfcc9 100644 --- a/go.toolchain.version +++ b/go.toolchain.version @@ -1 +1 @@ -1.25.5 +1.25.6 From 76839587ebd51507df41532eba474c5fd68134b7 Mon Sep 17 00:00:00 2001 From: License Updater Date: Mon, 19 Jan 2026 15:04:12 +0000 Subject: [PATCH 023/202] licenses: update license notices Signed-off-by: License Updater --- licenses/android.md | 28 ++++++++++---------- licenses/apple.md | 59 ++++++++++++++++++++++--------------------- licenses/tailscale.md | 52 +++++++++++++++++++------------------- licenses/windows.md | 26 +++++++++---------- 4 files changed, 83 insertions(+), 82 deletions(-) diff --git a/licenses/android.md b/licenses/android.md index 4dc8e6c6de06c..5c46b3cb13340 100644 --- a/licenses/android.md +++ b/licenses/android.md @@ -11,23 +11,23 @@ Client][]. See also the dependencies in the [Tailscale CLI][]. - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/ebf49471dced/LICENSE)) - - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/go-tpm](https://pkg.go.dev/github.com/google/go-tpm) ([Apache-2.0](https://github.com/google/go-tpm/blob/v0.9.4/LICENSE)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) + - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/8c70d406f6d2/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.21/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/tailscale/peercred](https://pkg.go.dev/github.com/tailscale/peercred) ([BSD-3-Clause](https://github.com/tailscale/peercred/blob/35a0c7bd7edc/LICENSE)) - [github.com/tailscale/tailscale-android/libtailscale](https://pkg.go.dev/github.com/tailscale/tailscale-android/libtailscale) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE)) - [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/1d0488a3d7da/LICENSE)) @@ -36,16 +36,16 @@ Client][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/b7579e27:LICENSE)) - [golang.org/x/mobile](https://pkg.go.dev/golang.org/x/mobile) ([BSD-3-Clause](https://cs.opensource.google/go/x/mobile/+/81131f64:LICENSE)) - [golang.org/x/mod/semver](https://pkg.go.dev/golang.org/x/mod/semver) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.11.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) + - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [golang.org/x/tools](https://pkg.go.dev/golang.org/x/tools) ([BSD-3-Clause](https://cs.opensource.google/go/x/tools/+/v0.39.0:LICENSE)) - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) diff --git a/licenses/apple.md b/licenses/apple.md index c3f2d3bb7a3c3..d51d67190b1fa 100644 --- a/licenses/apple.md +++ b/licenses/apple.md @@ -12,43 +12,45 @@ See also the dependencies in the [Tailscale CLI][]. - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) - - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.39.6/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.29.5/config/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.17.58/credentials/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.16.27/feature/ec2/imds/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.4.13/internal/configsources/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.7.13/internal/endpoints/v2/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.2/internal/ini/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.39.6/internal/sync/singleflight/LICENSE)) - - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.12.2/service/internal/accept-encoding/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.12.12/service/internal/presigned-url/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.32.5/config/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.19.5/credentials/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.18.16/feature/ec2/imds/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.4.16/internal/configsources/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.7.16/internal/endpoints/v2/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.4/internal/ini/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.13.4/service/internal/accept-encoding/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.13.16/service/internal/presigned-url/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/signin](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/signin) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/signin/v1.0.4/service/signin/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.45.0/service/ssm/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.24.14/service/sso/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.28.13/service/ssooidc/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.33.13/service/sts/LICENSE.txt)) - - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.23.2/LICENSE)) - - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.23.2/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.30.7/service/sso/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.35.12/service/ssooidc/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.41.5/service/sts/LICENSE.txt)) + - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.24.0/LICENSE)) + - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.24.0/internal/sync/singleflight/LICENSE)) - [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/65c67c9f46e6/LICENSE)) - - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) + - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.8.1/LICENSE)) - [github.com/digitalocean/go-smbios/smbios](https://pkg.go.dev/github.com/digitalocean/go-smbios/smbios) ([Apache-2.0](https://github.com/digitalocean/go-smbios/blob/390a4f403a8e/LICENSE.md)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) - - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/cc2cfa0554c3/LICENSE)) + - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/4849db3c2f7e/LICENSE)) - [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/76236955d466/LICENSE)) - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/nftables](https://pkg.go.dev/github.com/google/nftables) ([Apache-2.0](https://github.com/google/nftables/blob/5e242ec57806/LICENSE)) - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) + - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/illarion/gonotify/v3](https://pkg.go.dev/github.com/illarion/gonotify/v3) ([MIT](https://github.com/illarion/gonotify/blob/v3.0.2/LICENSE)) - [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/15c9b8791914/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE)) - [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.4.1/LICENSE.md)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/mdlayher/genetlink](https://pkg.go.dev/github.com/mdlayher/genetlink) ([MIT](https://github.com/mdlayher/genetlink/blob/v1.3.2/LICENSE.md)) - [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/fbb4dce95f42/LICENSE.md)) @@ -59,7 +61,6 @@ See also the dependencies in the [Tailscale CLI][]. - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - [github.com/prometheus-community/pro-bing](https://pkg.go.dev/github.com/prometheus-community/pro-bing) ([MIT](https://github.com/prometheus-community/pro-bing/blob/v0.4.0/LICENSE)) - [github.com/safchain/ethtool](https://pkg.go.dev/github.com/safchain/ethtool) ([Apache-2.0](https://github.com/safchain/ethtool/blob/v0.3.0/LICENSE)) - - [github.com/huin/goupnp](https://pkg.go.dev/github.com/huin/goupnp) ([BSD-2-Clause](https://github.com/huin/goupnp/blob/v1.3.0/LICENSE)) - [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/4d49adab4de7/LICENSE)) - [github.com/tailscale/peercred](https://pkg.go.dev/github.com/tailscale/peercred) ([BSD-3-Clause](https://github.com/tailscale/peercred/blob/35a0c7bd7edc/LICENSE)) - [github.com/tailscale/wireguard-go](https://pkg.go.dev/github.com/tailscale/wireguard-go) ([MIT](https://github.com/tailscale/wireguard-go/blob/1d0488a3d7da/LICENSE)) @@ -69,13 +70,13 @@ See also the dependencies in the [Tailscale CLI][]. - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/df929982:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) diff --git a/licenses/tailscale.md b/licenses/tailscale.md index 85c0f33fc09d2..28eb73db42cc6 100644 --- a/licenses/tailscale.md +++ b/licenses/tailscale.md @@ -20,22 +20,22 @@ Some packages may only be included on certain architectures or operating systems - [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/1a75b4708caa/LICENSE)) - [github.com/anmitsu/go-shlex](https://pkg.go.dev/github.com/anmitsu/go-shlex) ([MIT](https://github.com/anmitsu/go-shlex/blob/38f4b401e2be/LICENSE)) - [github.com/atotto/clipboard](https://pkg.go.dev/github.com/atotto/clipboard) ([BSD-3-Clause](https://github.com/atotto/clipboard/blob/v0.1.4/LICENSE)) - - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.36.0/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.29.5/config/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.17.58/credentials/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/feature/ec2/imds](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/ec2/imds) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.16.27/feature/ec2/imds/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.3.31/internal/configsources/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.6.31/internal/endpoints/v2/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/configsources](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/configsources) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.4.16/internal/configsources/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/endpoints/v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.7.16/internal/endpoints/v2/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/internal/ini](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/ini) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.2/internal/ini/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.36.0/internal/sync/singleflight/LICENSE)) - - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.12.2/service/internal/accept-encoding/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.12.12/service/internal/presigned-url/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.13.4/service/internal/accept-encoding/LICENSE.txt)) + - [github.com/aws/aws-sdk-go-v2/service/internal/presigned-url](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.13.16/service/internal/presigned-url/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/ssm](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssm) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.44.7/service/ssm/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/sso](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sso) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.24.14/service/sso/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/service/ssooidc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/ssooidc) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.28.13/service/ssooidc/LICENSE.txt)) - - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.33.13/service/sts/LICENSE.txt)) - - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.22.2/LICENSE)) - - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.22.2/internal/sync/singleflight/LICENSE)) + - [github.com/aws/aws-sdk-go-v2/service/sts](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/sts) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.41.5/service/sts/LICENSE.txt)) + - [github.com/aws/smithy-go](https://pkg.go.dev/github.com/aws/smithy-go) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.24.0/LICENSE)) + - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.24.0/internal/sync/singleflight/LICENSE)) - [github.com/coder/websocket](https://pkg.go.dev/github.com/coder/websocket) ([ISC](https://github.com/coder/websocket/blob/v1.8.12/LICENSE.txt)) - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) - [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.23/LICENSE)) @@ -43,24 +43,24 @@ Some packages may only be included on certain architectures or operating systems - [github.com/digitalocean/go-smbios/smbios](https://pkg.go.dev/github.com/digitalocean/go-smbios/smbios) ([Apache-2.0](https://github.com/digitalocean/go-smbios/blob/390a4f403a8e/LICENSE.md)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - [github.com/fogleman/gg](https://pkg.go.dev/github.com/fogleman/gg) ([MIT](https://github.com/fogleman/gg/blob/v1.3.0/LICENSE.md)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/ebf49471dced/LICENSE)) - [github.com/go-ole/go-ole](https://pkg.go.dev/github.com/go-ole/go-ole) ([MIT](https://github.com/go-ole/go-ole/blob/v1.3.0/LICENSE)) - [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/76236955d466/LICENSE)) - [github.com/golang/freetype/raster](https://pkg.go.dev/github.com/golang/freetype/raster) ([Unknown](Unknown)) - [github.com/golang/freetype/truetype](https://pkg.go.dev/github.com/golang/freetype/truetype) ([Unknown](Unknown)) - - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) - [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/8c70d406f6d2/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - [github.com/jmespath/go-jmespath](https://pkg.go.dev/github.com/jmespath/go-jmespath) ([Apache-2.0](https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE)) - [github.com/kballard/go-shellquote](https://pkg.go.dev/github.com/kballard/go-shellquote) ([MIT](https://github.com/kballard/go-shellquote/blob/95032a82bc51/LICENSE)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/kr/fs](https://pkg.go.dev/github.com/kr/fs) ([BSD-3-Clause](https://github.com/kr/fs/blob/v0.1.0/LICENSE)) - [github.com/mattn/go-colorable](https://pkg.go.dev/github.com/mattn/go-colorable) ([MIT](https://github.com/mattn/go-colorable/blob/v0.1.13/LICENSE)) @@ -83,24 +83,24 @@ Some packages may only be included on certain architectures or operating systems - [github.com/u-root/u-root/pkg/termios](https://pkg.go.dev/github.com/u-root/u-root/pkg/termios) ([BSD-3-Clause](https://github.com/u-root/u-root/blob/v0.14.0/LICENSE)) - [github.com/u-root/uio](https://pkg.go.dev/github.com/u-root/uio) ([BSD-3-Clause](https://github.com/u-root/uio/blob/d2acac8f3701/LICENSE)) - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) + - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.2/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/b7579e27:LICENSE)) - [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.27.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.30.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.11.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.32.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) + - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2)) - [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3)) - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) - - [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.32.0/LICENSE)) - - [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/LICENSE)) - - [sigs.k8s.io/yaml/goyaml.v2](https://pkg.go.dev/sigs.k8s.io/yaml/goyaml.v2) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/goyaml.v2/LICENSE)) + - [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.34.0/LICENSE)) + - [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.6.0/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) - [tailscale.com/tempfork/gliderlabs/ssh](https://pkg.go.dev/tailscale.com/tempfork/gliderlabs/ssh) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/tempfork/gliderlabs/ssh/LICENSE)) - [tailscale.com/tempfork/spf13/cobra](https://pkg.go.dev/tailscale.com/tempfork/spf13/cobra) ([Apache-2.0](https://github.com/tailscale/tailscale/blob/HEAD/tempfork/spf13/cobra/LICENSE.txt)) diff --git a/licenses/windows.md b/licenses/windows.md index 0b8344b4d66d4..902d0f2a1f5a8 100644 --- a/licenses/windows.md +++ b/licenses/windows.md @@ -15,22 +15,22 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/beorn7/perks/quantile](https://pkg.go.dev/github.com/beorn7/perks/quantile) ([MIT](https://github.com/beorn7/perks/blob/v1.0.1/LICENSE)) - [github.com/cespare/xxhash/v2](https://pkg.go.dev/github.com/cespare/xxhash/v2) ([MIT](https://github.com/cespare/xxhash/blob/v2.3.0/LICENSE.txt)) - [github.com/coder/websocket](https://pkg.go.dev/github.com/coder/websocket) ([ISC](https://github.com/coder/websocket/blob/v1.8.12/LICENSE.txt)) - - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) + - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.8.1/LICENSE)) - [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/b75a8a7d7eb0/LICENSE)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE)) - - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/cc2cfa0554c3/LICENSE)) + - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) + - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/4849db3c2f7e/LICENSE)) - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) - - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE)) + - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) - [github.com/google/go-cmp/cmp](https://pkg.go.dev/github.com/google/go-cmp/cmp) ([BSD-3-Clause](https://github.com/google/go-cmp/blob/v0.7.0/LICENSE)) - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) - [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt)) - [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.2.0/LICENSE)) - [github.com/jellydator/ttlcache/v3](https://pkg.go.dev/github.com/jellydator/ttlcache/v3) ([MIT](https://github.com/jellydator/ttlcache/blob/v3.1.0/LICENSE)) - [github.com/jsimonetti/rtnetlink](https://pkg.go.dev/github.com/jsimonetti/rtnetlink) ([MIT](https://github.com/jsimonetti/rtnetlink/blob/v1.4.1/LICENSE.md)) - - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.0/LICENSE)) - - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE)) - - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt)) + - [github.com/klauspost/compress](https://pkg.go.dev/github.com/klauspost/compress) ([Apache-2.0](https://github.com/klauspost/compress/blob/v1.18.2/LICENSE)) + - [github.com/klauspost/compress/internal/snapref](https://pkg.go.dev/github.com/klauspost/compress/internal/snapref) ([BSD-3-Clause](https://github.com/klauspost/compress/blob/v1.18.2/internal/snapref/LICENSE)) + - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/mdlayher/netlink](https://pkg.go.dev/github.com/mdlayher/netlink) ([MIT](https://github.com/mdlayher/netlink/blob/fbb4dce95f42/LICENSE.md)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md)) @@ -51,17 +51,17 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.2/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/df929982:LICENSE)) - [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.27.0:LICENSE)) - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.47.0:LICENSE)) - - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.18.0:LICENSE)) - - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.38.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.37.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) + - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) - [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2)) - [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3)) - - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.8/LICENSE)) + - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) - [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE)) - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) From 1183f7a191739040c7e1abf77d9c555e82767b54 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 23 Jan 2026 15:07:50 -0800 Subject: [PATCH 024/202] tstest/integration/testcontrol: fix unguarded read of DNS config Fixes #18498 Signed-off-by: James Tucker --- tstest/integration/testcontrol/testcontrol.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go index 4607665924c45..f61d1b53a6d99 100644 --- a/tstest/integration/testcontrol/testcontrol.go +++ b/tstest/integration/testcontrol/testcontrol.go @@ -1327,16 +1327,19 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse, s.mu.Lock() nodeCapMap := maps.Clone(s.nodeCapMaps[nk]) + var dns *tailcfg.DNSConfig + if s.DNSConfig != nil { + dns = s.DNSConfig.Clone() + } + magicDNSDomain := s.MagicDNSDomain s.mu.Unlock() node.CapMap = nodeCapMap node.Capabilities = append(node.Capabilities, tailcfg.NodeAttrDisableUPnP) t := time.Date(2020, 8, 3, 0, 0, 0, 1, time.UTC) - dns := s.DNSConfig - if dns != nil && s.MagicDNSDomain != "" { - dns = dns.Clone() - dns.CertDomains = append(dns.CertDomains, node.Hostinfo.Hostname()+"."+s.MagicDNSDomain) + if dns != nil && magicDNSDomain != "" { + dns.CertDomains = append(dns.CertDomains, node.Hostinfo.Hostname()+"."+magicDNSDomain) } res = &tailcfg.MapResponse{ From 9d13a6df9c4d84f2db700960ee5e64f9b272fa34 Mon Sep 17 00:00:00 2001 From: Fran Bull Date: Wed, 14 Jan 2026 11:53:14 -0800 Subject: [PATCH 025/202] appc,ipn/ipnlocal: Add split DNS entries for conn25 peers If conn25 config is sent in the netmap: add split DNS entries to use appropriately tagged peers' PeerAPI to resolve DNS requests for those domains. This will enable future work where we use the peers as connectors for the configured domains. Updates tailscale/corp#34252 Signed-off-by: Fran Bull --- appc/conn25.go | 63 +++++++++++++++++ appc/conn25_test.go | 123 +++++++++++++++++++++++++++++++++ ipn/ipnlocal/dnsconfig_test.go | 91 ++++++++++++++++++++++++ ipn/ipnlocal/node_backend.go | 21 ++++++ 4 files changed, 298 insertions(+) diff --git a/appc/conn25.go b/appc/conn25.go index 2c3e8c519a976..08ca651fda7e9 100644 --- a/appc/conn25.go +++ b/appc/conn25.go @@ -4,10 +4,15 @@ package appc import ( + "cmp" "net/netip" + "slices" "sync" "tailscale.com/tailcfg" + "tailscale.com/types/appctype" + "tailscale.com/util/mak" + "tailscale.com/util/set" ) // Conn25 holds the developing state for the as yet nascent next generation app connector. @@ -108,3 +113,61 @@ type ConnectorTransitIPResponse struct { // correspond to the order of [ConnectorTransitIPRequest.TransitIPs]. TransitIPs []TransitIPResponse `json:"transitIPs,omitempty"` } + +const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental" + +// PickSplitDNSPeers looks at the netmap peers capabilities and finds which peers +// want to be connectors for which domains. +func PickSplitDNSPeers(hasCap func(c tailcfg.NodeCapability) bool, self tailcfg.NodeView, peers map[tailcfg.NodeID]tailcfg.NodeView) map[string][]tailcfg.NodeView { + var m map[string][]tailcfg.NodeView + if !hasCap(AppConnectorsExperimentalAttrName) { + return m + } + apps, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.AppConnectorAttr](self.CapMap(), AppConnectorsExperimentalAttrName) + if err != nil { + return m + } + tagToDomain := make(map[string][]string) + for _, app := range apps { + for _, tag := range app.Connectors { + tagToDomain[tag] = append(tagToDomain[tag], app.Domains...) + } + } + // NodeIDs are Comparable, and we have a map of NodeID to NodeView anyway, so + // use a Set of NodeIDs to deduplicate, and populate into a []NodeView later. + var work map[string]set.Set[tailcfg.NodeID] + for _, peer := range peers { + if !peer.Valid() || !peer.Hostinfo().Valid() { + continue + } + if isConn, _ := peer.Hostinfo().AppConnector().Get(); !isConn { + continue + } + for _, t := range peer.Tags().All() { + domains := tagToDomain[t] + for _, domain := range domains { + if work[domain] == nil { + mak.Set(&work, domain, set.Set[tailcfg.NodeID]{}) + } + work[domain].Add(peer.ID()) + } + } + } + + // Populate m. Make a []tailcfg.NodeView from []tailcfg.NodeID using the peers map. + // And sort it to our preference. + for domain, ids := range work { + nodes := make([]tailcfg.NodeView, 0, ids.Len()) + for id := range ids { + nodes = append(nodes, peers[id]) + } + // The ordering of the nodes in the map vals is semantic (dnsConfigForNetmap uses the first node it can + // get a peer api url for as its split dns target). We can think of it as a preference order, except that + // we don't (currently 2026-01-14) have any preference over which node is chosen. + slices.SortFunc(nodes, func(a, b tailcfg.NodeView) int { + return cmp.Compare(a.ID(), b.ID()) + }) + mak.Set(&m, domain, nodes) + } + return m +} diff --git a/appc/conn25_test.go b/appc/conn25_test.go index 76cc6cf8c69f4..33f89749ca748 100644 --- a/appc/conn25_test.go +++ b/appc/conn25_test.go @@ -4,10 +4,14 @@ package appc import ( + "encoding/json" "net/netip" + "reflect" "testing" "tailscale.com/tailcfg" + "tailscale.com/types/appctype" + "tailscale.com/types/opt" ) // TestHandleConnectorTransitIPRequestZeroLength tests that if sent a @@ -186,3 +190,122 @@ func TestTransitIPTargetUnknownTIP(t *testing.T) { t.Fatalf("Unknown transit addr, want: %v, got %v", want, got) } } + +func TestPickSplitDNSPeers(t *testing.T) { + getBytesForAttr := func(name string, domains []string, tags []string) []byte { + attr := appctype.AppConnectorAttr{ + Name: name, + Domains: domains, + Connectors: tags, + } + bs, err := json.Marshal(attr) + if err != nil { + t.Fatalf("test setup: %v", err) + } + return bs + } + appOneBytes := getBytesForAttr("app1", []string{"example.com"}, []string{"tag:one"}) + appTwoBytes := getBytesForAttr("app2", []string{"a.example.com"}, []string{"tag:two"}) + appThreeBytes := getBytesForAttr("app3", []string{"woo.b.example.com", "hoo.b.example.com"}, []string{"tag:three1", "tag:three2"}) + appFourBytes := getBytesForAttr("app4", []string{"woo.b.example.com", "c.example.com"}, []string{"tag:four1", "tag:four2"}) + + makeNodeView := func(id tailcfg.NodeID, name string, tags []string) tailcfg.NodeView { + return (&tailcfg.Node{ + ID: id, + Name: name, + Tags: tags, + Hostinfo: (&tailcfg.Hostinfo{AppConnector: opt.NewBool(true)}).View(), + }).View() + } + nvp1 := makeNodeView(1, "p1", []string{"tag:one"}) + nvp2 := makeNodeView(2, "p2", []string{"tag:four1", "tag:four2"}) + nvp3 := makeNodeView(3, "p3", []string{"tag:two", "tag:three1"}) + nvp4 := makeNodeView(4, "p4", []string{"tag:two", "tag:three2", "tag:four2"}) + + for _, tt := range []struct { + name string + want map[string][]tailcfg.NodeView + peers []tailcfg.NodeView + config []tailcfg.RawMessage + }{ + { + name: "empty", + }, + { + name: "bad-config", // bad config should return a nil map rather than error. + config: []tailcfg.RawMessage{tailcfg.RawMessage(`hey`)}, + }, + { + name: "no-peers", + config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)}, + }, + { + name: "peers-that-are-not-connectors", + config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)}, + peers: []tailcfg.NodeView{ + (&tailcfg.Node{ + ID: 5, + Name: "p5", + Tags: []string{"tag:one"}, + }).View(), + (&tailcfg.Node{ + ID: 6, + Name: "p6", + Tags: []string{"tag:one"}, + }).View(), + }, + }, + { + name: "peers-that-dont-match-tags", + config: []tailcfg.RawMessage{tailcfg.RawMessage(appOneBytes)}, + peers: []tailcfg.NodeView{ + makeNodeView(5, "p5", []string{"tag:seven"}), + makeNodeView(6, "p6", nil), + }, + }, + { + name: "matching-tagged-connector-peers", + config: []tailcfg.RawMessage{ + tailcfg.RawMessage(appOneBytes), + tailcfg.RawMessage(appTwoBytes), + tailcfg.RawMessage(appThreeBytes), + tailcfg.RawMessage(appFourBytes), + }, + peers: []tailcfg.NodeView{ + nvp1, + nvp2, + nvp3, + nvp4, + makeNodeView(5, "p5", nil), + }, + want: map[string][]tailcfg.NodeView{ + // p5 has no matching tags and so doesn't appear + "example.com": {nvp1}, + "a.example.com": {nvp3, nvp4}, + "woo.b.example.com": {nvp2, nvp3, nvp4}, + "hoo.b.example.com": {nvp3, nvp4}, + "c.example.com": {nvp2, nvp4}, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + selfNode := &tailcfg.Node{} + if tt.config != nil { + selfNode.CapMap = tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): tt.config, + } + } + selfView := selfNode.View() + peers := map[tailcfg.NodeID]tailcfg.NodeView{} + for _, p := range tt.peers { + peers[p.ID()] = p + } + got := PickSplitDNSPeers(func(_ tailcfg.NodeCapability) bool { + return true + }, selfView, peers) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("got %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index 52cc533ff29f6..594d2c5476177 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -10,14 +10,17 @@ import ( "reflect" "testing" + "tailscale.com/appc" "tailscale.com/ipn" "tailscale.com/net/dns" "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/types/dnstype" "tailscale.com/types/netmap" + "tailscale.com/types/opt" "tailscale.com/util/cloudenv" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) func ipps(ippStrs ...string) (ipps []netip.Prefix) { @@ -349,6 +352,94 @@ func TestDNSConfigForNetmap(t *testing.T) { prefs: &ipn.Prefs{}, want: &dns.Config{}, }, + { + name: "conn25-split-dns", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "a", + Addresses: ipps("100.101.101.101"), + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(`{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}`), + }, + }, + }).View(), + AllCaps: set.Of(tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName)), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "p1", + Addresses: ipps("100.102.0.1"), + Tags: []string{"tag:woo"}, + Hostinfo: (&tailcfg.Hostinfo{ + Services: []tailcfg.Service{ + { + Proto: tailcfg.PeerAPI4, + Port: 1234, + }, + }, + AppConnector: opt.NewBool(true), + }).View(), + }, + }), + prefs: &ipn.Prefs{ + CorpDNS: true, + }, + want: &dns.Config{ + Hosts: map[dnsname.FQDN][]netip.Addr{ + "a.": ips("100.101.101.101"), + "p1.": ips("100.102.0.1"), + }, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{ + dnsname.FQDN("example.com."): { + {Addr: "http://100.102.0.1:1234/dns-query"}, + }, + }, + }, + }, + { + name: "conn25-split-dns-no-matching-peers", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "a", + Addresses: ipps("100.101.101.101"), + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(`{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}`), + }, + }, + }).View(), + AllCaps: set.Of(tailcfg.NodeCapability(appc.AppConnectorsExperimentalAttrName)), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "p1", + Addresses: ipps("100.102.0.1"), + Tags: []string{"tag:nomatch"}, + Hostinfo: (&tailcfg.Hostinfo{ + Services: []tailcfg.Service{ + { + Proto: tailcfg.PeerAPI4, + Port: 1234, + }, + }, + AppConnector: opt.NewBool(true), + }).View(), + }, + }), + prefs: &ipn.Prefs{ + CorpDNS: true, + }, + want: &dns.Config{ + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netip.Addr{ + "a.": ips("100.101.101.101"), + "p1.": ips("100.102.0.1"), + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index a252f20fe2074..4a32b14dd49dc 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -6,12 +6,14 @@ package ipnlocal import ( "cmp" "context" + "fmt" "net/netip" "slices" "sync" "sync/atomic" "go4.org/netipx" + "tailscale.com/appc" "tailscale.com/feature/buildfeatures" "tailscale.com/ipn" "tailscale.com/net/dns" @@ -842,6 +844,25 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. // Add split DNS routes, with no regard to exit node configuration. addSplitDNSRoutes(nm.DNS.Routes) + // Add split DNS routes for conn25 + conn25DNSTargets := appc.PickSplitDNSPeers(nm.HasCap, nm.SelfNode, peers) + if conn25DNSTargets != nil { + var m map[string][]*dnstype.Resolver + for domain, candidateSplitDNSPeers := range conn25DNSTargets { + for _, peer := range candidateSplitDNSPeers { + base := peerAPIBase(nm, peer) + if base == "" { + continue + } + mak.Set(&m, domain, []*dnstype.Resolver{{Addr: fmt.Sprintf("%s/dns-query", base)}}) + break // Just make one resolver for the first peer we can get a peerAPIBase for. + } + } + if m != nil { + addSplitDNSRoutes(m) + } + } + // Set FallbackResolvers as the default resolvers in the // scenarios that can't handle a purely split-DNS config. See // https://github.com/tailscale/tailscale/issues/1743 for From 0e1b2b15f1a9a609213d99d527ca448711775b13 Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Mon, 26 Jan 2026 12:36:02 -0500 Subject: [PATCH 026/202] net/dns/publicdns: support CIRA Canadian Shield RELNOTE=Add DNS-over-HTTPS support for CIRA Canadian Shield Fixes #18524 Signed-off-by: Andrew Dunham --- net/dns/publicdns/publicdns.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/net/dns/publicdns/publicdns.go b/net/dns/publicdns/publicdns.go index e3148a5ae8a98..7ceaf1813db6c 100644 --- a/net/dns/publicdns/publicdns.go +++ b/net/dns/publicdns/publicdns.go @@ -275,6 +275,26 @@ func populate() { addDoH("76.76.10.4", "https://freedns.controld.com/family") addDoH("2606:1a40::4", "https://freedns.controld.com/family") addDoH("2606:1a40:1::4", "https://freedns.controld.com/family") + + // CIRA Canadian Shield: https://www.cira.ca/en/canadian-shield/configure/summary-cira-canadian-shield-dns-resolver-addresses/ + + // CIRA Canadian Shield Private (DNS resolution only) + addDoH("149.112.121.10", "https://private.canadianshield.cira.ca/dns-query") + addDoH("149.112.122.10", "https://private.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bb::10", "https://private.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bc::10", "https://private.canadianshield.cira.ca/dns-query") + + // CIRA Canadian Shield Protected (Malware and phishing protection) + addDoH("149.112.121.20", "https://protected.canadianshield.cira.ca/dns-query") + addDoH("149.112.122.20", "https://protected.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bb::20", "https://protected.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bc::20", "https://protected.canadianshield.cira.ca/dns-query") + + // CIRA Canadian Shield Family (Protected + blocking adult content) + addDoH("149.112.121.30", "https://family.canadianshield.cira.ca/dns-query") + addDoH("149.112.122.30", "https://family.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bb::30", "https://family.canadianshield.cira.ca/dns-query") + addDoH("2620:10a:80bc::30", "https://family.canadianshield.cira.ca/dns-query") } var ( From 8d875a301c8bdaceb5814eab100a90cb725b2018 Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Mon, 26 Jan 2026 12:43:24 -0500 Subject: [PATCH 027/202] net/dns: add test for DoH upgrade of system DNS Someone asked me if we use DNS-over-HTTPS if the system's resolver is an IP address that supports DoH and there's no global nameserver set (i.e. no "Override DNS servers" set). I didn't know the answer offhand, and it took a while for me to figure it out. The answer is yes, in cases where we take over the system's DNS configuration and read the base config, we do upgrade any DoH-capable resolver to use DoH. Here's a test that verifies this behaviour (and hopefully helps as documentation the next time someone has this question). Updates #cleanup Signed-off-by: Andrew Dunham --- net/dns/manager_test.go | 204 +++++++++++++++++++++++++++++++++ net/dns/publicdns/publicdns.go | 38 ++++++ 2 files changed, 242 insertions(+) diff --git a/net/dns/manager_test.go b/net/dns/manager_test.go index 679f81cd5d8a2..cf0c2458e395f 100644 --- a/net/dns/manager_test.go +++ b/net/dns/manager_test.go @@ -4,24 +4,36 @@ package dns import ( + "bytes" + "context" "errors" + "io" + "net/http" + "net/http/httptest" "net/netip" "reflect" "runtime" + "slices" "strings" + "sync" "testing" "testing/synctest" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + dns "golang.org/x/net/dns/dnsmessage" "tailscale.com/control/controlknobs" "tailscale.com/health" + "tailscale.com/net/dns/publicdns" "tailscale.com/net/dns/resolver" "tailscale.com/net/netmon" "tailscale.com/net/tsdial" + "tailscale.com/tstest" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" "tailscale.com/util/eventbus/eventbustest" + "tailscale.com/util/httpm" ) type fakeOSConfigurator struct { @@ -1116,3 +1128,195 @@ func TestTrampleRetrample(t *testing.T) { } }) } + +// TestSystemDNSDoHUpgrade tests that if the user doesn't configure DNS servers +// in their tailnet, and the system DNS happens to be a known DoH provider, +// queries will use DNS-over-HTTPS. +func TestSystemDNSDoHUpgrade(t *testing.T) { + var ( + // This is a non-routable TEST-NET-2 IP (RFC 5737). + testDoHResolverIP = netip.MustParseAddr("198.51.100.1") + // This is a non-routable TEST-NET-1 IP (RFC 5737). + testResponseIP = netip.MustParseAddr("192.0.2.1") + ) + const testDomain = "test.example.com." + + var ( + mu sync.Mutex + dohRequestSeen bool + receivedQuery []byte + ) + dohServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Logf("[DoH Server] received request: %v %v", r.Method, r.URL) + + if r.Method != httpm.POST { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + if r.Header.Get("Content-Type") != "application/dns-message" { + http.Error(w, "bad content type", http.StatusBadRequest) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "read error", http.StatusInternalServerError) + return + } + + mu.Lock() + defer mu.Unlock() + + dohRequestSeen = true + receivedQuery = body + + // Build a DNS response + response := buildTestDNSResponse(t, testDomain, testResponseIP) + w.Header().Set("Content-Type", "application/dns-message") + w.Write(response) + })) + t.Cleanup(dohServer.Close) + + // Register the test IP to route to our mock DoH server + cleanup := publicdns.RegisterTestDoHEndpoint(testDoHResolverIP, dohServer.URL) + t.Cleanup(cleanup) + + // This simulates a system with the single DoH-capable DNS server + // configured. + f := &fakeOSConfigurator{ + SplitDNS: false, // non-split DNS required to use the forwarder + BaseConfig: OSConfig{ + Nameservers: []netip.Addr{testDoHResolverIP}, + }, + } + + logf := tstest.WhileTestRunningLogger(t) + bus := eventbustest.NewBus(t) + dialer := tsdial.NewDialer(netmon.NewStatic()) + dialer.SetBus(bus) + m := NewManager(logf, f, health.NewTracker(bus), dialer, nil, &controlknobs.Knobs{}, "linux", bus) + t.Cleanup(func() { m.Down() }) + + // Set up hook to capture the resolver config + m.resolver.TestOnlySetHook(f.SetResolver) + + // Configure the manager with routes but no default resolvers, which + // reads BaseConfig from the OS configurator. + config := Config{ + Routes: upstreams("tailscale.com.", "10.0.0.1"), + SearchDomains: fqdns("tailscale.com."), + } + if err := m.Set(config); err != nil { + t.Fatal(err) + } + + // Verify the resolver config has our test IP in Routes["."] + if f.ResolverConfig.Routes == nil { + t.Fatal("ResolverConfig.Routes is nil (SetResolver hook not called)") + } + + const defaultRouteKey = "." + defaultRoute, ok := f.ResolverConfig.Routes[defaultRouteKey] + if !ok { + t.Fatalf("ResolverConfig.Routes[%q] not found", defaultRouteKey) + } + if !slices.ContainsFunc(defaultRoute, func(r *dnstype.Resolver) bool { + return r.Addr == testDoHResolverIP.String() + }) { + t.Errorf("test IP %v not found in Routes[%q], got: %v", testDoHResolverIP, defaultRouteKey, defaultRoute) + } + + // Build a DNS query to something not handled by our split DNS route + // (tailscale.com) above. + query := buildTestDNSQuery(t, testDomain) + + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + resp, err := m.Query(ctx, query, "udp", netip.MustParseAddrPort("127.0.0.1:12345")) + if err != nil { + t.Fatal(err) + } + if len(resp) == 0 { + t.Fatal("empty response") + } + + // Parse the response to verify we get our test IP back. + var parser dns.Parser + if _, err := parser.Start(resp); err != nil { + t.Fatalf("parsing response header: %v", err) + } + if err := parser.SkipAllQuestions(); err != nil { + t.Fatalf("skipping questions: %v", err) + } + answers, err := parser.AllAnswers() + if err != nil { + t.Fatalf("parsing answers: %v", err) + } + if len(answers) == 0 { + t.Fatal("no answers in response") + } + aRecord, ok := answers[0].Body.(*dns.AResource) + if !ok { + t.Fatalf("first answer is not A record: %T", answers[0].Body) + } + gotIP := netip.AddrFrom4(aRecord.A) + if gotIP != testResponseIP { + t.Errorf("wrong A record IP: got %v, want %v", gotIP, testResponseIP) + } + + // Also verify that our DoH server received the query. + mu.Lock() + defer mu.Unlock() + if !dohRequestSeen { + t.Error("DoH server never received request") + } + if !bytes.Equal(receivedQuery, query) { + t.Errorf("DoH server received wrong query:\ngot: %x\nwant: %x", receivedQuery, query) + } +} + +// buildTestDNSQuery builds a simple DNS A query for the given domain. +func buildTestDNSQuery(t *testing.T, domain string) []byte { + t.Helper() + + builder := dns.NewBuilder(nil, dns.Header{}) + builder.StartQuestions() + builder.Question(dns.Question{ + Name: dns.MustNewName(domain), + Type: dns.TypeA, + Class: dns.ClassINET, + }) + msg, err := builder.Finish() + if err != nil { + t.Fatal(err) + } + + return msg +} + +// buildTestDNSResponse builds a DNS response for the given query with the specified IP. +func buildTestDNSResponse(t *testing.T, domain string, ip netip.Addr) []byte { + t.Helper() + + builder := dns.NewBuilder(nil, dns.Header{Response: true}) + builder.StartQuestions() + builder.Question(dns.Question{ + Name: dns.MustNewName(domain), + Type: dns.TypeA, + Class: dns.ClassINET, + }) + + builder.StartAnswers() + builder.AResource(dns.ResourceHeader{ + Name: dns.MustNewName(domain), + Class: dns.ClassINET, + TTL: 300, + }, dns.AResource{A: ip.As4()}) + + msg, err := builder.Finish() + if err != nil { + t.Fatal(err) + } + + return msg +} diff --git a/net/dns/publicdns/publicdns.go b/net/dns/publicdns/publicdns.go index 7ceaf1813db6c..3666bd77847c9 100644 --- a/net/dns/publicdns/publicdns.go +++ b/net/dns/publicdns/publicdns.go @@ -13,12 +13,14 @@ import ( "log" "math/big" "net/netip" + "slices" "sort" "strconv" "strings" "sync" "tailscale.com/feature/buildfeatures" + "tailscale.com/util/testenv" ) // dohOfIP maps from public DNS IPs to their DoH base URL. @@ -367,3 +369,39 @@ func IPIsDoHOnlyServer(ip netip.Addr) bool { controlDv6RangeA.Contains(ip) || controlDv6RangeB.Contains(ip) || ip == controlDv4One || ip == controlDv4Two } + +var testMu sync.Mutex + +// RegisterTestDoHEndpoint registers a test DoH endpoint mapping for use in tests. +// It maps the given IP to the DoH base URL, and the URL back to the IP. +// +// This function panics if called outside of tests, and cannot be called +// concurrently with any usage of this package (i.e. before any DNS forwarders +// are created). It is safe to call concurrently with itself. +// +// It returns a cleanup function that removes the registration. +func RegisterTestDoHEndpoint(ip netip.Addr, dohBase string) func() { + if !testenv.InTest() { + panic("RegisterTestDoHEndpoint called outside of tests") + } + populateOnce.Do(populate) + + testMu.Lock() + defer testMu.Unlock() + + dohOfIP[ip] = dohBase + dohIPsOfBase[dohBase] = append(dohIPsOfBase[dohBase], ip) + + return func() { + testMu.Lock() + defer testMu.Unlock() + + delete(dohOfIP, ip) + dohIPsOfBase[dohBase] = slices.DeleteFunc(dohIPsOfBase[dohBase], func(addr netip.Addr) bool { + return addr == ip + }) + if len(dohIPsOfBase[dohBase]) == 0 { + delete(dohIPsOfBase, dohBase) + } + } +} From 6e44cb6ab30404ef03e16aedd0ccd476431d843b Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Mon, 26 Jan 2026 14:34:01 -0700 Subject: [PATCH 028/202] tsnet: make ListenService examples consistent with other tsnet examples Fixes tailscale/corp#36365 Signed-off-by: Harry Harpham --- ...e_tsnet_listen_service_multiple_ports_test.go | 10 ++++------ tsnet/example_tsnet_test.go | 16 ++++++---------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/tsnet/example_tsnet_listen_service_multiple_ports_test.go b/tsnet/example_tsnet_listen_service_multiple_ports_test.go index 0c7b3899955e1..5fe86a9ecf9fe 100644 --- a/tsnet/example_tsnet_listen_service_multiple_ports_test.go +++ b/tsnet/example_tsnet_listen_service_multiple_ports_test.go @@ -19,21 +19,19 @@ import ( // Service on multiple ports. In this example, we run an HTTPS server on 443 and // an HTTP server handling pprof requests to the same runtime on 6060. func ExampleServer_ListenService_multiplePorts() { - s := &tsnet.Server{ - Hostname: "tsnet-services-demo", + srv := &tsnet.Server{ + Hostname: "shu", } - defer s.Close() - ln, err := s.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ + ln, err := srv.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ HTTPS: true, Port: 443, }) if err != nil { log.Fatal(err) } - defer ln.Close() - pprofLn, err := s.ListenService("svc:my-service", tsnet.ServiceModeTCP{ + pprofLn, err := srv.ListenService("svc:my-service", tsnet.ServiceModeTCP{ Port: 6060, }) if err != nil { diff --git a/tsnet/example_tsnet_test.go b/tsnet/example_tsnet_test.go index dbaa8111fb623..2af31a76f787f 100644 --- a/tsnet/example_tsnet_test.go +++ b/tsnet/example_tsnet_test.go @@ -205,19 +205,17 @@ func ExampleServer_ListenFunnel_funnelOnly() { // ExampleServer_ListenService demonstrates how to advertise an HTTPS Service. func ExampleServer_ListenService() { - s := &tsnet.Server{ - Hostname: "tsnet-services-demo", + srv := &tsnet.Server{ + Hostname: "atum", } - defer s.Close() - ln, err := s.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ + ln, err := srv.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ HTTPS: true, Port: 443, }) if err != nil { log.Fatal(err) } - defer ln.Close() log.Printf("Listening on https://%v\n", ln.FQDN) log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -238,19 +236,17 @@ func ExampleServer_ListenService_reverseProxy() { Host: targetAddress, }) - s := &tsnet.Server{ - Hostname: "tsnet-services-demo", + srv := &tsnet.Server{ + Hostname: "tefnut", } - defer s.Close() - ln, err := s.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ + ln, err := srv.ListenService("svc:my-service", tsnet.ServiceModeHTTP{ HTTPS: true, Port: 443, }) if err != nil { log.Fatal(err) } - defer ln.Close() log.Printf("Listening on https://%v\n", ln.FQDN) log.Fatal(http.Serve(ln, reverseProxy)) From 9385dfe7f654d74c177ffc3e7f4b6fe428562022 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 26 Jan 2026 14:55:30 -0800 Subject: [PATCH 029/202] ipn/ipnlocal/netmapcache: add a package to split and cache network maps (#18497) This commit is based on part of #17925, reworked as a separate package. Add a package that can store and load netmap.NetworkMap values in persistent storage, using a basic columnar representation. This commit includes a default storage interface based on plain files, but the interface can be implemented with more structured storage if we want to later. The tests are set up to require that all the fields of the NetworkMap are handled, except those explicitly designated as not-cached, and check that a fully-populated value can round-trip correctly through the cache. Adding or removing fields, either in the NetworkMap or in the cached representation, will trigger either build failures (e.g., for type mismatch) or test failures (e.g., for representation changes or missing fields). This isn't quite as nice as automatically updating the representation, which I also prototyped, but is much simpler to maintain and less code. This commit does not yet hook up the cache to the backend, that will be a subsequent change. Updates #12639 Change-Id: Icb48639e1d61f2aec59904ecd172c73e05ba7bf9 Signed-off-by: M. J. Fromberger --- flake.nix | 2 +- go.mod | 1 + go.mod.sri | 2 +- ipn/ipnlocal/netmapcache/netmapcache.go | 351 +++++++++++++++++++ ipn/ipnlocal/netmapcache/netmapcache_test.go | 298 ++++++++++++++++ ipn/ipnlocal/netmapcache/types.go | 52 +++ shell.nix | 2 +- types/netmap/netmap.go | 2 + 8 files changed, 707 insertions(+), 3 deletions(-) create mode 100644 ipn/ipnlocal/netmapcache/netmapcache.go create mode 100644 ipn/ipnlocal/netmapcache/netmapcache_test.go create mode 100644 ipn/ipnlocal/netmapcache/types.go diff --git a/flake.nix b/flake.nix index 149223d0aac60..76e68e4acd57f 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-WeMTOkERj4hvdg4yPaZ1gRgKnhRIBXX55kUVbX/k/xM= +# nix-direnv cache busting line: sha256-+tOYqRV8ZUA95dfVyRpjnJvwuSMobu/EhtXxq4bwvio= diff --git a/go.mod b/go.mod index 7fd487382e574..bcdc7e19d3162 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/coder/websocket v1.8.12 github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf + github.com/creachadair/mds v0.25.9 github.com/creachadair/msync v0.7.1 github.com/creachadair/taskgroup v0.13.2 github.com/creack/pty v1.1.23 diff --git a/go.mod.sri b/go.mod.sri index b533a75654aa6..d46c84a110095 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-WeMTOkERj4hvdg4yPaZ1gRgKnhRIBXX55kUVbX/k/xM= +sha256-+tOYqRV8ZUA95dfVyRpjnJvwuSMobu/EhtXxq4bwvio= diff --git a/ipn/ipnlocal/netmapcache/netmapcache.go b/ipn/ipnlocal/netmapcache/netmapcache.go new file mode 100644 index 0000000000000..6992e0691f125 --- /dev/null +++ b/ipn/ipnlocal/netmapcache/netmapcache.go @@ -0,0 +1,351 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package netmapcache implements a persistent cache for [netmap.NetworkMap] +// values, allowing a client to start up using stale but previously-valid state +// even if a connection to the control plane is not immediately available. +package netmapcache + +import ( + "cmp" + "context" + "crypto/sha256" + "encoding/hex" + jsonv1 "encoding/json" + "errors" + "fmt" + "io/fs" + "iter" + "os" + "path/filepath" + "slices" + "strings" + "time" + + "tailscale.com/feature/buildfeatures" + "tailscale.com/tailcfg" + "tailscale.com/types/netmap" + "tailscale.com/util/mak" + "tailscale.com/util/set" +) + +var ( + // ErrKeyNotFound is a sentinel error reported by implementations of the [Store] + // interface when loading a key that is not found in the store. + ErrKeyNotFound = errors.New("storage key not found") + + // ErrCacheNotAvailable is a sentinel error reported by cache methods when + // the netmap caching feature is not enabled in the build. + ErrCacheNotAvailable = errors.New("netmap cache is not available") +) + +// A Cache manages a columnar cache of a [netmap.NetworkMap]. Each Cache holds +// a single netmap value; use [Cache.Store] to update or replace the cached +// value and [Cache.Load] to read the cached value. +type Cache struct { + store Store + + // wantKeys records the storage keys from the last write or load of a cached + // netmap. This is used to prune keys that are no longer referenced after an + // update. + wantKeys set.Set[string] + + // lastWrote records the last values written to each stored key. + // + // TODO(creachadair): This is meant to avoid disk writes, but I'm not + // convinced we need it. Or maybe just track hashes of the content rather + // than caching a complete copy. + lastWrote map[string]lastWrote +} + +// NewCache constructs a new empty [Cache] from the given [Store]. +// It will panic if s == nil. +func NewCache(s Store) *Cache { + if s == nil { + panic("a non-nil Store is required") + } + return &Cache{ + store: s, + wantKeys: make(set.Set[string]), + lastWrote: make(map[string]lastWrote), + } +} + +type lastWrote struct { + digest string + at time.Time +} + +func (c *Cache) writeJSON(ctx context.Context, key string, v any) error { + j, err := jsonv1.Marshal(v) + if err != nil { + return fmt.Errorf("JSON marshalling %q: %w", key, err) + } + + // TODO(creachadair): Maybe use a hash instead of the contents? Do we need + // this at all? + last, ok := c.lastWrote[key] + if ok && cacheDigest(j) == last.digest { + return nil + } + + if err := c.store.Store(ctx, key, j); err != nil { + return err + } + + // Track the storage keys the current map is using, for storage GC. + c.wantKeys.Add(key) + c.lastWrote[key] = lastWrote{ + digest: cacheDigest(j), + at: time.Now(), + } + return nil +} + +func (c *Cache) removeUnwantedKeys(ctx context.Context) error { + var errs []error + for key, err := range c.store.List(ctx, "") { + if err != nil { + errs = append(errs, err) + break + } + if !c.wantKeys.Contains(key) { + if err := c.store.Remove(ctx, key); err != nil { + errs = append(errs, fmt.Errorf("remove key %q: %w", key, err)) + } + delete(c.lastWrote, key) // even if removal failed, we don't want it + } + } + return errors.Join(errs...) +} + +// FileStore implements the [Store] interface using a directory of files, in +// which each key is encoded as a filename in the directory. +// The caller is responsible to ensure the directory path exists before +// using the store methods. +type FileStore string + +// List implements part of the [Store] interface. +func (s FileStore) List(ctx context.Context, prefix string) iter.Seq2[string, error] { + return func(yield func(string, error) bool) { + des, err := os.ReadDir(string(s)) + if os.IsNotExist(err) { + return // nothing to read + } else if err != nil { + yield("", err) + return + } + + // os.ReadDir reports entries already sorted, and the encoding preserves that. + for _, de := range des { + key, err := hex.DecodeString(de.Name()) + if err != nil { + yield("", err) + return + } + name := string(key) + if !strings.HasPrefix(name, prefix) { + continue + } else if !yield(name, nil) { + return + } + } + } +} + +// Load implements part of the [Store] interface. +func (s FileStore) Load(ctx context.Context, key string) ([]byte, error) { + return os.ReadFile(filepath.Join(string(s), hex.EncodeToString([]byte(key)))) +} + +// Store implements part of the [Store] interface. +func (s FileStore) Store(ctx context.Context, key string, value []byte) error { + return os.WriteFile(filepath.Join(string(s), hex.EncodeToString([]byte(key))), value, 0600) +} + +// Remove implements part of the [Store] interface. +func (s FileStore) Remove(ctx context.Context, key string) error { + err := os.Remove(filepath.Join(string(s), hex.EncodeToString([]byte(key)))) + if errors.Is(err, fs.ErrNotExist) { + return nil + } + return err +} + +// Store records nm in the cache, replacing any previously-cached values. +func (c *Cache) Store(ctx context.Context, nm *netmap.NetworkMap) error { + if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached { + return nil + } + if selfID := nm.User(); selfID == 0 { + return errors.New("no user in netmap") + } + + clear(c.wantKeys) + if err := c.writeJSON(ctx, "misc", netmapMisc{ + MachineKey: &nm.MachineKey, + CollectServices: &nm.CollectServices, + DisplayMessages: &nm.DisplayMessages, + TKAEnabled: &nm.TKAEnabled, + TKAHead: &nm.TKAHead, + Domain: &nm.Domain, + DomainAuditLogID: &nm.DomainAuditLogID, + }); err != nil { + return err + } + if err := c.writeJSON(ctx, "dns", netmapDNS{DNS: &nm.DNS}); err != nil { + return err + } + if err := c.writeJSON(ctx, "derpmap", netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { + return err + } + if err := c.writeJSON(ctx, "self", netmapNode{Node: &nm.SelfNode}); err != nil { + return err + + // N.B. The NodeKey and AllCaps fields can be recovered from SelfNode on + // load, and do not need to be stored separately. + } + for _, p := range nm.Peers { + key := fmt.Sprintf("peer-%s", p.StableID()) + if err := c.writeJSON(ctx, key, netmapNode{Node: &p}); err != nil { + return err + } + } + for uid, u := range nm.UserProfiles { + key := fmt.Sprintf("user-%d", uid) + if err := c.writeJSON(ctx, key, netmapUserProfile{UserProfile: &u}); err != nil { + return err + } + } + + if buildfeatures.HasSSH && nm.SSHPolicy != nil { + if err := c.writeJSON(ctx, "ssh", netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { + return err + } + } + + return c.removeUnwantedKeys(ctx) +} + +// Load loads the cached [netmap.NetworkMap] value stored in c, if one is available. +// It reports [ErrCacheNotAvailable] if no cached data are available. +// On success, the Cached field of the returned network map is true. +func (c *Cache) Load(ctx context.Context) (*netmap.NetworkMap, error) { + if !buildfeatures.HasCacheNetMap { + return nil, ErrCacheNotAvailable + } + + nm := netmap.NetworkMap{Cached: true} + + // At minimum, we require that the cache contain a "self" node, or the data + // are not usable. + if self, err := c.store.Load(ctx, "self"); errors.Is(err, ErrKeyNotFound) { + return nil, ErrCacheNotAvailable + } else if err := jsonv1.Unmarshal(self, &netmapNode{Node: &nm.SelfNode}); err != nil { + return nil, err + } + c.wantKeys.Add("self") + + // If we successfully recovered a SelfNode, pull out its related fields. + if s := nm.SelfNode; s.Valid() { + nm.NodeKey = s.Key() + nm.AllCaps = make(set.Set[tailcfg.NodeCapability]) + for _, c := range s.Capabilities().All() { + nm.AllCaps.Add(c) + } + for c := range s.CapMap().All() { + nm.AllCaps.Add(c) + } + } + + // Unmarshal the contents of each specified cache entry directly into the + // fields of the output. See the comment in types.go for more detail. + + if err := c.readJSON(ctx, "misc", &netmapMisc{ + MachineKey: &nm.MachineKey, + CollectServices: &nm.CollectServices, + DisplayMessages: &nm.DisplayMessages, + TKAEnabled: &nm.TKAEnabled, + TKAHead: &nm.TKAHead, + Domain: &nm.Domain, + DomainAuditLogID: &nm.DomainAuditLogID, + }); err != nil { + return nil, err + } + + if err := c.readJSON(ctx, "dns", &netmapDNS{DNS: &nm.DNS}); err != nil { + return nil, err + } + if err := c.readJSON(ctx, "derpmap", &netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { + return nil, err + } + + for key, err := range c.store.List(ctx, "peer-") { + if err != nil { + return nil, err + } + var peer tailcfg.NodeView + if err := c.readJSON(ctx, key, &netmapNode{Node: &peer}); err != nil { + return nil, err + } + nm.Peers = append(nm.Peers, peer) + } + slices.SortFunc(nm.Peers, func(a, b tailcfg.NodeView) int { return cmp.Compare(a.ID(), b.ID()) }) + for key, err := range c.store.List(ctx, "user-") { + if err != nil { + return nil, err + } + var up tailcfg.UserProfileView + if err := c.readJSON(ctx, key, &netmapUserProfile{UserProfile: &up}); err != nil { + return nil, err + } + mak.Set(&nm.UserProfiles, up.ID(), up) + } + if err := c.readJSON(ctx, "ssh", &netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { + return nil, err + } + + return &nm, nil +} + +func (c *Cache) readJSON(ctx context.Context, key string, value any) error { + data, err := c.store.Load(ctx, key) + if errors.Is(err, ErrKeyNotFound) { + return nil + } else if err != nil { + return err + } + if err := jsonv1.Unmarshal(data, value); err != nil { + return err + } + c.wantKeys.Add(key) + c.lastWrote[key] = lastWrote{digest: cacheDigest(data), at: time.Now()} + return nil +} + +// Store is the interface to persistent key-value storage used by a [Cache]. +type Store interface { + // List lists all the stored keys having the specified prefixes, in + // lexicographic order. + // + // Each pair yielded by the iterator is either a valid storage key and a nil + // error, or an empty key and a non-nil error. After reporting an error, the + // iterator must immediately return. + List(ctx context.Context, prefix string) iter.Seq2[string, error] + + // Load fetches the contents of the specified key. + // If the key is not found in the store, Load must report [ErrKeyNotFound]. + Load(ctx context.Context, key string) ([]byte, error) + + // Store marshals and stores the contents of the specified value under key. + // If the key already exists, its contents are replaced. + Store(ctx context.Context, key string, value []byte) error + + // Remove removes the specified key from the store. If the key does not exist, + // Remove reports success (nil). + Remove(ctx context.Context, key string) error +} + +// cacheDigest computes a string digest of the specified data, for use in +// detecting cache hits. +func cacheDigest(data []byte) string { h := sha256.Sum256(data); return string(h[:]) } diff --git a/ipn/ipnlocal/netmapcache/netmapcache_test.go b/ipn/ipnlocal/netmapcache/netmapcache_test.go new file mode 100644 index 0000000000000..1f7d9b3bf6f07 --- /dev/null +++ b/ipn/ipnlocal/netmapcache/netmapcache_test.go @@ -0,0 +1,298 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package netmapcache_test + +import ( + "context" + "errors" + "flag" + "fmt" + "iter" + "os" + "reflect" + "slices" + "strings" + "testing" + + "github.com/creachadair/mds/mtest" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "tailscale.com/ipn/ipnlocal/netmapcache" + "tailscale.com/tailcfg" + "tailscale.com/tka" + "tailscale.com/types/key" + "tailscale.com/types/netmap" + "tailscale.com/types/views" + "tailscale.com/util/set" +) + +// Input values for valid-looking placeholder values for keys, hashes, etc. +const ( + testNodeKeyString = "nodekey:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + testMachineKeyString = "mkey:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" + testAUMHashString = "APPLEPEARPLUMCHERRYAPPLEPEARPLUMCHERRYAPPLEPEARPLUMA" // base32, no padding +) + +var keepTestOutput = flag.String("keep-output", "", "directory to keep test output (if empty, use a test temp)") + +var ( + testNode1 = (&tailcfg.Node{ + ID: 99001, + StableID: "n99001FAKE", + Name: "test1.example.com.", + }).View() + testNode2 = (&tailcfg.Node{ + ID: 99002, + StableID: "n99002FAKE", + Name: "test2.example.com.", + }).View() + + // The following fields are set in init. + testNodeKey key.NodePublic + testMachineKey key.MachinePublic + testAUMHash tka.AUMHash + testMap *netmap.NetworkMap +) + +func init() { + if err := testNodeKey.UnmarshalText([]byte(testNodeKeyString)); err != nil { + panic(fmt.Sprintf("invalid test nodekey %q: %v", testNodeKeyString, err)) + } + if err := testMachineKey.UnmarshalText([]byte(testMachineKeyString)); err != nil { + panic(fmt.Sprintf("invalid test machine key %q: %v", testMachineKeyString, err)) + } + if err := testAUMHash.UnmarshalText([]byte(testAUMHashString)); err != nil { + panic(fmt.Sprintf("invalid test AUM hash %q: %v", testAUMHashString, err)) + } + + // The following network map must have a non-zero non-empty value for every + // field that is to be stored in the cache. The test checks for this using + // reflection, as a way to ensure that new fields added to the type are + // covered by a test (see checkFieldCoverage). + // + // The exact values are unimportant, except that they should be values that + // give us confidence that a network map round-tripped through the cache and + // compared will accurately reflect the information we care about. + testMap = &netmap.NetworkMap{ + Cached: false, // not cached, this is metadata for the cache machinery + + PacketFilter: nil, // not cached + PacketFilterRules: views.Slice[tailcfg.FilterRule]{}, // not cached + + // Fields stored under the "self" key. + // Note that SelfNode must have a valid user in order to be considered + // cacheable. Moreover, it must mention all the capabilities we expect + // to see advertised in the AllCaps set, and its public key must match the + // one advertised in the NodeKey field. + SelfNode: (&tailcfg.Node{ + ID: 12345, + StableID: "n12345FAKE", + User: 30337, + Name: "test.example.com.", + Key: testNodeKey, + Capabilities: []tailcfg.NodeCapability{"cap1"}, + CapMap: map[tailcfg.NodeCapability][]tailcfg.RawMessage{ + "cap2": nil, + }, + }).View(), + AllCaps: set.Of[tailcfg.NodeCapability]("cap1", "cap2"), + NodeKey: testNodeKey, + + DNS: tailcfg.DNSConfig{Domains: []string{"example1.com", "example2.ac.uk"}}, // "dns" + + SSHPolicy: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{{ // "ssh" + SSHUsers: map[string]string{"amelie": "ubuntu"}, + Action: &tailcfg.SSHAction{Message: "hello", Accept: true}, + AcceptEnv: []string{"MAGIC_SSH_*"}, + }}}, + + DERPMap: &tailcfg.DERPMap{ // "derp" + HomeParams: &tailcfg.DERPHomeParams{ + RegionScore: map[int]float64{10: 0.31, 20: 0.141, 30: 0.592}, + }, + OmitDefaultRegions: true, + }, + + // Peers stored under "peer-" keys. + Peers: []tailcfg.NodeView{testNode1, testNode2}, + + // Profiles stored under "user-" keys. + UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{ + 12345: (&tailcfg.UserProfile{ID: 12345, DisplayName: "me"}).View(), + 67890: (&tailcfg.UserProfile{ID: 67890, DisplayName: "you"}).View(), + }, + + // Fields stored under "misc" + MachineKey: testMachineKey, + CollectServices: true, + DisplayMessages: map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage{ + "test-message-1": {Title: "hello", Text: "this is your wakeup call"}, + "test-message-2": {Title: "goodbye", Text: "good night", ImpactsConnectivity: true}, + }, + TKAEnabled: true, + TKAHead: testAUMHash, + Domain: "example.com", + DomainAuditLogID: "0f1e2d3c4b5a67890f1e2d3c4b5a67890f1e2d3c4b5a67890f1e2d3c4b5a6789", + } +} + +func TestNewStore(t *testing.T) { + mtest.MustPanicf(t, func() { netmapcache.NewCache(nil) }, "NewCache should panic for a nil store") +} + +func TestRoundTrip(t *testing.T) { + checkFieldCoverage(t, testMap) + + dir := *keepTestOutput + if dir == "" { + dir = t.TempDir() + } else if err := os.MkdirAll(dir, 0700); err != nil { + t.Fatalf("Create --keep-output directory: %v", err) + } + + tests := []struct { + name string + store netmapcache.Store + }{ + {"MemStore", make(testStore)}, + {"FileStore", netmapcache.FileStore(dir)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := netmapcache.NewCache(tt.store) + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store netmap failed; %v", err) + } + + cmap, err := c.Load(t.Context()) + if err != nil { + t.Fatalf("Load netmap failed: %v", err) + } + + if !cmap.Cached { + t.Error("Cached map is not marked as such") + } + + opts := []cmp.Option{ + cmpopts.IgnoreFields(netmap.NetworkMap{}, skippedMapFields...), + cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}), + } + if diff := cmp.Diff(cmap, testMap, opts...); diff != "" { + t.Fatalf("Cached map differs (-got, +want):\n%s", diff) + } + + }) + } +} + +func TestInvalidCache(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + c := netmapcache.NewCache(make(testStore)) + got, err := c.Load(t.Context()) + if !errors.Is(err, netmapcache.ErrCacheNotAvailable) { + t.Errorf("Load from empty cache: got %+v, %v; want nil, %v", got, err, netmapcache.ErrCacheNotAvailable) + } + }) + + t.Run("Incomplete", func(t *testing.T) { + s := make(testStore) + c := netmapcache.NewCache(s) + + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store initial netmap: %v", err) + } + + // Drop the "self" node from the cache, and verify it makes the results + // unloadable. + if err := s.Remove(t.Context(), "self"); err != nil { + t.Fatalf("Remove self: %v", err) + } + + got, err := c.Load(t.Context()) + if !errors.Is(err, netmapcache.ErrCacheNotAvailable) { + t.Errorf("Load from invalid cache: got %+v, %v; want nil, %v", got, err, netmapcache.ErrCacheNotAvailable) + } + }) +} + +// skippedMapFields are the names of fields that should not be considered by +// network map caching, and thus skipped when comparing test results. +var skippedMapFields = []string{ + "Cached", "PacketFilter", "PacketFilterRules", +} + +// checkFieldCoverage logs an error in t if any of the fields of nm are zero +// valued, except those listed in skippedMapFields. +// +// This ensures if any new fields are added to the [netmap.NetworkMap] type in +// the future, the test will fail until non-trivial test data are added to this +// test, or the fields are recorded as skipped. It also helps ensure that +// changing the field types or deleting fields will make compilation fail, so +// the tests get updated. +func checkFieldCoverage(t *testing.T, nm *netmap.NetworkMap) { + t.Helper() + + mt := reflect.TypeOf(nm).Elem() + mv := reflect.ValueOf(nm).Elem() + for i := 0; i < mt.NumField(); i++ { + f := mt.Field(i) + if slices.Contains(skippedMapFields, f.Name) { + continue + } + fv := mv.Field(i) + if fv.IsZero() { + t.Errorf("Field %d (%q) of test value is zero (%+v). "+ + "A non-zero value is required for each cached field in the test value.", + i, f.Name, fv.Interface()) + } + } + + // Verify that skip-listed fields exist on the type. FieldByName thwarts the + // linker, but it's OK in a test. + for _, skip := range skippedMapFields { + if _, ok := mt.FieldByName(skip); !ok { + t.Errorf("Skipped field %q not found on type %T. "+ + "If a field was deleted from the type, you may need to update skippedMapFields.", + skip, nm) + } + } + if t.Failed() { + t.FailNow() + } +} + +// testStore is an in-memory implementation of the [netmapcache.Store] interface. +type testStore map[string][]byte + +func (t testStore) List(_ context.Context, prefix string) iter.Seq2[string, error] { + var matching []string + for key := range t { + if strings.HasPrefix(key, prefix) { + matching = append(matching, key) + } + } + slices.Sort(matching) + return func(yield func(string, error) bool) { + for _, key := range matching { + if !yield(key, nil) { + return + } + } + } +} + +func (t testStore) Load(_ context.Context, key string) ([]byte, error) { + val, ok := t[key] + if !ok { + return nil, netmapcache.ErrKeyNotFound + } + return val, nil +} + +func (t testStore) Store(_ context.Context, key string, value []byte) error { + t[key] = value + return nil +} + +func (t testStore) Remove(_ context.Context, key string) error { delete(t, key); return nil } diff --git a/ipn/ipnlocal/netmapcache/types.go b/ipn/ipnlocal/netmapcache/types.go new file mode 100644 index 0000000000000..2fb5a1575f1b3 --- /dev/null +++ b/ipn/ipnlocal/netmapcache/types.go @@ -0,0 +1,52 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package netmapcache + +import ( + "tailscale.com/tailcfg" + "tailscale.com/tka" + "tailscale.com/types/key" +) + +// The fields in the following wrapper types are all pointers, even when their +// target type is also a pointer, so that they can be used to unmarshal +// directly into the fields of another value. These wrappers intentionally do +// not omit zero or empty values, since we want the cache to reflect the value +// the object had at the time it was written, even if the default changes +// later. +// +// Moreover, these are all struct types so that each cached record will be a +// JSON object even if the underlying value marshals to an array or primitive +// type, and so that we have a seam if we want to replace or version the cached +// representation separately from the default JSON layout. + +type netmapMisc struct { + MachineKey *key.MachinePublic + CollectServices *bool + DisplayMessages *map[tailcfg.DisplayMessageID]tailcfg.DisplayMessage + TKAEnabled *bool + TKAHead *tka.AUMHash + Domain *string + DomainAuditLogID *string +} + +type netmapSSH struct { + SSHPolicy **tailcfg.SSHPolicy +} + +type netmapDNS struct { + DNS *tailcfg.DNSConfig +} + +type netmapDERPMap struct { + DERPMap **tailcfg.DERPMap +} + +type netmapNode struct { + Node *tailcfg.NodeView +} + +type netmapUserProfile struct { + UserProfile *tailcfg.UserProfileView +} diff --git a/shell.nix b/shell.nix index ccec5faf538e0..3accd73c55ffb 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-WeMTOkERj4hvdg4yPaZ1gRgKnhRIBXX55kUVbX/k/xM= +# nix-direnv cache busting line: sha256-+tOYqRV8ZUA95dfVyRpjnJvwuSMobu/EhtXxq4bwvio= diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index d809cbab4ad5d..ac95254daee1d 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -27,6 +27,8 @@ import ( // The fields should all be considered read-only. They might // alias parts of previous NetworkMap values. type NetworkMap struct { + Cached bool // whether this NetworkMap was loaded from disk cache (as opposed to live from network) + SelfNode tailcfg.NodeView AllCaps set.Set[tailcfg.NodeCapability] // set version of SelfNode.Capabilities + SelfNode.CapMap NodeKey key.NodePublic From 6de5b01e04beeb8504c1644e26b7b239a8a12e8c Mon Sep 17 00:00:00 2001 From: Amal Bansode Date: Mon, 26 Jan 2026 16:41:03 -0800 Subject: [PATCH 030/202] ipn/localapi: stop logging "broken pipe" errors (#18487) The Tailscale CLI has some methods to watch the IPN bus for messages, say, the current netmap (`tailscale debug netmap`). The Tailscale daemon supports this using a streaming HTTP response. Sometimes, the client can close its connection abruptly -- due to an interruption, or in the case of `debug netmap`, intentionally after consuming one message. If the server daemon is writing a response as the client closes its end of the socket, the daemon typically encounters a "broken pipe" error. The "Watch IPN Bus" handler currently logs such errors after they're propagated by a JSON encoding/writer helper. Since the Tailscale CLI nominally closes its socket with the daemon in this slightly ungraceful way (viz. `debug netmap`), stop logging these broken pipe errors as far as possible. This will help avoid confounding users when they scan backend logs. Updates #18477 Signed-off-by: Amal Bansode --- ipn/localapi/localapi.go | 5 +++- net/neterror/neterror_js.go | 20 +++++++++++++++ net/neterror/neterror_plan9.go | 24 ++++++++++++++++++ net/neterror/neterror_posix.go | 32 ++++++++++++++++++++++++ wgengine/magicsock/magicsock_notplan9.go | 4 ++- 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 net/neterror/neterror_js.go create mode 100644 net/neterror/neterror_plan9.go create mode 100644 net/neterror/neterror_posix.go diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 248c0377ec968..dc558b36e61d9 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -35,6 +35,7 @@ import ( "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnstate" "tailscale.com/logtail" + "tailscale.com/net/neterror" "tailscale.com/net/netns" "tailscale.com/net/netutil" "tailscale.com/tailcfg" @@ -913,7 +914,9 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) { h.b.WatchNotificationsAs(ctx, h.Actor, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) { err := enc.Encode(roNotify) if err != nil { - h.logf("json.Encode: %v", err) + if !neterror.IsClosedPipeError(err) { + h.logf("json.Encode: %v", err) + } return false } f.Flush() diff --git a/net/neterror/neterror_js.go b/net/neterror/neterror_js.go new file mode 100644 index 0000000000000..591367120fd85 --- /dev/null +++ b/net/neterror/neterror_js.go @@ -0,0 +1,20 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build js || wasip1 || wasm + +package neterror + +import ( + "errors" + "io" + "io/fs" +) + +// Reports whether err resulted from reading or writing to a closed or broken pipe. +func IsClosedPipeError(err error) bool { + // Libraries may also return root errors like fs.ErrClosed/io.ErrClosedPipe + // due to a closed socket. + return errors.Is(err, fs.ErrClosed) || + errors.Is(err, io.ErrClosedPipe) +} diff --git a/net/neterror/neterror_plan9.go b/net/neterror/neterror_plan9.go new file mode 100644 index 0000000000000..a60c4dd6496fa --- /dev/null +++ b/net/neterror/neterror_plan9.go @@ -0,0 +1,24 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build plan9 + +package neterror + +import ( + "errors" + "io" + "io/fs" + "strings" +) + +// Reports whether err resulted from reading or writing to a closed or broken pipe. +func IsClosedPipeError(err error) bool { + // Libraries may also return root errors like fs.ErrClosed/io.ErrClosedPipe + // due to a closed socket. + // For a raw syscall error, check for error string containing "closed pipe", + // per the note set by the system: https://9p.io/magic/man2html/2/pipe + return errors.Is(err, fs.ErrClosed) || + errors.Is(err, io.ErrClosedPipe) || + strings.Contains(err.Error(), "closed pipe") +} diff --git a/net/neterror/neterror_posix.go b/net/neterror/neterror_posix.go new file mode 100644 index 0000000000000..71dda6b4cf3a8 --- /dev/null +++ b/net/neterror/neterror_posix.go @@ -0,0 +1,32 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 && !js && !wasip1 && !wasm + +package neterror + +import ( + "errors" + "io" + "io/fs" + "runtime" + "syscall" +) + +// Reports whether err resulted from reading or writing to a closed or broken pipe. +func IsClosedPipeError(err error) bool { + // 232 is Windows error code ERROR_NO_DATA, "The pipe is being closed". + if runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(232)) { + return true + } + + // EPIPE/ENOTCONN are common errors when a send fails due to a closed + // socket. There is some platform and version inconsistency in which + // error is returned, but the meaning is the same. + // Libraries may also return root errors like fs.ErrClosed/io.ErrClosedPipe + // due to a closed socket. + return errors.Is(err, syscall.EPIPE) || + errors.Is(err, syscall.ENOTCONN) || + errors.Is(err, fs.ErrClosed) || + errors.Is(err, io.ErrClosedPipe) +} diff --git a/wgengine/magicsock/magicsock_notplan9.go b/wgengine/magicsock/magicsock_notplan9.go index db2c5fca052b9..6bb9db5d7f1d6 100644 --- a/wgengine/magicsock/magicsock_notplan9.go +++ b/wgengine/magicsock/magicsock_notplan9.go @@ -8,6 +8,8 @@ package magicsock import ( "errors" "syscall" + + "tailscale.com/net/neterror" ) // shouldRebind returns if the error is one that is known to be healed by a @@ -17,7 +19,7 @@ func shouldRebind(err error) (ok bool, reason string) { // EPIPE/ENOTCONN are common errors when a send fails due to a closed // socket. There is some platform and version inconsistency in which // error is returned, but the meaning is the same. - case errors.Is(err, syscall.EPIPE), errors.Is(err, syscall.ENOTCONN): + case neterror.IsClosedPipeError(err): return true, "broken-pipe" // EPERM is typically caused by EDR software, and has been observed to be From ae625691597787dab7fc6aa04c17ac78fcebc9af Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Tue, 27 Jan 2026 14:25:27 +0000 Subject: [PATCH 031/202] hostinfo: retrieve OS version for Macs running the OSS client Updates #18520 Change-Id: If86a1f702c704b003002aa7e2f5a6b1418b469cc Signed-off-by: Alex Chan --- hostinfo/hostinfo_darwin.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/hostinfo/hostinfo_darwin.go b/hostinfo/hostinfo_darwin.go index bce99d7003406..cd551ca425790 100644 --- a/hostinfo/hostinfo_darwin.go +++ b/hostinfo/hostinfo_darwin.go @@ -8,14 +8,31 @@ package hostinfo import ( "os" "path/filepath" + + "golang.org/x/sys/unix" + "tailscale.com/types/ptr" ) func init() { + osVersion = lazyOSVersion.Get packageType = packageTypeDarwin } +var ( + lazyOSVersion = &lazyAtomicValue[string]{f: ptr.To(osVersionDarwin)} +) + func packageTypeDarwin() string { // Using tailscaled or IPNExtension? exe, _ := os.Executable() return filepath.Base(exe) } + +// Returns the marketing version (e.g., "15.0.1" or "26.0.0") +func osVersionDarwin() string { + version, err := unix.Sysctl("kern.osproductversion") + if err != nil { + return "" + } + return version +} From aac12ba799f1af9021cac5dfbdcc0e4df4601626 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 27 Jan 2026 13:42:04 -0800 Subject: [PATCH 032/202] cmd/tailscale/cli: add json output option to `switch --list` (#18501) * cmd/tailscale/cli: add json output option to `switch --list` Closes #14783 Signed-off-by: Cameron Stokes --- cmd/tailscale/cli/switch.go | 50 ++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/cmd/tailscale/cli/switch.go b/cmd/tailscale/cli/switch.go index 34ed2c7687c67..bd90c522e3393 100644 --- a/cmd/tailscale/cli/switch.go +++ b/cmd/tailscale/cli/switch.go @@ -5,6 +5,7 @@ package cli import ( "context" + "encoding/json" "flag" "fmt" "os" @@ -18,9 +19,12 @@ import ( ) var switchCmd = &ffcli.Command{ - Name: "switch", - ShortUsage: "tailscale switch ", - ShortHelp: "Switch to a different Tailscale account", + Name: "switch", + ShortUsage: strings.Join([]string{ + "tailscale switch ", + "tailscale switch --list [--json]", + }, "\n"), + ShortHelp: "Switch to a different Tailscale account", LongHelp: `"tailscale switch" switches between logged in accounts. You can use the ID that's returned from 'tailnet switch -list' to pick which profile you want to switch to. Alternatively, you @@ -31,6 +35,7 @@ This command is currently in alpha and may change in the future.`, FlagSet: func() *flag.FlagSet { fs := flag.NewFlagSet("switch", flag.ExitOnError) fs.BoolVar(&switchArgs.list, "list", false, "list available accounts") + fs.BoolVar(&switchArgs.json, "json", false, "list available accounts in JSON format") return fs }(), Exec: switchProfile, @@ -82,6 +87,7 @@ func init() { var switchArgs struct { list bool + json bool } func listProfiles(ctx context.Context) error { @@ -109,10 +115,48 @@ func listProfiles(ctx context.Context) error { return nil } +type switchProfileJSON struct { + ID string `json:"id"` + Nickname string `json:"nickname"` + Tailnet string `json:"tailnet"` + Account string `json:"account"` + Selected bool `json:"selected"` +} + +func listProfilesJSON(ctx context.Context) error { + curP, all, err := localClient.ProfileStatus(ctx) + if err != nil { + return err + } + profiles := make([]switchProfileJSON, 0, len(all)) + for _, prof := range all { + profiles = append(profiles, switchProfileJSON{ + ID: string(prof.ID), + Tailnet: prof.NetworkProfile.DisplayNameOrDefault(), + Account: prof.UserProfile.LoginName, + Nickname: prof.Name, + Selected: prof.ID == curP.ID, + }) + } + profilesJSON, err := json.MarshalIndent(profiles, "", " ") + if err != nil { + return err + } + printf("%s\n", profilesJSON) + return nil +} + func switchProfile(ctx context.Context, args []string) error { if switchArgs.list { + if switchArgs.json { + return listProfilesJSON(ctx) + } return listProfiles(ctx) } + if switchArgs.json { + outln("--json argument cannot be used with tailscale switch NAME") + os.Exit(1) + } if len(args) != 1 { outln("usage: tailscale switch NAME") os.Exit(1) From a374cc344e48067a64cacf5bebd49fbe99596688 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 26 Jan 2026 17:21:08 -0800 Subject: [PATCH 033/202] tool/gocross, pull-toolchain.sh: support a "next" Go toolchain When TS_GO_NEXT=1 is set, update/use the go.toolchain.next.{branch,rev} files instead. This lets us do test deploys of Go release candidates on some backends, without affecting all backends. Updates tailscale/corp#36382 Change-Id: I00dbde87b219b720be5ea142325c4711f101a364 Signed-off-by: Brad Fitzpatrick --- go.toolchain.next.branch | 1 + go.toolchain.next.rev | 1 + pull-toolchain.sh | 27 ++++++++++++++++++++------- tool/gocross/gocross-wrapper.sh | 12 +++++++++--- 4 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 go.toolchain.next.branch create mode 100644 go.toolchain.next.rev diff --git a/go.toolchain.next.branch b/go.toolchain.next.branch new file mode 100644 index 0000000000000..6022b95593bbe --- /dev/null +++ b/go.toolchain.next.branch @@ -0,0 +1 @@ +tailscale.go1.26 diff --git a/go.toolchain.next.rev b/go.toolchain.next.rev new file mode 100644 index 0000000000000..ee8816b6ff3dd --- /dev/null +++ b/go.toolchain.next.rev @@ -0,0 +1 @@ +07d023ba9bb6d17a84b492f1524fabfa69a31bda diff --git a/pull-toolchain.sh b/pull-toolchain.sh index eb8febf6bb32d..b10e3cd68cf11 100755 --- a/pull-toolchain.sh +++ b/pull-toolchain.sh @@ -1,20 +1,33 @@ #!/bin/sh # Retrieve the latest Go toolchain. +# Set TS_GO_NEXT=1 to update go.toolchain.next.rev instead. # set -eu cd "$(dirname "$0")" -read -r go_branch go.toolchain.rev + echo "$upstream" >"$go_toolchain_rev_file" fi -./tool/go version 2>/dev/null | awk '{print $3}' | sed 's/^go//' > go.toolchain.version - -./update-flake.sh +# Only update go.toolchain.version and go.toolchain.rev.sri for the main toolchain, +# skipping it if TS_GO_NEXT=1. Those two files are only used by Nix, and as of 2026-01-26 +# don't yet support TS_GO_NEXT=1 with flake.nix or in our corp CI. +if [ "${TS_GO_NEXT:-}" != "1" ]; then + ./tool/go version 2>/dev/null | awk '{print $3}' | sed 's/^go//' > go.toolchain.version + ./update-flake.sh +fi -if [ -n "$(git diff-index --name-only HEAD -- go.toolchain.rev go.toolchain.rev.sri go.toolchain.version)" ]; then +if [ -n "$(git diff-index --name-only HEAD -- "$go_toolchain_rev_file" go.toolchain.rev.sri go.toolchain.version)" ]; then echo "pull-toolchain.sh: changes imported. Use git commit to make them permanent." >&2 fi diff --git a/tool/gocross/gocross-wrapper.sh b/tool/gocross/gocross-wrapper.sh index 352d639b75530..05a35ba424cc2 100755 --- a/tool/gocross/gocross-wrapper.sh +++ b/tool/gocross/gocross-wrapper.sh @@ -4,7 +4,7 @@ # # gocross-wrapper.sh is a wrapper that can be aliased to 'go', which # transparently runs the version of github.com/tailscale/go as specified repo's -# go.toolchain.rev file. +# go.toolchain.rev file (or go.toolchain.next.rev if TS_GO_NEXT=1). # # It also conditionally (if TS_USE_GOCROSS=1) builds gocross and uses it as a go # wrapper to inject certain go flags. @@ -21,6 +21,12 @@ if [[ "${OSTYPE:-}" == "cygwin" || "${OSTYPE:-}" == "msys" ]]; then exit fi +if [[ "${TS_GO_NEXT:-}" == "1" ]]; then + go_toolchain_rev_file="go.toolchain.next.rev" +else + go_toolchain_rev_file="go.toolchain.rev" +fi + # Locate a bootstrap toolchain and (re)build gocross if necessary. We run all of # this in a subshell because posix shell semantics make it very easy to # accidentally mutate the input environment that will get passed to gocross at @@ -45,7 +51,7 @@ cd "$repo_root" # https://github.com/tailscale/go release artifact to download. toolchain="" -read -r REV Date: Tue, 27 Jan 2026 14:44:32 -0800 Subject: [PATCH 034/202] cmd/printdep: add --next flag to use rc Go build hash instead Updates tailscale/corp#36382 Change-Id: Ib7474b0aab901e98f0fe22761e26fd181650743c Signed-off-by: Brad Fitzpatrick --- assert_ts_toolchain_match.go | 3 +++ cmd/printdep/printdep.go | 9 +++++++-- version-embed.go | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/assert_ts_toolchain_match.go b/assert_ts_toolchain_match.go index f0760ec039414..901dbb8ec83a1 100644 --- a/assert_ts_toolchain_match.go +++ b/assert_ts_toolchain_match.go @@ -17,6 +17,9 @@ func init() { panic("binary built with tailscale_go build tag but failed to read build info or find tailscale.toolchain.rev in build info") } want := strings.TrimSpace(GoToolchainRev) + if os.Getenv("TS_GO_NEXT") == "1" { + want = strings.TrimSpace(GoToolchainNextRev) + } if tsRev != want { if os.Getenv("TS_PERMIT_TOOLCHAIN_MISMATCH") == "1" { fmt.Fprintf(os.Stderr, "tailscale.toolchain.rev = %q, want %q; but ignoring due to TS_PERMIT_TOOLCHAIN_MISMATCH=1\n", tsRev, want) diff --git a/cmd/printdep/printdep.go b/cmd/printdep/printdep.go index c4ba5b79a3357..f5aeab7a561b6 100644 --- a/cmd/printdep/printdep.go +++ b/cmd/printdep/printdep.go @@ -19,6 +19,7 @@ var ( goToolchain = flag.Bool("go", false, "print the supported Go toolchain git hash (a github.com/tailscale/go commit)") goToolchainURL = flag.Bool("go-url", false, "print the URL to the tarball of the Tailscale Go toolchain") alpine = flag.Bool("alpine", false, "print the tag of alpine docker image") + next = flag.Bool("next", false, "if set, modifies --go or --go-url to use the upcoming/unreleased/rc Go release version instead") ) func main() { @@ -27,8 +28,12 @@ func main() { fmt.Println(strings.TrimSpace(ts.AlpineDockerTag)) return } + goRev := strings.TrimSpace(ts.GoToolchainRev) + if *next { + goRev = strings.TrimSpace(ts.GoToolchainNextRev) + } if *goToolchain { - fmt.Println(strings.TrimSpace(ts.GoToolchainRev)) + fmt.Println(goRev) } if *goToolchainURL { switch runtime.GOOS { @@ -36,6 +41,6 @@ func main() { default: log.Fatalf("unsupported GOOS %q", runtime.GOOS) } - fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s-%s.tar.gz\n", strings.TrimSpace(ts.GoToolchainRev), runtime.GOOS, runtime.GOARCH) + fmt.Printf("https://github.com/tailscale/go/releases/download/build-%s/%s-%s.tar.gz\n", goRev, runtime.GOOS, runtime.GOARCH) } } diff --git a/version-embed.go b/version-embed.go index 9f48d1384ff67..c368186ab3a7f 100644 --- a/version-embed.go +++ b/version-embed.go @@ -26,6 +26,12 @@ var AlpineDockerTag string //go:embed go.toolchain.rev var GoToolchainRev string +// GoToolchainNextRev is like GoToolchainRev, but when using the +// "go.toolchain.next.rev" when TS_GO_NEXT=1 is set in the environment. +// +//go:embed go.toolchain.next.rev +var GoToolchainNextRev string + //lint:ignore U1000 used by tests + assert_ts_toolchain_match.go w/ right build tags func tailscaleToolchainRev() (gitHash string, ok bool) { bi, ok := debug.ReadBuildInfo() From d7d12761ba8c9fc029ef4fae5e5644eb6cdae2d7 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Tue, 27 Jan 2026 16:15:17 -0800 Subject: [PATCH 035/202] Add .stignore for syncthing (#18540) This symlink tells synchting to ignore stuff that's in .gitignore. Updates https://github.com/tailscale/corp/issues/36250 Signed-off-by: Andrew Lytvynov --- .gitignore | 3 +++ .stignore | 1 + 2 files changed, 4 insertions(+) create mode 120000 .stignore diff --git a/.gitignore b/.gitignore index 3941fd06ef6d5..4bfabc80f0415 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ client/web/build/assets # Ignore personal IntelliJ settings .idea/ + +# Ignore syncthing state directory. +/.stfolder diff --git a/.stignore b/.stignore new file mode 120000 index 0000000000000..3e4e48b0b5fe6 --- /dev/null +++ b/.stignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file From 72f736134d741b5825b8952a1e33f37a79e4acfb Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 28 Jan 2026 08:41:38 -0800 Subject: [PATCH 036/202] cmd/testwrapper/flakytest: skip flaky tests if TS_SKIP_FLAKY_TESTS set This is for a future test scheduler, so it can run potentially flaky tests separately, doing all the non-flaky ones together in one batch. Updates tailscale/corp#28679 Change-Id: Ic4a11f9bf394528ef75792fd622f17bc01a4ec8a Signed-off-by: Brad Fitzpatrick --- cmd/testwrapper/flakytest/flakytest.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmd/testwrapper/flakytest/flakytest.go b/cmd/testwrapper/flakytest/flakytest.go index b98d739c63620..5e1591e817e00 100644 --- a/cmd/testwrapper/flakytest/flakytest.go +++ b/cmd/testwrapper/flakytest/flakytest.go @@ -11,6 +11,7 @@ import ( "os" "path" "regexp" + "strconv" "sync" "testing" @@ -60,6 +61,10 @@ func Mark(t testing.TB, issue string) { // And then remove this Logf a month or so after that. t.Logf("flakytest: issue tracking this flaky test: %s", issue) + if boolEnv("TS_SKIP_FLAKY_TESTS") { + t.Skipf("skipping due to TS_SKIP_FLAKY_TESTS") + } + // Record the root test name as flakey. rootFlakesMu.Lock() defer rootFlakesMu.Unlock() @@ -80,3 +85,12 @@ func Marked(t testing.TB) bool { } return false } + +func boolEnv(k string) bool { + s := os.Getenv(k) + if s == "" { + return false + } + v, _ := strconv.ParseBool(s) + return v +} From aca1b5da0f91729c6cde1d634ef65a4f7f74d278 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 28 Jan 2026 10:12:32 -0800 Subject: [PATCH 037/202] go.toolchain.rev: bump for cmd/go caching work This pulls in tailscale/go#151, which we want to begin experimenting with. Updates tailscale/go#150 Change-Id: I69aa2631ecf36356430969f423ea3943643a144a Signed-off-by: Brad Fitzpatrick --- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.toolchain.rev b/go.toolchain.rev index dbf37cef1af47..db7deab6f2baa 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -0c028efa1dac96fbb046b793877061645d01ed74 +485c68998494d1343d75389bd493d4dca20df644 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 26fe3501b5d26..3e92fc65dd7e0 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-1AG7yXAbDsBdKUNe5FQ45YXWJ3eLekD4t9mwKrqxiOY= +sha256-JXvC+IS+n+0y7gWDKUv1iAO+6ihm9tviNqyHpSK3cGs= From 99584b26aee31597d40c9e6e1949ef23cef83e13 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Wed, 28 Jan 2026 14:32:40 -0800 Subject: [PATCH 038/202] ipn/ipnlocal/netmapcache: report the correct error for a missing column (#18547) The file-based cache implementation was not reporting the correct error when attempting to load a missing column key. Make it do so, and update the tests to cover that case. Updates #12639 Change-Id: Ie2c45a0a7e528d4125f857859c92df807116a56e Signed-off-by: M. J. Fromberger --- ipn/ipnlocal/netmapcache/netmapcache.go | 6 +- ipn/ipnlocal/netmapcache/netmapcache_test.go | 64 ++++++++++++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/ipn/ipnlocal/netmapcache/netmapcache.go b/ipn/ipnlocal/netmapcache/netmapcache.go index 6992e0691f125..d5706f9b773ac 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache.go +++ b/ipn/ipnlocal/netmapcache/netmapcache.go @@ -155,7 +155,11 @@ func (s FileStore) List(ctx context.Context, prefix string) iter.Seq2[string, er // Load implements part of the [Store] interface. func (s FileStore) Load(ctx context.Context, key string) ([]byte, error) { - return os.ReadFile(filepath.Join(string(s), hex.EncodeToString([]byte(key)))) + data, err := os.ReadFile(filepath.Join(string(s), hex.EncodeToString([]byte(key)))) + if errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("key %q not found: %w", key, ErrKeyNotFound) + } + return data, err } // Store implements part of the [Store] interface. diff --git a/ipn/ipnlocal/netmapcache/netmapcache_test.go b/ipn/ipnlocal/netmapcache/netmapcache_test.go index 1f7d9b3bf6f07..437015ccc53e8 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache_test.go +++ b/ipn/ipnlocal/netmapcache/netmapcache_test.go @@ -5,6 +5,7 @@ package netmapcache_test import ( "context" + jsonv1 "encoding/json" "errors" "flag" "fmt" @@ -174,11 +175,7 @@ func TestRoundTrip(t *testing.T) { t.Error("Cached map is not marked as such") } - opts := []cmp.Option{ - cmpopts.IgnoreFields(netmap.NetworkMap{}, skippedMapFields...), - cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}), - } - if diff := cmp.Diff(cmap, testMap, opts...); diff != "" { + if diff := diffNetMaps(cmap, testMap); diff != "" { t.Fatalf("Cached map differs (-got, +want):\n%s", diff) } @@ -262,6 +259,56 @@ func checkFieldCoverage(t *testing.T, nm *netmap.NetworkMap) { } } +func TestPartial(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + c := netmapcache.NewCache(make(testStore)) // empty + nm, err := c.Load(t.Context()) + if !errors.Is(err, netmapcache.ErrCacheNotAvailable) { + t.Errorf("Load empty cache: got %+v, %v; want %v", nm, err, netmapcache.ErrCacheNotAvailable) + } + }) + + t.Run("SelfOnly", func(t *testing.T) { + self := (&tailcfg.Node{ + ID: 24680, + StableID: "u24680FAKE", + User: 6174, + Name: "test.example.com.", + Key: testNodeKey, + }).View() + + // A cached netmap must at least have a self node to be loaded without error, + // but other parts can be omitted without error. + // + // Set up a cache store with only the self node populated, and verify we + // can load that back into something with the right shape. + data, err := jsonv1.Marshal(struct { + Node tailcfg.NodeView + }{Node: self}) + if err != nil { + t.Fatalf("Marshal test node: %v", err) + } + + s := netmapcache.FileStore(t.TempDir()) + if err := s.Store(t.Context(), "self", data); err != nil { + t.Fatalf("Write test cache: %v", err) + } + + c := netmapcache.NewCache(s) + got, err := c.Load(t.Context()) + if err != nil { + t.Fatalf("Load cached netmap: %v", err) + } + if diff := diffNetMaps(got, &netmap.NetworkMap{ + Cached: true, // because we loaded it + SelfNode: self, // what we originally stored + NodeKey: testNodeKey, // the self-related field is populated + }); diff != "" { + t.Errorf("Cached map differs (-got, +want):\n%s", diff) + } + }) +} + // testStore is an in-memory implementation of the [netmapcache.Store] interface. type testStore map[string][]byte @@ -296,3 +343,10 @@ func (t testStore) Store(_ context.Context, key string, value []byte) error { } func (t testStore) Remove(_ context.Context, key string) error { delete(t, key); return nil } + +func diffNetMaps(got, want *netmap.NetworkMap) string { + return cmp.Diff(got, want, + cmpopts.IgnoreFields(netmap.NetworkMap{}, skippedMapFields...), + cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}), + ) +} From e39a7305942e958ba4b9333cc2d3222023e33f0d Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 28 Jan 2026 14:52:10 -0800 Subject: [PATCH 039/202] go.toolchain.rev: bump for cmd/go caching work This pulls in tailscale/go#153, which we want to begin experimenting with. Updates tailscale/go#150 Change-Id: Id3e03558ee69e74361431650530e8227dfdef978 Signed-off-by: Brad Fitzpatrick --- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.toolchain.rev b/go.toolchain.rev index db7deab6f2baa..930ed5ad251a9 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -485c68998494d1343d75389bd493d4dca20df644 +799b25336eeb52e2f8b4521fba5870c2ad2d9f43 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 3e92fc65dd7e0..ae95ed0ff869d 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-JXvC+IS+n+0y7gWDKUv1iAO+6ihm9tviNqyHpSK3cGs= +sha256-27ymqBnopujAGo02TZ5IPX8bVkp+rLTuVSn/QzZufJc= From 9e7f536a7c145fd54a36e508b272d0312c8b7dad Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 28 Jan 2026 16:39:26 -0800 Subject: [PATCH 040/202] cmd/testwrapper: show "(cached)" for packages that hit the cache We weren't parsing that out previously, making it look like tests were re-running even though they were cached. Updates tailscale/go#150 Updates tailscale/corp#28679 Updates tailscale/corp#34696 Change-Id: I6254362852a82ccc86ac464a805379d941408dad Signed-off-by: Brad Fitzpatrick --- cmd/testwrapper/testwrapper.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cmd/testwrapper/testwrapper.go b/cmd/testwrapper/testwrapper.go index df10a53bc1a14..d9d3cc7db60a7 100644 --- a/cmd/testwrapper/testwrapper.go +++ b/cmd/testwrapper/testwrapper.go @@ -36,6 +36,7 @@ type testAttempt struct { pkg string // "tailscale.com/types/key" testName string // "TestFoo" outcome string // "pass", "fail", "skip" + cached bool // whether package-level (non-testName specific) was pass due to being cached logs bytes.Buffer start, end time.Time isMarkedFlaky bool // set if the test is marked as flaky @@ -108,6 +109,8 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te os.Exit(1) } + pkgCached := map[string]bool{} + s := bufio.NewScanner(r) resultMap := make(map[string]map[string]*testAttempt) // pkg -> test -> testAttempt for s.Scan() { @@ -127,6 +130,9 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te resultMap[pkg] = pkgTests } if goOutput.Test == "" { + if strings.HasSuffix(goOutput.Output, "\t(cached)\n") && goOutput.Package != "" { + pkgCached[goOutput.Package] = true + } switch goOutput.Action { case "start": pkgTests[""].start = goOutput.Time @@ -151,6 +157,7 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te end: goOutput.Time, logs: pkgTests[""].logs, pkgFinished: true, + cached: pkgCached[goOutput.Package], } case "output": // Capture all output from the package except for the final @@ -235,7 +242,7 @@ func main() { firstRun.tests = append(firstRun.tests, &packageTests{Pattern: pkg}) } toRun := []*nextRun{firstRun} - printPkgOutcome := func(pkg, outcome string, attempt int, runtime time.Duration) { + printPkgOutcome := func(pkg, outcome string, cached bool, attempt int, testDur time.Duration) { if pkg == "" { return // We reach this path on a build error. } @@ -250,10 +257,16 @@ func main() { outcome = "FAIL" } if attempt > 1 { - fmt.Printf("%s\t%s\t%.3fs\t[attempt=%d]\n", outcome, pkg, runtime.Seconds(), attempt) + fmt.Printf("%s\t%s\t%.3fs\t[attempt=%d]\n", outcome, pkg, testDur.Seconds(), attempt) return } - fmt.Printf("%s\t%s\t%.3fs\n", outcome, pkg, runtime.Seconds()) + var lastCol string + if cached { + lastCol = "(cached)" + } else { + lastCol = fmt.Sprintf("%.3f", testDur.Seconds()) + } + fmt.Printf("%s\t%s\t%v\n", outcome, pkg, lastCol) } for len(toRun) > 0 { @@ -300,7 +313,7 @@ func main() { // panics outside tests will be printed io.Copy(os.Stdout, &tr.logs) } - printPkgOutcome(tr.pkg, tr.outcome, thisRun.attempt, tr.end.Sub(tr.start)) + printPkgOutcome(tr.pkg, tr.outcome, tr.cached, thisRun.attempt, tr.end.Sub(tr.start)) continue } if testingVerbose || tr.outcome == "fail" { From 6f55309f348bc545b80ddf036a5cb1cac86a719b Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Wed, 28 Jan 2026 18:28:25 -0800 Subject: [PATCH 041/202] logtail/filch: fix panic in concurrent file access (#18555) In the event of multiple Filch intances being backed by the same file, it is possible that concurrent rotateLocked calls occur. One operation might clear the file, resulting in another skipping the call to resetReadBuffer, resulting in a later panic because the read index is invalid. To at least avoid the panic, always call resetReadBuffer. Note that the behavior of Filch is undefined when using the same file. While this avoids the panic, we may still experience data corruption or less. Fixes #18552 Signed-off-by: Joe Tsai --- logtail/filch/filch.go | 4 ++-- logtail/filch/filch_test.go | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/logtail/filch/filch.go b/logtail/filch/filch.go index 32b0b88b15990..1bd82d8c41e8a 100644 --- a/logtail/filch/filch.go +++ b/logtail/filch/filch.go @@ -316,7 +316,7 @@ func waitIdleStderrForTest() { // No data should be lost under this condition. // // - The writer exceeded a limit for f.newer. -// Data may be lost under this cxondition. +// Data may be lost under this condition. func (f *Filch) rotateLocked() error { f.rotateCalls.Add(1) @@ -329,7 +329,6 @@ func (f *Filch) rotateLocked() error { rdPos := pos - int64(len(f.unreadReadBuffer())) // adjust for data already read into the read buffer f.droppedBytes.Add(max(0, fi.Size()-rdPos)) } - f.resetReadBuffer() // Truncate the older file and write relative to the start. if err := f.older.Truncate(0); err != nil { @@ -339,6 +338,7 @@ func (f *Filch) rotateLocked() error { return err } } + f.resetReadBuffer() // Swap newer and older. f.newer, f.older = f.older, f.newer diff --git a/logtail/filch/filch_test.go b/logtail/filch/filch_test.go index 0975a2d11f8a3..3c7ba03ca3358 100644 --- a/logtail/filch/filch_test.go +++ b/logtail/filch/filch_test.go @@ -381,3 +381,26 @@ func testMaxFileSize(t *testing.T, replaceStderr bool) { t.Errorf("readBytes = %v, want %v", f.readBytes.Value(), readBytes) } } + +// TestConcurrentSameFile tests that concurrent Filch operations on the same +// set of log files does not result in a panic. +// The exact behavior is undefined, but we should at least avoid a panic. +func TestConcurrentSameFile(t *testing.T) { + filePrefix := filepath.Join(t.TempDir(), "testlog") + f1 := must.Get(New(filePrefix, Options{MaxFileSize: 1000})) + f2 := must.Get(New(filePrefix, Options{MaxFileSize: 1000})) + var group sync.WaitGroup + for _, f := range []*Filch{f1, f2} { + group.Go(func() { + for range 1000 { + for range rand.IntN(10) { + f.Write([]byte("hello, world")) + } + for range rand.IntN(10) { + f.TryReadLine() + } + } + }) + } + group.Wait() +} From 2d2d5e6cc7ab097c43516c5db4372cec8b63c81a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 28 Jan 2026 17:04:50 -0800 Subject: [PATCH 042/202] .github/workflows: set CMD_GO_USE_GIT_HASH=true for our cmd/go Updates tailscale/go#150 Updates tailscale/corp#28679 Change-Id: Ieb4780f157451f5c6660c96c6efaec9ddcfcb415 Signed-off-by: Brad Fitzpatrick --- .github/workflows/test.yml | 1 + .github/workflows/vet.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e99e75b22f8a6..a6906e53ef680 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ env: # toplevel directories "src" (for the checked out source code), and "gomodcache" # and other caches as siblings to follow. GOMODCACHE: ${{ github.workspace }}/gomodcache + CMD_GO_USE_GIT_HASH: "true" on: push: diff --git a/.github/workflows/vet.yml b/.github/workflows/vet.yml index b7862889daa7f..c85e3ec86a67f 100644 --- a/.github/workflows/vet.yml +++ b/.github/workflows/vet.yml @@ -6,6 +6,7 @@ env: # toplevel directories "src" (for the checked out source code), and "gomodcache" # and other caches as siblings to follow. GOMODCACHE: ${{ github.workspace }}/gomodcache + CMD_GO_USE_GIT_HASH: "true" on: push: From afc90ce804e0f0684d3887c1bcf56498ede399a9 Mon Sep 17 00:00:00 2001 From: Paul Scott <408401+icio@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:45:13 +0000 Subject: [PATCH 043/202] control/controlclient: add PersistView.Valid() check in NetmapFromMapResponseForDebug (#17878) We were seeing some panics from nodes: panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xd42570] goroutine 362555 [running]: tailscale.com/types/persist.PersistView.PrivateNodeKey(...) tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/types/persist/persist_view.go:89 tailscale.com/control/controlclient.NetmapFromMapResponseForDebug({0x1bac2e0, 0xc0a8692380}, {0xc0de5da0c0?}, 0xc0de66fd40) tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/control/controlclient/direct.go:1175 +0x90 tailscale.com/ipn/ipnlocal.handleC2NDebugNetMap(0xc0b3f5af08, {0x1baa520, 0xc0a887b0c0}, 0xc0a869a280) tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/ipn/ipnlocal/c2n.go:186 +0x405 tailscale.com/ipn/ipnlocal.(*LocalBackend).handleC2N(0xc0b3f5af08, {0x1baa520, 0xc0a887b0c0}, 0xc0a869a280) tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/ipn/ipnlocal/c2n.go:121 +0x155 net/http.HandlerFunc.ServeHTTP(0x1bac150?, {0x1baa520?, 0xc0a887b0c0?}, 0xc049d47b20?) net/http/server.go:2322 +0x29 tailscale.com/control/controlclient.answerC2NPing(0xc0d9808f20, {0x1b90f40, 0xc0c3bd0db0}, 0xc0b1c84ea0, 0xc0a29b3c80) tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/control/controlclient/direct.go:1454 +0x455 tailscale.com/control/controlclient.(*Direct).answerPing(0xc09b173b88, 0xc0a29b3c80) tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/control/controlclient/direct.go:1398 +0x127 created by tailscale.com/control/controlclient.(*Direct).sendMapRequest in goroutine 361922 tailscale.com@v1.89.0-pre.0.20250926180200-7cbf56345bb3/control/controlclient/direct.go:1104 +0x20e5 Updates tailscale/corp#31367 Updates tailscale/corp#32095 Signed-off-by: Paul Scott <408401+icio@users.noreply.github.com> --- control/controlclient/direct.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index eb49cf4ab44fb..a368d6f858384 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -1230,6 +1230,9 @@ func NetmapFromMapResponseForDebug(ctx context.Context, pr persist.PersistView, if resp.Node == nil { return nil, errors.New("MapResponse lacks Node") } + if !pr.Valid() { + return nil, errors.New("PersistView invalid") + } nu := &rememberLastNetmapUpdater{} sess := newMapSession(pr.PrivateNodeKey(), nu, nil) From ce5c08e4cbdb03b9652e15142e4a596e1f054ef1 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Thu, 29 Jan 2026 16:09:19 +0000 Subject: [PATCH 044/202] cmd/testwrapper: detect cached tests with coverage output (#18559) Using -coverprofile was breaking the (cached) detection logic because that adds extra information to the end of the line. Updates tailscale/go#150 Change-Id: Ie1bf4e1e04e21db00a6829695098fb61d80a2641 Signed-off-by: Tom Proctor --- cmd/testwrapper/testwrapper.go | 5 ++- cmd/testwrapper/testwrapper_test.go | 58 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/cmd/testwrapper/testwrapper.go b/cmd/testwrapper/testwrapper.go index d9d3cc7db60a7..e35b83407bbb8 100644 --- a/cmd/testwrapper/testwrapper.go +++ b/cmd/testwrapper/testwrapper.go @@ -130,7 +130,10 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, goTestArgs, te resultMap[pkg] = pkgTests } if goOutput.Test == "" { - if strings.HasSuffix(goOutput.Output, "\t(cached)\n") && goOutput.Package != "" { + // Detect output lines like: + // ok \ttailscale.com/cmd/testwrapper\t(cached) + // ok \ttailscale.com/cmd/testwrapper\t(cached)\tcoverage: 17.0% of statements + if goOutput.Package != "" && strings.Contains(goOutput.Output, fmt.Sprintf("%s\t(cached)", goOutput.Package)) { pkgCached[goOutput.Package] = true } switch goOutput.Action { diff --git a/cmd/testwrapper/testwrapper_test.go b/cmd/testwrapper/testwrapper_test.go index 0ca13e854ff7a..cf023f4367483 100644 --- a/cmd/testwrapper/testwrapper_test.go +++ b/cmd/testwrapper/testwrapper_test.go @@ -11,6 +11,7 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strings" "sync" "testing" @@ -214,6 +215,63 @@ func TestTimeout(t *testing.T) { } } +func TestCached(t *testing.T) { + t.Parallel() + + // Construct our trivial package. + pkgDir := t.TempDir() + goMod := fmt.Sprintf(`module example.com + +go %s +`, runtime.Version()[2:]) // strip leading "go" + + test := `package main +import "testing" + +func TestCached(t *testing.T) {} +` + + for f, c := range map[string]string{ + "go.mod": goMod, + "cached_test.go": test, + } { + err := os.WriteFile(filepath.Join(pkgDir, f), []byte(c), 0o644) + if err != nil { + t.Fatalf("writing package: %s", err) + } + } + + for name, args := range map[string][]string{ + "without_flags": {"./..."}, + "with_short": {"./...", "-short"}, + "with_coverprofile": {"./...", "-coverprofile=" + filepath.Join(t.TempDir(), "coverage.out")}, + } { + t.Run(name, func(t *testing.T) { + var ( + out []byte + err error + ) + for range 2 { + cmd := cmdTestwrapper(t, args...) + cmd.Dir = pkgDir + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("testwrapper ./...: expected no error but got: %v; output was:\n%s", err, out) + } + } + + want := []byte("ok\texample.com\t(cached)") + if !bytes.Contains(out, want) { + t.Fatalf("wanted output containing %q but got:\n%s", want, out) + } + + if testing.Verbose() { + t.Logf("success - output:\n%s", out) + } + }) + } +} + func errExitCode(err error) (int, bool) { var exit *exec.ExitError if errors.As(err, &exit) { From 65d6793204893983e89824797253e349ff114558 Mon Sep 17 00:00:00 2001 From: License Updater Date: Thu, 29 Jan 2026 17:21:00 +0000 Subject: [PATCH 045/202] licenses: update license notices Signed-off-by: License Updater --- licenses/apple.md | 10 +++++----- licenses/windows.md | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/licenses/apple.md b/licenses/apple.md index d51d67190b1fa..f61291c943cb8 100644 --- a/licenses/apple.md +++ b/licenses/apple.md @@ -70,13 +70,13 @@ See also the dependencies in the [Tailscale CLI][]. - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/df929982:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) + - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a4bb9ffd:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.49.0:LICENSE)) - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) - - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) + - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) diff --git a/licenses/windows.md b/licenses/windows.md index 902d0f2a1f5a8..03d0ce40ef717 100644 --- a/licenses/windows.md +++ b/licenses/windows.md @@ -39,7 +39,7 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE)) - [github.com/prometheus/client_golang/prometheus](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus) ([Apache-2.0](https://github.com/prometheus/client_golang/blob/v1.23.2/LICENSE)) - [github.com/prometheus/client_model/go](https://pkg.go.dev/github.com/prometheus/client_model/go) ([Apache-2.0](https://github.com/prometheus/client_model/blob/v0.6.2/LICENSE)) - - [github.com/prometheus/common](https://pkg.go.dev/github.com/prometheus/common) ([Apache-2.0](https://github.com/prometheus/common/blob/v0.66.1/LICENSE)) + - [github.com/prometheus/common](https://pkg.go.dev/github.com/prometheus/common) ([Apache-2.0](https://github.com/prometheus/common/blob/v0.67.5/LICENSE)) - [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE)) - [github.com/tailscale/go-winio](https://pkg.go.dev/github.com/tailscale/go-winio) ([MIT](https://github.com/tailscale/go-winio/blob/c4f33415bf55/LICENSE)) - [github.com/tailscale/hujson](https://pkg.go.dev/github.com/tailscale/hujson) ([BSD-3-Clause](https://github.com/tailscale/hujson/blob/992244df8c5a/LICENSE)) @@ -48,17 +48,17 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/tailscale/xnet/webdav](https://pkg.go.dev/github.com/tailscale/xnet/webdav) ([BSD-3-Clause](https://github.com/tailscale/xnet/blob/8497ac4dab2e/LICENSE)) - [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.1/LICENSE)) - [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE)) - - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.2/LICENSE)) + - [go.yaml.in/yaml/v2](https://pkg.go.dev/go.yaml.in/yaml/v2) ([Apache-2.0](https://github.com/yaml/go-yaml/blob/v2.4.3/LICENSE)) - [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/ae6ca9944745/LICENSE)) - [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/fdeea329fbba/LICENSE)) - - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE)) - - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/df929982:LICENSE)) + - [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE)) + - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/a4bb9ffd:LICENSE)) - [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.27.0:LICENSE)) - - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.30.0:LICENSE)) - - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) + - [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.32.0:LICENSE)) + - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.49.0:LICENSE)) - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) - - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) + - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) - [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2)) - [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3)) - [google.golang.org/protobuf](https://pkg.go.dev/google.golang.org/protobuf) ([BSD-3-Clause](https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE)) From bcceef36825278a7406dd38d2832f20540d698a0 Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Wed, 14 Jan 2026 02:29:06 -0500 Subject: [PATCH 046/202] cmd/tailscale/cli: allow fetching keys from AWS Parameter Store This allows fetching auth keys, OAuth client secrets, and ID tokens (for workload identity federation) from AWS Parameter Store by passing an ARN as the value. This is a relatively low-overhead mechanism for fetching these values from an external secret store without needing to run a secret service. Usage examples: # Auth key tailscale up \ --auth-key=arn:aws:ssm:us-east-1:123456789012:parameter/tailscale/auth-key # OAuth client secret tailscale up \ --client-secret=arn:aws:ssm:us-east-1:123456789012:parameter/tailscale/oauth-secret \ --advertise-tags=tag:server # ID token (for workload identity federation) tailscale up \ --client-id=my-client \ --id-token=arn:aws:ssm:us-east-1:123456789012:parameter/tailscale/id-token \ --advertise-tags=tag:server Updates tailscale/corp#28792 Signed-off-by: Andrew Dunham --- cmd/tailscale/cli/cli_test.go | 76 ++++++++++++++++ cmd/tailscale/cli/up.go | 42 +++++++-- cmd/tailscale/depaware.txt | 14 ++- cmd/tailscaled/depaware-minbox.txt | 1 + feature/awsparamstore/awsparamstore.go | 88 +++++++++++++++++++ feature/awsparamstore/awsparamstore_test.go | 83 +++++++++++++++++ feature/condregister/awsparamstore/doc.go | 6 ++ .../awsparamstore/maybe_awsparamstore.go | 8 ++ internal/client/tailscale/awsparamstore.go | 21 +++++ 9 files changed, 327 insertions(+), 12 deletions(-) create mode 100644 feature/awsparamstore/awsparamstore.go create mode 100644 feature/awsparamstore/awsparamstore_test.go create mode 100644 feature/condregister/awsparamstore/doc.go create mode 100644 feature/condregister/awsparamstore/maybe_awsparamstore.go create mode 100644 internal/client/tailscale/awsparamstore.go diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 41824701df551..370b730af8f35 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -6,11 +6,14 @@ package cli import ( "bytes" stdcmp "cmp" + "context" "encoding/json" "flag" "fmt" "io" "net/netip" + "os" + "path/filepath" "reflect" "strings" "testing" @@ -20,6 +23,7 @@ import ( "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/envknob" "tailscale.com/health/healthmsg" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" @@ -1696,6 +1700,78 @@ func TestDocs(t *testing.T) { walk(t, root) } +func TestUpResolves(t *testing.T) { + const testARN = "arn:aws:ssm:us-east-1:123456789012:parameter/my-parameter" + undo := tailscale.HookResolveValueFromParameterStore.SetForTest(func(_ context.Context, valueOrARN string) (string, error) { + if valueOrARN == testARN { + return "resolved-value", nil + } + return valueOrARN, nil + }) + defer undo() + + const content = "file-content" + fpath := filepath.Join(t.TempDir(), "testfile") + if err := os.WriteFile(fpath, []byte(content), 0600); err != nil { + t.Fatal(err) + } + + testCases := []struct { + name string + arg string + want string + }{ + {"parameter_store", testARN, "resolved-value"}, + {"file", "file:" + fpath, "file-content"}, + } + + for _, tt := range testCases { + t.Run(tt.name+"_auth_key", func(t *testing.T) { + args := upArgsT{authKeyOrFile: tt.arg} + got, err := args.getAuthKey(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + + t.Run(tt.name+"_client_secret", func(t *testing.T) { + args := upArgsT{clientSecretOrFile: tt.arg} + got, err := args.getClientSecret(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + + t.Run(tt.name+"_id_token", func(t *testing.T) { + args := upArgsT{idTokenOrFile: tt.arg} + got, err := args.getIDToken(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } + + t.Run("passthrough", func(t *testing.T) { + args := upArgsT{authKeyOrFile: "tskey-abcd1234"} + got, err := args.getAuthKey(t.Context()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != "tskey-abcd1234" { + t.Errorf("got %q, want %q", got, "tskey-abcd1234") + } + }) +} + func TestDeps(t *testing.T) { deptest.DepChecker{ GOOS: "linux", diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index cdb1d38234cec..79f7cc3f44a88 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -24,6 +24,7 @@ import ( shellquote "github.com/kballard/go-shellquote" "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/feature/buildfeatures" + _ "tailscale.com/feature/condregister/awsparamstore" _ "tailscale.com/feature/condregister/identityfederation" _ "tailscale.com/feature/condregister/oauthkey" "tailscale.com/health/healthmsg" @@ -220,16 +221,39 @@ func resolveValueFromFile(v string) (string, error) { return v, nil } -func (a upArgsT) getAuthKey() (string, error) { - return resolveValueFromFile(a.authKeyOrFile) +// resolveValueFromParameterStore resolves a value from AWS Parameter Store if +// the value looks like an SSM ARN. If the hook is not available or the value +// is not an SSM ARN, it returns the value unchanged. +func resolveValueFromParameterStore(ctx context.Context, v string) (string, error) { + if f, ok := tailscale.HookResolveValueFromParameterStore.GetOk(); ok { + return f(ctx, v) + } + return v, nil +} + +// resolveValue will take the given value (e.g. as passed to --auth-key), and +// depending on the prefix, resolve the value from either a file or AWS +// Parameter Store. Values with an unknown prefix are returned as-is. +func resolveValue(ctx context.Context, v string) (string, error) { + switch { + case strings.HasPrefix(v, "file:"): + return resolveValueFromFile(v) + case strings.HasPrefix(v, tailscale.ResolvePrefixAWSParameterStore): + return resolveValueFromParameterStore(ctx, v) + } + return v, nil +} + +func (a upArgsT) getAuthKey(ctx context.Context) (string, error) { + return resolveValue(ctx, a.authKeyOrFile) } -func (a upArgsT) getClientSecret() (string, error) { - return resolveValueFromFile(a.clientSecretOrFile) +func (a upArgsT) getClientSecret(ctx context.Context) (string, error) { + return resolveValue(ctx, a.clientSecretOrFile) } -func (a upArgsT) getIDToken() (string, error) { - return resolveValueFromFile(a.idTokenOrFile) +func (a upArgsT) getIDToken(ctx context.Context) (string, error) { + return resolveValue(ctx, a.idTokenOrFile) } var upArgsGlobal upArgsT @@ -602,7 +626,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE return err } - authKey, err := upArgs.getAuthKey() + authKey, err := upArgs.getAuthKey(ctx) if err != nil { return err } @@ -611,7 +635,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE if f, ok := tailscale.HookResolveAuthKey.GetOk(); ok { clientSecret := authKey // the authkey argument accepts client secrets, if both arguments are provided authkey has precedence if clientSecret == "" { - clientSecret, err = upArgs.getClientSecret() + clientSecret, err = upArgs.getClientSecret(ctx) if err != nil { return err } @@ -625,7 +649,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE // Try to resolve the auth key via workload identity federation if that functionality // is available and no auth key is yet determined. if f, ok := tailscale.HookResolveAuthKeyViaWIF.GetOk(); ok && authKey == "" { - idToken, err := upArgs.getIDToken() + idToken, err := upArgs.getIDToken(ctx) if err != nil { return err } diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 67ffa4fbc0fda..b148423750b97 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -11,6 +11,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy L github.com/atotto/clipboard from tailscale.com/client/systray github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/defaults+ + L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/feature/awsparamstore github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+ github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts @@ -21,7 +22,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4 from github.com/aws/aws-sdk-go-v2/aws/signer/v4 github.com/aws/aws-sdk-go-v2/aws/signer/v4 from github.com/aws/aws-sdk-go-v2/internal/auth/smithy+ github.com/aws/aws-sdk-go-v2/aws/transport/http from github.com/aws/aws-sdk-go-v2/config+ - github.com/aws/aws-sdk-go-v2/config from tailscale.com/wif + github.com/aws/aws-sdk-go-v2/config from tailscale.com/wif+ github.com/aws/aws-sdk-go-v2/credentials from github.com/aws/aws-sdk-go-v2/config github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds from github.com/aws/aws-sdk-go-v2/config github.com/aws/aws-sdk-go-v2/credentials/endpointcreds from github.com/aws/aws-sdk-go-v2/config @@ -49,6 +50,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding from github.com/aws/aws-sdk-go-v2/service/sts github.com/aws/aws-sdk-go-v2/service/internal/presigned-url from github.com/aws/aws-sdk-go-v2/service/sts + L github.com/aws/aws-sdk-go-v2/service/ssm from tailscale.com/feature/awsparamstore + L github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm + L github.com/aws/aws-sdk-go-v2/service/ssm/types from github.com/aws/aws-sdk-go-v2/service/ssm github.com/aws/aws-sdk-go-v2/service/sso from github.com/aws/aws-sdk-go-v2/config+ github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sso github.com/aws/aws-sdk-go-v2/service/sso/types from github.com/aws/aws-sdk-go-v2/service/sso @@ -65,7 +69,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/smithy-go/encoding from github.com/aws/smithy-go/encoding/json+ github.com/aws/smithy-go/encoding/httpbinding from github.com/aws/aws-sdk-go-v2/aws/protocol/query+ - github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssooidc + github.com/aws/smithy-go/encoding/json from github.com/aws/aws-sdk-go-v2/service/ssooidc+ github.com/aws/smithy-go/encoding/xml from github.com/aws/aws-sdk-go-v2/service/sts github.com/aws/smithy-go/endpoints from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/smithy-go/endpoints/private/rulesfn from github.com/aws/aws-sdk-go-v2/service/sts @@ -76,11 +80,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/aws/smithy-go/middleware from github.com/aws/aws-sdk-go-v2/aws+ github.com/aws/smithy-go/private/requestcompression from github.com/aws/aws-sdk-go-v2/config github.com/aws/smithy-go/ptr from github.com/aws/aws-sdk-go-v2/aws+ - github.com/aws/smithy-go/rand from github.com/aws/aws-sdk-go-v2/aws/middleware + github.com/aws/smithy-go/rand from github.com/aws/aws-sdk-go-v2/aws/middleware+ github.com/aws/smithy-go/time from github.com/aws/aws-sdk-go-v2/service/sso+ github.com/aws/smithy-go/tracing from github.com/aws/aws-sdk-go-v2/aws/middleware+ github.com/aws/smithy-go/transport/http from github.com/aws/aws-sdk-go-v2/aws+ github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http + L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm github.com/coder/websocket from tailscale.com/util/eventbus github.com/coder/websocket/internal/errd from github.com/coder/websocket github.com/coder/websocket/internal/util from github.com/coder/websocket @@ -112,6 +117,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/huin/goupnp/scpd from github.com/huin/goupnp github.com/huin/goupnp/soap from github.com/huin/goupnp+ github.com/huin/goupnp/ssdp from github.com/huin/goupnp + L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli @@ -168,8 +174,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/envknob from tailscale.com/client/local+ tailscale.com/envknob/featureknob from tailscale.com/client/web tailscale.com/feature from tailscale.com/tsweb+ + L tailscale.com/feature/awsparamstore from tailscale.com/feature/condregister/awsparamstore tailscale.com/feature/buildfeatures from tailscale.com/cmd/tailscale/cli+ tailscale.com/feature/capture/dissector from tailscale.com/cmd/tailscale/cli + tailscale.com/feature/condregister/awsparamstore from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/identityfederation from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/oauthkey from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/portmapper from tailscale.com/cmd/tailscale/cli diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 4b2f71983d441..083db4c5af1d4 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -73,6 +73,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/buildfeatures from tailscale.com/ipn/ipnlocal+ tailscale.com/feature/condlite/expvar from tailscale.com/wgengine/magicsock tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled + tailscale.com/feature/condregister/awsparamstore from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/identityfederation from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/oauthkey from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister+ diff --git a/feature/awsparamstore/awsparamstore.go b/feature/awsparamstore/awsparamstore.go new file mode 100644 index 0000000000000..f63f546ed70ed --- /dev/null +++ b/feature/awsparamstore/awsparamstore.go @@ -0,0 +1,88 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_aws + +// Package awsparamstore registers support for fetching secret values from AWS +// Parameter Store. +package awsparamstore + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ssm" + "tailscale.com/feature" + "tailscale.com/internal/client/tailscale" +) + +func init() { + feature.Register("awsparamstore") + tailscale.HookResolveValueFromParameterStore.Set(ResolveValue) +} + +// parseARN parses and verifies that the input string is an +// ARN for AWS Parameter Store, returning the region and parameter name if so. +// +// If the input is not a valid Parameter Store ARN, it returns ok==false. +func parseARN(s string) (region, parameterName string, ok bool) { + parsed, err := arn.Parse(s) + if err != nil { + return "", "", false + } + + if parsed.Service != "ssm" { + return "", "", false + } + parameterName, ok = strings.CutPrefix(parsed.Resource, "parameter/") + if !ok { + return "", "", false + } + + // NOTE: parameter names must have a leading slash + return parsed.Region, "/" + parameterName, true +} + +// ResolveValue fetches a value from AWS Parameter Store if the input +// looks like an SSM ARN (e.g., arn:aws:ssm:us-east-1:123456789012:parameter/my-secret). +// +// If the input is not a Parameter Store ARN, it returns the value unchanged. +// +// If the input is a Parameter Store ARN and fetching the parameter fails, it +// returns an error. +func ResolveValue(ctx context.Context, valueOrARN string) (string, error) { + // If it doesn't look like an ARN, return as-is + region, parameterName, ok := parseARN(valueOrARN) + if !ok { + return valueOrARN, nil + } + + // Load AWS config with the region from the ARN + cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) + if err != nil { + return "", fmt.Errorf("loading AWS config in region %q: %w", region, err) + } + + // Create SSM client and fetch the parameter + client := ssm.NewFromConfig(cfg) + output, err := client.GetParameter(ctx, &ssm.GetParameterInput{ + // The parameter to fetch. + Name: aws.String(parameterName), + + // If the parameter is a SecureString, decrypt it. + WithDecryption: aws.Bool(true), + }) + if err != nil { + return "", fmt.Errorf("getting SSM parameter %q: %w", parameterName, err) + } + + if output.Parameter == nil || output.Parameter.Value == nil { + return "", fmt.Errorf("SSM parameter %q has no value", parameterName) + } + + return strings.TrimSpace(*output.Parameter.Value), nil +} diff --git a/feature/awsparamstore/awsparamstore_test.go b/feature/awsparamstore/awsparamstore_test.go new file mode 100644 index 0000000000000..9ccea63ec11e1 --- /dev/null +++ b/feature/awsparamstore/awsparamstore_test.go @@ -0,0 +1,83 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_aws + +package awsparamstore + +import ( + "testing" +) + +func TestParseARN(t *testing.T) { + tests := []struct { + name string + input string + wantOk bool + wantRegion string + wantParamName string + }{ + { + name: "non-arn-passthrough", + input: "tskey-abcd1234", + wantOk: false, + }, + { + name: "file-prefix-passthrough", + input: "file:/path/to/key", + wantOk: false, + }, + { + name: "empty-passthrough", + input: "", + wantOk: false, + }, + { + name: "non-ssm-arn-passthrough", + input: "arn:aws:s3:::my-bucket", + wantOk: false, + }, + { + name: "invalid-arn-passthrough", + input: "arn:invalid", + wantOk: false, + }, + { + name: "arn-invalid-resource-passthrough", + input: "arn:aws:ssm:us-east-1:123456789012:document/myDoc", + wantOk: false, + }, + { + name: "valid-arn", + input: "arn:aws:ssm:us-west-2:123456789012:parameter/my-secret", + wantOk: true, + wantRegion: "us-west-2", + wantParamName: "/my-secret", + }, + { + name: "valid-arn-with-path", + input: "arn:aws:ssm:eu-central-1:123456789012:parameter/path/to/secret", + wantOk: true, + wantRegion: "eu-central-1", + wantParamName: "/path/to/secret", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRegion, gotParamName, gotOk := parseARN(tt.input) + if gotOk != tt.wantOk { + t.Errorf("parseARN(%q) got ok=%v, want %v", tt.input, gotOk, tt.wantOk) + } + if !tt.wantOk { + return + } + if gotRegion != tt.wantRegion { + t.Errorf("parseARN(%q) got region=%q, want %q", tt.input, gotRegion, tt.wantRegion) + } + if gotParamName != tt.wantParamName { + t.Errorf("parseARN(%q) got paramName=%q, want %q", tt.input, gotParamName, tt.wantParamName) + } + }) + } +} diff --git a/feature/condregister/awsparamstore/doc.go b/feature/condregister/awsparamstore/doc.go new file mode 100644 index 0000000000000..93a26e3c22fae --- /dev/null +++ b/feature/condregister/awsparamstore/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package awsparamstore conditionally registers the awsparamstore feature for +// resolving secrets from AWS Parameter Store. +package awsparamstore diff --git a/feature/condregister/awsparamstore/maybe_awsparamstore.go b/feature/condregister/awsparamstore/maybe_awsparamstore.go new file mode 100644 index 0000000000000..78c3f31006765 --- /dev/null +++ b/feature/condregister/awsparamstore/maybe_awsparamstore.go @@ -0,0 +1,8 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build (ts_aws || (linux && (arm64 || amd64) && !android)) && !ts_omit_aws + +package awsparamstore + +import _ "tailscale.com/feature/awsparamstore" diff --git a/internal/client/tailscale/awsparamstore.go b/internal/client/tailscale/awsparamstore.go new file mode 100644 index 0000000000000..bb0a31d45cc8a --- /dev/null +++ b/internal/client/tailscale/awsparamstore.go @@ -0,0 +1,21 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package tailscale + +import ( + "context" + + "tailscale.com/feature" +) + +// ResolvePrefixAWSParameterStore is the string prefix for values that can be +// resolved from AWS Parameter Store. +const ResolvePrefixAWSParameterStore = "arn:aws:ssm:" + +// HookResolveValueFromParameterStore resolves to [awsparamstore.ResolveValue] when +// the corresponding feature tag is enabled in the build process. +// +// It fetches a value from AWS Parameter Store given an ARN. If the provided +// value is not an Parameter Store ARN, it returns the value unchanged. +var HookResolveValueFromParameterStore feature.Hook[func(ctx context.Context, valueOrARN string) (string, error)] From db96e52d6f82e594f93eb44431a1b7fc732299be Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Fri, 30 Jan 2026 09:00:46 -0800 Subject: [PATCH 047/202] cmd/tailscale/cli: redact auth keys in FlagSet output (#18563) Running a command like `tailscale up --auth-key tskey-foo --auth-key tskey-bar` used to print ``` invalid value "tskey-bar" for flag -auth-key: flag provided multiple times ``` but now we print ``` invalid value "tskey-REDACTED" for flag -auth-key: flag provided multiple times ``` Fixes #18562 Signed-off-by: Andrew Lytvynov --- cmd/tailscale/cli/cli.go | 22 ++++++++++++++++++++++ cmd/tailscaled/depaware-minbox.txt | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 4d16cfe699537..b8ac768746d27 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -14,6 +14,7 @@ import ( "io" "log" "os" + "regexp" "runtime" "strings" "sync" @@ -294,6 +295,10 @@ change in the future. if w.UsageFunc == nil { w.UsageFunc = usageFunc } + if w.FlagSet != nil { + // If flags cannot be parsed, redact any keys in the error output . + w.FlagSet.SetOutput(sanitizeOutput(w.FlagSet.Output())) + } return true }) @@ -566,3 +571,20 @@ func fixTailscaledConnectError(origErr error) error { } return origErr } + +func sanitizeOutput(w io.Writer) io.Writer { + return sanitizeWriter{w} +} + +type sanitizeWriter struct { + w io.Writer +} + +var reTskey = regexp.MustCompile(`tskey-\w+`) + +func (w sanitizeWriter) Write(buf []byte) (int, error) { + sanitized := reTskey.ReplaceAll(buf, []byte("tskey-REDACTED")) + diff := len(buf) - len(sanitized) + n, err := w.w.Write(sanitized) + return n - diff, err +} diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 083db4c5af1d4..5121b56d0d281 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -428,7 +428,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de path from io/fs+ path/filepath from crypto/x509+ reflect from crypto/x509+ - regexp from tailscale.com/clientupdate + regexp from tailscale.com/clientupdate+ regexp/syntax from regexp runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ From 214b70cc1aeeee7205a22a25cce261de40e2c0d9 Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Fri, 30 Jan 2026 12:14:47 -0500 Subject: [PATCH 048/202] net/dns: skip DNS base config when using userspace networking (#18355) When tailscaled gets started with userspace networking, it won't modify your system's network configuration. For this, it creates a noopManager for DNS management. noopManager correctly observes that there's no real OS DNS to send queries to. This leads to we completely dropping any DNS internal resolution from `dns query` This change alters this so that even without a base config we'll still allow the internal resolver to handle internal DNS queries Fixes #18354 Signed-off-by: Fernando Serboncini --- net/dns/manager.go | 6 +++--- net/dns/noop.go | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/net/dns/manager.go b/net/dns/manager.go index 0b7ae465f59eb..0d74febffe7ca 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -388,9 +388,9 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig cfg, err := m.os.GetBaseConfig() if err == nil { baseCfg = &cfg - } else if isApple && err == ErrGetBaseConfigNotSupported { - // This is currently (2022-10-13) expected on certain iOS and macOS - // builds. + } else if (isApple || isNoopManager(m.os)) && err == ErrGetBaseConfigNotSupported { + // Expected when using noopManager (userspace networking) or on + // certain iOS/macOS builds. Continue without base config. } else { m.health.SetUnhealthy(osConfigurationReadWarnable, health.Args{health.ArgError: err.Error()}) return resolver.Config{}, OSConfig{}, err diff --git a/net/dns/noop.go b/net/dns/noop.go index 70dd93ed22220..aaf3a56ed68eb 100644 --- a/net/dns/noop.go +++ b/net/dns/noop.go @@ -15,3 +15,8 @@ func (m noopManager) GetBaseConfig() (OSConfig, error) { func NewNoopManager() (noopManager, error) { return noopManager{}, nil } + +func isNoopManager(c OSConfigurator) bool { + _, ok := c.(noopManager) + return ok +} From f48cd466624e06b2110eb6171c712eccfd0b4abe Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Fri, 30 Jan 2026 13:32:34 -0500 Subject: [PATCH 049/202] net/dns,ipn/ipnlocal: add nodecap to resolve subdomains (#18258) This adds a new node capability 'dns-subdomain-resolve' that signals that all of hosts' subdomains should resolve to the same IP address. It allows wildcard matching on any node marked with this capability. This change also includes an util/dnsname utility function that lets us access the parent of a full qualified domain name. MagicDNS takes this function and recursively searchs for a matching real node name. One important thing to observe is that, in this context, a subdomain can have multiple sub labels. This means that for a given node named machine, both my.machine and be.my.machine will be a positive match. Updates #1196 Signed-off-by: Fernando Serboncini --- ipn/ipnlocal/dnsconfig_test.go | 33 ++++++++++++++++++++++ ipn/ipnlocal/node_backend.go | 12 ++++++++ net/dns/config.go | 6 ++++ net/dns/dns_clone.go | 4 +++ net/dns/dns_view.go | 10 +++++++ net/dns/manager.go | 1 + net/dns/resolver/tsdns.go | 26 ++++++++++++++--- net/dns/resolver/tsdns_test.go | 51 ++++++++++++++++++++++++++++++++++ tailcfg/tailcfg.go | 7 +++++ util/dnsname/dnsname.go | 12 ++++++++ util/dnsname/dnsname_test.go | 28 +++++++++++++++++++ 11 files changed, 186 insertions(+), 4 deletions(-) diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index 594d2c5476177..ab00b47404216 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -106,6 +106,39 @@ func TestDNSConfigForNetmap(t *testing.T) { }, }, }, + { + name: "subdomain_resolve_capability", + nm: &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "myname.net.", + Addresses: ipps("100.101.101.101"), + }).View(), + AllCaps: set.SetOf([]tailcfg.NodeCapability{tailcfg.NodeAttrDNSSubdomainResolve}), + }, + peers: nodeViews([]*tailcfg.Node{ + { + ID: 1, + Name: "peer-with-cap.net.", + Addresses: ipps("100.102.0.1"), + CapMap: tailcfg.NodeCapMap{tailcfg.NodeAttrDNSSubdomainResolve: nil}, + }, + { + ID: 2, + Name: "peer-without-cap.net.", + Addresses: ipps("100.102.0.2"), + }, + }), + prefs: &ipn.Prefs{}, + want: &dns.Config{ + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netip.Addr{ + "myname.net.": ips("100.101.101.101"), + "peer-with-cap.net.": ips("100.102.0.1"), + "peer-without-cap.net.": ips("100.102.0.2"), + }, + SubdomainHosts: set.Of[dnsname.FQDN]("myname.net.", "peer-with-cap.net."), + }, + }, { // An ephemeral node with only an IPv6 address // should get IPv6 records for all its peers, diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index 4a32b14dd49dc..170dae9569c8c 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -751,8 +751,20 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. dcfg.Hosts[fqdn] = ips } set(nm.SelfName(), nm.GetAddresses()) + if nm.AllCaps.Contains(tailcfg.NodeAttrDNSSubdomainResolve) { + if fqdn, err := dnsname.ToFQDN(nm.SelfName()); err == nil { + dcfg.SubdomainHosts.Make() + dcfg.SubdomainHosts.Add(fqdn) + } + } for _, peer := range peers { set(peer.Name(), peer.Addresses()) + if peer.CapMap().Contains(tailcfg.NodeAttrDNSSubdomainResolve) { + if fqdn, err := dnsname.ToFQDN(peer.Name()); err == nil { + dcfg.SubdomainHosts.Make() + dcfg.SubdomainHosts.Add(fqdn) + } + } } for _, rec := range nm.DNS.ExtraRecords { switch rec.Type { diff --git a/net/dns/config.go b/net/dns/config.go index 2b5505fc9734c..f776d1af04443 100644 --- a/net/dns/config.go +++ b/net/dns/config.go @@ -21,6 +21,7 @@ import ( "tailscale.com/net/tsaddr" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) // Config is a DNS configuration. @@ -48,6 +49,11 @@ type Config struct { // it to resolve, you also need to add appropriate routes to // Routes. Hosts map[dnsname.FQDN][]netip.Addr + // SubdomainHosts is a set of FQDNs from Hosts that should also + // resolve subdomain queries to the same IPs. For example, if + // "node.tailnet.ts.net" is in SubdomainHosts, then queries for + // "anything.node.tailnet.ts.net" will resolve to node's IPs. + SubdomainHosts set.Set[dnsname.FQDN] // OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS) // instead of the IPv4 version (100.100.100.100). OnlyIPv6 bool diff --git a/net/dns/dns_clone.go b/net/dns/dns_clone.go index de08be8a27b8e..ea5e5299beb7d 100644 --- a/net/dns/dns_clone.go +++ b/net/dns/dns_clone.go @@ -6,10 +6,12 @@ package dns import ( + "maps" "net/netip" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) // Clone makes a deep copy of Config. @@ -43,6 +45,7 @@ func (src *Config) Clone() *Config { dst.Hosts[k] = append([]netip.Addr{}, src.Hosts[k]...) } } + dst.SubdomainHosts = maps.Clone(src.SubdomainHosts) return dst } @@ -52,6 +55,7 @@ var _ConfigCloneNeedsRegeneration = Config(struct { Routes map[dnsname.FQDN][]*dnstype.Resolver SearchDomains []dnsname.FQDN Hosts map[dnsname.FQDN][]netip.Addr + SubdomainHosts set.Set[dnsname.FQDN] OnlyIPv6 bool }{}) diff --git a/net/dns/dns_view.go b/net/dns/dns_view.go index b10861cca8821..313621c86e85b 100644 --- a/net/dns/dns_view.go +++ b/net/dns/dns_view.go @@ -15,6 +15,7 @@ import ( "tailscale.com/types/dnstype" "tailscale.com/types/views" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) //go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type=Config @@ -123,6 +124,14 @@ func (v ConfigView) Hosts() views.MapSlice[dnsname.FQDN, netip.Addr] { return views.MapSliceOf(v.ж.Hosts) } +// SubdomainHosts is a set of FQDNs from Hosts that should also +// resolve subdomain queries to the same IPs. For example, if +// "node.tailnet.ts.net" is in SubdomainHosts, then queries for +// "anything.node.tailnet.ts.net" will resolve to node's IPs. +func (v ConfigView) SubdomainHosts() views.Map[dnsname.FQDN, struct{}] { + return views.MapOf(v.ж.SubdomainHosts) +} + // OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS) // instead of the IPv4 version (100.100.100.100). func (v ConfigView) OnlyIPv6() bool { return v.ж.OnlyIPv6 } @@ -134,5 +143,6 @@ var _ConfigViewNeedsRegeneration = Config(struct { Routes map[dnsname.FQDN][]*dnstype.Resolver SearchDomains []dnsname.FQDN Hosts map[dnsname.FQDN][]netip.Addr + SubdomainHosts set.Set[dnsname.FQDN] OnlyIPv6 bool }{}) diff --git a/net/dns/manager.go b/net/dns/manager.go index 0d74febffe7ca..faca1053cf852 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -291,6 +291,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig // authoritative suffixes, even if we don't propagate MagicDNS to // the OS. rcfg.Hosts = cfg.Hosts + rcfg.SubdomainHosts = cfg.SubdomainHosts routes := map[dnsname.FQDN][]*dnstype.Resolver{} // assigned conditionally to rcfg.Routes below. var propagateHostsToOS bool for suffix, resolvers := range cfg.Routes { diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index a6f05c4702550..f71c1b7708b4c 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -39,6 +39,7 @@ import ( "tailscale.com/util/clientmetric" "tailscale.com/util/cloudenv" "tailscale.com/util/dnsname" + "tailscale.com/util/set" ) const dnsSymbolicFQDN = "magicdns.localhost-tailscale-daemon." @@ -79,6 +80,12 @@ type Config struct { // LocalDomains is a list of DNS name suffixes that should not be // routed to upstream resolvers. LocalDomains []dnsname.FQDN + // SubdomainHosts is a set of FQDNs from Hosts that should also + // resolve subdomain queries to the same IPs. If a query like + // "sub.node.tailnet.ts.net" doesn't match Hosts directly, and + // "node.tailnet.ts.net" is in SubdomainHosts, the query resolves + // to the IPs for "node.tailnet.ts.net". + SubdomainHosts set.Set[dnsname.FQDN] } // WriteToBufioWriter write a debug version of c for logs to w, omitting @@ -214,10 +221,11 @@ type Resolver struct { closed chan struct{} // mu guards the following fields from being updated while used. - mu syncs.Mutex - localDomains []dnsname.FQDN - hostToIP map[dnsname.FQDN][]netip.Addr - ipToHost map[netip.Addr]dnsname.FQDN + mu syncs.Mutex + localDomains []dnsname.FQDN + hostToIP map[dnsname.FQDN][]netip.Addr + ipToHost map[netip.Addr]dnsname.FQDN + subdomainHosts set.Set[dnsname.FQDN] } type ForwardLinkSelector interface { @@ -278,6 +286,7 @@ func (r *Resolver) SetConfig(cfg Config) error { r.localDomains = cfg.LocalDomains r.hostToIP = cfg.Hosts r.ipToHost = reverse + r.subdomainHosts = cfg.SubdomainHosts return nil } @@ -642,9 +651,18 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, r.mu.Lock() hosts := r.hostToIP localDomains := r.localDomains + subdomainHosts := r.subdomainHosts r.mu.Unlock() addrs, found := hosts[domain] + if !found { + for parent := domain.Parent(); parent != ""; parent = parent.Parent() { + if subdomainHosts.Contains(parent) { + addrs, found = hosts[parent] + break + } + } + } if !found { for _, suffix := range localDomains { if suffix.Contains(domain) { diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index 5597c2cf2d921..712fa88dcad82 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -32,6 +32,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/util/dnsname" "tailscale.com/util/eventbus/eventbustest" + "tailscale.com/util/set" ) var ( @@ -429,6 +430,56 @@ func TestResolveLocal(t *testing.T) { } } +func TestResolveLocalSubdomain(t *testing.T) { + r := newResolver(t) + defer r.Close() + + // Configure with SubdomainHosts set for test1.ipn.dev + cfg := Config{ + Hosts: map[dnsname.FQDN][]netip.Addr{ + "test1.ipn.dev.": {testipv4}, + "test2.ipn.dev.": {testipv6}, + }, + LocalDomains: []dnsname.FQDN{"ipn.dev."}, + SubdomainHosts: set.Of[dnsname.FQDN]("test1.ipn.dev."), + } + r.SetConfig(cfg) + + tests := []struct { + name string + qname dnsname.FQDN + qtype dns.Type + ip netip.Addr + code dns.RCode + }{ + // Exact matches still work + {"exact-ipv4", "test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, + {"exact-ipv6", "test2.ipn.dev.", dns.TypeAAAA, testipv6, dns.RCodeSuccess}, + + // Subdomain of test1 resolves (test1 has SubdomainHosts set) + {"subdomain-ipv4", "foo.test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, + {"subdomain-deep", "bar.foo.test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, // Multi-level subdomain + + // Subdomain of test2 does NOT resolve (test2 lacks SubdomainHosts) + {"subdomain-no-cap", "foo.test2.ipn.dev.", dns.TypeAAAA, netip.Addr{}, dns.RCodeNameError}, + + // Non-existent parent still returns NXDOMAIN + {"subdomain-no-parent", "foo.test3.ipn.dev.", dns.TypeA, netip.Addr{}, dns.RCodeNameError}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ip, code := r.resolveLocal(tt.qname, tt.qtype) + if code != tt.code { + t.Errorf("code = %v; want %v", code, tt.code) + } + if ip != tt.ip { + t.Errorf("ip = %v; want %v", ip, tt.ip) + } + }) + } +} + func TestResolveLocalReverse(t *testing.T) { r := newResolver(t) defer r.Close() diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 535c42b212b2e..f76eb8f55d241 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2707,6 +2707,13 @@ const ( // server to answer AAAA queries about its peers. See tailscale/tailscale#1152. NodeAttrMagicDNSPeerAAAA NodeCapability = "magicdns-aaaa" + // NodeAttrDNSSubdomainResolve, when set on Self or a Peer node, indicates + // that the subdomains of that node's MagicDNS name should resolve to the + // same IP addresses as the node itself. + // For example, if node "myserver.tailnet.ts.net" has this capability, + // then "anything.myserver.tailnet.ts.net" will resolve to myserver's IPs. + NodeAttrDNSSubdomainResolve NodeCapability = "dns-subdomain-resolve" + // NodeAttrTrafficSteering configures the node to use the traffic // steering subsystem for via routes. See tailscale/corp#29966. NodeAttrTrafficSteering NodeCapability = "traffic-steering" diff --git a/util/dnsname/dnsname.go b/util/dnsname/dnsname.go index 09b44e73e2faa..263c376aac674 100644 --- a/util/dnsname/dnsname.go +++ b/util/dnsname/dnsname.go @@ -94,6 +94,18 @@ func (f FQDN) Contains(other FQDN) bool { return strings.HasSuffix(other.WithTrailingDot(), cmp) } +// Parent returns the parent domain by stripping the first label. +// For "foo.bar.baz.", it returns "bar.baz." +// It returns an empty FQDN for root or single-label domains. +func (f FQDN) Parent() FQDN { + s := f.WithTrailingDot() + _, rest, ok := strings.Cut(s, ".") + if !ok || rest == "" { + return "" + } + return FQDN(rest) +} + // ValidLabel reports whether label is a valid DNS label. All errors are // [vizerror.Error]. func ValidLabel(label string) error { diff --git a/util/dnsname/dnsname_test.go b/util/dnsname/dnsname_test.go index 35e04de2ebb35..e349e51c7ad99 100644 --- a/util/dnsname/dnsname_test.go +++ b/util/dnsname/dnsname_test.go @@ -123,6 +123,34 @@ func TestFQDNContains(t *testing.T) { } } +func TestFQDNParent(t *testing.T) { + tests := []struct { + in string + want FQDN + }{ + {"", ""}, + {".", ""}, + {"com.", ""}, + {"foo.com.", "com."}, + {"www.foo.com.", "foo.com."}, + {"a.b.c.d.", "b.c.d."}, + {"sub.node.tailnet.ts.net.", "node.tailnet.ts.net."}, + } + + for _, test := range tests { + t.Run(test.in, func(t *testing.T) { + in, err := ToFQDN(test.in) + if err != nil { + t.Fatalf("ToFQDN(%q): %v", test.in, err) + } + got := in.Parent() + if got != test.want { + t.Errorf("ToFQDN(%q).Parent() = %q, want %q", test.in, got, test.want) + } + }) + } +} + func TestSanitizeLabel(t *testing.T) { tests := []struct { name string From 698e92a761d7a0d95f6b9929c2654f565b219793 Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Fri, 30 Jan 2026 14:18:45 -0500 Subject: [PATCH 050/202] logtail/filch: close Filch instances in TestConcurrentSameFile (#18571) On Windows, TempDir cleanup fails if file handles are still open. TestConcurrentSameFile wasn't closing Filch instances before exit Fixes #18570 Signed-off-by: Fernando Serboncini --- logtail/filch/filch_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/logtail/filch/filch_test.go b/logtail/filch/filch_test.go index 3c7ba03ca3358..2538233cfd84c 100644 --- a/logtail/filch/filch_test.go +++ b/logtail/filch/filch_test.go @@ -388,7 +388,9 @@ func testMaxFileSize(t *testing.T, replaceStderr bool) { func TestConcurrentSameFile(t *testing.T) { filePrefix := filepath.Join(t.TempDir(), "testlog") f1 := must.Get(New(filePrefix, Options{MaxFileSize: 1000})) + defer f1.Close() f2 := must.Get(New(filePrefix, Options{MaxFileSize: 1000})) + defer f2.Close() var group sync.WaitGroup for _, f := range []*Filch{f1, f2} { group.Go(func() { From 3ce13eb2b9c0a654da964e29ff8d2d145f3d396b Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 30 Jan 2026 12:35:00 -0800 Subject: [PATCH 051/202] cmd/testwrapper: add support for the -vet test flag So callers can run testwrapper with -vet=off if they're already running vet explicitly in a concurrent test job. Updates tailscale/corp#28679 Change-Id: I74ad56e560076d187f5e3a7d7381e1dac89d860c Signed-off-by: Brad Fitzpatrick --- cmd/testwrapper/args.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/testwrapper/args.go b/cmd/testwrapper/args.go index 11ed1aeaad0bd..350197d4f1271 100644 --- a/cmd/testwrapper/args.go +++ b/cmd/testwrapper/args.go @@ -89,6 +89,7 @@ func newTestFlagSet() *flag.FlagSet { // TODO(maisem): figure out what other flags we need to register explicitly. fs.String("exec", "", "Command to run tests with") fs.Bool("race", false, "build with race detector") + fs.String("vet", "", "vet checks to run, or 'off' or 'all'") return fs } From 3b6d542923cc1e53fa304a5b366c94789662e260 Mon Sep 17 00:00:00 2001 From: Jordan Whited Date: Thu, 29 Jan 2026 15:41:55 -0800 Subject: [PATCH 052/202] wgengine/magicsock: make debugNeverDirectUDP influence remote peer decisions By dropping inbound disco.Ping messages received over direct UDP paths. Fixes #18560 Signed-off-by: Jordan Whited --- wgengine/magicsock/debugknobs.go | 3 ++- wgengine/magicsock/magicsock.go | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/wgengine/magicsock/debugknobs.go b/wgengine/magicsock/debugknobs.go index 39cec25e64885..580d954c0bc40 100644 --- a/wgengine/magicsock/debugknobs.go +++ b/wgengine/magicsock/debugknobs.go @@ -62,7 +62,8 @@ var ( // //lint:ignore U1000 used on Linux/Darwin only debugPMTUD = envknob.RegisterBool("TS_DEBUG_PMTUD") - // debugNeverDirectUDP disables the use of direct UDP connections, forcing + // debugNeverDirectUDP disables the use of direct UDP connections by + // suppressing/dropping inbound/outbound [disco.Ping] messages, forcing // all peer communication over DERP or peer relay. debugNeverDirectUDP = envknob.RegisterBool("TS_DEBUG_NEVER_DIRECT_UDP") // Hey you! Adding a new debugknob? Make sure to stub it out in the diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 7c5442d0b996c..d6f411f4ac2dc 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2555,6 +2555,10 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, src epAddr, di *discoInfo, derpN // This is a naked [disco.Ping] without a VNI. + if debugNeverDirectUDP() && !isDerp { + return + } + // If we can figure out with certainty which node key this disco // message is for, eagerly update our [epAddr]<>node and disco<>node // mappings to make p2p path discovery faster in simple From 03461ea7fb9c2c318a355498811481ad9c74b119 Mon Sep 17 00:00:00 2001 From: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:46:03 -0500 Subject: [PATCH 053/202] wgengine/netstack: add local tailscale service IPs to route and terminate locally (#18461) * wgengine/netstack: add local tailscale service IPs to route and terminate locally This commit adds the tailscales service IPs served locally to OS routes, and make interception to packets so that the traffic terminates locally without making affects to the HA traffics. Fixes tailscale/corp#34048 Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> * fix test Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> * add ready field to avoid accessing lb before netstack starts Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> * wgengine/netstack: store values from lb to avoid acquiring a lock Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> * add active services to netstack on starts with stored prefs. Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> * fix comments Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> * update comments Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> --------- Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> --- ipn/ipnlocal/local.go | 48 +++++++++++++++++-- ipn/ipnlocal/local_test.go | 25 +++++++++- tsd/tsd.go | 3 ++ wgengine/netstack/netstack.go | 77 +++++++++++++++++++++++++++++- wgengine/netstack/netstack_test.go | 62 ++++++++++++++++++++++-- 5 files changed, 205 insertions(+), 10 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 0fc26cd041cb6..300f7a4c3186d 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -922,6 +922,22 @@ func (b *LocalBackend) setStateLocked(state ipn.State) { } } +func (b *LocalBackend) IPServiceMappings() netmap.IPServiceMappings { + b.mu.Lock() + defer b.mu.Unlock() + return b.ipVIPServiceMap +} + +func (b *LocalBackend) SetIPServiceMappingsForTest(m netmap.IPServiceMappings) { + b.mu.Lock() + defer b.mu.Unlock() + testenv.AssertInTest() + b.ipVIPServiceMap = m + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateIPServiceMappings(m) + } +} + // setConfigLocked uses the provided config to update the backend's prefs // and other state. func (b *LocalBackend) setConfigLocked(conf *conffile.Config) error { @@ -4502,6 +4518,12 @@ func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, o } } + if mp.AdvertiseServicesSet { + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateActiveVIPServices(newPrefs.AdvertiseServices()) + } + } + // This is recorded here in the EditPrefs path, not the setPrefs path on purpose. // recordForEdit records metrics related to edits and changes, not the final state. // If, in the future, we want to record gauge-metrics related to the state of prefs, @@ -5125,7 +5147,7 @@ func (b *LocalBackend) authReconfigLocked() { } oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.NetMon.Get(), b.sys.ControlKnobs(), version.OS()) - rcfg := b.routerConfigLocked(cfg, prefs, oneCGNATRoute) + rcfg := b.routerConfigLocked(cfg, prefs, nm, oneCGNATRoute) err = b.e.Reconfig(cfg, rcfg, dcfg) if err == wgengine.ErrNoChanges { @@ -5500,7 +5522,7 @@ func peerRoutes(logf logger.Logf, peers []wgcfg.Peer, cgnatThreshold int, routeA // routerConfig produces a router.Config from a wireguard config and IPN prefs. // // b.mu must be held. -func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView, oneCGNATRoute bool) *router.Config { +func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView, nm *netmap.NetworkMap, oneCGNATRoute bool) *router.Config { singleRouteThreshold := 10_000 if oneCGNATRoute { singleRouteThreshold = 1 @@ -5585,11 +5607,23 @@ func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView } } + // Get the VIPs for VIP services this node hosts. We will add all locally served VIPs to routes then + // we terminate these connection locally in netstack instead of routing to peer. + vipServiceIPs := nm.GetIPVIPServiceMap() + v4, v6 := false, false + if slices.ContainsFunc(rs.LocalAddrs, tsaddr.PrefixIs4) { rs.Routes = append(rs.Routes, netip.PrefixFrom(tsaddr.TailscaleServiceIP(), 32)) + v4 = true } if slices.ContainsFunc(rs.LocalAddrs, tsaddr.PrefixIs6) { rs.Routes = append(rs.Routes, netip.PrefixFrom(tsaddr.TailscaleServiceIPv6(), 128)) + v6 = true + } + for vip := range vipServiceIPs { + if (vip.Is4() && v4) || (vip.Is6() && v6) { + rs.Routes = append(rs.Routes, netip.PrefixFrom(vip, vip.BitLen())) + } } return rs @@ -6267,7 +6301,15 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs()) if buildfeatures.HasServe { - b.ipVIPServiceMap = nm.GetIPVIPServiceMap() + m := nm.GetIPVIPServiceMap() + b.ipVIPServiceMap = m + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateIPServiceMappings(m) + // In case the prefs reloaded from Profile Manager but didn't change, + // we still need to load the active VIP services into netstack. + ns.UpdateActiveVIPServices(b.pm.CurrentPrefs().AdvertiseServices()) + } + } if !oldSelf.Equal(nm.SelfNodeOrZero()) { diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 53607cfaaa737..cd44acdd1fecf 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -7430,8 +7430,31 @@ func TestRouteAllDisabled(t *testing.T) { cfg := &wgcfg.Config{ Peers: tt.peers, } + ServiceIPMappings := tailcfg.ServiceIPMappings{ + "svc:test-service": []netip.Addr{ + netip.MustParseAddr("100.64.1.2"), + netip.MustParseAddr("fd7a:abcd:1234::1"), + }, + } + svcIPMapJSON, err := json.Marshal(ServiceIPMappings) + if err != nil { + t.Fatalf("failed to marshal ServiceIPMappings: %v", err) + } + nm := &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "test-node", + Addresses: []netip.Prefix{ + pp("100.64.1.1/32"), + }, + CapMap: tailcfg.NodeCapMap{ + tailcfg.NodeAttrServiceHost: []tailcfg.RawMessage{ + tailcfg.RawMessage(svcIPMapJSON), + }, + }, + }).View(), + } - rcfg := lb.routerConfigLocked(cfg, prefs.View(), false) + rcfg := lb.routerConfigLocked(cfg, prefs.View(), nm, false) for _, p := range rcfg.Routes { found := false for _, r := range tt.wantEndpoints { diff --git a/tsd/tsd.go b/tsd/tsd.go index 4284a8cd3bade..9d79334d68e2b 100644 --- a/tsd/tsd.go +++ b/tsd/tsd.go @@ -32,6 +32,7 @@ import ( "tailscale.com/net/tstun" "tailscale.com/proxymap" "tailscale.com/types/netmap" + "tailscale.com/types/views" "tailscale.com/util/eventbus" "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/usermetric" @@ -111,6 +112,8 @@ type LocalBackend = any type NetstackImpl interface { Start(LocalBackend) error UpdateNetstackIPs(*netmap.NetworkMap) + UpdateIPServiceMappings(netmap.IPServiceMappings) + UpdateActiveVIPServices(views.Slice[string]) } // Set is a convenience method to set a subsystem value. diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 59fc0e0694bcc..42ac0ab1e4dba 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -51,6 +51,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/netmap" "tailscale.com/types/nettype" + "tailscale.com/types/views" "tailscale.com/util/clientmetric" "tailscale.com/util/set" "tailscale.com/version" @@ -200,6 +201,10 @@ type Impl struct { lb *ipnlocal.LocalBackend // or nil dns *dns.Manager + // Before Start is called, there can IPv6 Neighbor Discovery from the + // OS landing on netstack. We need to drop those packets until Start. + ready atomic.Bool // set to true once Start has been called + // loopbackPort, if non-nil, will enable Impl to loop back (dnat to // :loopbackPort) TCP & UDP flows originally // destined to serviceIP{v6}:loopbackPort. @@ -216,6 +221,10 @@ type Impl struct { atomicIsVIPServiceIPFunc syncs.AtomicValue[func(netip.Addr) bool] + atomicIPVIPServiceMap syncs.AtomicValue[netmap.IPServiceMappings] + // make this a set of strings for faster lookup + atomicActiveVIPServices syncs.AtomicValue[set.Set[tailcfg.ServiceName]] + // forwardDialFunc, if non-nil, is the net.Dialer.DialContext-style // function that is used to make outgoing connections when forwarding a // TCP connection to another host (e.g. in subnet router mode). @@ -608,6 +617,9 @@ func (ns *Impl) Start(b LocalBackend) error { ns.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, ns.wrapTCPProtocolHandler(tcpFwd.HandlePacket)) ns.ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, ns.wrapUDPProtocolHandler(udpFwd.HandlePacket)) go ns.inject() + if ns.ready.Swap(true) { + panic("already started") + } return nil } @@ -765,6 +777,25 @@ func (ns *Impl) UpdateNetstackIPs(nm *netmap.NetworkMap) { } } +// UpdateIPServiceMappings updates the IPServiceMappings when there is a change +// in this value in localbackend. This is usually triggered from a netmap update. +func (ns *Impl) UpdateIPServiceMappings(mappings netmap.IPServiceMappings) { + ns.mu.Lock() + defer ns.mu.Unlock() + ns.atomicIPVIPServiceMap.Store(mappings) +} + +// UpdateActiveVIPServices updates the set of active VIP services names. +func (ns *Impl) UpdateActiveVIPServices(activeServices views.Slice[string]) { + ns.mu.Lock() + defer ns.mu.Unlock() + activeServicesSet := make(set.Set[tailcfg.ServiceName], activeServices.Len()) + for _, s := range activeServices.All() { + activeServicesSet.Add(tailcfg.AsServiceName(s)) + } + ns.atomicActiveVIPServices.Store(activeServicesSet) +} + func (ns *Impl) isLoopbackPort(port uint16) bool { if ns.loopbackPort != nil && int(port) == *ns.loopbackPort { return true @@ -775,13 +806,15 @@ func (ns *Impl) isLoopbackPort(port uint16) bool { // handleLocalPackets is hooked into the tun datapath for packets leaving // the host and arriving at tailscaled. This method returns filter.DropSilently // to intercept a packet for handling, for instance traffic to quad-100. +// Caution: can be called before Start func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) { - if ns.ctx.Err() != nil { + if !ns.ready.Load() || ns.ctx.Err() != nil { return filter.DropSilently, gro } // Determine if we care about this local packet. dst := p.Dst.Addr() + serviceName, isVIPServiceIP := ns.atomicIPVIPServiceMap.Load()[dst] switch { case dst == serviceIP || dst == serviceIPv6: // We want to intercept some traffic to the "service IP" (e.g. @@ -798,6 +831,25 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro. return filter.Accept, gro } } + case isVIPServiceIP: + // returns all active VIP services in a set, since the IPVIPServiceMap + // contains inactive service IPs when node hosts the service, we need to + // check the service is active or not before dropping the packet. + activeServices := ns.atomicActiveVIPServices.Load() + if !activeServices.Contains(serviceName) { + // Other host might have the service active, so we let the packet go through. + return filter.Accept, gro + } + if p.IPProto != ipproto.TCP { + // We currenly only support VIP services over TCP. If service is in Tun mode, + // it's up to the service host to set up local packet handling which shouldn't + // arrive here. + return filter.DropSilently, gro + } + if debugNetstack() { + ns.logf("netstack: intercepting local VIP service packet: proto=%v dst=%v src=%v", + p.IPProto, p.Dst, p.Src) + } case viaRange.Contains(dst): // We need to handle 4via6 packets leaving the host if the via // route is for this host; otherwise the packet will be dropped @@ -1009,12 +1061,32 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool { return true } + if ns.isVIPServiceIP(srcIP) { + dstIP := netip.AddrFrom4(v.DestinationAddress().As4()) + if ns.isLocalIP(dstIP) { + if debugNetstack() { + ns.logf("netstack: sending VIP service packet to host: src=%v dst=%v", srcIP, dstIP) + } + return true + } + } + case header.IPv6: srcIP := netip.AddrFrom16(v.SourceAddress().As16()) if srcIP == serviceIPv6 { return true } + if ns.isVIPServiceIP(srcIP) { + dstIP := netip.AddrFrom16(v.DestinationAddress().As16()) + if ns.isLocalIP(dstIP) { + if debugNetstack() { + ns.logf("netstack: sending VIP service packet to host: src=%v dst=%v", srcIP, dstIP) + } + return true + } + } + if viaRange.Contains(srcIP) { // Only send to the host if this 4via6 route is // something this node handles. @@ -1233,8 +1305,9 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte, direction userPing // continue normally (typically being delivered to the host networking stack), // whereas returning filter.DropSilently is done when netstack intercepts the // packet and no further processing towards to host should be done. +// Caution: can be called before Start func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) { - if ns.ctx.Err() != nil { + if !ns.ready.Load() || ns.ctx.Err() != nil { return filter.DropSilently, gro } diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index f9903c0c210d5..eea598937e4cf 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -31,6 +31,7 @@ import ( "tailscale.com/tstest" "tailscale.com/types/ipproto" "tailscale.com/types/logid" + "tailscale.com/types/netmap" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" ) @@ -125,6 +126,7 @@ func makeNetstack(tb testing.TB, config func(*Impl)) *Impl { tb.Fatal(err) } tb.Cleanup(func() { ns.Close() }) + sys.Set(ns) lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0) if err != nil { @@ -741,13 +743,20 @@ func TestHandleLocalPackets(t *testing.T) { // fd7a:115c:a1e0:b1a:0:7:a01:100/120 netip.MustParsePrefix("fd7a:115c:a1e0:b1a:0:7:a01:100/120"), } + prefs.AdvertiseServices = []string{"svc:test-service"} _, err := impl.lb.EditPrefs(&ipn.MaskedPrefs{ - Prefs: *prefs, - AdvertiseRoutesSet: true, + Prefs: *prefs, + AdvertiseRoutesSet: true, + AdvertiseServicesSet: true, }) if err != nil { t.Fatalf("EditPrefs: %v", err) } + IPServiceMap := netmap.IPServiceMappings{ + netip.MustParseAddr("100.99.55.111"): "svc:test-service", + netip.MustParseAddr("fd7a:115c:a1e0::abcd"): "svc:test-service", + } + impl.lb.SetIPServiceMappingsForTest(IPServiceMap) t.Run("ShouldHandleServiceIP", func(t *testing.T) { pkt := &packet.Parsed{ @@ -784,6 +793,19 @@ func TestHandleLocalPackets(t *testing.T) { t.Errorf("got filter outcome %v, want filter.DropSilently", resp) } }) + t.Run("ShouldHandleLocalTailscaleServices", func(t *testing.T) { + pkt := &packet.Parsed{ + IPVersion: 4, + IPProto: ipproto.TCP, + Src: netip.MustParseAddrPort("127.0.0.1:9999"), + Dst: netip.MustParseAddrPort("100.99.55.111:80"), + TCPFlags: packet.TCPSyn, + } + resp, _ := impl.handleLocalPackets(pkt, impl.tundev, nil) + if resp != filter.DropSilently { + t.Errorf("got filter outcome %v, want filter.DropSilently", resp) + } + }) t.Run("OtherNonHandled", func(t *testing.T) { pkt := &packet.Parsed{ IPVersion: 6, @@ -809,8 +831,10 @@ func TestHandleLocalPackets(t *testing.T) { func TestShouldSendToHost(t *testing.T) { var ( - selfIP4 = netip.MustParseAddr("100.64.1.2") - selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123") + selfIP4 = netip.MustParseAddr("100.64.1.2") + selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123") + tailscaleServiceIP4 = netip.MustParseAddr("100.99.55.111") + tailscaleServiceIP6 = netip.MustParseAddr("fd7a:115c:a1e0::abcd") ) makeTestNetstack := func(tb testing.TB) *Impl { @@ -820,6 +844,9 @@ func TestShouldSendToHost(t *testing.T) { impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool { return addr == selfIP4 || addr == selfIP6 }) + impl.atomicIsVIPServiceIPFunc.Store(func(addr netip.Addr) bool { + return addr == tailscaleServiceIP4 || addr == tailscaleServiceIP6 + }) }) prefs := ipn.NewPrefs() @@ -919,6 +946,33 @@ func TestShouldSendToHost(t *testing.T) { dst: netip.MustParseAddrPort("[fd7a:115:a1e0::99]:7777"), want: false, }, + // After accessing the Tailscale service from host, replies from Tailscale Service IPs + // to the local Tailscale IPs should be sent to the host. + { + name: "from_service_ip_to_local_ip", + src: netip.AddrPortFrom(tailscaleServiceIP4, 80), + dst: netip.AddrPortFrom(selfIP4, 12345), + want: true, + }, + { + name: "from_service_ip_to_local_ip_v6", + src: netip.AddrPortFrom(tailscaleServiceIP6, 80), + dst: netip.AddrPortFrom(selfIP6, 12345), + want: true, + }, + // Traffic from remote IPs to Tailscale Service IPs should be sent over WireGuard. + { + name: "from_service_ip_to_remote", + src: netip.AddrPortFrom(tailscaleServiceIP4, 80), + dst: netip.MustParseAddrPort("173.201.32.56:54321"), + want: false, + }, + { + name: "from_service_ip_to_remote_v6", + src: netip.AddrPortFrom(tailscaleServiceIP6, 80), + dst: netip.MustParseAddrPort("[2001:4860:4860::8888]:54321"), + want: false, + }, } for _, tt := range testCases { From b4d39e2fd92538384aa7388fdbeda0ec51973bfc Mon Sep 17 00:00:00 2001 From: Mario Minardi Date: Fri, 30 Jan 2026 17:03:17 -0700 Subject: [PATCH 054/202] cmd/gitops-pusher: fix precedence when id token env var is empty Fix precedence logic to skip federated identity logic when the associated environment variables are empty. Updates https://github.com/tailscale/gitops-acl-action/issues/71 Signed-off-by: Mario Minardi --- cmd/gitops-pusher/gitops-pusher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gitops-pusher/gitops-pusher.go b/cmd/gitops-pusher/gitops-pusher.go index 39a60d3064432..11448e30da1aa 100644 --- a/cmd/gitops-pusher/gitops-pusher.go +++ b/cmd/gitops-pusher/gitops-pusher.go @@ -252,7 +252,7 @@ func getCredentials() (*http.Client, string) { TokenURL: fmt.Sprintf("https://%s/api/v2/oauth/token", *apiServer), } client = oauthConfig.Client(context.Background()) - } else if idok { + } else if idok && idToken != "" && oiok && oauthId != "" { if exchangeJWTForToken, ok := tailscale.HookExchangeJWTForTokenViaWIF.GetOk(); ok { var err error apiKeyEnv, err = exchangeJWTForToken(context.Background(), fmt.Sprintf("https://%s", *apiServer), oauthId, idToken) From 8cac8b117b0b2da307369fe3dd0b5cd6b9ed3711 Mon Sep 17 00:00:00 2001 From: Brendan Creane Date: Fri, 30 Jan 2026 17:52:54 -0800 Subject: [PATCH 055/202] net/dns/resolver: set TC flag when UDP responses exceed size limits (#18157) The forwarder was not setting the Truncated (TC) flag when UDP DNS responses exceeded either the EDNS buffer size (if present) or the RFC 1035 default 512-byte limit. This affected DoH, TCP fallback, and UDP response paths. The fix ensures checkResponseSizeAndSetTC is called in all code paths that return UDP responses, enforcing both EDNS and default UDP size limits. Added comprehensive unit tests and consolidated duplicate test helpers. Updates #18107 Signed-off-by: Brendan Creane --- net/dns/resolver/forwarder.go | 137 +++++++-- net/dns/resolver/forwarder_test.go | 475 ++++++++++++++++++++++++++--- net/dns/resolver/tsdns.go | 7 +- net/dns/resolver/tsdns_test.go | 99 ++++++ 4 files changed, 654 insertions(+), 64 deletions(-) diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 0a3daa3bc46ca..189911ee24c0a 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -63,6 +63,17 @@ func truncatedFlagSet(pkt []byte) bool { return (binary.BigEndian.Uint16(pkt[2:4]) & dnsFlagTruncated) != 0 } +// setTCFlag sets the TC (truncated) flag in the DNS packet header. +// The packet must be at least headerBytes in length. +func setTCFlag(packet []byte) { + if len(packet) < headerBytes { + return + } + flags := binary.BigEndian.Uint16(packet[2:4]) + flags |= dnsFlagTruncated + binary.BigEndian.PutUint16(packet[2:4], flags) +} + const ( // dohIdleConnTimeout is how long to keep idle HTTP connections // open to DNS-over-HTTPS servers. 10 seconds is a sensible @@ -131,47 +142,59 @@ func getRCode(packet []byte) dns.RCode { return dns.RCode(packet[3] & 0x0F) } -// clampEDNSSize attempts to limit the maximum EDNS response size. This is not -// an exhaustive solution, instead only easy cases are currently handled in the -// interest of speed and reduced complexity. Only OPT records at the very end of -// the message with no option codes are addressed. -// TODO: handle more situations if we discover that they happen often -func clampEDNSSize(packet []byte, maxSize uint16) { - // optFixedBytes is the size of an OPT record with no option codes. - const optFixedBytes = 11 - const edns0Version = 0 +// findOPTRecord finds and validates the EDNS OPT record at the end of a DNS packet. +// Returns the requested buffer size and a pointer to the OPT record bytes if valid, +// or (0, nil) if no valid OPT record is found. +// The OPT record must be at the very end of the packet with no option codes. +func findOPTRecord(packet []byte) (requestedSize uint16, opt []byte) { + const optFixedBytes = 11 // size of an OPT record with no option codes + const edns0Version = 0 // EDNS version number (currently only version 0 is defined) if len(packet) < headerBytes+optFixedBytes { - return + return 0, nil } arCount := binary.BigEndian.Uint16(packet[10:12]) if arCount == 0 { // OPT shows up in an AR, so there must be no OPT - return + return 0, nil } // https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.2 - opt := packet[len(packet)-optFixedBytes:] + opt = packet[len(packet)-optFixedBytes:] if opt[0] != 0 { // OPT NAME must be 0 (root domain) - return + return 0, nil } if dns.Type(binary.BigEndian.Uint16(opt[1:3])) != dns.TypeOPT { // Not an OPT record - return + return 0, nil } - requestedSize := binary.BigEndian.Uint16(opt[3:5]) + requestedSize = binary.BigEndian.Uint16(opt[3:5]) // Ignore extended RCODE in opt[5] if opt[6] != edns0Version { // Be conservative and don't touch unknown versions. - return + return 0, nil } // Ignore flags in opt[6:9] if binary.BigEndian.Uint16(opt[9:11]) != 0 { // RDLEN must be 0 (no variable length data). We're at the end of the - // packet so this should be 0 anyway).. + // packet so this should be 0 anyway. + return 0, nil + } + + return requestedSize, opt +} + +// clampEDNSSize attempts to limit the maximum EDNS response size. This is not +// an exhaustive solution, instead only easy cases are currently handled in the +// interest of speed and reduced complexity. Only OPT records at the very end of +// the message with no option codes are addressed. +// TODO: handle more situations if we discover that they happen often +func clampEDNSSize(packet []byte, maxSize uint16) { + requestedSize, opt := findOPTRecord(packet) + if opt == nil { return } @@ -183,6 +206,57 @@ func clampEDNSSize(packet []byte, maxSize uint16) { binary.BigEndian.PutUint16(opt[3:5], maxSize) } +// getEDNSBufferSize extracts the EDNS buffer size from a DNS request packet. +// Returns (bufferSize, true) if a valid EDNS OPT record is found, +// or (0, false) if no EDNS OPT record is found or if there's an error. +func getEDNSBufferSize(packet []byte) (uint16, bool) { + requestedSize, opt := findOPTRecord(packet) + return requestedSize, opt != nil +} + +// checkResponseSizeAndSetTC sets the TC (truncated) flag in the DNS header when +// the response exceeds the maximum UDP size. If no EDNS OPT record is present +// in the request, it sets the TC flag when the response is bigger than 512 bytes +// per RFC 1035. If an EDNS OPT record is present, it sets the TC flag when the +// response is bigger than the EDNS buffer size. The response buffer is not +// truncated; only the TC flag is set. Returns the response unchanged except for +// the TC flag being set if needed. +func checkResponseSizeAndSetTC(response []byte, request []byte, family string, logf logger.Logf) []byte { + const defaultUDPSize = 512 // default maximum UDP DNS packet size per RFC 1035 + + // Only check for UDP queries; TCP can handle larger responses + if family != "udp" { + return response + } + + // Check if TC flag is already set + if len(response) < headerBytes { + return response + } + if truncatedFlagSet(response) { + // TC flag already set, nothing to do + return response + } + + ednsSize, hasEDNS := getEDNSBufferSize(request) + + // Determine maximum allowed size + var maxSize int + if hasEDNS { + maxSize = int(ednsSize) + } else { + // No EDNS: enforce default UDP size limit per RFC 1035 + maxSize = defaultUDPSize + } + + // Check if response exceeds maximum size + if len(response) > maxSize { + setTCFlag(response) + } + + return response +} + // dnsForwarderFailing should be raised when the forwarder is unable to reach the // upstream resolvers. This is a high severity warning as it results in "no internet". // This warning must be cleared when the forwarder is working again. @@ -535,7 +609,13 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe if !buildfeatures.HasPeerAPIClient { return nil, feature.ErrUnavailable } - return f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet) + res, err := f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet) + if err != nil { + return nil, err + } + // Check response size and set TC flag if needed (only for UDP queries) + res = checkResponseSizeAndSetTC(res, fq.packet, fq.family, f.logf) + return res, nil } if strings.HasPrefix(rr.name.Addr, "https://") { // Only known DoH providers are supported currently. Specifically, we @@ -546,7 +626,13 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe // them. urlBase := rr.name.Addr if hc, ok := f.getKnownDoHClientForProvider(urlBase); ok { - return f.sendDoH(ctx, urlBase, hc, fq.packet) + res, err := f.sendDoH(ctx, urlBase, hc, fq.packet) + if err != nil { + return nil, err + } + // Check response size and set TC flag if needed (only for UDP queries) + res = checkResponseSizeAndSetTC(res, fq.packet, fq.family, f.logf) + return res, nil } metricDNSFwdErrorType.Add(1) return nil, fmt.Errorf("arbitrary https:// resolvers not supported yet") @@ -710,12 +796,15 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn f.logf("recv: packet too small (%d bytes)", n) } out = out[:n] + tcFlagAlreadySet := truncatedFlagSet(out) + txid := getTxID(out) if txid != fq.txid { metricDNSFwdUDPErrorTxID.Add(1) return nil, errTxIDMismatch } rcode := getRCode(out) + // don't forward transient errors back to the client when the server fails if rcode == dns.RCodeServerFailure { f.logf("recv: response code indicating server failure: %d", rcode) @@ -723,11 +812,9 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn return nil, errServerFailure } - if truncated { - // Set the truncated bit if it wasn't already. - flags := binary.BigEndian.Uint16(out[2:4]) - flags |= dnsFlagTruncated - binary.BigEndian.PutUint16(out[2:4], flags) + // Set the truncated bit if buffer was truncated during read and the flag isn't already set + if truncated && !tcFlagAlreadySet { + setTCFlag(out) // TODO(#2067): Remove any incomplete records? RFC 1035 section 6.2 // states that truncation should head drop so that the authority @@ -736,6 +823,8 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn // best we can do. } + out = checkResponseSizeAndSetTC(out, fq.packet, fq.family, f.logf) + if truncatedFlagSet(out) { metricDNSFwdTruncated.Add(1) } diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 3165bb9783faa..6c7459b1f619c 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -34,6 +34,46 @@ func (rr resolverAndDelay) String() string { return fmt.Sprintf("%v+%v", rr.name, rr.startDelay) } +// setTCFlagInPacket sets the TC flag in a DNS packet (for testing). +func setTCFlagInPacket(packet []byte) { + if len(packet) < headerBytes { + return + } + flags := binary.BigEndian.Uint16(packet[2:4]) + flags |= dnsFlagTruncated + binary.BigEndian.PutUint16(packet[2:4], flags) +} + +// clearTCFlagInPacket clears the TC flag in a DNS packet (for testing). +func clearTCFlagInPacket(packet []byte) { + if len(packet) < headerBytes { + return + } + flags := binary.BigEndian.Uint16(packet[2:4]) + flags &^= dnsFlagTruncated + binary.BigEndian.PutUint16(packet[2:4], flags) +} + +// verifyEDNSBufferSize verifies a request has the expected EDNS buffer size. +func verifyEDNSBufferSize(t *testing.T, request []byte, expectedSize uint16) { + t.Helper() + ednsSize, hasEDNS := getEDNSBufferSize(request) + if !hasEDNS { + t.Fatalf("request should have EDNS OPT record") + } + if ednsSize != expectedSize { + t.Fatalf("request EDNS size = %d, want %d", ednsSize, expectedSize) + } +} + +// setupForwarderWithTCPRetriesDisabled returns a forwarder modifier that disables TCP retries. +func setupForwarderWithTCPRetriesDisabled() func(*forwarder) { + return func(fwd *forwarder) { + fwd.controlKnobs = &controlknobs.Knobs{} + fwd.controlKnobs.DisableDNSForwarderTCPRetries.Store(true) + } +} + func TestResolversWithDelays(t *testing.T) { // query q := func(ss ...string) (ipps []*dnstype.Resolver) { @@ -428,22 +468,16 @@ func makeLargeResponse(tb testing.TB, domain string) (request, response []byte) } // Our request is a single A query for the domain in the answer, above. - builder = dns.NewBuilder(nil, dns.Header{}) - builder.StartQuestions() - builder.Question(dns.Question{ - Name: dns.MustNewName(domain), - Type: dns.TypeA, - Class: dns.ClassINET, - }) - request, err = builder.Finish() - if err != nil { - tb.Fatal(err) - } + request = makeTestRequest(tb, domain, dns.TypeA, 0) return } func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports ...uint16) ([]byte, error) { + return runTestQueryWithFamily(tb, request, "udp", modify, ports...) +} + +func runTestQueryWithFamily(tb testing.TB, request []byte, family string, modify func(*forwarder), ports ...uint16) ([]byte, error) { logf := tstest.WhileTestRunningLogger(tb) bus := eventbustest.NewBus(tb) netMon, err := netmon.New(bus, logf) @@ -467,7 +501,7 @@ func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports rpkt := packet{ bs: request, - family: "tcp", + family: family, addr: netip.MustParseAddrPort("127.0.0.1:12345"), } @@ -483,17 +517,29 @@ func runTestQuery(tb testing.TB, request []byte, modify func(*forwarder), ports } } -// makeTestRequest returns a new TypeA request for the given domain. -func makeTestRequest(tb testing.TB, domain string) []byte { +// makeTestRequest returns a new DNS request for the given domain. +// If queryType is 0, it defaults to TypeA. If ednsSize > 0, it adds an EDNS OPT record. +func makeTestRequest(tb testing.TB, domain string, queryType dns.Type, ednsSize uint16) []byte { tb.Helper() + if queryType == 0 { + queryType = dns.TypeA + } name := dns.MustNewName(domain) builder := dns.NewBuilder(nil, dns.Header{}) builder.StartQuestions() builder.Question(dns.Question{ Name: name, - Type: dns.TypeA, + Type: queryType, Class: dns.ClassINET, }) + if ednsSize > 0 { + builder.StartAdditionals() + builder.OPTResource(dns.ResourceHeader{ + Name: dns.MustNewName("."), + Type: dns.TypeOPT, + Class: dns.Class(ednsSize), + }, dns.OPTResource{}) + } request, err := builder.Finish() if err != nil { tb.Fatal(err) @@ -549,6 +595,371 @@ func beVerbose(f *forwarder) { f.verboseFwd = true } +// makeTestRequestWithEDNS returns a new TypeTXT request for the given domain with EDNS buffer size. +// Deprecated: Use makeTestRequest with queryType and ednsSize parameters instead. +func makeTestRequestWithEDNS(tb testing.TB, domain string, ednsSize uint16) []byte { + return makeTestRequest(tb, domain, dns.TypeTXT, ednsSize) +} + +// makeEDNSResponse creates a DNS response of approximately the specified size +// with TXT records and an OPT record. The response will NOT have the TC flag set +// (simulating a non-compliant server that doesn't set TC when response exceeds EDNS buffer). +// The actual size may vary significantly due to DNS packet structure constraints. +func makeEDNSResponse(tb testing.TB, domain string, targetSize int) []byte { + tb.Helper() + // Use makeResponseOfSize with includeOPT=true + // Allow significant variance since DNS packet sizes are hard to predict exactly + // Use a combination of fixed tolerance (200 bytes) and percentage (25%) for larger targets + response := makeResponseOfSize(tb, domain, targetSize, true) + actualSize := len(response) + maxVariance := 200 + if targetSize > 400 { + // For larger targets, allow 25% variance + maxVariance = targetSize * 25 / 100 + } + if actualSize < targetSize-maxVariance || actualSize > targetSize+maxVariance { + tb.Fatalf("response size = %d, want approximately %d (variance: %d, allowed: ±%d)", + actualSize, targetSize, actualSize-targetSize, maxVariance) + } + return response +} + +func TestEDNSBufferSizeTruncation(t *testing.T) { + const domain = "edns-test.example.com." + const ednsBufferSize = 500 // Small EDNS buffer + const responseSize = 800 // Response exceeds EDNS but < maxResponseBytes + + // Create a response that exceeds EDNS buffer size but doesn't have TC flag set + response := makeEDNSResponse(t, domain, responseSize) + + // Create a request with EDNS buffer size + request := makeTestRequest(t, domain, dns.TypeTXT, ednsBufferSize) + verifyEDNSBufferSize(t, request, ednsBufferSize) + + // Verify response doesn't have TC flag set initially + if truncatedFlagSet(response) { + t.Fatal("test response should not have TC flag set initially") + } + + // Set up test DNS server + port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { + verifyEDNSBufferSize(t, gotRequest, ednsBufferSize) + }) + + // Disable TCP retries to ensure we test UDP path + resp := mustRunTestQuery(t, request, setupForwarderWithTCPRetriesDisabled(), port) + + // Verify the response has TC flag set by forwarder + if !truncatedFlagSet(resp) { + t.Errorf("TC flag not set in response (response size=%d, EDNS=%d)", len(resp), ednsBufferSize) + } + + // Verify response size is preserved (not truncated by buffer) + if len(resp) != len(response) { + t.Errorf("response size = %d, want %d (response should not be truncated by buffer)", len(resp), len(response)) + } + + // Verify response size exceeds EDNS buffer + if len(resp) <= int(ednsBufferSize) { + t.Errorf("response size = %d, should exceed EDNS buffer size %d", len(resp), ednsBufferSize) + } +} + +// makeResponseOfSize creates a DNS response of approximately the specified size +// with TXT records. The response will NOT have the TC flag set initially. +// If includeOPT is true, an OPT record is added to the response. +func makeResponseOfSize(tb testing.TB, domain string, targetSize int, includeOPT bool) []byte { + tb.Helper() + name := dns.MustNewName(domain) + + // Estimate how many TXT records we need + // Each TXT record with ~200 bytes of data adds roughly 220-230 bytes to the packet + // (including DNS headers, name compression, etc.) + bytesPerRecord := 220 + baseSize := 50 // Approximate base packet size (header + question) + if includeOPT { + baseSize += 11 // OPT record adds ~11 bytes + } + estimatedRecords := (targetSize - baseSize) / bytesPerRecord + if estimatedRecords < 1 { + estimatedRecords = 1 + } + + // Start with estimated records and adjust + txtLen := 200 + var response []byte + var err error + + for attempt := 0; attempt < 10; attempt++ { + testBuilder := dns.NewBuilder(nil, dns.Header{ + Response: true, + Authoritative: true, + RCode: dns.RCodeSuccess, + }) + testBuilder.StartQuestions() + testBuilder.Question(dns.Question{ + Name: name, + Type: dns.TypeTXT, + Class: dns.ClassINET, + }) + testBuilder.StartAnswers() + + for i := 0; i < estimatedRecords; i++ { + txtValue := strings.Repeat("x", txtLen) + testBuilder.TXTResource(dns.ResourceHeader{ + Name: name, + Type: dns.TypeTXT, + Class: dns.ClassINET, + TTL: 300, + }, dns.TXTResource{ + TXT: []string{txtValue}, + }) + } + + // Optionally add OPT record + if includeOPT { + testBuilder.StartAdditionals() + testBuilder.OPTResource(dns.ResourceHeader{ + Name: dns.MustNewName("."), + Type: dns.TypeOPT, + Class: dns.Class(4096), + }, dns.OPTResource{}) + } + + response, err = testBuilder.Finish() + if err != nil { + tb.Fatal(err) + } + + actualSize := len(response) + // Stop if we've reached or slightly exceeded the target + // Allow up to 20% overshoot to avoid excessive iterations + if actualSize >= targetSize && actualSize <= targetSize*120/100 { + break + } + // If we've overshot significantly, we're done (better than undershooting) + if actualSize > targetSize*120/100 { + break + } + + // Adjust for next attempt + needed := targetSize - actualSize + additionalRecords := (needed / bytesPerRecord) + 1 + estimatedRecords += additionalRecords + if estimatedRecords > 200 { + // If we need too many records, increase TXT length instead + txtLen = 255 // Max single TXT string length + bytesPerRecord = 280 // Adjusted estimate + estimatedRecords = (targetSize - baseSize) / bytesPerRecord + if estimatedRecords < 1 { + estimatedRecords = 1 + } + } + } + + // Ensure TC flag is NOT set initially + clearTCFlagInPacket(response) + + return response +} + +func TestCheckResponseSizeAndSetTC(t *testing.T) { + const domain = "test.example.com." + logf := func(format string, args ...any) { + // Silent logger for tests + } + + tests := []struct { + name string + responseSize int + requestHasEDNS bool + ednsSize uint16 + family string + responseTCSet bool // Whether response has TC flag set initially + wantTCSet bool // Whether TC flag should be set after function call + skipIfNotExact bool // Skip test if we can't hit exact size (for edge cases) + }{ + // Default UDP size (512 bytes) without EDNS + { + name: "UDP_noEDNS_small_should_not_set_TC", + responseSize: 400, + requestHasEDNS: false, + family: "udp", + wantTCSet: false, + }, + { + name: "UDP_noEDNS_512bytes_should_not_set_TC", + responseSize: 512, + requestHasEDNS: false, + family: "udp", + wantTCSet: false, + skipIfNotExact: true, + }, + { + name: "UDP_noEDNS_513bytes_should_set_TC", + responseSize: 513, + requestHasEDNS: false, + family: "udp", + wantTCSet: true, + skipIfNotExact: true, + }, + { + name: "UDP_noEDNS_large_should_set_TC", + responseSize: 600, + requestHasEDNS: false, + family: "udp", + wantTCSet: true, + }, + + // EDNS edge cases + { + name: "UDP_EDNS_small_under_limit_should_not_set_TC", + responseSize: 450, + requestHasEDNS: true, + ednsSize: 500, + family: "udp", + wantTCSet: false, + }, + { + name: "UDP_EDNS_at_limit_should_not_set_TC", + responseSize: 500, + requestHasEDNS: true, + ednsSize: 500, + family: "udp", + wantTCSet: false, + }, + { + name: "UDP_EDNS_over_limit_should_set_TC", + responseSize: 550, + requestHasEDNS: true, + ednsSize: 500, + family: "udp", + wantTCSet: true, + }, + { + name: "UDP_EDNS_large_over_limit_should_set_TC", + responseSize: 1500, + requestHasEDNS: true, + ednsSize: 1200, + family: "udp", + wantTCSet: true, + }, + + // Early return paths + { + name: "TCP_query_should_skip", + responseSize: 1000, + family: "tcp", + wantTCSet: false, + }, + { + name: "response_too_small_should_skip", + responseSize: headerBytes - 1, + family: "udp", + wantTCSet: false, + }, + { + name: "response_exactly_headerBytes_should_not_set_TC", + responseSize: headerBytes, + family: "udp", + wantTCSet: false, + }, + { + name: "response_TC_already_set_should_skip", + responseSize: 600, + family: "udp", + responseTCSet: true, + wantTCSet: true, // Should remain set + }, + { + name: "UDP_noEDNS_large_TC_already_set_should_skip", + responseSize: 600, + requestHasEDNS: false, + family: "udp", + responseTCSet: true, + wantTCSet: true, // Should remain set + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var response []byte + + // Create response of specified size + if tt.responseSize < headerBytes { + // For too-small test, create minimal invalid packet + response = make([]byte, tt.responseSize) + // Don't set any flags, just make it too small + } else { + response = makeResponseOfSize(t, domain, tt.responseSize, false) + actualSize := len(response) + + // Only adjust expectations for UDP queries that go through size checking + // TCP queries and other early-return cases should keep their original expectations + if tt.family == "udp" && !tt.responseTCSet && actualSize >= headerBytes { + // Determine the maximum allowed size based on request + var maxSize int + if tt.requestHasEDNS { + maxSize = int(tt.ednsSize) + } else { + maxSize = 512 // default UDP size per RFC 1035 + } + + // For edge cases where exact size matters, verify we're close enough + if tt.skipIfNotExact { + // For 512/513 byte tests, we need to be very close + if actualSize < tt.responseSize-10 || actualSize > tt.responseSize+10 { + t.Skipf("skipping: could not create response close to target size %d (got %d)", tt.responseSize, actualSize) + } + // Function sets TC if response > maxSize, so adjust expectation based on actual size + tt.wantTCSet = actualSize > maxSize + } else { + // For non-exact tests, adjust expectation based on actual response size + // The function sets TC if actualSize > maxSize + tt.wantTCSet = actualSize > maxSize + } + } + } + + // Set TC flag initially if requested + if tt.responseTCSet { + setTCFlagInPacket(response) + } + + // Create request with or without EDNS + var ednsSize uint16 + if tt.requestHasEDNS { + ednsSize = tt.ednsSize + } + request := makeTestRequest(t, domain, dns.TypeTXT, ednsSize) + + // Call the function + result := checkResponseSizeAndSetTC(response, request, tt.family, logf) + + // Verify response size is preserved (function should not truncate, only set flag) + if len(result) != len(response) { + t.Errorf("response size changed: got %d, want %d", len(result), len(response)) + } + + // Verify TC flag state + if len(result) >= headerBytes { + hasTC := truncatedFlagSet(result) + if hasTC != tt.wantTCSet { + t.Errorf("TC flag: got %v, want %v (response size=%d)", hasTC, tt.wantTCSet, len(result)) + } + } else if tt.responseSize >= headerBytes { + // If we expected a valid response but got too small, that's unexpected + t.Errorf("response too small (%d bytes) but expected valid response", len(result)) + } + + // Verify response pointer is same (should be in-place modification) + if &result[0] != &response[0] { + t.Errorf("function should modify response in place, but got new slice") + } + }) + } +} + func TestForwarderTCPFallback(t *testing.T) { const domain = "large-dns-response.tailscale.com." @@ -569,7 +980,10 @@ func TestForwarderTCPFallback(t *testing.T) { } }) - resp := mustRunTestQuery(t, request, beVerbose, port) + resp, err := runTestQueryWithFamily(t, request, "tcp", beVerbose, port) + if err != nil { + t.Fatalf("error making request: %v", err) + } if !bytes.Equal(resp, largeResponse) { t.Errorf("invalid response\ngot: %+v\nwant: %+v", resp, largeResponse) } @@ -636,17 +1050,13 @@ func TestForwarderTCPFallbackDisabled(t *testing.T) { resp := mustRunTestQuery(t, request, func(fwd *forwarder) { fwd.verboseFwd = true - // Disable retries for this test. - fwd.controlKnobs = &controlknobs.Knobs{} - fwd.controlKnobs.DisableDNSForwarderTCPRetries.Store(true) + setupForwarderWithTCPRetriesDisabled()(fwd) }, port) wantResp := append([]byte(nil), largeResponse[:maxResponseBytes]...) // Set the truncated flag on the expected response, since that's what we expect. - flags := binary.BigEndian.Uint16(wantResp[2:4]) - flags |= dnsFlagTruncated - binary.BigEndian.PutUint16(wantResp[2:4], flags) + setTCFlagInPacket(wantResp) if !bytes.Equal(resp, wantResp) { t.Errorf("invalid response\ngot (%d): %+v\nwant (%d): %+v", len(resp), resp, len(wantResp), wantResp) @@ -664,7 +1074,7 @@ func TestForwarderTCPFallbackError(t *testing.T) { response := makeTestResponse(t, domain, dns.RCodeServerFailure) // Our request is a single A query for the domain in the answer, above. - request := makeTestRequest(t, domain) + request := makeTestRequest(t, domain, dns.TypeA, 0) var sawRequest atomic.Bool port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { @@ -695,7 +1105,7 @@ func TestForwarderTCPFallbackError(t *testing.T) { // returns a successful response, we propagate it. func TestForwarderWithManyResolvers(t *testing.T) { const domain = "example.com." - request := makeTestRequest(t, domain) + request := makeTestRequest(t, domain, dns.TypeA, 0) tests := []struct { name string @@ -837,20 +1247,7 @@ func TestNXDOMAINIncludesQuestion(t *testing.T) { }() // Our request is a single PTR query for the domain in the answer, above. - request := func() []byte { - builder := dns.NewBuilder(nil, dns.Header{}) - builder.StartQuestions() - builder.Question(dns.Question{ - Name: dns.MustNewName(domain), - Type: dns.TypePTR, - Class: dns.ClassINET, - }) - request, err := builder.Finish() - if err != nil { - t.Fatal(err) - } - return request - }() + request := makeTestRequest(t, domain, dns.TypePTR, 0) port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { }) @@ -868,7 +1265,7 @@ func TestNXDOMAINIncludesQuestion(t *testing.T) { func TestForwarderVerboseLogs(t *testing.T) { const domain = "test.tailscale.com." response := makeTestResponse(t, domain, dns.RCodeServerFailure) - request := makeTestRequest(t, domain) + request := makeTestRequest(t, domain, dns.TypeA, 0) port := runDNSServer(t, nil, response, func(isTCP bool, gotRequest []byte) { if !bytes.Equal(request, gotRequest) { diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index f71c1b7708b4c..5b44f6c2d586f 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -337,7 +337,12 @@ func (r *Resolver) Query(ctx context.Context, bs []byte, family string, from net return (<-responses).bs, nil } - return out, err + if err != nil { + return out, err + } + + out = checkResponseSizeAndSetTC(out, bs, family, r.logf) + return out, nil } // GetUpstreamResolvers returns the resolvers that would be used to resolve diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index 712fa88dcad82..8ee22dd1384c0 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -1572,3 +1572,102 @@ func TestServfail(t *testing.T) { t.Errorf("response was %X, want %X", pkt, wantPkt) } } + +// TestLocalResponseTCFlagIntegration tests that checkResponseSizeAndSetTC is +// correctly applied to local DNS responses through the Resolver.Query integration path. +// This complements the unit test in forwarder_test.go by verifying the end-to-end behavior. +func TestLocalResponseTCFlagIntegration(t *testing.T) { + r := newResolver(t) + defer r.Close() + + r.SetConfig(dnsCfg) + + tests := []struct { + name string + query []byte + family string + wantTCSet bool + desc string + }{ + { + name: "UDP_small_local_response_no_TC", + query: dnspacket("test1.ipn.dev.", dns.TypeA, noEdns), + family: "udp", + wantTCSet: false, + desc: "Small local response (< 512 bytes) should not have TC flag set", + }, + { + name: "TCP_local_response_no_TC", + query: dnspacket("test1.ipn.dev.", dns.TypeA, noEdns), + family: "tcp", + wantTCSet: false, + desc: "TCP queries should skip TC flag setting (even for large responses)", + }, + { + name: "UDP_EDNS_request_small_response", + query: dnspacket("test1.ipn.dev.", dns.TypeA, 1500), + family: "udp", + wantTCSet: false, + desc: "Small response with EDNS request should not have TC flag set", + }, + { + name: "UDP_IPv6_response_no_TC", + query: dnspacket("test2.ipn.dev.", dns.TypeAAAA, noEdns), + family: "udp", + wantTCSet: false, + desc: "Small IPv6 local response should not have TC flag set", + }, + { + name: "UDP_reverse_lookup_no_TC", + query: dnspacket("4.3.2.1.in-addr.arpa.", dns.TypePTR, noEdns), + family: "udp", + wantTCSet: false, + desc: "Small reverse lookup response should not have TC flag set", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + response, err := r.Query(context.Background(), tt.query, tt.family, netip.AddrPort{}) + if err != nil { + t.Fatalf("Query failed: %v", err) + } + + if len(response) < headerBytes { + t.Fatalf("Response too small: %d bytes", len(response)) + } + + hasTC := truncatedFlagSet(response) + if hasTC != tt.wantTCSet { + t.Errorf("%s: TC flag = %v, want %v (response size=%d bytes)", tt.desc, hasTC, tt.wantTCSet, len(response)) + } + + // Verify response is valid by parsing it (if possible) + // Note: unpackResponse may not support all record types (e.g., PTR) + parsed, err := unpackResponse(response) + if err == nil { + // Verify the truncated field in parsed response matches the flag + if parsed.truncated != hasTC { + t.Errorf("Parsed truncated field (%v) doesn't match TC flag (%v)", parsed.truncated, hasTC) + } + } else { + // For unsupported types, just verify we can parse the header + var parser dns.Parser + h, err := parser.Start(response) + if err != nil { + t.Errorf("Failed to parse DNS header: %v", err) + } else { + // Verify header truncated flag matches + if h.Truncated != hasTC { + t.Errorf("Header truncated field (%v) doesn't match TC flag (%v)", h.Truncated, hasTC) + } + } + } + + // Verify response size is reasonable (local responses are typically small) + if len(response) > 1000 { + t.Logf("Warning: Local response is unusually large: %d bytes", len(response)) + } + }) + } +} From 274ab995d29a9c0a9160b0534a20b9f6db29726e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 1 Feb 2026 13:09:33 -0800 Subject: [PATCH 056/202] go.toolchain.*: bump our Go 1.25 and Go 1.26 toolchains Go1.25 for tailscale/go#149 Go1.26 for tailscale/go#149 + upstream release-branch.go1.26 work since rc2. Updates tailscale/go#149 Change-Id: Ib56b5b5119f181c4a81d4b599b8bbdb405ee6704 Signed-off-by: Brad Fitzpatrick --- go.toolchain.next.rev | 2 +- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.toolchain.next.rev b/go.toolchain.next.rev index ee8816b6ff3dd..be0f53a9c5a18 100644 --- a/go.toolchain.next.rev +++ b/go.toolchain.next.rev @@ -1 +1 @@ -07d023ba9bb6d17a84b492f1524fabfa69a31bda +64a6cb4cba579e2865654747d4d672ead07b8375 diff --git a/go.toolchain.rev b/go.toolchain.rev index 930ed5ad251a9..cb3fa64623175 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -799b25336eeb52e2f8b4521fba5870c2ad2d9f43 +779d878b6a943cecd2f359699001a03d7cedf222 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index ae95ed0ff869d..3b8ce70f3ac31 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-27ymqBnopujAGo02TZ5IPX8bVkp+rLTuVSn/QzZufJc= +sha256-e081DbI45vGMmi3drwqz2UOxRwffEuEDSVZupDtOVuk= From abdbca47af098469fba238c408dd1f4b373d254c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neal=20Gompa=20=28=E3=83=8B=E3=83=BC=E3=83=AB=E3=83=BB?= =?UTF-8?q?=E3=82=B4=E3=83=B3=E3=83=91=29?= Date: Mon, 2 Feb 2026 11:49:44 -0500 Subject: [PATCH 057/202] client/systray: Update systemd unit to use correct dependencies (#18457) This ensures that D-Bus is active for the unit and will correctly shut down when the default target ends. Fixes: https://github.com/tailscale/tailscale/issues/18458 Signed-off-by: Neal Gompa --- client/systray/tailscale-systray.service | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/systray/tailscale-systray.service b/client/systray/tailscale-systray.service index 01d0b383c0634..bc4b470043bf7 100644 --- a/client/systray/tailscale-systray.service +++ b/client/systray/tailscale-systray.service @@ -1,6 +1,9 @@ [Unit] Description=Tailscale System Tray -After=graphical.target +Documentation=https://tailscale.com/kb/1597/linux-systray +Requires=dbus.service +After=dbus.service +PartOf=default.target [Service] Type=simple From 8736fbb754e7f6ce1cc391b7013ce7e184504faa Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 30 Jan 2026 13:59:09 -0800 Subject: [PATCH 058/202] cmd/tailscale/cli: add 'wait' listening subcommand and ip --assert= This provides a mechanism to block, waiting for Tailscale's IP to be ready for a bind/listen, to gate the starting of other services. It also adds a new --assert=[IP] option to "tailscale ip", for services that want extra paranoia about what IP is in use, if they're worried about having switched to the wrong tailnet prior to reboot or something. Updates #3340 Updates #11504 ... and many more, IIRC Change-Id: I88ab19ac5fae58fd8c516065bab685e292395565 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 1 + cmd/tailscale/cli/ip.go | 16 +++- cmd/tailscale/cli/wait.go | 157 +++++++++++++++++++++++++++++++++++++ cmd/tailscale/depaware.txt | 1 + 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 cmd/tailscale/cli/wait.go diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index b8ac768746d27..07c7656dfa875 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -278,6 +278,7 @@ change in the future. configureHostCmd(), systrayCmd, appcRoutesCmd, + waitCmd, ), FlagSet: rootfs, Exec: func(ctx context.Context, args []string) error { diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go index 01373a073b169..7159904c732d6 100644 --- a/cmd/tailscale/cli/ip.go +++ b/cmd/tailscale/cli/ip.go @@ -25,14 +25,16 @@ var ipCmd = &ffcli.Command{ fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address") fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address") fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address") + fs.StringVar(&ipArgs.assert, "assert", "", "assert that one of the node's IP(s) matches this IP address") return fs })(), } var ipArgs struct { - want1 bool - want4 bool - want6 bool + want1 bool + want4 bool + want6 bool + assert string } func runIP(ctx context.Context, args []string) error { @@ -62,6 +64,14 @@ func runIP(ctx context.Context, args []string) error { return err } ips := st.TailscaleIPs + if ipArgs.assert != "" { + for _, ip := range ips { + if ip.String() == ipArgs.assert { + return nil + } + } + return fmt.Errorf("assertion failed: IP %q not found among %v", ipArgs.assert, ips) + } if of != "" { ip, _, err := tailscaleIPFromArg(ctx, of) if err != nil { diff --git a/cmd/tailscale/cli/wait.go b/cmd/tailscale/cli/wait.go new file mode 100644 index 0000000000000..ce9c6aba65b40 --- /dev/null +++ b/cmd/tailscale/cli/wait.go @@ -0,0 +1,157 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "context" + "flag" + "fmt" + "net" + "net/netip" + "strings" + "time" + + "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/ipn" + "tailscale.com/types/logger" + "tailscale.com/util/backoff" +) + +var waitCmd = &ffcli.Command{ + Name: "wait", + ShortHelp: "Wait for Tailscale interface/IPs to be ready for binding", + LongHelp: strings.TrimSpace(` +Wait for Tailscale resources to be available. As of 2026-01-02, the only +resource that's available to wait for by is the Tailscale interface and its +IPs. + +With no arguments, this command will block until tailscaled is up, its backend is running, +and the Tailscale interface is up and has a Tailscale IP address assigned to it. + +If running in userspace-networking mode, this command only waits for tailscaled and +the Running state, as no physical network interface exists. + +A future version of this command may support waiting for other types of resources. + +The command returns exit code 0 on success, and non-zero on failure or timeout. + +To wait on a specific type of IP address, use 'tailscale ip' in combination with +the 'tailscale wait' command. For example, to wait for an IPv4 address: + + tailscale wait && tailscale ip --assert= + +Linux systemd users can wait for the "tailscale-online.target" target, which runs +this command. + +More generally, a service that wants to bind to (listen on) a Tailscale interface or IP address +can run it like 'tailscale wait && /path/to/service [...]' to ensure that Tailscale is ready +before the program starts. +`), + + ShortUsage: "tailscale wait", + Exec: runWait, + FlagSet: (func() *flag.FlagSet { + fs := newFlagSet("wait") + fs.DurationVar(&waitArgs.timeout, "timeout", 0, "how long to wait before giving up (0 means wait indefinitely)") + return fs + })(), +} + +var waitArgs struct { + timeout time.Duration +} + +func runWait(ctx context.Context, args []string) error { + if len(args) > 0 { + return fmt.Errorf("unexpected arguments: %q", args) + } + if waitArgs.timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, waitArgs.timeout) + defer cancel() + } + + bo := backoff.NewBackoff("wait", logger.Discard, 2*time.Second) + for { + _, err := localClient.StatusWithoutPeers(ctx) + bo.BackOff(ctx, err) + if err == nil { + break + } + if ctx.Err() != nil { + return ctx.Err() + } + } + + watcher, err := localClient.WatchIPNBus(ctx, ipn.NotifyInitialState) + if err != nil { + return err + } + defer watcher.Close() + var firstIP netip.Addr + for { + not, err := watcher.Next() + if err != nil { + return err + } + if not.State != nil && *not.State == ipn.Running { + + st, err := localClient.StatusWithoutPeers(ctx) + if err != nil { + return err + } + if len(st.TailscaleIPs) > 0 { + firstIP = st.TailscaleIPs[0] + break + } + } + } + + st, err := localClient.StatusWithoutPeers(ctx) + if err != nil { + return err + } + if !st.TUN { + // No TUN; nothing more to wait for. + return nil + } + + // Verify we have an interface using that IP. + for { + err := checkForInterfaceIP(firstIP) + if err == nil { + return nil + } + bo.BackOff(ctx, err) + if ctx.Err() != nil { + return ctx.Err() + } + } +} + +func checkForInterfaceIP(ip netip.Addr) error { + ifs, err := net.Interfaces() + if err != nil { + return err + } + for _, ifi := range ifs { + addrs, err := ifi.Addrs() + if err != nil { + return err + } + for _, addr := range addrs { + var aip netip.Addr + switch v := addr.(type) { + case *net.IPNet: + aip, _ = netip.AddrFromSlice(v.IP) + case *net.IPAddr: + aip, _ = netip.AddrFromSlice(v.IP) + } + if aip.Unmap() == ip { + return nil + } + } + } + return fmt.Errorf("no interface has IP %v", ip) +} diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index b148423750b97..85bf2312a5f0f 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -251,6 +251,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/structs from tailscale.com/ipn+ tailscale.com/types/tkatype from tailscale.com/types/key+ tailscale.com/types/views from tailscale.com/tailcfg+ + tailscale.com/util/backoff from tailscale.com/cmd/tailscale/cli tailscale.com/util/cibuild from tailscale.com/health+ tailscale.com/util/clientmetric from tailscale.com/net/netcheck+ tailscale.com/util/cloudenv from tailscale.com/net/dnscache+ From ae95d8d22231a6e24e11bf44eadaf48b9a62aa3d Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Mon, 2 Feb 2026 15:38:40 -0800 Subject: [PATCH 059/202] cmd/tailscale: fix sanitizeOutput and add a test (#18589) Follow up from https://github.com/tailscale/tailscale/pull/18563 which I totally botched. Updates #18562 Signed-off-by: Andrew Lytvynov --- cmd/tailscale/cli/cli.go | 6 +++--- cmd/tailscale/cli/cli_test.go | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 07c7656dfa875..dca7559cf1923 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -581,11 +581,11 @@ type sanitizeWriter struct { w io.Writer } -var reTskey = regexp.MustCompile(`tskey-\w+`) +var rxTskey = regexp.MustCompile(`tskey-[\w-]+`) func (w sanitizeWriter) Write(buf []byte) (int, error) { - sanitized := reTskey.ReplaceAll(buf, []byte("tskey-REDACTED")) - diff := len(buf) - len(sanitized) + sanitized := rxTskey.ReplaceAll(buf, []byte("tskey-REDACTED")) + diff := len(sanitized) - len(buf) n, err := w.w.Write(sanitized) return n - diff, err } diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 370b730af8f35..ac6a94d52f88d 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -1799,3 +1799,21 @@ func TestDepsNoCapture(t *testing.T) { }.Check(t) } + +func TestSanitizeWriter(t *testing.T) { + buf := new(bytes.Buffer) + w := sanitizeOutput(buf) + + in := []byte(`my auth key is tskey-auth-abc123-def456, what's yours?`) + want := []byte(`my auth key is tskey-REDACTED, what's yours?`) + n, err := w.Write(in) + if err != nil { + t.Fatal(err) + } + if n != len(in) { + t.Errorf("unexpected write length %d, want %d", n, len(in)) + } + if got := buf.Bytes(); !bytes.Equal(got, want) { + t.Errorf("unexpected sanitized content\ngot: %q\nwant: %q", got, want) + } +} From f2b4d7065dbf18a2171205965dfde06b8051a034 Mon Sep 17 00:00:00 2001 From: David Bond Date: Tue, 3 Feb 2026 11:16:59 +0000 Subject: [PATCH 060/202] cmd/containerboot: handle v6 pod ips that are missing square brackets (#18519) This commit fixes an issue within containerboot that arose from the kubernetes operator. When users enable metrics on custom resources that are running on dual stack or ipv6 only clusters, they end up with an error as we pass the hostport combintation using $(POD_IP):PORT. In go, `netip.ParseAddrPort` expects square brackets `[]` to wrap the host portion of an ipv6 address and would naturally, crash. When loading the containerboot configuration from the environment we now check if the `TS_LOCAL_ADDR_PORT` value contains the pod's v6 ip address. If it does & does not already contain brackets, we add the brackets in. Closes: #15762 Closes: #15467 Signed-off-by: David Bond --- cmd/containerboot/settings.go | 12 ++++++++++++ cmd/containerboot/settings_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/cmd/containerboot/settings.go b/cmd/containerboot/settings.go index c35fc14079d85..181a94dd71114 100644 --- a/cmd/containerboot/settings.go +++ b/cmd/containerboot/settings.go @@ -126,6 +126,7 @@ func configFromEnv() (*settings, error) { IngressProxiesCfgPath: defaultEnv("TS_INGRESS_PROXIES_CONFIG_PATH", ""), PodUID: defaultEnv("POD_UID", ""), } + podIPs, ok := os.LookupEnv("POD_IPS") if ok { ips := strings.Split(podIPs, ",") @@ -144,6 +145,7 @@ func configFromEnv() (*settings, error) { cfg.PodIPv6 = parsed.String() } } + // If cert share is enabled, set the replica as read or write. Only 0th // replica should be able to write. isInCertShareMode := defaultBool("TS_EXPERIMENTAL_CERT_SHARE", false) @@ -165,9 +167,19 @@ func configFromEnv() (*settings, error) { cfg.AcceptDNS = &acceptDNSNew } + // In Kubernetes clusters, people like to use the "$(POD_IP):PORT" combination to configure the TS_LOCAL_ADDR_PORT + // environment variable (we even do this by default in the operator when enabling metrics), leading to a v6 address + // and port combo we cannot parse, as netip.ParseAddrPort expects the host segment to be enclosed in square brackets. + // We perform a check here to see if TS_LOCAL_ADDR_PORT is using the pod's IPv6 address and is not using brackets, + // adding the brackets in if need be. + if cfg.PodIPv6 != "" && strings.Contains(cfg.LocalAddrPort, cfg.PodIPv6) && !strings.ContainsAny(cfg.LocalAddrPort, "[]") { + cfg.LocalAddrPort = strings.Replace(cfg.LocalAddrPort, cfg.PodIPv6, "["+cfg.PodIPv6+"]", 1) + } + if err := cfg.validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %v", err) } + return cfg, nil } diff --git a/cmd/containerboot/settings_test.go b/cmd/containerboot/settings_test.go index 5fa0c7dd10724..eca50101b6c70 100644 --- a/cmd/containerboot/settings_test.go +++ b/cmd/containerboot/settings_test.go @@ -6,6 +6,7 @@ package main import ( + "net/netip" "strings" "testing" ) @@ -226,3 +227,30 @@ func TestValidateAuthMethods(t *testing.T) { }) } } + +func TestHandlesKubeIPV6(t *testing.T) { + t.Setenv("TS_LOCAL_ADDR_PORT", "fd7a:115c:a1e0::6c34:352:9002") + t.Setenv("POD_IPS", "fd7a:115c:a1e0::6c34:352") + + cfg, err := configFromEnv() + if err != nil { + t.Fatal(err) + } + + if cfg.LocalAddrPort != "[fd7a:115c:a1e0::6c34:352]:9002" { + t.Errorf("LocalAddrPort is not set correctly") + } + + parsed, err := netip.ParseAddrPort(cfg.LocalAddrPort) + if err != nil { + t.Fatal(err) + } + + if !parsed.Addr().Is6() { + t.Errorf("expected v6 address but got %s", parsed) + } + + if parsed.Port() != 9002 { + t.Errorf("expected port 9002 but got %d", parsed.Port()) + } +} From 77f5200164378213da4a9eda6de3a5801472a297 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Tue, 3 Feb 2026 14:12:38 +0000 Subject: [PATCH 061/202] cmd/k8s-operator,k8s-operator:ensure that recorder replicas default to 1 (#18375) Updates #17965 Signed-off-by: chaosinthecrd --- cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml | 1 + cmd/k8s-operator/deploy/manifests/operator.yaml | 1 + k8s-operator/api.md | 2 +- k8s-operator/apis/v1alpha1/types_recorder.go | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml index 28d2be78e509c..ca43a72a557f2 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_recorders.yaml @@ -72,6 +72,7 @@ spec: description: Replicas specifies how many instances of tsrecorder to run. Defaults to 1. type: integer format: int32 + default: 1 minimum: 0 statefulSet: description: |- diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index 4c9822847d677..b31e45eb7befc 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -3355,6 +3355,7 @@ spec: Required if S3 storage is not set up, to ensure that recordings are accessible. type: boolean replicas: + default: 1 description: Replicas specifies how many instances of tsrecorder to run. Defaults to 1. format: int32 minimum: 0 diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 31f351013164a..51a354b925574 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -904,7 +904,7 @@ _Appears in:_ | `tags` _[Tags](#tags)_ | Tags that the Tailscale device will be tagged with. Defaults to [tag:k8s].
If you specify custom tags here, make sure you also make the operator
an owner of these tags.
See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.
Tags cannot be changed once a Recorder node has been created.
Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$. | | Pattern: `^tag:[a-zA-Z][a-zA-Z0-9-]*$`
Type: string
| | `enableUI` _boolean_ | Set to true to enable the Recorder UI. The UI lists and plays recorded sessions.
The UI will be served at :443. Defaults to false.
Corresponds to --ui tsrecorder flag https://tailscale.com/kb/1246/tailscale-ssh-session-recording#deploy-a-recorder-node.
Required if S3 storage is not set up, to ensure that recordings are accessible. | | | | `storage` _[Storage](#storage)_ | Configure where to store session recordings. By default, recordings will
be stored in a local ephemeral volume, and will not be persisted past the
lifetime of a specific pod. | | | -| `replicas` _integer_ | Replicas specifies how many instances of tsrecorder to run. Defaults to 1. | | Minimum: 0
| +| `replicas` _integer_ | Replicas specifies how many instances of tsrecorder to run. Defaults to 1. | 1 | Minimum: 0
| | `tailnet` _string_ | Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this
name must match that of a valid Tailnet resource. This field is immutable and cannot be changed once set. | | | diff --git a/k8s-operator/apis/v1alpha1/types_recorder.go b/k8s-operator/apis/v1alpha1/types_recorder.go index 6cc5e3dd572c9..284c3b0ae48f4 100644 --- a/k8s-operator/apis/v1alpha1/types_recorder.go +++ b/k8s-operator/apis/v1alpha1/types_recorder.go @@ -80,6 +80,7 @@ type RecorderSpec struct { // Replicas specifies how many instances of tsrecorder to run. Defaults to 1. // +optional // +kubebuilder:validation:Minimum=0 + // +kubebuilder:default=1 Replicas *int32 `json:"replicas,omitzero"` // Tailnet specifies the tailnet this Recorder should join. If blank, the default tailnet is used. When set, this From 14322713a5847176dcc29b8970d71a9ef4361713 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Tue, 3 Feb 2026 07:55:41 -0800 Subject: [PATCH 062/202] ipn/ipnlocal/netmapcache: ensure cache updates preserve unchanged data (#18590) Found by @cmol. When rewriting the same value into the cache, we were dropping the unchanged keys, resulting in the cache being pruned incorrectly. Also update the tests to catch this. Updates #12639 Change-Id: Iab67e444eb7ddc22ccc680baa2f6a741a00eb325 Signed-off-by: M. J. Fromberger --- ipn/ipnlocal/netmapcache/netmapcache.go | 1 + ipn/ipnlocal/netmapcache/netmapcache_test.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ipn/ipnlocal/netmapcache/netmapcache.go b/ipn/ipnlocal/netmapcache/netmapcache.go index d5706f9b773ac..b12443b99f473 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache.go +++ b/ipn/ipnlocal/netmapcache/netmapcache.go @@ -86,6 +86,7 @@ func (c *Cache) writeJSON(ctx context.Context, key string, v any) error { // this at all? last, ok := c.lastWrote[key] if ok && cacheDigest(j) == last.digest { + c.wantKeys.Add(key) return nil } diff --git a/ipn/ipnlocal/netmapcache/netmapcache_test.go b/ipn/ipnlocal/netmapcache/netmapcache_test.go index 437015ccc53e8..b31db2d5eb8b5 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache_test.go +++ b/ipn/ipnlocal/netmapcache/netmapcache_test.go @@ -10,6 +10,7 @@ import ( "flag" "fmt" "iter" + "maps" "os" "reflect" "slices" @@ -181,6 +182,24 @@ func TestRoundTrip(t *testing.T) { }) } + + t.Run("Twice", func(t *testing.T) { + // Verify that storing the same network map twice results in no change. + + s := make(testStore) + c := netmapcache.NewCache(s) + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store 1 netmap failed: %v", err) + } + scp := maps.Clone(s) // for comparison, see below + + if err := c.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store 2 netmap failed; %v", err) + } + if diff := cmp.Diff(s, scp); diff != "" { + t.Errorf("Updated store (-got, +want):\n%s", diff) + } + }) } func TestInvalidCache(t *testing.T) { From 7b96c4c23e76beaf4c78ff1d8f96e11e0b07863a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 2 Feb 2026 17:11:01 -0800 Subject: [PATCH 063/202] cmd/testwrapper: support experimental -cachelink Updates tailscale/go#149 Change-Id: If0483466eb1fc2196838c75f6d53925b1809abff Signed-off-by: Brad Fitzpatrick --- cmd/testwrapper/args.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cmd/testwrapper/args.go b/cmd/testwrapper/args.go index 350197d4f1271..22e5d4c902f8e 100644 --- a/cmd/testwrapper/args.go +++ b/cmd/testwrapper/args.go @@ -8,6 +8,7 @@ import ( "io" "os" "slices" + "strconv" "strings" "testing" ) @@ -60,6 +61,9 @@ func splitArgs(args []string) (pre, pkgs, post []string, _ error) { return nil, nil, nil, err } fs.Visit(func(f *flag.Flag) { + if f.Name == "cachelink" && !cacheLink.enabled { + return + } if f.Value.String() != f.DefValue && f.DefValue != "false" { pre = append(pre, "-"+f.Name, f.Value.String()) } else { @@ -79,6 +83,37 @@ func splitArgs(args []string) (pre, pkgs, post []string, _ error) { return pre, pkgs, post, nil } +// cacheLink is whether the -cachelink flag is enabled. +// +// The -cachelink flag is Tailscale-specific addition to the "go test" command; +// see https://github.com/tailscale/go/issues/149 and +// https://github.com/golang/go/issues/77349. +// +// In that PR, it's only a boolean, but we implement a custom flag type +// so we can support -cachelink=auto, which enables cachelink if GOCACHEPROG +// is set, which is a behavior we want in our CI environment. +var cacheLink cacheLinkVal + +type cacheLinkVal struct { + enabled bool +} + +func (c *cacheLinkVal) String() string { + return strconv.FormatBool(c.enabled) +} + +func (c *cacheLinkVal) Set(s string) error { + if s == "auto" { + c.enabled = os.Getenv("GOCACHEPROG") != "" + return nil + } + var err error + c.enabled, err = strconv.ParseBool(s) + return err +} + +func (*cacheLinkVal) IsBoolFlag() bool { return true } + func newTestFlagSet() *flag.FlagSet { fs := flag.NewFlagSet("testwrapper", flag.ContinueOnError) fs.SetOutput(io.Discard) @@ -90,6 +125,8 @@ func newTestFlagSet() *flag.FlagSet { fs.String("exec", "", "Command to run tests with") fs.Bool("race", false, "build with race detector") fs.String("vet", "", "vet checks to run, or 'off' or 'all'") + + fs.Var(&cacheLink, "cachelink", "Go -cachelink value (bool); or 'auto' to enable if GOCACHEPROG is set") return fs } From 54d70c831226010cf12a388ec69dee333c4eb915 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Tue, 3 Feb 2026 12:57:05 -0800 Subject: [PATCH 064/202] clientupdate: best-effort restart of tailscaled on init.d systems (#18568) Not all Linux distros use systemd yet, for example GL.iNet KVM devices use busybox's init, which is similar to SysV init. This is a best-effort restart attempt after the update, it probably won't cover 100% of init.d setups out there. Fixes #18567 Signed-off-by: Andrew Lytvynov --- clientupdate/clientupdate.go | 58 +++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index e75e425a455b8..09f9d0be1787d 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -11,11 +11,11 @@ import ( "bufio" "bytes" "compress/gzip" - "context" "encoding/json" "errors" "fmt" "io" + "io/fs" "maps" "net/http" "os" @@ -27,6 +27,7 @@ import ( "strconv" "strings" + "tailscale.com/envknob" "tailscale.com/feature" "tailscale.com/hostinfo" "tailscale.com/types/lazy" @@ -288,6 +289,10 @@ func Update(args Arguments) error { } func (up *Updater) confirm(ver string) bool { + if envknob.Bool("TS_UPDATE_SKIP_VERSION_CHECK") { + up.Logf("current version: %v, latest version %v; forcing an update due to TS_UPDATE_SKIP_VERSION_CHECK", up.currentVersion, ver) + return true + } // Only check version when we're not switching tracks. if up.Track == "" || up.Track == CurrentTrack { switch c := cmpver.Compare(up.currentVersion, ver); { @@ -865,12 +870,17 @@ func (up *Updater) updateLinuxBinary() error { if err := os.Remove(dlPath); err != nil { up.Logf("failed to clean up %q: %v", dlPath, err) } - if err := restartSystemdUnit(context.Background()); err != nil { + + err = restartSystemdUnit(up.Logf) + if errors.Is(err, errors.ErrUnsupported) { + err = restartInitD() if errors.Is(err, errors.ErrUnsupported) { - up.Logf("Tailscale binaries updated successfully.\nPlease restart tailscaled to finish the update.") - } else { - up.Logf("Tailscale binaries updated successfully, but failed to restart tailscaled: %s.\nPlease restart tailscaled to finish the update.", err) + err = errors.New("tailscaled is not running under systemd or init.d") } + } + if err != nil { + up.Logf("Tailscale binaries updated successfully, but failed to restart tailscaled: %s.\nPlease restart tailscaled to finish the update.", err) + } else { up.Logf("Success") } @@ -878,13 +888,13 @@ func (up *Updater) updateLinuxBinary() error { return nil } -func restartSystemdUnit(ctx context.Context) error { +func restartSystemdUnit(logf logger.Logf) error { if _, err := exec.LookPath("systemctl"); err != nil { // Likely not a systemd-managed distro. return errors.ErrUnsupported } if out, err := exec.Command("systemctl", "daemon-reload").CombinedOutput(); err != nil { - return fmt.Errorf("systemctl daemon-reload failed: %w\noutput: %s", err, out) + logf("systemctl daemon-reload failed: %w\noutput: %s", err, out) } if out, err := exec.Command("systemctl", "restart", "tailscaled.service").CombinedOutput(); err != nil { return fmt.Errorf("systemctl restart failed: %w\noutput: %s", err, out) @@ -892,6 +902,40 @@ func restartSystemdUnit(ctx context.Context) error { return nil } +// restartInitD attempts best-effort restart of tailscale on init.d systems +// (for example, GL.iNet KVM devices running busybox). It returns +// errors.ErrUnsupported if the expected service script is not found. +// +// There's probably a million variations of init.d configs out there, and this +// function does not intend to support all of them. +func restartInitD() error { + const initDir = "/etc/init.d/" + files, err := os.ReadDir(initDir) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return errors.ErrUnsupported + } + return err + } + for _, f := range files { + // Skip anything other than regular files. + if !f.Type().IsRegular() { + continue + } + // The script will be called something like /etc/init.d/tailscale or + // /etc/init.d/S99tailscale. + if n := f.Name(); strings.HasSuffix(n, "tailscale") { + path := filepath.Join(initDir, n) + if out, err := exec.Command(path, "restart").CombinedOutput(); err != nil { + return fmt.Errorf("%q failed: %w\noutput: %s", path+" restart", err, out) + } + return nil + } + } + // Init script for tailscale not found. + return errors.ErrUnsupported +} + func (up *Updater) downloadLinuxTarball(ver string) (string, error) { dlDir, err := os.UserCacheDir() if err != nil { From 5edfa6f9a8b409908861172882de03e9a67f0c2f Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Tue, 3 Feb 2026 16:08:36 -0500 Subject: [PATCH 065/202] ipn/ipnlocal: add wildcard TLS certificate support for subdomains (#18356) When the NodeAttrDNSSubdomainResolve capability is present, enable wildcard certificate issuance to cover all single-level subdomains of a node's CertDomain. Without the capability, only exact CertDomain matches are allowed, so node.ts.net yields a cert for node.ts.net. With the capability, we now generate wildcard certificates. Wildcard certs include both the wildcard and base domain in their SANs, and ACME authorization requests both identifiers. The cert filenames are kept still based on the base domain with the wildcard prefix stripped, so we aren't creating separate files. DNS challenges still used the base domain The checkCertDomain function is replaced by resolveCertDomain that both validates and returns the appropriate cert domain to request. Name validation is now moved earlier into GetCertPEMWithValidity() Fixes #1196 Signed-off-by: Fernando Serboncini --- ipn/ipnlocal/cert.go | 135 ++++++++++++++++++------ ipn/ipnlocal/cert_test.go | 217 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 312 insertions(+), 40 deletions(-) diff --git a/ipn/ipnlocal/cert.go b/ipn/ipnlocal/cert.go index 764634d30c276..027e7c810778b 100644 --- a/ipn/ipnlocal/cert.go +++ b/ipn/ipnlocal/cert.go @@ -37,7 +37,6 @@ import ( "tailscale.com/feature/buildfeatures" "tailscale.com/hostinfo" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/store" "tailscale.com/ipn/store/mem" "tailscale.com/net/bakedroots" @@ -106,6 +105,13 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK // // If a cert is expired, or expires sooner than minValidity, it will be renewed // synchronously. Otherwise it will be renewed asynchronously. +// +// The domain must be one of: +// +// - An exact CertDomain (e.g., "node.ts.net") +// - A wildcard domain (e.g., "*.node.ts.net") +// +// The wildcard format requires the NodeAttrDNSSubdomainResolve capability. func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string, minValidity time.Duration) (*TLSCertKeyPair, error) { b.mu.Lock() getCertForTest := b.getCertForTest @@ -119,6 +125,13 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string if !validLookingCertDomain(domain) { return nil, errors.New("invalid domain") } + + certDomain, err := b.resolveCertDomain(domain) + if err != nil { + return nil, err + } + storageKey := strings.TrimPrefix(certDomain, "*.") + logf := logger.WithPrefix(b.logf, fmt.Sprintf("cert(%q): ", domain)) now := b.clock.Now() traceACME := func(v any) { @@ -134,13 +147,13 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string return nil, err } - if pair, err := getCertPEMCached(cs, domain, now); err == nil { + if pair, err := getCertPEMCached(cs, storageKey, now); err == nil { if envknob.IsCertShareReadOnlyMode() { return pair, nil } // If we got here, we have a valid unexpired cert. // Check whether we should start an async renewal. - shouldRenew, err := b.shouldStartDomainRenewal(cs, domain, now, pair, minValidity) + shouldRenew, err := b.shouldStartDomainRenewal(cs, storageKey, now, pair, minValidity) if err != nil { logf("error checking for certificate renewal: %v", err) // Renewal check failed, but the current cert is valid and not @@ -154,7 +167,7 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string logf("starting async renewal") // Start renewal in the background, return current valid cert. b.goTracker.Go(func() { - if _, err := getCertPEM(context.Background(), b, cs, logf, traceACME, domain, now, minValidity); err != nil { + if _, err := getCertPEM(context.Background(), b, cs, logf, traceACME, certDomain, now, minValidity); err != nil { logf("async renewal failed: getCertPem: %v", err) } }) @@ -169,7 +182,7 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string return nil, fmt.Errorf("retrieving cached TLS certificate failed and cert store is configured in read-only mode, not attempting to issue a new certificate: %w", err) } - pair, err := getCertPEM(ctx, b, cs, logf, traceACME, domain, now, minValidity) + pair, err := getCertPEM(ctx, b, cs, logf, traceACME, certDomain, now, minValidity) if err != nil { logf("getCertPEM: %v", err) return nil, err @@ -506,19 +519,24 @@ func getCertPEMCached(cs certStore, domain string, now time.Time) (p *TLSCertKey } // getCertPem checks if a cert needs to be renewed and if so, renews it. +// domain is the resolved cert domain (e.g., "*.node.ts.net" for wildcards). // It can be overridden in tests. var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf logger.Logf, traceACME func(any), domain string, now time.Time, minValidity time.Duration) (*TLSCertKeyPair, error) { acmeMu.Lock() defer acmeMu.Unlock() + // storageKey is used for file storage and renewal tracking. + // For wildcards, "*.node.ts.net" -> "node.ts.net" + storageKey, isWildcard := strings.CutPrefix(domain, "*.") + // In case this method was triggered multiple times in parallel (when // serving incoming requests), check whether one of the other goroutines // already renewed the cert before us. - previous, err := getCertPEMCached(cs, domain, now) + previous, err := getCertPEMCached(cs, storageKey, now) if err == nil { // shouldStartDomainRenewal caches its result so it's OK to call this // frequently. - shouldRenew, err := b.shouldStartDomainRenewal(cs, domain, now, previous, minValidity) + shouldRenew, err := b.shouldStartDomainRenewal(cs, storageKey, now, previous, minValidity) if err != nil { logf("error checking for certificate renewal: %v", err) } else if !shouldRenew { @@ -561,12 +579,6 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l return nil, fmt.Errorf("unexpected ACME account status %q", a.Status) } - // Before hitting LetsEncrypt, see if this is a domain that Tailscale will do DNS challenges for. - st := b.StatusWithoutPeers() - if err := checkCertDomain(st, domain); err != nil { - return nil, err - } - // If we have a previous cert, include it in the order. Assuming we're // within the ARI renewal window this should exclude us from LE rate // limits. @@ -580,7 +592,18 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l opts = append(opts, acme.WithOrderReplacesCert(prevCrt)) } } - order, err := ac.AuthorizeOrder(ctx, []acme.AuthzID{{Type: "dns", Value: domain}}, opts...) + + // For wildcards, we need to authorize both the wildcard and base domain. + var authzIDs []acme.AuthzID + if isWildcard { + authzIDs = []acme.AuthzID{ + {Type: "dns", Value: domain}, + {Type: "dns", Value: storageKey}, + } + } else { + authzIDs = []acme.AuthzID{{Type: "dns", Value: domain}} + } + order, err := ac.AuthorizeOrder(ctx, authzIDs, opts...) if err != nil { return nil, err } @@ -598,7 +621,9 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l if err != nil { return nil, err } - key := "_acme-challenge." + domain + // For wildcards, the challenge is on the base domain. + // e.g., "*.node.ts.net" -> "_acme-challenge.node.ts.net" + key := "_acme-challenge." + strings.TrimPrefix(az.Identifier.Value, "*.") // Do a best-effort lookup to see if we've already created this DNS name // in a previous attempt. Don't burn too much time on it, though. Worst @@ -608,14 +633,14 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l txts, _ := resolver.LookupTXT(lookupCtx, key) lookupCancel() if slices.Contains(txts, rec) { - logf("TXT record already existed") + logf("TXT record already existed for %s", key) } else { - logf("starting SetDNS call...") + logf("starting SetDNS call for %s...", key) err = b.SetDNS(ctx, key, rec) if err != nil { return nil, fmt.Errorf("SetDNS %q => %q: %w", key, rec, err) } - logf("did SetDNS") + logf("did SetDNS for %s", key) } chal, err := ac.Accept(ctx, ch) @@ -672,19 +697,27 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l return nil, err } } - if err := cs.WriteTLSCertAndKey(domain, certPEM.Bytes(), privPEM.Bytes()); err != nil { + if err := cs.WriteTLSCertAndKey(storageKey, certPEM.Bytes(), privPEM.Bytes()); err != nil { return nil, err } - b.domainRenewed(domain) + b.domainRenewed(storageKey) return &TLSCertKeyPair{CertPEM: certPEM.Bytes(), KeyPEM: privPEM.Bytes()}, nil } -// certRequest generates a CSR for the given common name cn and optional SANs. -func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) { +// certRequest generates a CSR for the given domain and optional SANs. +func certRequest(key crypto.Signer, domain string, ext []pkix.Extension) ([]byte, error) { + dnsNames := []string{domain} + if base, ok := strings.CutPrefix(domain, "*."); ok { + // Wildcard cert must also include the base domain as a SAN. + // This is load-bearing: getCertPEMCached validates certs using + // the storage key (base domain), which only passes x509 verification + // if the base domain is in DNSNames. + dnsNames = append(dnsNames, base) + } req := &x509.CertificateRequest{ - Subject: pkix.Name{CommonName: name}, - DNSNames: []string{name}, + Subject: pkix.Name{CommonName: domain}, + DNSNames: dnsNames, ExtraExtensions: ext, } return x509.CreateCertificateRequest(rand.Reader, req, key) @@ -844,7 +877,7 @@ func isDefaultDirectoryURL(u string) bool { // we might be able to get a cert for. // // It's a light check primarily for double checking before it's used -// as part of a filesystem path. The actual validation happens in checkCertDomain. +// as part of a filesystem path. The actual validation happens in resolveCertDomain. func validLookingCertDomain(name string) bool { if name == "" || strings.Contains(name, "..") || @@ -852,22 +885,56 @@ func validLookingCertDomain(name string) bool { !strings.Contains(name, ".") { return false } + // Only allow * as a wildcard prefix "*.domain.tld" + if rest, ok := strings.CutPrefix(name, "*."); ok { + if strings.Contains(rest, "*") || !strings.Contains(rest, ".") { + return false + } + } else if strings.Contains(name, "*") { + return false + } return true } -func checkCertDomain(st *ipnstate.Status, domain string) error { +// resolveCertDomain validates a domain and returns the cert domain to use. +// +// - "node.ts.net" -> "node.ts.net" (exact CertDomain match) +// - "*.node.ts.net" -> "*.node.ts.net" (explicit wildcard, requires NodeAttrDNSSubdomainResolve) +// +// Subdomain requests like "app.node.ts.net" are rejected; callers should +// request "*.node.ts.net" explicitly for subdomain coverage. +func (b *LocalBackend) resolveCertDomain(domain string) (string, error) { if domain == "" { - return errors.New("missing domain name") + return "", errors.New("missing domain name") } - for _, d := range st.CertDomains { - if d == domain { - return nil + + // Read the netmap once to get both CertDomains and capabilities atomically. + nm := b.NetMap() + if nm == nil { + return "", errors.New("no netmap available") + } + certDomains := nm.DNS.CertDomains + if len(certDomains) == 0 { + return "", errors.New("your Tailscale account does not support getting TLS certs") + } + + // Wildcard request like "*.node.ts.net". + if base, ok := strings.CutPrefix(domain, "*."); ok { + if !nm.AllCaps.Contains(tailcfg.NodeAttrDNSSubdomainResolve) { + return "", fmt.Errorf("wildcard certificates are not enabled for this node") } + if !slices.Contains(certDomains, base) { + return "", fmt.Errorf("invalid domain %q; parent domain must be one of %q", domain, certDomains) + } + return domain, nil } - if len(st.CertDomains) == 0 { - return errors.New("your Tailscale account does not support getting TLS certs") + + // Exact CertDomain match. + if slices.Contains(certDomains, domain) { + return domain, nil } - return fmt.Errorf("invalid domain %q; must be one of %q", domain, st.CertDomains) + + return "", fmt.Errorf("invalid domain %q; must be one of %q", domain, certDomains) } // handleC2NTLSCertStatus returns info about the last TLS certificate issued for the @@ -884,7 +951,7 @@ func handleC2NTLSCertStatus(b *LocalBackend, w http.ResponseWriter, r *http.Requ return } - domain := r.FormValue("domain") + domain := strings.TrimPrefix(r.FormValue("domain"), "*.") if domain == "" { http.Error(w, "no 'domain'", http.StatusBadRequest) return diff --git a/ipn/ipnlocal/cert_test.go b/ipn/ipnlocal/cert_test.go index ec7be570c78f7..b8acb710ac7d3 100644 --- a/ipn/ipnlocal/cert_test.go +++ b/ipn/ipnlocal/cert_test.go @@ -17,17 +17,205 @@ import ( "math/big" "os" "path/filepath" + "slices" "testing" "time" "github.com/google/go-cmp/cmp" "tailscale.com/envknob" "tailscale.com/ipn/store/mem" + "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/util/must" + "tailscale.com/util/set" ) +func TestCertRequest(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("GenerateKey: %v", err) + } + + tests := []struct { + domain string + wantSANs []string + }{ + { + domain: "example.com", + wantSANs: []string{"example.com"}, + }, + { + domain: "*.example.com", + wantSANs: []string{"*.example.com", "example.com"}, + }, + { + domain: "*.foo.bar.com", + wantSANs: []string{"*.foo.bar.com", "foo.bar.com"}, + }, + } + + for _, tt := range tests { + t.Run(tt.domain, func(t *testing.T) { + csrDER, err := certRequest(key, tt.domain, nil) + if err != nil { + t.Fatalf("certRequest: %v", err) + } + csr, err := x509.ParseCertificateRequest(csrDER) + if err != nil { + t.Fatalf("ParseCertificateRequest: %v", err) + } + if csr.Subject.CommonName != tt.domain { + t.Errorf("CommonName = %q, want %q", csr.Subject.CommonName, tt.domain) + } + if !slices.Equal(csr.DNSNames, tt.wantSANs) { + t.Errorf("DNSNames = %v, want %v", csr.DNSNames, tt.wantSANs) + } + }) + } +} + +func TestResolveCertDomain(t *testing.T) { + tests := []struct { + name string + domain string + certDomains []string + hasCap bool + skipNetmap bool + want string + wantErr string + }{ + { + name: "exact_match", + domain: "node.ts.net", + certDomains: []string{"node.ts.net"}, + want: "node.ts.net", + }, + { + name: "exact_match_with_cap", + domain: "node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + want: "node.ts.net", + }, + { + name: "wildcard_with_cap", + domain: "*.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + want: "*.node.ts.net", + }, + { + name: "wildcard_without_cap", + domain: "*.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: false, + wantErr: "wildcard certificates are not enabled for this node", + }, + { + name: "subdomain_with_cap_rejected", + domain: "app.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "app.node.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "subdomain_without_cap_rejected", + domain: "app.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: false, + wantErr: `invalid domain "app.node.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "multi_level_subdomain_rejected", + domain: "a.b.node.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "a.b.node.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "wildcard_no_matching_parent", + domain: "*.unrelated.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "*.unrelated.ts.net"; parent domain must be one of ["node.ts.net"]`, + }, + { + name: "subdomain_unrelated_rejected", + domain: "app.unrelated.ts.net", + certDomains: []string{"node.ts.net"}, + hasCap: true, + wantErr: `invalid domain "app.unrelated.ts.net"; must be one of ["node.ts.net"]`, + }, + { + name: "no_cert_domains", + domain: "node.ts.net", + certDomains: nil, + wantErr: "your Tailscale account does not support getting TLS certs", + }, + { + name: "wildcard_no_cert_domains", + domain: "*.foo.ts.net", + certDomains: nil, + hasCap: true, + wantErr: "your Tailscale account does not support getting TLS certs", + }, + { + name: "empty_domain", + domain: "", + certDomains: []string{"node.ts.net"}, + wantErr: "missing domain name", + }, + { + name: "nil_netmap", + domain: "node.ts.net", + skipNetmap: true, + wantErr: "no netmap available", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := newTestLocalBackend(t) + + if !tt.skipNetmap { + // Set up netmap with CertDomains and capability + var allCaps set.Set[tailcfg.NodeCapability] + if tt.hasCap { + allCaps = set.Of(tailcfg.NodeAttrDNSSubdomainResolve) + } + b.mu.Lock() + b.currentNode().SetNetMap(&netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{}).View(), + DNS: tailcfg.DNSConfig{ + CertDomains: tt.certDomains, + }, + AllCaps: allCaps, + }) + b.mu.Unlock() + } + + got, err := b.resolveCertDomain(tt.domain) + if tt.wantErr != "" { + if err == nil { + t.Errorf("resolveCertDomain(%q) = %q, want error %q", tt.domain, got, tt.wantErr) + } else if err.Error() != tt.wantErr { + t.Errorf("resolveCertDomain(%q) error = %q, want %q", tt.domain, err.Error(), tt.wantErr) + } + return + } + if err != nil { + t.Errorf("resolveCertDomain(%q) error = %v, want nil", tt.domain, err) + return + } + if got != tt.want { + t.Errorf("resolveCertDomain(%q) = %q, want %q", tt.domain, got, tt.want) + } + }) + } +} + func TestValidLookingCertDomain(t *testing.T) { tests := []struct { in string @@ -40,6 +228,16 @@ func TestValidLookingCertDomain(t *testing.T) { {"", false}, {"foo\\bar.com", false}, {"foo\x00bar.com", false}, + // Wildcard tests + {"*.foo.com", true}, + {"*.foo.bar.com", true}, + {"*foo.com", false}, // must be *. + {"*.com", false}, // must have domain after *. + {"*.", false}, // must have domain after *. + {"*.*.foo.com", false}, // no nested wildcards + {"foo.*.bar.com", false}, // no wildcard mid-string + {"app.foo.com", true}, // regular subdomain + {"*", false}, // bare asterisk } for _, tt := range tests { if got := validLookingCertDomain(tt.in); got != tt.want { @@ -231,12 +429,19 @@ func TestDebugACMEDirectoryURL(t *testing.T) { func TestGetCertPEMWithValidity(t *testing.T) { const testDomain = "example.com" - b := &LocalBackend{ - store: &mem.Store{}, - varRoot: t.TempDir(), - ctx: context.Background(), - logf: t.Logf, - } + b := newTestLocalBackend(t) + b.varRoot = t.TempDir() + + // Set up netmap with CertDomains so resolveCertDomain works + b.mu.Lock() + b.currentNode().SetNetMap(&netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{}).View(), + DNS: tailcfg.DNSConfig{ + CertDomains: []string{testDomain}, + }, + }) + b.mu.Unlock() + certDir, err := b.certDir() if err != nil { t.Fatalf("certDir error: %v", err) From 569caefeb55dd3af2d86d558d46cb2d2d1ef4258 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Thu, 29 Jan 2026 14:25:32 -0800 Subject: [PATCH 066/202] tsnet: add tests to TestListenService for user-supplied TUN devices This resolves a gap in test coverage, ensuring Server.ListenService functions as expected in combination with user-supplied TUN devices Fixes tailscale/corp#36603 Co-authored-by: Harry Harpham Signed-off-by: Harry Harpham --- tsnet/tsnet_test.go | 195 +++++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 92 deletions(-) diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index aeee43646cb0a..41d239e3b91be 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -1141,83 +1141,91 @@ func TestListenService(t *testing.T) { // This ends up also testing the Service forwarding logic in // LocalBackend, but that's useful too. t.Run(tt.name, func(t *testing.T) { - ctx := t.Context() - - controlURL, control := startControl(t) - serviceHost, _, _ := startServer(t, ctx, controlURL, "service-host") - serviceClient, _, _ := startServer(t, ctx, controlURL, "service-client") - - const serviceName = tailcfg.ServiceName("svc:foo") - const serviceVIP = "100.11.22.33" - - // == Set up necessary state in our mock == - - // The Service host must have the 'service-host' capability, which - // is a mapping from the Service name to the Service VIP. - var serviceHostCaps map[tailcfg.ServiceName]views.Slice[netip.Addr] - mak.Set(&serviceHostCaps, serviceName, views.SliceOf([]netip.Addr{netip.MustParseAddr(serviceVIP)})) - j := must.Get(json.Marshal(serviceHostCaps)) - cm := serviceHost.lb.NetMap().SelfNode.CapMap().AsMap() - mak.Set(&cm, tailcfg.NodeAttrServiceHost, []tailcfg.RawMessage{tailcfg.RawMessage(j)}) - control.SetNodeCapMap(serviceHost.lb.NodeKey(), cm) - - // The Service host must be allowed to advertise the Service VIP. - control.SetSubnetRoutes(serviceHost.lb.NodeKey(), []netip.Prefix{ - netip.MustParsePrefix(serviceVIP + `/32`), - }) - - // The Service host must be a tagged node (any tag will do). - serviceHostNode := control.Node(serviceHost.lb.NodeKey()) - serviceHostNode.Tags = append(serviceHostNode.Tags, "some-tag") - control.UpdateNode(serviceHostNode) - - // The service client must accept routes advertised by other nodes - // (RouteAll is equivalent to --accept-routes). - must.Get(serviceClient.localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ - RouteAllSet: true, - Prefs: ipn.Prefs{ - RouteAll: true, - }, - })) - - // Set up DNS for our Service. - control.AddDNSRecords(tailcfg.DNSRecord{ - Name: serviceName.WithoutPrefix() + "." + control.MagicDNSDomain, - Value: serviceVIP, - }) + // We run each test with and without a TUN device ([Server.Tun]). + // Note that this TUN device is distinct from TUN mode for Services. + doTest := func(t *testing.T, withTUNDevice bool) { + ctx := t.Context() + + lt := setupTwoClientTest(t, withTUNDevice) + serviceHost := lt.s2 + serviceClient := lt.s1 + control := lt.control + + const serviceName = tailcfg.ServiceName("svc:foo") + const serviceVIP = "100.11.22.33" + + // == Set up necessary state in our mock == + + // The Service host must have the 'service-host' capability, which + // is a mapping from the Service name to the Service VIP. + var serviceHostCaps map[tailcfg.ServiceName]views.Slice[netip.Addr] + mak.Set(&serviceHostCaps, serviceName, views.SliceOf([]netip.Addr{netip.MustParseAddr(serviceVIP)})) + j := must.Get(json.Marshal(serviceHostCaps)) + cm := serviceHost.lb.NetMap().SelfNode.CapMap().AsMap() + mak.Set(&cm, tailcfg.NodeAttrServiceHost, []tailcfg.RawMessage{tailcfg.RawMessage(j)}) + control.SetNodeCapMap(serviceHost.lb.NodeKey(), cm) + + // The Service host must be allowed to advertise the Service VIP. + control.SetSubnetRoutes(serviceHost.lb.NodeKey(), []netip.Prefix{ + netip.MustParsePrefix(serviceVIP + `/32`), + }) - if tt.extraSetup != nil { - tt.extraSetup(t, control) - } + // The Service host must be a tagged node (any tag will do). + serviceHostNode := control.Node(serviceHost.lb.NodeKey()) + serviceHostNode.Tags = append(serviceHostNode.Tags, "some-tag") + control.UpdateNode(serviceHostNode) + + // The service client must accept routes advertised by other nodes + // (RouteAll is equivalent to --accept-routes). + must.Get(serviceClient.localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ + RouteAllSet: true, + Prefs: ipn.Prefs{ + RouteAll: true, + }, + })) - // Force netmap updates to avoid race conditions. The nodes need to - // see our control updates before we can start the test. - must.Do(control.ForceNetmapUpdate(ctx, serviceHost.lb.NodeKey())) - must.Do(control.ForceNetmapUpdate(ctx, serviceClient.lb.NodeKey())) - netmapUpToDate := func(s *Server) bool { - nm := s.lb.NetMap() - return slices.ContainsFunc(nm.DNS.ExtraRecords, func(r tailcfg.DNSRecord) bool { - return r.Value == serviceVIP + // Set up DNS for our Service. + control.AddDNSRecords(tailcfg.DNSRecord{ + Name: serviceName.WithoutPrefix() + "." + control.MagicDNSDomain, + Value: serviceVIP, }) - } - for !netmapUpToDate(serviceClient) { - time.Sleep(10 * time.Millisecond) - } - for !netmapUpToDate(serviceHost) { - time.Sleep(10 * time.Millisecond) - } - // == Done setting up mock state == + if tt.extraSetup != nil { + tt.extraSetup(t, control) + } + + // Force netmap updates to avoid race conditions. The nodes need to + // see our control updates before we can start the test. + must.Do(control.ForceNetmapUpdate(ctx, serviceHost.lb.NodeKey())) + must.Do(control.ForceNetmapUpdate(ctx, serviceClient.lb.NodeKey())) + netmapUpToDate := func(s *Server) bool { + nm := s.lb.NetMap() + return slices.ContainsFunc(nm.DNS.ExtraRecords, func(r tailcfg.DNSRecord) bool { + return r.Value == serviceVIP + }) + } + for !netmapUpToDate(serviceClient) { + time.Sleep(10 * time.Millisecond) + } + for !netmapUpToDate(serviceHost) { + time.Sleep(10 * time.Millisecond) + } - // Start the Service listeners. - listeners := make([]*ServiceListener, 0, len(tt.modes)) - for _, input := range tt.modes { - ln := must.Get(serviceHost.ListenService(serviceName.String(), input)) - defer ln.Close() - listeners = append(listeners, ln) + // == Done setting up mock state == + + // Start the Service listeners. + listeners := make([]*ServiceListener, 0, len(tt.modes)) + for _, input := range tt.modes { + ln := must.Get(serviceHost.ListenService(serviceName.String(), input)) + defer ln.Close() + listeners = append(listeners, ln) + } + + tt.run(t, listeners, serviceClient) } - tt.run(t, listeners, serviceClient) + t.Run("TUN", func(t *testing.T) { doTest(t, true) }) + t.Run("netstack", func(t *testing.T) { doTest(t, false) }) }) } } @@ -1928,20 +1936,21 @@ func (t *chanTUN) BatchSize() int { return 1 } // listenTest provides common setup for listener and TUN tests. type listenTest struct { + control *testcontrol.Server s1, s2 *Server s1ip4, s1ip6 netip.Addr s2ip4, s2ip6 netip.Addr tun *chanTUN // nil for netstack mode } -// setupListenTest creates two tsnet servers for testing. +// setupTwoClientTest creates two tsnet servers for testing. // If useTUN is true, s2 uses a chanTUN; otherwise it uses netstack only. -func setupListenTest(t *testing.T, useTUN bool) *listenTest { +func setupTwoClientTest(t *testing.T, useTUN bool) *listenTest { t.Helper() tstest.Shard(t) tstest.ResourceCheck(t) ctx := t.Context() - controlURL, _ := startControl(t) + controlURL, control := startControl(t) s1, _, _ := startServer(t, ctx, controlURL, "s1") tmp := filepath.Join(t.TempDir(), "s2") @@ -1969,6 +1978,7 @@ func setupListenTest(t *testing.T, useTUN bool) *listenTest { if err != nil { t.Fatal(err) } + s2.lb.ConfigureCertsForTest(testCertRoot.getCert) s1ip4, s1ip6 := s1.TailscaleIPs() s2ip4 := s2status.TailscaleIPs[0] @@ -1981,13 +1991,14 @@ func setupListenTest(t *testing.T, useTUN bool) *listenTest { must.Get(lc1.Ping(ctx, s2ip4, tailcfg.PingTSMP)) return &listenTest{ - s1: s1, - s2: s2, - s1ip4: s1ip4, - s1ip6: s1ip6, - s2ip4: s2ip4, - s2ip6: s2ip6, - tun: tun, + control: control, + s1: s1, + s2: s2, + s1ip4: s1ip4, + s1ip6: s1ip6, + s2ip4: s2ip4, + s2ip6: s2ip6, + tun: tun, } } @@ -2016,7 +2027,7 @@ func echoUDP(pkt []byte) []byte { } func TestTUN(t *testing.T) { - tt := setupListenTest(t, true) + tt := setupTwoClientTest(t, true) go func() { for pkt := range tt.tun.Inbound { @@ -2059,7 +2070,7 @@ func TestTUN(t *testing.T) { // responses. This verifies that handleLocalPackets intercepts outbound traffic // to the service IP. func TestTUNDNS(t *testing.T) { - tt := setupListenTest(t, true) + tt := setupTwoClientTest(t, true) test := func(t *testing.T, srcIP netip.Addr, serviceIP netip.Addr) { tt.tun.Outbound <- buildDNSQuery("s2", srcIP) @@ -2149,13 +2160,13 @@ func TestListenPacket(t *testing.T) { } t.Run("Netstack", func(t *testing.T) { - lt := setupListenTest(t, false) + lt := setupTwoClientTest(t, false) t.Run("IPv4", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip4) }) t.Run("IPv6", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip6) }) }) t.Run("TUN", func(t *testing.T) { - lt := setupListenTest(t, true) + lt := setupTwoClientTest(t, true) t.Run("IPv4", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip4) }) t.Run("IPv6", func(t *testing.T) { testListenPacket(t, lt, lt.s2ip6) }) }) @@ -2221,13 +2232,13 @@ func TestListenTCP(t *testing.T) { } t.Run("Netstack", func(t *testing.T) { - lt := setupListenTest(t, false) + lt := setupTwoClientTest(t, false) t.Run("IPv4", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip4) }) t.Run("IPv6", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip6) }) }) t.Run("TUN", func(t *testing.T) { - lt := setupListenTest(t, true) + lt := setupTwoClientTest(t, true) t.Run("IPv4", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip4) }) t.Run("IPv6", func(t *testing.T) { testListenTCP(t, lt, lt.s2ip6) }) }) @@ -2299,13 +2310,13 @@ func TestListenTCPDualStack(t *testing.T) { } t.Run("Netstack", func(t *testing.T) { - lt := setupListenTest(t, false) + lt := setupTwoClientTest(t, false) t.Run("DialIPv4", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip4) }) t.Run("DialIPv6", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip6) }) }) t.Run("TUN", func(t *testing.T) { - lt := setupListenTest(t, true) + lt := setupTwoClientTest(t, true) t.Run("DialIPv4", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip4) }) t.Run("DialIPv6", func(t *testing.T) { testListenTCPDualStack(t, lt, lt.s2ip6) }) }) @@ -2372,13 +2383,13 @@ func TestDialTCP(t *testing.T) { } t.Run("Netstack", func(t *testing.T) { - lt := setupListenTest(t, false) + lt := setupTwoClientTest(t, false) t.Run("IPv4", func(t *testing.T) { testDialTCP(t, lt, lt.s1ip4) }) t.Run("IPv6", func(t *testing.T) { testDialTCP(t, lt, lt.s1ip6) }) }) t.Run("TUN", func(t *testing.T) { - lt := setupListenTest(t, true) + lt := setupTwoClientTest(t, true) var escapedTCPPackets atomic.Int32 var wg sync.WaitGroup @@ -2460,13 +2471,13 @@ func TestDialUDP(t *testing.T) { } t.Run("Netstack", func(t *testing.T) { - lt := setupListenTest(t, false) + lt := setupTwoClientTest(t, false) t.Run("IPv4", func(t *testing.T) { testDialUDP(t, lt, lt.s1ip4) }) t.Run("IPv6", func(t *testing.T) { testDialUDP(t, lt, lt.s1ip6) }) }) t.Run("TUN", func(t *testing.T) { - lt := setupListenTest(t, true) + lt := setupTwoClientTest(t, true) var escapedUDPPackets atomic.Int32 var wg sync.WaitGroup From 40cd54daf73a154c3f8b60c020d70b11c1b5aa85 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Feb 2026 10:30:55 -0800 Subject: [PATCH 067/202] cmd/tailscale: remove dep on clientupdate package if feature is omitted We already had a featuretag for clientupdate, but the CLI wasn't using it, making the "minbox" build (minimal combined tailscaled + CLI build) larger than necessary. Updates #12614 Change-Id: Idd7546c67dece7078f25b8f2ae9886f58d599002 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 3 ++- cmd/tailscale/cli/update.go | 10 ++++++++++ cmd/tailscale/cli/version.go | 10 ++++++++-- cmd/tailscaled/depaware-minbox.txt | 9 +-------- cmd/tailscaled/deps_test.go | 11 ++++++++--- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index dca7559cf1923..cde9d341c43c7 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -219,6 +219,7 @@ var ( maybeFunnelCmd, maybeServeCmd, maybeCertCmd, + maybeUpdateCmd, _ func() *ffcli.Command ) @@ -270,7 +271,7 @@ change in the future. nilOrCall(maybeNetlockCmd), licensesCmd, exitNodeCmd(), - updateCmd, + nilOrCall(maybeUpdateCmd), whoisCmd, debugCmd(), nilOrCall(maybeDriveCmd), diff --git a/cmd/tailscale/cli/update.go b/cmd/tailscale/cli/update.go index 291bf4330cd63..6d57e6d41f110 100644 --- a/cmd/tailscale/cli/update.go +++ b/cmd/tailscale/cli/update.go @@ -1,6 +1,8 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause +//go:build !ts_omit_clientupdate + package cli import ( @@ -17,6 +19,14 @@ import ( "tailscale.com/version/distro" ) +func init() { + maybeUpdateCmd = func() *ffcli.Command { return updateCmd } + + clientupdateLatestTailscaleVersion.Set(func() (string, error) { + return clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) + }) +} + var updateCmd = &ffcli.Command{ Name: "update", ShortUsage: "tailscale update", diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index f23ee0b69f834..2c6a3738bd36a 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -10,7 +10,7 @@ import ( "fmt" "github.com/peterbourgon/ff/v3/ffcli" - "tailscale.com/clientupdate" + "tailscale.com/feature" "tailscale.com/ipn/ipnstate" "tailscale.com/version" ) @@ -35,6 +35,8 @@ var versionArgs struct { upstream bool } +var clientupdateLatestTailscaleVersion feature.Hook[func() (string, error)] + func runVersion(ctx context.Context, args []string) error { if len(args) > 0 { return fmt.Errorf("too many non-flag arguments: %q", args) @@ -51,7 +53,11 @@ func runVersion(ctx context.Context, args []string) error { var upstreamVer string if versionArgs.upstream { - upstreamVer, err = clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) + f, ok := clientupdateLatestTailscaleVersion.GetOk() + if !ok { + return fmt.Errorf("fetching latest version not supported in this build") + } + upstreamVer, err = f() if err != nil { return err } diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 5121b56d0d281..938df6bb46be8 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -1,7 +1,5 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) - filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus - filippo.io/edwards25519/field from filippo.io/edwards25519 github.com/gaissmai/bart from tailscale.com/net/ipset+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart @@ -12,7 +10,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/go-json-experiment/json/internal/jsonwire from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+ github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/netmon github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli @@ -50,8 +47,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/client/local from tailscale.com/client/tailscale+ tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnauth+ - tailscale.com/clientupdate from tailscale.com/cmd/tailscale/cli - tailscale.com/clientupdate/distsign from tailscale.com/clientupdate tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscaled tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete @@ -175,7 +170,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/clientmetric from tailscale.com/appc+ tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/cloudinfo from tailscale.com/wgengine/magicsock - tailscale.com/util/cmpver from tailscale.com/clientupdate tailscale.com/util/ctxkey from tailscale.com/client/tailscale/apitype+ tailscale.com/util/dnsname from tailscale.com/appc+ tailscale.com/util/eventbus from tailscale.com/client/local+ @@ -268,7 +262,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de vendor/golang.org/x/text/transform from vendor/golang.org/x/text/secure/bidirule+ vendor/golang.org/x/text/unicode/bidi from vendor/golang.org/x/net/idna+ vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna - archive/tar from tailscale.com/clientupdate bufio from compress/flate+ bytes from bufio+ cmp from encoding/json+ @@ -428,7 +421,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de path from io/fs+ path/filepath from crypto/x509+ reflect from crypto/x509+ - regexp from tailscale.com/clientupdate+ + regexp from tailscale.com/cmd/tailscale/cli regexp/syntax from regexp runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index d06924b927a97..9a6f532c1f803 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -285,9 +285,14 @@ func TestMinTailscaledWithCLI(t *testing.T) { } }, BadDeps: map[string]string{ - "golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305", - "expvar": "unexpected expvar dep", - "github.com/mdlayher/genetlink": "unexpected genetlink dep", + "golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305", + "expvar": "unexpected expvar dep", + "github.com/mdlayher/genetlink": "unexpected genetlink dep", + "tailscale.com/clientupdate": "unexpected clientupdate dep", + "filippo.io/edwards25519": "unexpected edwards25519 dep", + "github.com/hdevalence/ed25519consensus": "unexpected ed25519consensus dep", + "tailscale.com/clientupdate/distsign": "unexpected distsign dep", + "archive/tar": "unexpected archive/tar dep", }, }.Check(t) } From 642d1aaa6096993e0a3640dd6afccc6b74f083be Mon Sep 17 00:00:00 2001 From: Fran Bull Date: Wed, 4 Feb 2026 12:11:00 -0800 Subject: [PATCH 068/202] cmd/tailscaled,feature/conn25,feature/featuretags: add conn25 to featuretags Package feature/conn25 is excludeable from a build via the featuretag. Test it is excluded for minimal builds. Updates #12614 Signed-off-by: Fran Bull --- cmd/tailscaled/depaware-min.txt | 5 ++--- cmd/tailscaled/depaware-minbox.txt | 5 ++--- cmd/tailscaled/deps_test.go | 1 + feature/buildfeatures/feature_conn25_disabled.go | 13 +++++++++++++ feature/buildfeatures/feature_conn25_enabled.go | 13 +++++++++++++ feature/conn25/conn25.go | 3 +++ feature/featuretags/featuretags.go | 1 + 7 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 feature/buildfeatures/feature_conn25_disabled.go create mode 100644 feature/buildfeatures/feature_conn25_enabled.go diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index a2d20dedaf243..e536ac59dde37 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -35,7 +35,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnauth+ tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled @@ -58,14 +58,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister tailscale.com/feature/condregister/useproxy from tailscale.com/feature/condregister - tailscale.com/feature/conn25 from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ tailscale.com/hostinfo from tailscale.com/cmd/tailscaled+ tailscale.com/ipn from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ - tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal+ + tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 938df6bb46be8..41bf4d9842084 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -42,7 +42,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/local from tailscale.com/client/tailscale+ tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale @@ -73,7 +73,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/condregister/oauthkey from tailscale.com/cmd/tailscale/cli tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister+ tailscale.com/feature/condregister/useproxy from tailscale.com/cmd/tailscale/cli+ - tailscale.com/feature/conn25 from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ tailscale.com/hostinfo from tailscale.com/cmd/tailscaled+ @@ -81,7 +80,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/ipn from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ - tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal+ + tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index 9a6f532c1f803..118913848a52b 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -293,6 +293,7 @@ func TestMinTailscaledWithCLI(t *testing.T) { "github.com/hdevalence/ed25519consensus": "unexpected ed25519consensus dep", "tailscale.com/clientupdate/distsign": "unexpected distsign dep", "archive/tar": "unexpected archive/tar dep", + "tailscale.com/feature/conn25": "unexpected conn25 dep", }, }.Check(t) } diff --git a/feature/buildfeatures/feature_conn25_disabled.go b/feature/buildfeatures/feature_conn25_disabled.go new file mode 100644 index 0000000000000..29d6452400cb3 --- /dev/null +++ b/feature/buildfeatures/feature_conn25_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_conn25 + +package buildfeatures + +// HasConn25 is whether the binary was built with support for modular feature "Route traffic for configured domains through connector devices". +// Specifically, it's whether the binary was NOT built with the "ts_omit_conn25" build tag. +// It's a const so it can be used for dead code elimination. +const HasConn25 = false diff --git a/feature/buildfeatures/feature_conn25_enabled.go b/feature/buildfeatures/feature_conn25_enabled.go new file mode 100644 index 0000000000000..a0d95477cf2d1 --- /dev/null +++ b/feature/buildfeatures/feature_conn25_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_conn25 + +package buildfeatures + +// HasConn25 is whether the binary was built with support for modular feature "Route traffic for configured domains through connector devices". +// Specifically, it's whether the binary was NOT built with the "ts_omit_conn25" build tag. +// It's a const so it can be used for dead code elimination. +const HasConn25 = true diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index 2a2b75a2d8b19..33ba0e486abe3 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -2,6 +2,9 @@ // SPDX-License-Identifier: BSD-3-Clause // Package conn25 registers the conn25 feature and implements its associated ipnext.Extension. +// conn25 will be an app connector like feature that routes traffic for configured domains via +// connector devices and avoids the "too many routes" pitfall of app connector. It is currently +// (2026-02-04) some peer API routes for clients to tell connectors about their desired routing. package conn25 import ( diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index c0a72a38d1fdd..5f72e3dda98b4 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -138,6 +138,7 @@ var Features = map[FeatureTag]FeatureMeta{ Deps: []FeatureTag{"c2n"}, }, "completion": {Sym: "Completion", Desc: "CLI shell completion"}, + "conn25": {Sym: "Conn25", Desc: "Route traffic for configured domains through connector devices"}, "cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"}, "dbus": { Sym: "DBus", From 036b6a12621306da8368b167deb9858d4a8d6ce9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Feb 2026 12:10:46 -0800 Subject: [PATCH 069/202] feature/featuretags: add test that all ts_omit_foo tags are declared Updates #12614 Change-Id: I49351fe0c463af0b8d940e8088d4748906a8aec3 Signed-off-by: Brad Fitzpatrick --- .../feature_completion_scripts_disabled.go | 13 ++++++ .../feature_completion_scripts_enabled.go | 13 ++++++ feature/featuretags/featuretags.go | 6 ++- feature/featuretags/featuretags_test.go | 40 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 feature/buildfeatures/feature_completion_scripts_disabled.go create mode 100644 feature/buildfeatures/feature_completion_scripts_enabled.go diff --git a/feature/buildfeatures/feature_completion_scripts_disabled.go b/feature/buildfeatures/feature_completion_scripts_disabled.go new file mode 100644 index 0000000000000..e22ce69fc708a --- /dev/null +++ b/feature/buildfeatures/feature_completion_scripts_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_completion_scripts + +package buildfeatures + +// HasCompletionScripts is whether the binary was built with support for modular feature "embed CLI shell completion scripts". +// Specifically, it's whether the binary was NOT built with the "ts_omit_completion_scripts" build tag. +// It's a const so it can be used for dead code elimination. +const HasCompletionScripts = false diff --git a/feature/buildfeatures/feature_completion_scripts_enabled.go b/feature/buildfeatures/feature_completion_scripts_enabled.go new file mode 100644 index 0000000000000..c3ecd83cac661 --- /dev/null +++ b/feature/buildfeatures/feature_completion_scripts_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_completion_scripts + +package buildfeatures + +// HasCompletionScripts is whether the binary was built with support for modular feature "embed CLI shell completion scripts". +// Specifically, it's whether the binary was NOT built with the "ts_omit_completion_scripts" build tag. +// It's a const so it can be used for dead code elimination. +const HasCompletionScripts = true diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index 5f72e3dda98b4..45daaec5ec29f 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -139,7 +139,11 @@ var Features = map[FeatureTag]FeatureMeta{ }, "completion": {Sym: "Completion", Desc: "CLI shell completion"}, "conn25": {Sym: "Conn25", Desc: "Route traffic for configured domains through connector devices"}, - "cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"}, + "completion_scripts": { + Sym: "CompletionScripts", Desc: "embed CLI shell completion scripts", + Deps: []FeatureTag{"completion"}, + }, + "cloud": {Sym: "Cloud", Desc: "detect cloud environment to learn instances IPs and DNS servers"}, "dbus": { Sym: "DBus", Desc: "Linux DBus support", diff --git a/feature/featuretags/featuretags_test.go b/feature/featuretags/featuretags_test.go index b970295779591..19c9722a6f300 100644 --- a/feature/featuretags/featuretags_test.go +++ b/feature/featuretags/featuretags_test.go @@ -5,7 +5,12 @@ package featuretags import ( "maps" + "os" + "os/exec" + "path/filepath" + "regexp" "slices" + "strings" "testing" "tailscale.com/util/set" @@ -83,3 +88,38 @@ func TestRequiredBy(t *testing.T) { } } } + +// Verify that all "ts_omit_foo" build tags are declared in featuretags.go +func TestAllOmitBuildTagsDeclared(t *testing.T) { + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + root := filepath.Join(dir, "..", "..") + + cmd := exec.Command("git", "grep", "ts_omit_") + cmd.Dir = root + out, err := cmd.CombinedOutput() + if err != nil { + if _, err := exec.LookPath("git"); err != nil { + t.Skipf("git not found in PATH; skipping test") + } + t.Fatalf("git grep failed: %v\nOutput:\n%s", err, out) + } + rx := regexp.MustCompile(`\bts_omit_[\w_]+\b`) + found := set.Set[string]{} + rx.ReplaceAllFunc(out, func(tag []byte) []byte { + tagStr := string(tag) + found.Add(tagStr) + return tag + }) + for tag := range found { + if strings.EqualFold(tag, "ts_omit_foo") { + continue + } + ft := FeatureTag(strings.TrimPrefix(tag, "ts_omit_")) + if _, ok := Features[ft]; !ok { + t.Errorf("found undeclared ts_omit_* build tags: %v", tag) + } + } +} From 6587cafb3fa3c59b81c92e566f851b2efd65524b Mon Sep 17 00:00:00 2001 From: Mario Minardi Date: Thu, 5 Feb 2026 10:45:24 -0700 Subject: [PATCH 070/202] cmd/tailscale: use advertise tags from prefs for OAuth and id federation Use the parsed and validated advertise tags value from prefs instead of doing a strings.Split on the raw tags value as an input to the OAuth and identity federation auth key generation methods. The previous strings.Split method would return an array with a single empty string element which would pass downstream length checks on the tags argument before eventually failing with a confusing message when hitting the API. Fixes https://github.com/tailscale/tailscale/issues/18617 Signed-off-by: Mario Minardi --- cmd/tailscale/cli/up.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 79f7cc3f44a88..d78cb2d44bfb2 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -641,7 +641,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE } } - authKey, err = f(ctx, clientSecret, strings.Split(upArgs.advertiseTags, ",")) + authKey, err = f(ctx, clientSecret, prefs.AdvertiseTags) if err != nil { return err } @@ -654,7 +654,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE return err } - authKey, err = f(ctx, prefs.ControlURL, upArgs.clientID, idToken, upArgs.audience, strings.Split(upArgs.advertiseTags, ",")) + authKey, err = f(ctx, prefs.ControlURL, upArgs.clientID, idToken, upArgs.audience, prefs.AdvertiseTags) if err != nil { return err } From 058cc3f82bfcaa8d5b49d00d5e9c46fdcd289bbd Mon Sep 17 00:00:00 2001 From: Will Hannah Date: Fri, 6 Feb 2026 09:40:55 -0500 Subject: [PATCH 071/202] ipn/ipnlocal: skip AuthKey use if profiles exist (#18619) If any profiles exist and an Authkey is provided via syspolicy, the AuthKey is ignored on backend start, preventing re-auth attempts. This is useful for one-time device provisioning scenarios, skipping authKey use after initial setup when the authKey may no longer be valid. updates #18618 Signed-off-by: Will Hannah --- ipn/ipnlocal/local.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 300f7a4c3186d..821f79abfbcec 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -2478,7 +2478,9 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { if b.state != ipn.Running && b.conf == nil && opts.AuthKey == "" { sysak, _ := b.polc.GetString(pkey.AuthKey, "") - if sysak != "" { + if sysak != "" && len(b.pm.Profiles()) > 0 && b.state != ipn.NeedsLogin { + logf("not setting opts.AuthKey from syspolicy; login profiles exist, state=%v", b.state) + } else if sysak != "" { logf("setting opts.AuthKey by syspolicy, len=%v", len(sysak)) opts.AuthKey = strings.TrimSpace(sysak) } From 0c5b17c1d34ce1a67d3af7fbc0e7908e8b74cf09 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Feb 2026 11:12:47 -0800 Subject: [PATCH 072/202] cmd/tailscale: don't depend on regexp in minbox builds Updates #12614 Updates #18562 Change-Id: Ife4f10c55d1d68569938ffd68ffe72eef889e200 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 35 ++++++++++++++++++++++++------ cmd/tailscale/cli/cli_test.go | 4 ++-- cmd/tailscaled/depaware-minbox.txt | 2 -- cmd/tailscaled/deps_test.go | 1 + 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index cde9d341c43c7..1ba66531a7cb5 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -6,6 +6,7 @@ package cli import ( + "bytes" "context" "encoding/json" "errors" @@ -14,7 +15,6 @@ import ( "io" "log" "os" - "regexp" "runtime" "strings" "sync" @@ -582,11 +582,32 @@ type sanitizeWriter struct { w io.Writer } -var rxTskey = regexp.MustCompile(`tskey-[\w-]+`) - +// Write logically replaces /tskey-[A-Za-z0-9-]+/ with /tskey-XXXX.../ in buf +// before writing to the underlying writer. +// +// We avoid the "regexp" package to not bloat the minbox build, and without +// making this a featuretag-omittable protection. func (w sanitizeWriter) Write(buf []byte) (int, error) { - sanitized := rxTskey.ReplaceAll(buf, []byte("tskey-REDACTED")) - diff := len(sanitized) - len(buf) - n, err := w.w.Write(sanitized) - return n - diff, err + const prefix = "tskey-" + scrub := buf + for { + i := bytes.Index(scrub, []byte(prefix)) + if i == -1 { + break + } + scrub = scrub[i+len(prefix):] + + for i, b := range scrub { + if (b >= 'a' && b <= 'z') || + (b >= 'A' && b <= 'Z') || + (b >= '0' && b <= '9') || + b == '-' { + scrub[i] = 'X' + } else { + break + } + } + } + + return w.w.Write(buf) } diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index ac6a94d52f88d..537e641fc4160 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -1804,8 +1804,8 @@ func TestSanitizeWriter(t *testing.T) { buf := new(bytes.Buffer) w := sanitizeOutput(buf) - in := []byte(`my auth key is tskey-auth-abc123-def456, what's yours?`) - want := []byte(`my auth key is tskey-REDACTED, what's yours?`) + in := []byte(`my auth key is tskey-auth-abc123-def456 and tskey-foo, what's yours?`) + want := []byte(`my auth key is tskey-XXXXXXXXXXXXXXXXXX and tskey-XXX, what's yours?`) n, err := w.Write(in) if err != nil { t.Fatal(err) diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 41bf4d9842084..f087e68096750 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -420,8 +420,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de path from io/fs+ path/filepath from crypto/x509+ reflect from crypto/x509+ - regexp from tailscale.com/cmd/tailscale/cli - regexp/syntax from regexp runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ slices from crypto/tls+ diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index 118913848a52b..5969850435b41 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -294,6 +294,7 @@ func TestMinTailscaledWithCLI(t *testing.T) { "tailscale.com/clientupdate/distsign": "unexpected distsign dep", "archive/tar": "unexpected archive/tar dep", "tailscale.com/feature/conn25": "unexpected conn25 dep", + "regexp": "unexpected regexp dep; bloats binary", }, }.Check(t) } From de4a8dbcfcca4b3cdc4b437d7d2aceacfb89f0d0 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 6 Feb 2026 09:07:33 -0800 Subject: [PATCH 073/202] control/controlclient: fix canSkipStatus online conditions concurrent netmaps that if the first is logged in, it is never skipped. This should have been covered be the skip test case, but that case wasn't updated to include level set state. Updates #12639 Updates #17869 Signed-off-by: James Tucker --- control/controlclient/auto.go | 15 +++++++-------- control/controlclient/controlclient_test.go | 5 +++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index fe227b45e57aa..783ca36c4f45d 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -674,17 +674,16 @@ func canSkipStatus(s1, s2 *Status) bool { // we can't skip it. return false } - if s1.Err != nil || s1.URL != "" || s1.LoggedIn { - // If s1 has an error, a URL, or LoginFinished set, we shouldn't skip it, - // lest the error go away in s2 or in-between. We want to make sure all - // the subsystems see it. Plus there aren't many of these, so not worth - // skipping. + if s1.Err != nil || s1.URL != "" { + // If s1 has an error or an URL, we shouldn't skip it, lest the error go + // away in s2 or in-between. We want to make sure all the subsystems see + // it. Plus there aren't many of these, so not worth skipping. return false } if !s1.Persist.Equals(s2.Persist) || s1.LoggedIn != s2.LoggedIn || s1.InMapPoll != s2.InMapPoll || s1.URL != s2.URL { - // If s1 has a different Persist, LoginFinished, Synced, or URL than s2, - // don't skip it. We only care about skipping the typical - // entries where the only difference is the NetMap. + // If s1 has a different Persist, has changed login state, changed map + // poll state, or has a new login URL, don't skip it. We only care about + // skipping the typical entries where the only difference is the NetMap. return false } // If nothing above precludes it, and both s1 and s2 have NetMaps, then diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index c7d61f6b2d13d..dca1d8ddf2f8b 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -98,6 +98,7 @@ func TestCanSkipStatus(t *testing.T) { nm1 := &netmap.NetworkMap{} nm2 := &netmap.NetworkMap{} + commonPersist := new(persist.Persist).View() tests := []struct { name string s1, s2 *Status @@ -165,8 +166,8 @@ func TestCanSkipStatus(t *testing.T) { }, { name: "skip", - s1: &Status{NetMap: nm1}, - s2: &Status{NetMap: nm2}, + s1: &Status{NetMap: nm1, LoggedIn: true, InMapPoll: true, Persist: commonPersist}, + s2: &Status{NetMap: nm2, LoggedIn: true, InMapPoll: true, Persist: commonPersist}, want: true, }, } From 826fd544cc6ae1a995dd0bd47c6b4f8f82caa448 Mon Sep 17 00:00:00 2001 From: Anton Tolchanov Date: Fri, 6 Feb 2026 16:55:25 +0000 Subject: [PATCH 074/202] tsweb/varz: only export numeric expvar.Map values Currently the expvar exporter attempts to write expvar.String, which breaks the Prometheus metric page. Updates tailscale/corp#36552 Signed-off-by: Anton Tolchanov --- tsweb/varz/varz.go | 14 ++++++++++++-- tsweb/varz/varz_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/tsweb/varz/varz.go b/tsweb/varz/varz.go index d6100672c6c56..a2286c7603be3 100644 --- a/tsweb/varz/varz.go +++ b/tsweb/varz/varz.go @@ -245,11 +245,21 @@ func writePromExpVar(w io.Writer, prefix string, kv expvar.KeyValue) { if label != "" && typ != "" { fmt.Fprintf(w, "# TYPE %s %s\n", name, typ) v.Do(func(kv expvar.KeyValue) { - fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value) + switch kv.Value.(type) { + case *expvar.Int, *expvar.Float: + fmt.Fprintf(w, "%s{%s=%q} %v\n", name, label, kv.Key, kv.Value) + default: + fmt.Fprintf(w, "# skipping %q expvar map key %q with unknown value type %T\n", name, kv.Key, kv.Value) + } }) } else { v.Do(func(kv expvar.KeyValue) { - fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value) + switch kv.Value.(type) { + case *expvar.Int, *expvar.Float: + fmt.Fprintf(w, "%s_%s %v\n", name, kv.Key, kv.Value) + default: + fmt.Fprintf(w, "# skipping %q expvar map key %q with unknown value type %T\n", name, kv.Key, kv.Value) + } }) } } diff --git a/tsweb/varz/varz_test.go b/tsweb/varz/varz_test.go index 6505ba985160e..770144016b7fc 100644 --- a/tsweb/varz/varz_test.go +++ b/tsweb/varz/varz_test.go @@ -180,6 +180,43 @@ func TestVarzHandler(t *testing.T) { }, "# TYPE m counter\nm{label=\"bar\"} 2\nm{label=\"foo\"} 1\n", }, + { + "metrics_label_map_float", + "float_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Init() + f := new(expvar.Float) + f.Set(1.5) + m.Set("a", f) + return m + }(), + "float_map_a 1.5\n", + }, + { + "metrics_label_map_int", + "int_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Init() + f := new(expvar.Int) + f.Set(55) + m.Set("a", f) + return m + }(), + "int_map_a 55\n", + }, + { + "metrics_label_map_string", + "string_map", + func() *expvar.Map { + m := new(expvar.Map) + m.Init() + m.Set("a", expvar.NewString("foo")) + return m + }(), + "# skipping \"string_map\" expvar map key \"a\" with unknown value type *expvar.String\n", + }, { "metrics_label_map_untyped", "control_save_config", @@ -298,6 +335,12 @@ foo_foo_b 1 api_status_code 42 `) + "\n", }, + { + "string_expvar_is_not_exported", + "foo_string", + new(expvar.String), + "# skipping expvar \"foo_string\" (Go type *expvar.String) with undeclared Prometheus type\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 35e656a35f7b8166358cc4ba230f2853c0f616bb Mon Sep 17 00:00:00 2001 From: Anton Tolchanov Date: Fri, 6 Feb 2026 17:48:50 +0000 Subject: [PATCH 075/202] tsweb/varz: remove unnecessary Map.Init() calls in tests Updates #cleanup Signed-off-by: Anton Tolchanov --- tsweb/varz/varz_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tsweb/varz/varz_test.go b/tsweb/varz/varz_test.go index 770144016b7fc..d041edb4b93d4 100644 --- a/tsweb/varz/varz_test.go +++ b/tsweb/varz/varz_test.go @@ -113,7 +113,6 @@ func TestVarzHandler(t *testing.T) { &metrics.Set{ Map: *(func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -127,7 +126,6 @@ func TestVarzHandler(t *testing.T) { &metrics.Set{ Map: *(func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -140,7 +138,6 @@ func TestVarzHandler(t *testing.T) { "api_status_code", func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("2xx", 100) m.Add("5xx", 2) return m @@ -172,7 +169,6 @@ func TestVarzHandler(t *testing.T) { Label: "label", Map: *(func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m @@ -185,7 +181,6 @@ func TestVarzHandler(t *testing.T) { "float_map", func() *expvar.Map { m := new(expvar.Map) - m.Init() f := new(expvar.Float) f.Set(1.5) m.Set("a", f) @@ -198,7 +193,6 @@ func TestVarzHandler(t *testing.T) { "int_map", func() *expvar.Map { m := new(expvar.Map) - m.Init() f := new(expvar.Int) f.Set(55) m.Set("a", f) @@ -211,7 +205,6 @@ func TestVarzHandler(t *testing.T) { "string_map", func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Set("a", expvar.NewString("foo")) return m }(), @@ -256,7 +249,6 @@ func TestVarzHandler(t *testing.T) { "counter_labelmap_keyname_m", func() *expvar.Map { m := new(expvar.Map) - m.Init() m.Add("foo", 1) m.Add("bar", 2) return m From fe69b7f0e50e9015d451ba61c8279b0eae993116 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 6 Feb 2026 01:06:41 -0800 Subject: [PATCH 076/202] cmd/tailscale: add event bus queue depth debugging Under extremely high load it appears we may have some retention issues as a result of queue depth build up, but there is currently no direct way to observe this. The scenario does not trigger the slow subscriber log message, and the event stream debugging endpoint produces a saturating volume of information. Updates tailscale/corp#36904 Signed-off-by: James Tucker --- client/local/local.go | 5 ++++ cmd/tailscale/cli/debug.go | 15 ++++++++++ ipn/localapi/debug.go | 58 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/client/local/local.go b/client/local/local.go index 465ba0d67c820..5794734f27133 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -446,6 +446,11 @@ func (lc *Client) EventBusGraph(ctx context.Context) ([]byte, error) { return lc.get200(ctx, "/localapi/v0/debug-bus-graph") } +// EventBusQueues returns a JSON snapshot of event bus queue depths per client. +func (lc *Client) EventBusQueues(ctx context.Context) ([]byte, error) { + return lc.get200(ctx, "/localapi/v0/debug-bus-queues") +} + // StreamBusEvents returns an iterator of Tailscale bus events as they arrive. // Each pair is a valid event and a nil error, or a zero event a non-nil error. // In case of error, the iterator ends after the pair reporting the error. diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index f406b9f226249..629c694c0c6b4 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -124,6 +124,12 @@ func debugCmd() *ffcli.Command { return fs })(), }, + { + Name: "daemon-bus-queues", + ShortUsage: "tailscale debug daemon-bus-queues", + Exec: runDaemonBusQueues, + ShortHelp: "Print event bus queue depths per client", + }, { Name: "metrics", ShortUsage: "tailscale debug metrics", @@ -840,6 +846,15 @@ func runDaemonBusGraph(ctx context.Context, args []string) error { return nil } +func runDaemonBusQueues(ctx context.Context, args []string) error { + data, err := localClient.EventBusQueues(ctx) + if err != nil { + return err + } + fmt.Print(string(data)) + return nil +} + // generateDOTGraph generates the DOT graph format based on the events func generateDOTGraph(topics []eventbus.DebugTopic) string { var sb strings.Builder diff --git a/ipn/localapi/debug.go b/ipn/localapi/debug.go index fe936db6ab78d..d1348abaafef5 100644 --- a/ipn/localapi/debug.go +++ b/ipn/localapi/debug.go @@ -6,6 +6,7 @@ package localapi import ( + "cmp" "context" "encoding/json" "fmt" @@ -35,6 +36,7 @@ func init() { Register("dev-set-state-store", (*Handler).serveDevSetStateStore) Register("debug-bus-events", (*Handler).serveDebugBusEvents) Register("debug-bus-graph", (*Handler).serveEventBusGraph) + Register("debug-bus-queues", (*Handler).serveDebugBusQueues) Register("debug-derp-region", (*Handler).serveDebugDERPRegion) Register("debug-dial-types", (*Handler).serveDebugDialTypes) Register("debug-log", (*Handler).serveDebugLog) @@ -424,6 +426,62 @@ func (h *Handler) serveEventBusGraph(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(topics) } +func (h *Handler) serveDebugBusQueues(w http.ResponseWriter, r *http.Request) { + if r.Method != httpm.GET { + http.Error(w, "GET required", http.StatusMethodNotAllowed) + return + } + + bus, ok := h.LocalBackend().Sys().Bus.GetOK() + if !ok { + http.Error(w, "event bus not running", http.StatusPreconditionFailed) + return + } + + debugger := bus.Debugger() + + type clientQueue struct { + Name string `json:"name"` + SubscribeDepth int `json:"subscribeDepth"` + SubscribeTypes []string `json:"subscribeTypes,omitempty"` + PublishTypes []string `json:"publishTypes,omitempty"` + } + + publishQueue := debugger.PublishQueue() + clients := debugger.Clients() + result := struct { + PublishQueueDepth int `json:"publishQueueDepth"` + Clients []clientQueue `json:"clients"` + }{ + PublishQueueDepth: len(publishQueue), + } + + for _, c := range clients { + sq := debugger.SubscribeQueue(c) + cq := clientQueue{ + Name: c.Name(), + SubscribeDepth: len(sq), + } + for _, t := range debugger.SubscribeTypes(c) { + cq.SubscribeTypes = append(cq.SubscribeTypes, t.String()) + } + for _, t := range debugger.PublishTypes(c) { + cq.PublishTypes = append(cq.PublishTypes, t.String()) + } + result.Clients = append(result.Clients, cq) + } + + slices.SortFunc(result.Clients, func(a, b clientQueue) int { + if a.SubscribeDepth != b.SubscribeDepth { + return b.SubscribeDepth - a.SubscribeDepth + } + return cmp.Compare(a.Name, b.Name) + }) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} + func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) { if !buildfeatures.HasLogTail { http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented) From 9ba2a80ab64f5507ca6e6cbba4e91d082ec2d8df Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Fri, 6 Feb 2026 12:54:11 -0800 Subject: [PATCH 077/202] go.toolchain.{rev,next.rev}: update to Go 1.25.7 / Go 1.26rc3 (#18633) Updates #18629 Signed-off-by: Andrew Lytvynov --- go.mod | 2 +- go.toolchain.next.rev | 2 +- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- go.toolchain.version | 2 +- pull-toolchain.sh | 1 + 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index bcdc7e19d3162..12ffd3ebd5b2b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module tailscale.com -go 1.25.6 +go 1.25.7 require ( filippo.io/mkcert v1.4.4 diff --git a/go.toolchain.next.rev b/go.toolchain.next.rev index be0f53a9c5a18..abdc21022aa19 100644 --- a/go.toolchain.next.rev +++ b/go.toolchain.next.rev @@ -1 +1 @@ -64a6cb4cba579e2865654747d4d672ead07b8375 +5ba287c89a4cef2f4a419aed4e6bc3121c5c4dad diff --git a/go.toolchain.rev b/go.toolchain.rev index cb3fa64623175..05e37f312da5c 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -779d878b6a943cecd2f359699001a03d7cedf222 +692441891e061f8ae2cb2f8f2c898f86bb1c5dca diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 3b8ce70f3ac31..b7a7163f79f68 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-e081DbI45vGMmi3drwqz2UOxRwffEuEDSVZupDtOVuk= +sha256-gWKrpBTXfsQmgOWoMrbvCaWGsBXCt5X12BAcwfAPMQY= diff --git a/go.toolchain.version b/go.toolchain.version index 198ec23ccfcc9..f1968aa8818d5 100644 --- a/go.toolchain.version +++ b/go.toolchain.version @@ -1 +1 @@ -1.25.6 +1.25.7 diff --git a/pull-toolchain.sh b/pull-toolchain.sh index b10e3cd68cf11..c80c913bb17b2 100755 --- a/pull-toolchain.sh +++ b/pull-toolchain.sh @@ -25,6 +25,7 @@ fi # don't yet support TS_GO_NEXT=1 with flake.nix or in our corp CI. if [ "${TS_GO_NEXT:-}" != "1" ]; then ./tool/go version 2>/dev/null | awk '{print $3}' | sed 's/^go//' > go.toolchain.version + ./tool/go mod edit -go "$(cat go.toolchain.version)" ./update-flake.sh fi From 5eaaf9786b84931ac3cba16cc4bc737e4b60502a Mon Sep 17 00:00:00 2001 From: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:30:07 -0500 Subject: [PATCH 078/202] tailcfg: add peerRelay bool to hostinfo This commit adds a bool named PeerRelay to Hostinfo, to identify the host's status of acting as a peer relay. Considering the RelayServerPort number can be 0, I just made this a bool in stead of a port number. If the port info is needed in future this would also help indicating if the port was set to 0 (meaning any port in peer relay context). Updates tailscale/corp#35862 Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> --- ipn/ipnlocal/local.go | 4 ++++ tailcfg/tailcfg.go | 1 + tailcfg/tailcfg_clone.go | 1 + tailcfg/tailcfg_test.go | 11 +++++++++++ tailcfg/tailcfg_view.go | 4 ++++ 5 files changed, 21 insertions(+) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 821f79abfbcec..8f8051f4b7273 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -5671,6 +5671,10 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip } hi.SSH_HostKeys = sshHostKeys + if buildfeatures.HasRelayServer { + hi.PeerRelay = prefs.RelayServerPort().Valid() + } + for _, f := range hookMaybeMutateHostinfoLocked { f(b, hi, prefs) } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index f76eb8f55d241..171f88fd77b5c 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -887,6 +887,7 @@ type Hostinfo struct { UserspaceRouter opt.Bool `json:",omitzero"` // if the client's subnet router is running in userspace (netstack) mode AppConnector opt.Bool `json:",omitzero"` // if the client is running the app-connector service ServicesHash string `json:",omitzero"` // opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n + PeerRelay bool `json:",omitzero"` // if the client is willing to relay traffic for other peers ExitNodeID StableNodeID `json:",omitzero"` // the client’s selected exit node, empty when unselected. // Location represents geographical location data about a diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 483746145b6e1..a60f301d763c7 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -186,6 +186,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct { UserspaceRouter opt.Bool AppConnector opt.Bool ServicesHash string + PeerRelay bool ExitNodeID StableNodeID Location *Location TPM *TPMInfo diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 4e9909db09f89..f649e43ab57b8 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -67,6 +67,7 @@ func TestHostinfoEqual(t *testing.T) { "UserspaceRouter", "AppConnector", "ServicesHash", + "PeerRelay", "ExitNodeID", "Location", "TPM", @@ -244,6 +245,16 @@ func TestHostinfoEqual(t *testing.T) { &Hostinfo{AppConnector: opt.Bool("false")}, false, }, + { + &Hostinfo{PeerRelay: true}, + &Hostinfo{PeerRelay: true}, + true, + }, + { + &Hostinfo{PeerRelay: true}, + &Hostinfo{PeerRelay: false}, + false, + }, { &Hostinfo{ServicesHash: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"}, &Hostinfo{ServicesHash: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"}, diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index b2734d8af36c9..7960000fd3d6a 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -606,6 +606,9 @@ func (v HostinfoView) AppConnector() opt.Bool { return v.ж.AppConnector } // opaque hash of the most recent list of tailnet services, change in hash indicates config should be fetched via c2n func (v HostinfoView) ServicesHash() string { return v.ж.ServicesHash } +// if the client is willing to relay traffic for other peers +func (v HostinfoView) PeerRelay() bool { return v.ж.PeerRelay } + // the client’s selected exit node, empty when unselected. func (v HostinfoView) ExitNodeID() StableNodeID { return v.ж.ExitNodeID } @@ -664,6 +667,7 @@ var _HostinfoViewNeedsRegeneration = Hostinfo(struct { UserspaceRouter opt.Bool AppConnector opt.Bool ServicesHash string + PeerRelay bool ExitNodeID StableNodeID Location *Location TPM *TPMInfo From a3215f1f9d3afd4a35973e4df12dc5fca87a3056 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 8 Feb 2026 04:46:09 +0000 Subject: [PATCH 079/202] cmd/tailscale,feature/featuretags: make webbrowser and colorable deps omittable Add new "webbrowser" and "colorable" feature tags so that the github.com/toqueteos/webbrowser and mattn/go-colorable packages can be excluded from minbox builds. Updates #12614 Change-Id: Iabd38b242f5a56aa10ef2050113785283f4e1fe8 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 16 ----------- cmd/tailscale/cli/colorable.go | 28 +++++++++++++++++++ cmd/tailscale/cli/colorable_omit.go | 12 ++++++++ cmd/tailscale/cli/open_browser.go | 12 ++++++++ cmd/tailscale/cli/status.go | 7 +++-- cmd/tailscaled/depaware-minbox.txt | 4 +-- cmd/tailscaled/deps_test.go | 2 ++ .../feature_colorable_disabled.go | 13 +++++++++ .../feature_colorable_enabled.go | 13 +++++++++ .../feature_webbrowser_disabled.go | 13 +++++++++ .../feature_webbrowser_enabled.go | 13 +++++++++ feature/featuretags/featuretags.go | 9 ++++-- 12 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 cmd/tailscale/cli/colorable.go create mode 100644 cmd/tailscale/cli/colorable_omit.go create mode 100644 cmd/tailscale/cli/open_browser.go create mode 100644 feature/buildfeatures/feature_colorable_disabled.go create mode 100644 feature/buildfeatures/feature_colorable_enabled.go create mode 100644 feature/buildfeatures/feature_webbrowser_disabled.go create mode 100644 feature/buildfeatures/feature_webbrowser_enabled.go diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 1ba66531a7cb5..fda6b4546324a 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -21,8 +21,6 @@ import ( "text/tabwriter" "time" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/client/local" "tailscale.com/cmd/tailscale/cli/ffcomplete" @@ -484,20 +482,6 @@ func countFlags(fs *flag.FlagSet) (n int) { return n } -// colorableOutput returns a colorable writer if stdout is a terminal (not, say, -// redirected to a file or pipe), the Stdout writer is os.Stdout (we're not -// embedding the CLI in wasm or a mobile app), and NO_COLOR is not set (see -// https://no-color.org/). If any of those is not the case, ok is false -// and w is Stdout. -func colorableOutput() (w io.Writer, ok bool) { - if Stdout != os.Stdout || - os.Getenv("NO_COLOR") != "" || - !isatty.IsTerminal(os.Stdout.Fd()) { - return Stdout, false - } - return colorable.NewColorableStdout(), true -} - type commandDoc struct { Name string Desc string diff --git a/cmd/tailscale/cli/colorable.go b/cmd/tailscale/cli/colorable.go new file mode 100644 index 0000000000000..6ecd36b1a409f --- /dev/null +++ b/cmd/tailscale/cli/colorable.go @@ -0,0 +1,28 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_colorable + +package cli + +import ( + "io" + "os" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// colorableOutput returns a colorable writer if stdout is a terminal (not, say, +// redirected to a file or pipe), the Stdout writer is os.Stdout (we're not +// embedding the CLI in wasm or a mobile app), and NO_COLOR is not set (see +// https://no-color.org/). If any of those is not the case, ok is false +// and w is Stdout. +func colorableOutput() (w io.Writer, ok bool) { + if Stdout != os.Stdout || + os.Getenv("NO_COLOR") != "" || + !isatty.IsTerminal(os.Stdout.Fd()) { + return Stdout, false + } + return colorable.NewColorableStdout(), true +} diff --git a/cmd/tailscale/cli/colorable_omit.go b/cmd/tailscale/cli/colorable_omit.go new file mode 100644 index 0000000000000..a821bdbbdc92e --- /dev/null +++ b/cmd/tailscale/cli/colorable_omit.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build ts_omit_colorable + +package cli + +import "io" + +func colorableOutput() (w io.Writer, ok bool) { + return Stdout, false +} diff --git a/cmd/tailscale/cli/open_browser.go b/cmd/tailscale/cli/open_browser.go new file mode 100644 index 0000000000000..a006b9765da7a --- /dev/null +++ b/cmd/tailscale/cli/open_browser.go @@ -0,0 +1,12 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !ts_omit_webbrowser + +package cli + +import "github.com/toqueteos/webbrowser" + +func init() { + hookOpenURL.Set(webbrowser.Open) +} diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index ae4df4da9b51b..49c565febb9cc 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -18,7 +18,6 @@ import ( "text/tabwriter" "github.com/peterbourgon/ff/v3/ffcli" - "github.com/toqueteos/webbrowser" "golang.org/x/net/idna" "tailscale.com/feature" "tailscale.com/ipn" @@ -113,7 +112,9 @@ func runStatus(ctx context.Context, args []string) error { ln.Close() }() if statusArgs.browser { - go webbrowser.Open(statusURL) + if f, ok := hookOpenURL.GetOk(); ok { + go f(statusURL) + } } err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.RequestURI != "/" { @@ -252,6 +253,8 @@ func runStatus(ctx context.Context, args []string) error { return nil } +var hookOpenURL feature.Hook[func(string) error] + var hookPrintFunnelStatus feature.Hook[func(context.Context)] // isRunningOrStarting reports whether st is in state Running or Starting. diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index f087e68096750..15ba39dbacb0d 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -21,8 +21,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd from tailscale.com/util/zstdframe github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd - github.com/mattn/go-colorable from tailscale.com/cmd/tailscale/cli - github.com/mattn/go-isatty from github.com/mattn/go-colorable+ + github.com/mattn/go-isatty from tailscale.com/util/prompt 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink @@ -38,7 +37,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ - github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli 💣 go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index 5969850435b41..c7ab01298f223 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -295,6 +295,8 @@ func TestMinTailscaledWithCLI(t *testing.T) { "archive/tar": "unexpected archive/tar dep", "tailscale.com/feature/conn25": "unexpected conn25 dep", "regexp": "unexpected regexp dep; bloats binary", + "github.com/toqueteos/webbrowser": "unexpected webbrowser dep with ts_omit_webbrowser", + "github.com/mattn/go-colorable": "unexpected go-colorable dep with ts_omit_colorable", }, }.Check(t) } diff --git a/feature/buildfeatures/feature_colorable_disabled.go b/feature/buildfeatures/feature_colorable_disabled.go new file mode 100644 index 0000000000000..3a7bc54234fe5 --- /dev/null +++ b/feature/buildfeatures/feature_colorable_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_colorable + +package buildfeatures + +// HasColorable is whether the binary was built with support for modular feature "Colorized terminal output". +// Specifically, it's whether the binary was NOT built with the "ts_omit_colorable" build tag. +// It's a const so it can be used for dead code elimination. +const HasColorable = false diff --git a/feature/buildfeatures/feature_colorable_enabled.go b/feature/buildfeatures/feature_colorable_enabled.go new file mode 100644 index 0000000000000..b6a08366eba32 --- /dev/null +++ b/feature/buildfeatures/feature_colorable_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_colorable + +package buildfeatures + +// HasColorable is whether the binary was built with support for modular feature "Colorized terminal output". +// Specifically, it's whether the binary was NOT built with the "ts_omit_colorable" build tag. +// It's a const so it can be used for dead code elimination. +const HasColorable = true diff --git a/feature/buildfeatures/feature_webbrowser_disabled.go b/feature/buildfeatures/feature_webbrowser_disabled.go new file mode 100644 index 0000000000000..e6484479c979b --- /dev/null +++ b/feature/buildfeatures/feature_webbrowser_disabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build ts_omit_webbrowser + +package buildfeatures + +// HasWebBrowser is whether the binary was built with support for modular feature "Open URLs in the user's web browser". +// Specifically, it's whether the binary was NOT built with the "ts_omit_webbrowser" build tag. +// It's a const so it can be used for dead code elimination. +const HasWebBrowser = false diff --git a/feature/buildfeatures/feature_webbrowser_enabled.go b/feature/buildfeatures/feature_webbrowser_enabled.go new file mode 100644 index 0000000000000..68d80b49f5444 --- /dev/null +++ b/feature/buildfeatures/feature_webbrowser_enabled.go @@ -0,0 +1,13 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by gen.go; DO NOT EDIT. + +//go:build !ts_omit_webbrowser + +package buildfeatures + +// HasWebBrowser is whether the binary was built with support for modular feature "Open URLs in the user's web browser". +// Specifically, it's whether the binary was NOT built with the "ts_omit_webbrowser" build tag. +// It's a const so it can be used for dead code elimination. +const HasWebBrowser = true diff --git a/feature/featuretags/featuretags.go b/feature/featuretags/featuretags.go index 45daaec5ec29f..4220c02b75fa2 100644 --- a/feature/featuretags/featuretags.go +++ b/feature/featuretags/featuretags.go @@ -84,7 +84,7 @@ type FeatureMeta struct { Deps []FeatureTag // other features this feature requires // ImplementationDetail is whether the feature is an internal implementation - // detail. That is, it's not something a user wuold care about having or not + // detail. That is, it's not something a user would care about having or not // having, but we'd like to able to omit from builds if no other // user-visible features depend on it. ImplementationDetail bool @@ -130,6 +130,7 @@ var Features = map[FeatureTag]FeatureMeta{ "captiveportal": {Sym: "CaptivePortal", Desc: "Captive portal detection"}, "capture": {Sym: "Capture", Desc: "Packet capture"}, "cli": {Sym: "CLI", Desc: "embed the CLI into the tailscaled binary"}, + "colorable": {Sym: "Colorable", Desc: "Colorized terminal output"}, "cliconndiag": {Sym: "CLIConnDiag", Desc: "CLI connection error diagnostics"}, "clientmetrics": {Sym: "ClientMetrics", Desc: "Client metrics support"}, "clientupdate": { @@ -256,7 +257,7 @@ var Features = map[FeatureTag]FeatureMeta{ "systray": { Sym: "SysTray", Desc: "Linux system tray", - Deps: []FeatureTag{"dbus"}, + Deps: []FeatureTag{"dbus", "webbrowser"}, }, "taildrop": { Sym: "Taildrop", @@ -290,6 +291,10 @@ var Features = map[FeatureTag]FeatureMeta{ Desc: "Usermetrics (documented, stable) metrics support", }, "wakeonlan": {Sym: "WakeOnLAN", Desc: "Wake-on-LAN support"}, + "webbrowser": { + Sym: "WebBrowser", + Desc: "Open URLs in the user's web browser", + }, "webclient": { Sym: "WebClient", Desc: "Web client support", Deps: []FeatureTag{"serve"}, From dfba01ca9bd8c4df02c3c32f400d9aeb897c5fc7 Mon Sep 17 00:00:00 2001 From: Tim Walters Date: Sun, 8 Feb 2026 10:56:32 -0500 Subject: [PATCH 080/202] cmd/tailscaled: update documentation url This updates the URL shown by systemd to the new URL used by the docs after the recent migration. Fixes #18646 Signed-off-by: Tim Walters --- cmd/tailscaled/tailscaled.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tailscaled/tailscaled.service b/cmd/tailscaled/tailscaled.service index 719a3c0c96398..9950891a35fa4 100644 --- a/cmd/tailscaled/tailscaled.service +++ b/cmd/tailscaled/tailscaled.service @@ -1,6 +1,6 @@ [Unit] Description=Tailscale node agent -Documentation=https://tailscale.com/kb/ +Documentation=https://tailscale.com/docs/ Wants=network-pre.target After=network-pre.target NetworkManager.service systemd-resolved.service From fff623206ef14ca65e3436d94a4e7d8d4dadc905 Mon Sep 17 00:00:00 2001 From: faukah Date: Mon, 9 Feb 2026 21:39:28 +0100 Subject: [PATCH 081/202] flake.nix: update NixOS wiki link (#18662) wiki.nixos.org is and has been the official wiki for quite some time now. Signed-off-by: faukah --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 76e68e4acd57f..d4a10bcacf769 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ # environment for working on tailscale, for use with "nix develop". # # For more information about this and why this file is useful, see: -# https://nixos.wiki/wiki/Flakes +# https://wiki.nixos.org/wiki/Flakes # # Also look into direnv: https://direnv.net/, this can make it so that you can # automatically get your environment set up when you change folders into the From 00e180cdd79e1c82a77c29c7cac3e0116a427fa5 Mon Sep 17 00:00:00 2001 From: Amal Bansode Date: Mon, 9 Feb 2026 13:24:25 -0800 Subject: [PATCH 082/202] go.mod: update bart dep to v0.26.1 (#18659) bart has gained a bunch of purported performance and usability improvements since the current version we are using (0.18.0, from 1y ago) Updates tailscale/corp#36982 Signed-off-by: Amal Bansode --- cmd/k8s-operator/depaware.txt | 9 +++++++-- cmd/tailscale/depaware.txt | 7 ++++++- cmd/tailscaled/depaware-min.txt | 7 ++++++- cmd/tailscaled/depaware-minbox.txt | 7 ++++++- cmd/tailscaled/depaware.txt | 7 ++++++- cmd/tsidp/depaware.txt | 7 ++++++- flake.nix | 2 +- go.mod | 2 +- go.mod.sri | 2 +- go.sum | 4 ++-- shell.nix | 2 +- tsnet/depaware.txt | 7 ++++++- 12 files changed, 49 insertions(+), 14 deletions(-) diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index 6a6e7d61f9aa3..5565ec01921bb 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -99,8 +99,13 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ github.com/fsnotify/fsnotify/internal from github.com/fsnotify/fsnotify github.com/fxamacker/cbor/v2 from tailscale.com/tka+ github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+ @@ -1060,7 +1065,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna bufio from compress/flate+ bytes from bufio+ - cmp from github.com/gaissmai/bart+ + cmp from encoding/json+ compress/flate from compress/gzip+ compress/gzip from github.com/emicklei/go-restful/v3+ compress/zlib from github.com/emicklei/go-restful/v3+ diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 85bf2312a5f0f..58f9e1c0bfb83 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -95,8 +95,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L github.com/fogleman/gg from tailscale.com/client/systray github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/tsdial + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index e536ac59dde37..b7df3a48a6b1e 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -1,8 +1,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/drive+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 15ba39dbacb0d..ca029194c101e 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -1,8 +1,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/drive+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index da480d1a694e3..71a1df1d4c6c2 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -98,8 +98,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 github.com/djherbis/times from tailscale.com/drive/driveimpl github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/tstun+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+ diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index e29ae93484c95..4dfb831b59f43 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -88,8 +88,13 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+ github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ diff --git a/flake.nix b/flake.nix index d4a10bcacf769..b29d45aacf43b 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-+tOYqRV8ZUA95dfVyRpjnJvwuSMobu/EhtXxq4bwvio= +# nix-direnv cache busting line: sha256-5A6EShJ33yHQdr6tgsNCRFLvNUUjIKXDv5DvzsiUwFI= diff --git a/go.mod b/go.mod index 12ffd3ebd5b2b..c69f4f1edc5aa 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/fogleman/gg v1.3.0 github.com/frankban/quicktest v1.14.6 github.com/fxamacker/cbor/v2 v2.9.0 - github.com/gaissmai/bart v0.18.0 + github.com/gaissmai/bart v0.26.1 github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced github.com/go-logr/zapr v1.3.0 github.com/go-ole/go-ole v1.3.0 diff --git a/go.mod.sri b/go.mod.sri index d46c84a110095..4edd4e7acabad 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-+tOYqRV8ZUA95dfVyRpjnJvwuSMobu/EhtXxq4bwvio= +sha256-5A6EShJ33yHQdr6tgsNCRFLvNUUjIKXDv5DvzsiUwFI= diff --git a/go.sum b/go.sum index 541cef6058655..e925fcc3d4371 100644 --- a/go.sum +++ b/go.sum @@ -373,8 +373,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= -github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= +github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo= +github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= diff --git a/shell.nix b/shell.nix index 3accd73c55ffb..ff44b9b89631b 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-+tOYqRV8ZUA95dfVyRpjnJvwuSMobu/EhtXxq4bwvio= +# nix-direnv cache busting line: sha256-5A6EShJ33yHQdr6tgsNCRFLvNUUjIKXDv5DvzsiUwFI= diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index 5b08200c97f6d..46acadd1dd750 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -88,8 +88,13 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+ github.com/fxamacker/cbor/v2 from tailscale.com/tka github.com/gaissmai/bart from tailscale.com/net/ipset+ + github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ github.com/gaissmai/bart/internal/bitset from github.com/gaissmai/bart+ - github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/lpm from github.com/gaissmai/bart+ + github.com/gaissmai/bart/internal/nodes from github.com/gaissmai/bart + github.com/gaissmai/bart/internal/sparse from github.com/gaissmai/bart/internal/nodes + github.com/gaissmai/bart/internal/value from github.com/gaissmai/bart+ github.com/go-json-experiment/json from tailscale.com/types/opt+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json+ From 5a5572e48acc4eee0ddbb6680d47881efe807177 Mon Sep 17 00:00:00 2001 From: Michael Ben-Ami Date: Thu, 11 Dec 2025 15:31:15 -0500 Subject: [PATCH 083/202] tstun,wgengine: add new datapath hooks for intercepting Connectors 2025 app connector packets We introduce the Conn25PacketHooks interface to be used as a nil-able field in userspaceEngine. The engine then plumbs through the functions to the corresponding tstun.Wrapper intercepts. The new intercepts run pre-filter when egressing toward WireGuard, and post-filter when ingressing from WireGuard. This is preserve the design invariant that the filter recognizes the traffic as interesting app connector traffic. This commit does not plumb through implementation of the interface, so should be a functional no-op. Fixes tailscale/corp#35985 Signed-off-by: Michael Ben-Ami --- net/tstun/wrap.go | 20 +++++++++++++++ wgengine/userspace.go | 59 ++++++++++++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index d463948a208fa..3c1315437f510 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -171,6 +171,9 @@ type Wrapper struct { // PreFilterPacketInboundFromWireGuard is the inbound filter function that runs before the main filter // and therefore sees the packets that may be later dropped by it. PreFilterPacketInboundFromWireGuard FilterFunc + // PostFilterPacketInboundFromWireGuardAppConnector runs after the filter, but before PostFilterPacketInboundFromWireGuard. + // Non-app connector traffic is passed along. Invalid app connector traffic is dropped. + PostFilterPacketInboundFromWireGuardAppConnector FilterFunc // PostFilterPacketInboundFromWireGuard is the inbound filter function that runs after the main filter. PostFilterPacketInboundFromWireGuard GROFilterFunc // PreFilterPacketOutboundToWireGuardNetstackIntercept is a filter function that runs before the main filter @@ -183,6 +186,10 @@ type Wrapper struct { // packets which it handles internally. If both this and PreFilterFromTunToNetstack // filter functions are non-nil, this filter runs second. PreFilterPacketOutboundToWireGuardEngineIntercept FilterFunc + // PreFilterPacketOutboundToWireGuardAppConnectorIntercept runs after PreFilterPacketOutboundToWireGuardEngineIntercept + // for app connector specific traffic. Non-app connector traffic is passed along. Invalid app connector traffic is + // dropped. + PreFilterPacketOutboundToWireGuardAppConnectorIntercept FilterFunc // PostFilterPacketOutboundToWireGuard is the outbound filter function that runs after the main filter. PostFilterPacketOutboundToWireGuard FilterFunc @@ -872,6 +879,12 @@ func (t *Wrapper) filterPacketOutboundToWireGuard(p *packet.Parsed, pc *peerConf return res, gro } } + if t.PreFilterPacketOutboundToWireGuardAppConnectorIntercept != nil { + if res := t.PreFilterPacketOutboundToWireGuardAppConnectorIntercept(p, t); res.IsDrop() { + // Handled by userspaceEngine's configured hook for Connectors 2025 app connectors. + return res, gro + } + } // If the outbound packet is to a jailed peer, use our jailed peer // packet filter. @@ -1234,6 +1247,13 @@ func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook pa return filter.Drop, gro } + if t.PostFilterPacketInboundFromWireGuardAppConnector != nil { + if res := t.PostFilterPacketInboundFromWireGuardAppConnector(p, t); res.IsDrop() { + // Handled by userspaceEngine's configured hook for Connectors 2025 app connectors. + return res, gro + } + } + if t.PostFilterPacketInboundFromWireGuard != nil { var res filter.Response res, gro = t.PostFilterPacketInboundFromWireGuard(p, t, gro) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index e69712061f5c9..245ce421fbe5a 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -143,8 +143,9 @@ type userspaceEngine struct { trimmedNodes map[key.NodePublic]bool // set of node keys of peers currently excluded from wireguard config sentActivityAt map[netip.Addr]*mono.Time // value is accessed atomically destIPActivityFuncs map[netip.Addr]func() - lastStatusPollTime mono.Time // last time we polled the engine status - reconfigureVPN func() error // or nil + lastStatusPollTime mono.Time // last time we polled the engine status + reconfigureVPN func() error // or nil + conn25PacketHooks Conn25PacketHooks // or nil mu sync.Mutex // guards following; see lock order comment below netMap *netmap.NetworkMap // or nil @@ -175,6 +176,19 @@ type BIRDClient interface { Close() error } +// Conn25PacketHooks are hooks for Connectors 2025 app connectors. +// They are meant to be wired into to corresponding hooks in the +// [tstun.Wrapper]. They may modify the packet (e.g., NAT), or drop +// invalid app connector traffic. +type Conn25PacketHooks interface { + // HandlePacketsFromTunDevice sends packets originating from the tun device + // for further Connectors 2025 app connectors processing. + HandlePacketsFromTunDevice(*packet.Parsed) filter.Response + // HandlePacketsFromWireguard sends packets originating from WireGuard + // for further Connectors 2025 app connectors processing. + HandlePacketsFromWireGuard(*packet.Parsed) filter.Response +} + // Config is the engine configuration. type Config struct { // Tun is the device used by the Engine to exchange packets with @@ -247,6 +261,10 @@ type Config struct { // TODO(creachadair): As of 2025-03-19 this is optional, but is intended to // become required non-nil. EventBus *eventbus.Bus + + // Conn25PacketHooks, if non-nil, is used to hook packets for Connectors 2025 + // app connector handling logic. + Conn25PacketHooks Conn25PacketHooks } // NewFakeUserspaceEngine returns a new userspace engine for testing. @@ -348,19 +366,20 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } e := &userspaceEngine{ - eventBus: conf.EventBus, - timeNow: mono.Now, - logf: logf, - reqCh: make(chan struct{}, 1), - waitCh: make(chan struct{}), - tundev: tsTUNDev, - router: rtr, - dialer: conf.Dialer, - confListenPort: conf.ListenPort, - birdClient: conf.BIRDClient, - controlKnobs: conf.ControlKnobs, - reconfigureVPN: conf.ReconfigureVPN, - health: conf.HealthTracker, + eventBus: conf.EventBus, + timeNow: mono.Now, + logf: logf, + reqCh: make(chan struct{}, 1), + waitCh: make(chan struct{}), + tundev: tsTUNDev, + router: rtr, + dialer: conf.Dialer, + confListenPort: conf.ListenPort, + birdClient: conf.BIRDClient, + controlKnobs: conf.ControlKnobs, + reconfigureVPN: conf.ReconfigureVPN, + health: conf.HealthTracker, + conn25PacketHooks: conf.Conn25PacketHooks, } if e.birdClient != nil { @@ -434,6 +453,16 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } e.tundev.PreFilterPacketOutboundToWireGuardEngineIntercept = e.handleLocalPackets + if e.conn25PacketHooks != nil { + e.tundev.PreFilterPacketOutboundToWireGuardAppConnectorIntercept = func(p *packet.Parsed, _ *tstun.Wrapper) filter.Response { + return e.conn25PacketHooks.HandlePacketsFromTunDevice(p) + } + + e.tundev.PostFilterPacketInboundFromWireGuardAppConnector = func(p *packet.Parsed, _ *tstun.Wrapper) filter.Response { + return e.conn25PacketHooks.HandlePacketsFromWireGuard(p) + } + } + if buildfeatures.HasDebug && envknob.BoolDefaultTrue("TS_DEBUG_CONNECT_FAILURES") { if e.tundev.PreFilterPacketInboundFromWireGuard != nil { return nil, errors.New("unexpected PreFilterIn already set") From d26d3fcb95b75e9fdc3acb53529e97a1a14cc3c6 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 9 Feb 2026 13:25:07 -0800 Subject: [PATCH 084/202] .github/workflows: add macos runner Fixes #18118 Change-Id: I118fcc6537af9ccbdc7ce6b78134e8059b0b5ccf Signed-off-by: Brad Fitzpatrick --- .github/workflows/test.yml | 62 ++++++++++++++++++++++++++++++++++++- logtail/filch/filch_test.go | 14 +++++++++ ssh/tailssh/tailssh_test.go | 3 ++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6906e53ef680..152ef7bce9008 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -300,6 +300,63 @@ jobs: working-directory: src run: ./tool/go version + macos: + runs-on: macos-latest + needs: gomod-cache + steps: + - name: checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + path: src + - name: Restore Go module cache + uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: gomodcache + key: ${{ needs.gomod-cache.outputs.cache-key }} + enableCrossOsArchive: true + - name: Restore Cache + id: restore-cache + uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: ~/Library/Caches/go-build + key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}- + ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}- + ${{ runner.os }}-go-test- + - name: build test wrapper + working-directory: src + run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper + - name: test all + working-directory: src + run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... + - name: check that no tracked files changed + working-directory: src + run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1) + - name: check that no new files were added + working-directory: src + run: | + # Note: The "error: pathspec..." you see below is normal! + # In the success case in which there are no new untracked files, + # git ls-files complains about the pathspec not matching anything. + # That's OK. It's not worth the effort to suppress. Please ignore it. + if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' + then + echo "Build/test created untracked files in the repo (file names above)." + exit 1 + fi + - name: Tidy cache + working-directory: src + run: | + find $(./tool/go env GOCACHE) -type f -mmin +90 -delete + - name: Save Cache + # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. + if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' + uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: ~/Library/Caches/go-build + key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}-${{ github.run_id }} + privileged: needs: gomod-cache runs-on: ubuntu-24.04 @@ -851,10 +908,11 @@ jobs: notify_slack: if: always() # Any of these jobs failing causes a slack notification. - needs: + needs: - android - test - windows + - macos - vm - cross - ios @@ -900,6 +958,7 @@ jobs: - android - test - windows + - macos - vm - cross - ios @@ -949,6 +1008,7 @@ jobs: - check_mergeability_strict - test - windows + - macos - vm - wasm - fuzz diff --git a/logtail/filch/filch_test.go b/logtail/filch/filch_test.go index 2538233cfd84c..f2f9e9e3bcd6b 100644 --- a/logtail/filch/filch_test.go +++ b/logtail/filch/filch_test.go @@ -5,6 +5,7 @@ package filch import ( "bytes" + "crypto/sha256" "encoding/json" "fmt" "io" @@ -120,7 +121,19 @@ func setupStderr(t *testing.T) { tstest.Replace(t, &os.Stderr, pipeW) } +func skipDarwin(t testing.TB) { + if runtime.GOOS != "darwin" { + return + } + src := must.Get(os.ReadFile("filch.go")) + if fmt.Sprintf("%x", sha256.Sum256(src)) != "a32da5e22034823c19ac7f29960e3646f540d67f85a0028832cab1f1557fc693" { + t.Errorf("filch.go has changed since this test was skipped; please delete this skip") + } + t.Skip("skipping known failing test on darwin; fixed in progress by https://github.com/tailscale/tailscale/pull/18660") +} + func TestConcurrentWriteAndRead(t *testing.T) { + skipDarwin(t) if replaceStderrSupportedForTest { setupStderr(t) } @@ -283,6 +296,7 @@ func TestMaxLineSize(t *testing.T) { } func TestMaxFileSize(t *testing.T) { + skipDarwin(t) if replaceStderrSupportedForTest { t.Run("ReplaceStderr:true", func(t *testing.T) { testMaxFileSize(t, true) }) } diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index f91cbafe72213..44db0cc000beb 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -495,6 +495,9 @@ func TestSSHRecordingCancelsSessionsOnUploadFailure(t *testing.T) { if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { t.Skipf("skipping on %q; only runs on linux and darwin", runtime.GOOS) } + if runtime.GOOS == "darwin" && cibuild.On() { + t.Skipf("this fails on CI on macOS; see https://github.com/tailscale/tailscale/issues/7707") + } var handler http.HandlerFunc recordingServer := mockRecordingServer(t, func(w http.ResponseWriter, r *http.Request) { From 770bf000de965697ae3a194448994f015586c509 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Mon, 9 Feb 2026 16:16:44 -0700 Subject: [PATCH 085/202] tool/gocross: replace use of Start-Process -Wait flag with WaitForExit -Wait does not just wait for the created process; it waits for the entire process tree rooted at that process! This can cause the shell to wait indefinitely if something in that tree fired up any background processes. Instead we call WaitForExit on the returned process. Updates https://github.com/tailscale/corp/issues/29940 Signed-off-by: Aaron Klotz --- tool/gocross/gocross-wrapper.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tool/gocross/gocross-wrapper.ps1 b/tool/gocross/gocross-wrapper.ps1 index df00d36641ad7..23bd6eb2771dd 100644 --- a/tool/gocross/gocross-wrapper.ps1 +++ b/tool/gocross/gocross-wrapper.ps1 @@ -190,7 +190,8 @@ $bootstrapScriptBlock = { $goBuildEnv['GOROOT'] = $null $procExe = Join-Path $toolchain 'bin' 'go.exe' -Resolve - $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $goBuildEnv -ArgumentList 'build', '-o', $gocrossPath, "-ldflags=-X=tailscale.com/version.gitCommitStamp=$wantVer", 'tailscale.com/tool/gocross' -NoNewWindow -Wait -PassThru + $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $goBuildEnv -ArgumentList 'build', '-o', $gocrossPath, "-ldflags=-X=tailscale.com/version.gitCommitStamp=$wantVer", 'tailscale.com/tool/gocross' -NoNewWindow -PassThru + $proc.WaitForExit() if ($proc.ExitCode -ne 0) { throw 'error building gocross' } @@ -222,10 +223,12 @@ if ($Env:TS_USE_GOCROSS -ne '1') { } $procExe = Join-Path $toolchain 'bin' 'go.exe' -Resolve - $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -Wait -PassThru + $proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -PassThru + $proc.WaitForExit() exit $proc.ExitCode } $procExe = Join-Path $repoRoot 'gocross.exe' -Resolve -$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -Wait -PassThru +$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -PassThru +$proc.WaitForExit() exit $proc.ExitCode From e4008d1994db0ea71d1c64f3d5d8db9e67f5d427 Mon Sep 17 00:00:00 2001 From: BeckyPauley <64131207+BeckyPauley@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:19:06 +0000 Subject: [PATCH 086/202] cmd/containerboot: fix error handling for egress (#18657) Fixes #18631 Signed-off-by: Becky Pauley --- cmd/containerboot/egressservices.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/containerboot/egressservices.go b/cmd/containerboot/egressservices.go index 6526c255eeed7..e60d65c047f95 100644 --- a/cmd/containerboot/egressservices.go +++ b/cmd/containerboot/egressservices.go @@ -478,7 +478,8 @@ func (ep *egressProxy) tailnetTargetIPsForSvc(svc egressservices.Config, n ipn.N } egressAddrs, err := resolveTailnetFQDN(n.NetMap, svc.TailnetTarget.FQDN) if err != nil { - return nil, fmt.Errorf("error fetching backend addresses for %q: %w", svc.TailnetTarget.FQDN, err) + log.Printf("error fetching backend addresses for %q: %v", svc.TailnetTarget.FQDN, err) + return addrs, nil } if len(egressAddrs) == 0 { log.Printf("tailnet target %q does not have any backend addresses, skipping", svc.TailnetTarget.FQDN) From 086968c15b5b000f3533ab981ec0201678ca78f3 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Tue, 10 Feb 2026 09:29:14 -0500 Subject: [PATCH 087/202] net/dns, ipn/local: skip health warnings in dns forwarder when accept-dns is false (#18572) fixes tailscale/tailscale#18436 Queries can still make their way to the forwarder when accept-dns is disabled. Since we have not configured the forwarder if --accept-dns is false, this errors out (correctly) but it also generates a persistent health warning. This forwards the Pref setting all the way through the stack to the forwarder so that we can be more judicious about when we decide that the forward path is unintentionally missing, vs simply not configured. Testing: tailscale set --accept-dns=false. (or from the GUI) dig @100.100.100.100 example.com tailscale status No dns related health warnings should be surfaced. Signed-off-by: Jonathan Nobels --- ipn/ipnlocal/dnsconfig_test.go | 20 ++++++++++++------- ipn/ipnlocal/node_backend.go | 5 +++-- ipn/ipnlocal/state_test.go | 35 ++++++++++++++++++++-------------- net/dns/config.go | 4 ++++ net/dns/dns_clone.go | 1 + net/dns/dns_view.go | 6 ++++++ net/dns/manager.go | 1 + net/dns/resolver/forwarder.go | 21 ++++++++++++++++---- net/dns/resolver/tsdns.go | 5 ++++- 9 files changed, 70 insertions(+), 28 deletions(-) diff --git a/ipn/ipnlocal/dnsconfig_test.go b/ipn/ipnlocal/dnsconfig_test.go index ab00b47404216..9d30029ff8659 100644 --- a/ipn/ipnlocal/dnsconfig_test.go +++ b/ipn/ipnlocal/dnsconfig_test.go @@ -219,7 +219,8 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, Routes: map[dnsname.FQDN][]*dnstype.Resolver{ "0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.": nil, "100.100.in-addr.arpa.": nil, @@ -319,7 +320,8 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, DefaultResolvers: []*dnstype.Resolver{ {Addr: "8.8.8.8"}, }, @@ -342,8 +344,9 @@ func TestDNSConfigForNetmap(t *testing.T) { ExitNodeID: "some-id", }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, DefaultResolvers: []*dnstype.Resolver{ {Addr: "8.8.4.4"}, }, @@ -362,8 +365,9 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Hosts: map[dnsname.FQDN][]netip.Addr{}, - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + AcceptDNS: true, + Hosts: map[dnsname.FQDN][]netip.Addr{}, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, }, }, { @@ -420,6 +424,7 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ + AcceptDNS: true, Hosts: map[dnsname.FQDN][]netip.Addr{ "a.": ips("100.101.101.101"), "p1.": ips("100.102.0.1"), @@ -466,7 +471,8 @@ func TestDNSConfigForNetmap(t *testing.T) { CorpDNS: true, }, want: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, Hosts: map[dnsname.FQDN][]netip.Addr{ "a.": ips("100.101.101.101"), "p1.": ips("100.102.0.1"), diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index 170dae9569c8c..929ef34a48881 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -696,8 +696,9 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, peers map[tailcfg.NodeID]tailcfg. } dcfg := &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: map[dnsname.FQDN][]netip.Addr{}, + AcceptDNS: prefs.CorpDNS(), + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: map[dnsname.FQDN][]netip.Addr{}, } // selfV6Only is whether we only have IPv6 addresses ourselves. diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 97c2c4d8f9daf..ed6ad06ef191e 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -1300,8 +1300,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1356,8 +1357,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node2), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node2), }, }, { @@ -1404,8 +1406,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1436,8 +1439,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node3), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node3), }, }, { @@ -1500,8 +1504,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1529,8 +1534,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { @@ -1560,8 +1566,9 @@ func TestEngineReconfigOnStateChange(t *testing.T) { Routes: routesWithQuad100(), }, wantDNSCfg: &dns.Config{ - Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, - Hosts: hostsFor(node1), + AcceptDNS: true, + Routes: map[dnsname.FQDN][]*dnstype.Resolver{}, + Hosts: hostsFor(node1), }, }, { diff --git a/net/dns/config.go b/net/dns/config.go index f776d1af04443..47fac83c2df48 100644 --- a/net/dns/config.go +++ b/net/dns/config.go @@ -26,6 +26,10 @@ import ( // Config is a DNS configuration. type Config struct { + // AcceptDNS true if [Prefs.CorpDNS] is enabled (or --accept-dns=true). + // This should be used for error handling and health reporting + // purposes only. + AcceptDNS bool // DefaultResolvers are the DNS resolvers to use for DNS names // which aren't covered by more specific per-domain routes below. // If empty, the OS's default resolvers (the ones that predate diff --git a/net/dns/dns_clone.go b/net/dns/dns_clone.go index ea5e5299beb7d..291f96ec2b51f 100644 --- a/net/dns/dns_clone.go +++ b/net/dns/dns_clone.go @@ -51,6 +51,7 @@ func (src *Config) Clone() *Config { // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _ConfigCloneNeedsRegeneration = Config(struct { + AcceptDNS bool DefaultResolvers []*dnstype.Resolver Routes map[dnsname.FQDN][]*dnstype.Resolver SearchDomains []dnsname.FQDN diff --git a/net/dns/dns_view.go b/net/dns/dns_view.go index 313621c86e85b..70cb89dcaf128 100644 --- a/net/dns/dns_view.go +++ b/net/dns/dns_view.go @@ -87,6 +87,11 @@ func (v *ConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return nil } +// AcceptDNS true if [Prefs.CorpDNS] is enabled (or --accept-dns=true). +// This should be used for error handling and health reporting +// purposes only. +func (v ConfigView) AcceptDNS() bool { return v.ж.AcceptDNS } + // DefaultResolvers are the DNS resolvers to use for DNS names // which aren't covered by more specific per-domain routes below. // If empty, the OS's default resolvers (the ones that predate @@ -139,6 +144,7 @@ func (v ConfigView) Equal(v2 ConfigView) bool { return v.ж.Equal(v2.ж) } // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _ConfigViewNeedsRegeneration = Config(struct { + AcceptDNS bool DefaultResolvers []*dnstype.Resolver Routes map[dnsname.FQDN][]*dnstype.Resolver SearchDomains []dnsname.FQDN diff --git a/net/dns/manager.go b/net/dns/manager.go index faca1053cf852..c052055654f1d 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -292,6 +292,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig // the OS. rcfg.Hosts = cfg.Hosts rcfg.SubdomainHosts = cfg.SubdomainHosts + rcfg.AcceptDNS = cfg.AcceptDNS routes := map[dnsname.FQDN][]*dnstype.Resolver{} // assigned conditionally to rcfg.Routes below. var propagateHostsToOS bool for suffix, resolvers := range cfg.Routes { diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 189911ee24c0a..6fec32d6a2685 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -323,6 +323,12 @@ type forwarder struct { // /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS stub // resolver lookup. cloudHostFallback []resolverAndDelay + + // acceptDNS tracks the CorpDNS pref (--accept-dns) + // This lets us skip health warnings if the forwarder receives inbound + // queries directly - but we didn't configure it with any upstream resolvers. + // That's an error, but not a health error if the user has disabled CorpDNS. + acceptDNS bool } func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, health *health.Tracker, knobs *controlknobs.Knobs) *forwarder { @@ -434,7 +440,7 @@ func cloudResolvers() []resolverAndDelay { // Resolver.SetConfig on reconfig. // // The memory referenced by routesBySuffix should not be modified. -func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolver) { +func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolver, acceptDNS bool) { routes := make([]route, 0, len(routesBySuffix)) cloudHostFallback := cloudResolvers() @@ -468,6 +474,7 @@ func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolve f.mu.Lock() defer f.mu.Unlock() + f.acceptDNS = acceptDNS f.routes = routes f.cloudHostFallback = cloudHostFallback } @@ -1056,7 +1063,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo resolvers = f.resolvers(domain) if len(resolvers) == 0 { metricDNSFwdErrorNoUpstream.Add(1) - f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""}) + if f.acceptDNS { + f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""}) + } f.logf("no upstream resolvers set, returning SERVFAIL") res, err := servfailResponse(query) @@ -1156,7 +1165,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo for _, rr := range resolvers { resolverAddrs = append(resolverAddrs, rr.name.Addr) } - f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + if f.acceptDNS { + f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + } case responseChan <- res: if f.verboseFwd { f.logf("forwarder response(%d, %v, %d) = %d, %v", fq.txid, typ, len(domain), len(res.bs), firstErr) @@ -1181,7 +1192,9 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo for _, rr := range resolvers { resolverAddrs = append(resolverAddrs, rr.name.Addr) } - f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + if f.acceptDNS { + f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")}) + } return fmt.Errorf("waiting for response or error from %v: %w", resolverAddrs, ctx.Err()) } } diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 5b44f6c2d586f..d0601de7bfe25 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -70,6 +70,9 @@ type packet struct { // Else forward the query to the most specific matching entry in Routes. // Else return SERVFAIL. type Config struct { + // True if [Prefs.CorpDNS] is true or --accept-dns=true was specified. + // This should only be used for error handling and health reporting. + AcceptDNS bool // Routes is a map of DNS name suffix to the resolvers to use for // queries within that suffix. // Queries only match the most specific suffix. @@ -279,7 +282,7 @@ func (r *Resolver) SetConfig(cfg Config) error { } } - r.forwarder.setRoutes(cfg.Routes) + r.forwarder.setRoutes(cfg.Routes, cfg.AcceptDNS) r.mu.Lock() defer r.mu.Unlock() From dc1d811d4838cb73216244ecaf7be923f005548e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 8 Feb 2026 18:07:33 +0000 Subject: [PATCH 088/202] magicsock, ipnlocal: revert eventbus-based node/filter updates, remove Synchronize hack Restore synchronous method calls from LocalBackend to magicsock.Conn for node views, filter, and delta mutations. The eventbus delivery introduced in 8e6f63cf1 was invalid for these updates because subsequent operations in the same call chain depend on magicsock already having the current state. The Synchronize/settleEventBus workaround was fragile and kept requiring more workarounds and introducing new mystery bugs. Since eventbus was added, we've since learned more about when to use eventbus, and this wasn't one of the cases. We can take another swing at using eventbus for netmap changes in a future change. Fixes #16369 Updates #18575 (likely fixes) Change-Id: I79057cc9259993368bb1e350ff0e073adf6b9a8f Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 36 +++---- ipn/ipnlocal/node_backend.go | 18 ---- ipn/ipnlocal/state_test.go | 5 - wgengine/magicsock/magicsock.go | 146 ++++++++------------------- wgengine/magicsock/magicsock_test.go | 107 +++++++------------- 5 files changed, 95 insertions(+), 217 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 8f8051f4b7273..981e2df73a83b 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1562,22 +1562,6 @@ func (b *LocalBackend) GetFilterForTest() *filter.Filter { return nb.filterAtomic.Load() } -func (b *LocalBackend) settleEventBus() { - // The move to eventbus made some things racy that - // weren't before so we have to wait for it to all be settled - // before we call certain things. - // See https://github.com/tailscale/tailscale/issues/16369 - // But we can't do this while holding b.mu without deadlocks, - // (https://github.com/tailscale/tailscale/pull/17804#issuecomment-3514426485) so - // now we just do it in lots of places before acquiring b.mu. - // Is this winning?? - if b.sys != nil { - if ms, ok := b.sys.MagicSock.GetOK(); ok { - ms.Synchronize() - } - } -} - // SetControlClientStatus is the callback invoked by the control client whenever it posts a new status. // Among other things, this is where we update the netmap, packet filters, DNS and DERP maps. func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st controlclient.Status) { @@ -2115,16 +2099,16 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo } }() - // Gross. See https://github.com/tailscale/tailscale/issues/16369 - b.settleEventBus() - defer b.settleEventBus() - b.mu.Lock() defer b.mu.Unlock() cn := b.currentNode() cn.UpdateNetmapDelta(muts) + if ms, ok := b.sys.MagicSock.GetOK(); ok { + ms.UpdateNetmapDelta(muts) + } + // If auto exit nodes are enabled and our exit node went offline, // we need to schedule picking a new one. // TODO(nickkhyl): move the auto exit node logic to a feature package. @@ -2440,7 +2424,6 @@ func (b *LocalBackend) initOnce() { // actually a supported operation (it should be, but it's very unclear // from the following whether or not that is a safe transition). func (b *LocalBackend) Start(opts ipn.Options) error { - defer b.settleEventBus() // with b.mu unlocked b.mu.Lock() defer b.mu.Unlock() return b.startLocked(opts) @@ -2936,6 +2919,9 @@ func packetFilterPermitsUnlockedNodes(peers map[tailcfg.NodeID]tailcfg.NodeView, func (b *LocalBackend) setFilter(f *filter.Filter) { b.currentNode().setFilter(f) b.e.SetFilter(f) + if ms, ok := b.sys.MagicSock.GetOK(); ok { + ms.SetFilter(f) + } } var removeFromDefaultRoute = []netip.Prefix{ @@ -4352,7 +4338,6 @@ func (b *LocalBackend) EditPrefsAs(mp *ipn.MaskedPrefs, actor ipnauth.Actor) (ip if mp.SetsInternal() { return ipn.PrefsView{}, errors.New("can't set Internal fields") } - defer b.settleEventBus() b.mu.Lock() defer b.mu.Unlock() @@ -6264,6 +6249,13 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { login = cmp.Or(profileFromView(nm.UserProfiles[nm.User()]).LoginName, "") } b.currentNode().SetNetMap(nm) + if ms, ok := b.sys.MagicSock.GetOK(); ok { + if nm != nil { + ms.SetNetworkMap(nm.SelfNode, nm.Peers) + } else { + ms.SetNetworkMap(tailcfg.NodeView{}, nil) + } + } if login != b.activeLogin { b.logf("active login: %v", login) b.activeLogin = login diff --git a/ipn/ipnlocal/node_backend.go b/ipn/ipnlocal/node_backend.go index 929ef34a48881..b70d71cb934f2 100644 --- a/ipn/ipnlocal/node_backend.go +++ b/ipn/ipnlocal/node_backend.go @@ -31,7 +31,6 @@ import ( "tailscale.com/util/mak" "tailscale.com/util/slicesx" "tailscale.com/wgengine/filter" - "tailscale.com/wgengine/magicsock" ) // nodeBackend is node-specific [LocalBackend] state. It is usually the current node. @@ -79,9 +78,6 @@ type nodeBackend struct { // initialized once and immutable eventClient *eventbus.Client - filterPub *eventbus.Publisher[magicsock.FilterUpdate] - nodeViewsPub *eventbus.Publisher[magicsock.NodeViewsUpdate] - nodeMutsPub *eventbus.Publisher[magicsock.NodeMutationsUpdate] derpMapViewPub *eventbus.Publisher[tailcfg.DERPMapView] // TODO(nickkhyl): maybe use sync.RWMutex? @@ -122,11 +118,7 @@ func newNodeBackend(ctx context.Context, logf logger.Logf, bus *eventbus.Bus) *n // Default filter blocks everything and logs nothing. noneFilter := filter.NewAllowNone(logger.Discard, &netipx.IPSet{}) nb.filterAtomic.Store(noneFilter) - nb.filterPub = eventbus.Publish[magicsock.FilterUpdate](nb.eventClient) - nb.nodeViewsPub = eventbus.Publish[magicsock.NodeViewsUpdate](nb.eventClient) - nb.nodeMutsPub = eventbus.Publish[magicsock.NodeMutationsUpdate](nb.eventClient) nb.derpMapViewPub = eventbus.Publish[tailcfg.DERPMapView](nb.eventClient) - nb.filterPub.Publish(magicsock.FilterUpdate{Filter: nb.filterAtomic.Load()}) return nb } @@ -436,15 +428,11 @@ func (nb *nodeBackend) SetNetMap(nm *netmap.NetworkMap) { nb.netMap = nm nb.updateNodeByAddrLocked() nb.updatePeersLocked() - nv := magicsock.NodeViewsUpdate{} if nm != nil { - nv.SelfNode = nm.SelfNode - nv.Peers = nm.Peers nb.derpMapViewPub.Publish(nm.DERPMap.View()) } else { nb.derpMapViewPub.Publish(tailcfg.DERPMapView{}) } - nb.nodeViewsPub.Publish(nv) } func (nb *nodeBackend) updateNodeByAddrLocked() { @@ -520,9 +508,6 @@ func (nb *nodeBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo // call (e.g. its endpoints + online status both change) var mutableNodes map[tailcfg.NodeID]*tailcfg.Node - update := magicsock.NodeMutationsUpdate{ - Mutations: make([]netmap.NodeMutation, 0, len(muts)), - } for _, m := range muts { n, ok := mutableNodes[m.NodeIDBeingMutated()] if !ok { @@ -533,14 +518,12 @@ func (nb *nodeBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo } n = nv.AsStruct() mak.Set(&mutableNodes, nv.ID(), n) - update.Mutations = append(update.Mutations, m) } m.Apply(n) } for nid, n := range mutableNodes { nb.peers[nid] = n.View() } - nb.nodeMutsPub.Publish(update) return true } @@ -562,7 +545,6 @@ func (nb *nodeBackend) filter() *filter.Filter { func (nb *nodeBackend) setFilter(f *filter.Filter) { nb.filterAtomic.Store(f) - nb.filterPub.Publish(magicsock.FilterUpdate{Filter: f}) } func (nb *nodeBackend) dnsConfigForNetmap(prefs ipn.PrefsView, selfExpired bool, versionOS string) *dns.Config { diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index ed6ad06ef191e..39796ec325367 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -1600,11 +1600,6 @@ func TestEngineReconfigOnStateChange(t *testing.T) { tt.steps(t, lb, cc) } - // TODO(bradfitz): this whole event bus settling thing - // should be unnecessary once the bogus uses of eventbus - // are removed. (https://github.com/tailscale/tailscale/issues/16369) - lb.settleEventBus() - if gotState := lb.State(); gotState != tt.wantState { t.Errorf("State: got %v; want %v", gotState, tt.wantState) } diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index d6f411f4ac2dc..b2852d2e2fdbc 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -177,9 +177,6 @@ type Conn struct { connCtxCancel func() // closes connCtx donec <-chan struct{} // connCtx.Done()'s to avoid context.cancelCtx.Done()'s mutex per call - // A publisher for synchronization points to ensure correct ordering of - // config changes between magicsock and wireguard. - syncPub *eventbus.Publisher[syncPoint] allocRelayEndpointPub *eventbus.Publisher[UDPRelayAllocReq] portUpdatePub *eventbus.Publisher[router.PortUpdate] tsmpDiscoKeyAvailablePub *eventbus.Publisher[NewDiscoKeyAvailable] @@ -362,11 +359,11 @@ type Conn struct { netInfoLast *tailcfg.NetInfo derpMap *tailcfg.DERPMap // nil (or zero regions/nodes) means DERP is disabled - self tailcfg.NodeView // from last onNodeViewsUpdate - peers views.Slice[tailcfg.NodeView] // from last onNodeViewsUpdate, sorted by Node.ID; Note: [netmap.NodeMutation]'s rx'd in onNodeMutationsUpdate are never applied - filt *filter.Filter // from last onFilterUpdate + self tailcfg.NodeView // from last SetNetworkMap + peers views.Slice[tailcfg.NodeView] // from last SetNetworkMap, sorted by Node.ID; Note: [netmap.NodeMutation]'s rx'd in UpdateNetmapDelta are never applied + filt *filter.Filter // from last SetFilter relayClientEnabled bool // whether we can allocate UDP relay endpoints on UDP relay servers or receive CallMeMaybeVia messages from peers - lastFlags debugFlags // at time of last onNodeViewsUpdate + lastFlags debugFlags // at time of last SetNetworkMap privateKey key.NodePrivate // WireGuard private key for this node everHadKey bool // whether we ever had a non-zero private key myDerp int // nearest DERP region ID; 0 means none/unknown @@ -521,47 +518,6 @@ func (o *Options) derpActiveFunc() func() { return o.DERPActiveFunc } -// NodeViewsUpdate represents an update event of [tailcfg.NodeView] for all -// nodes. This event is published over an [eventbus.Bus]. It may be published -// with an invalid SelfNode, and/or zero/nil Peers. [magicsock.Conn] is the sole -// subscriber as of 2025-06. If you are adding more subscribers consider moving -// this type out of magicsock. -type NodeViewsUpdate struct { - SelfNode tailcfg.NodeView - Peers []tailcfg.NodeView // sorted by Node.ID -} - -// NodeMutationsUpdate represents an update event of one or more -// [netmap.NodeMutation]. This event is published over an [eventbus.Bus]. -// [magicsock.Conn] is the sole subscriber as of 2025-06. If you are adding more -// subscribers consider moving this type out of magicsock. -type NodeMutationsUpdate struct { - Mutations []netmap.NodeMutation -} - -// FilterUpdate represents an update event for a [*filter.Filter]. This event is -// signaled over an [eventbus.Bus]. [magicsock.Conn] is the sole subscriber as -// of 2025-06. If you are adding more subscribers consider moving this type out -// of magicsock. -type FilterUpdate struct { - *filter.Filter -} - -// syncPoint is an event published over an [eventbus.Bus] by [Conn.Synchronize]. -// It serves as a synchronization point, allowing to wait until magicsock -// has processed all pending events. -type syncPoint chan struct{} - -// Wait blocks until [syncPoint.Signal] is called. -func (s syncPoint) Wait() { - <-s -} - -// Signal signals the sync point, unblocking the [syncPoint.Wait] call. -func (s syncPoint) Signal() { - close(s) -} - // UDPRelayAllocReq represents a [*disco.AllocateUDPRelayEndpointRequest] // reception event. This is signaled over an [eventbus.Bus] from // [magicsock.Conn] towards [relayserver.extension]. @@ -654,21 +610,6 @@ func (c *Conn) onUDPRelayAllocResp(allocResp UDPRelayAllocResp) { } } -// Synchronize waits for all [eventbus] events published -// prior to this call to be processed by the receiver. -func (c *Conn) Synchronize() { - if c.syncPub == nil { - // Eventbus is not used; no need to synchronize (in certain tests). - return - } - sp := syncPoint(make(chan struct{})) - c.syncPub.Publish(sp) - select { - case <-sp: - case <-c.donec: - } -} - // NewConn creates a magic Conn listening on opts.Port. // As the set of possible endpoints for a Conn changes, the // callback opts.EndpointsFunc is called. @@ -694,18 +635,10 @@ func NewConn(opts Options) (*Conn, error) { // NewConn otherwise published events can be missed. ec := c.eventBus.Client("magicsock.Conn") c.eventClient = ec - c.syncPub = eventbus.Publish[syncPoint](ec) c.allocRelayEndpointPub = eventbus.Publish[UDPRelayAllocReq](ec) c.portUpdatePub = eventbus.Publish[router.PortUpdate](ec) c.tsmpDiscoKeyAvailablePub = eventbus.Publish[NewDiscoKeyAvailable](ec) eventbus.SubscribeFunc(ec, c.onPortMapChanged) - eventbus.SubscribeFunc(ec, c.onFilterUpdate) - eventbus.SubscribeFunc(ec, c.onNodeViewsUpdate) - eventbus.SubscribeFunc(ec, c.onNodeMutationsUpdate) - eventbus.SubscribeFunc(ec, func(sp syncPoint) { - c.dlogf("magicsock: received sync point after reconfig") - sp.Signal() - }) eventbus.SubscribeFunc(ec, c.onUDPRelayAllocResp) c.connCtx, c.connCtxCancel = context.WithCancel(context.Background()) @@ -2907,11 +2840,12 @@ func capVerIsRelayCapable(version tailcfg.CapabilityVersion) bool { return version >= 121 } -// onFilterUpdate is called when a [FilterUpdate] is received over the -// [eventbus.Bus]. -func (c *Conn) onFilterUpdate(f FilterUpdate) { +// SetFilter updates the packet filter used by the connection. +// It must be called synchronously from the caller's goroutine to ensure +// magicsock has the current filter before subsequent operations proceed. +func (c *Conn) SetFilter(f *filter.Filter) { c.mu.Lock() - c.filt = f.Filter + c.filt = f self := c.self peers := c.peers relayClientEnabled := c.relayClientEnabled @@ -2924,7 +2858,7 @@ func (c *Conn) onFilterUpdate(f FilterUpdate) { // The filter has changed, and we are operating as a relay server client. // Re-evaluate it in order to produce an updated relay server set. - c.updateRelayServersSet(f.Filter, self, peers) + c.updateRelayServersSet(f, self, peers) } // updateRelayServersSet iterates all peers and self, evaluating filt for each @@ -3015,21 +2949,24 @@ func (c *candidatePeerRelay) isValid() bool { return !c.nodeKey.IsZero() && !c.discoKey.IsZero() } -// onNodeViewsUpdate is called when a [NodeViewsUpdate] is received over the -// [eventbus.Bus]. -func (c *Conn) onNodeViewsUpdate(update NodeViewsUpdate) { - peersChanged := c.updateNodes(update) +// SetNetworkMap updates the network map with the given self node and peers. +// It must be called synchronously from the caller's goroutine to ensure +// magicsock has the current state before subsequent operations proceed. +// +// self may be invalid if there's no network map. +func (c *Conn) SetNetworkMap(self tailcfg.NodeView, peers []tailcfg.NodeView) { + peersChanged := c.updateNodes(self, peers) - relayClientEnabled := update.SelfNode.Valid() && - !update.SelfNode.HasCap(tailcfg.NodeAttrDisableRelayClient) && - !update.SelfNode.HasCap(tailcfg.NodeAttrOnlyTCP443) + relayClientEnabled := self.Valid() && + !self.HasCap(tailcfg.NodeAttrDisableRelayClient) && + !self.HasCap(tailcfg.NodeAttrOnlyTCP443) c.mu.Lock() relayClientChanged := c.relayClientEnabled != relayClientEnabled c.relayClientEnabled = relayClientEnabled filt := c.filt - self := c.self - peers := c.peers + selfView := c.self + peersView := c.peers isClosed := c.closed c.mu.Unlock() // release c.mu before potentially calling c.updateRelayServersSet which is O(m * n) @@ -3042,15 +2979,14 @@ func (c *Conn) onNodeViewsUpdate(update NodeViewsUpdate) { c.relayManager.handleRelayServersSet(nil) c.hasPeerRelayServers.Store(false) } else { - c.updateRelayServersSet(filt, self, peers) + c.updateRelayServersSet(filt, selfView, peersView) } } } -// updateNodes updates [Conn] to reflect the [tailcfg.NodeView]'s contained -// in update. It returns true if update.Peers was unequal to c.peers, otherwise -// false. -func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { +// updateNodes updates [Conn] to reflect the given self node and peers. +// It reports whether the peers were changed from before. +func (c *Conn) updateNodes(self tailcfg.NodeView, peers []tailcfg.NodeView) (peersChanged bool) { c.mu.Lock() defer c.mu.Unlock() @@ -3059,11 +2995,11 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { } priorPeers := c.peers - metricNumPeers.Set(int64(len(update.Peers))) + metricNumPeers.Set(int64(len(peers))) // Update c.self & c.peers regardless, before the following early return. - c.self = update.SelfNode - curPeers := views.SliceOf(update.Peers) + c.self = self + curPeers := views.SliceOf(peers) c.peers = curPeers // [debugFlags] are mutable in [Conn.SetSilentDisco] & @@ -3072,7 +3008,7 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { // [controlknobs.Knobs] are simply self [tailcfg.NodeCapability]'s. They are // useful as a global view of notable feature toggles, but the magicsock // setters are completely unnecessary as we have the same values right here - // (update.SelfNode.Capabilities) at a time they are considered most + // (self.Capabilities) at a time they are considered most // up-to-date. // TODO: mutate [debugFlags] here instead of in various [Conn] setters. flags := c.debugFlagsLocked() @@ -3088,16 +3024,16 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { c.lastFlags = flags - c.logf("[v1] magicsock: got updated network map; %d peers", len(update.Peers)) + c.logf("[v1] magicsock: got updated network map; %d peers", len(peers)) - entriesPerBuffer := debugRingBufferSize(len(update.Peers)) + entriesPerBuffer := debugRingBufferSize(len(peers)) // Try a pass of just upserting nodes and creating missing // endpoints. If the set of nodes is the same, this is an // efficient alloc-free update. If the set of nodes is different, // we'll fall through to the next pass, which allocates but can // handle full set updates. - for _, n := range update.Peers { + for _, n := range peers { if n.ID() == 0 { devPanicf("node with zero ID") continue @@ -3197,14 +3133,14 @@ func (c *Conn) updateNodes(update NodeViewsUpdate) (peersChanged bool) { c.peerMap.upsertEndpoint(ep, key.DiscoPublic{}) } - // If the set of nodes changed since the last onNodeViewsUpdate, the + // If the set of nodes changed since the last SetNetworkMap, the // upsert loop just above made c.peerMap contain the union of the // old and new peers - which will be larger than the set from the // current netmap. If that happens, go through the allocful // deletion path to clean up moribund nodes. - if c.peerMap.nodeCount() != len(update.Peers) { + if c.peerMap.nodeCount() != len(peers) { keep := set.Set[key.NodePublic]{} - for _, n := range update.Peers { + for _, n := range peers { keep.Add(n.Key()) } c.peerMap.forEachEndpoint(func(ep *endpoint) { @@ -3739,13 +3675,15 @@ func simpleDur(d time.Duration) time.Duration { return d.Round(time.Minute) } -// onNodeMutationsUpdate is called when a [NodeMutationsUpdate] is received over -// the [eventbus.Bus]. Note: It does not apply these mutations to c.peers. -func (c *Conn) onNodeMutationsUpdate(update NodeMutationsUpdate) { +// UpdateNetmapDelta applies the given node mutations to the connection's peer +// state. It must be called synchronously from the caller's goroutine to ensure +// magicsock has the current state before subsequent operations proceed. +// Note: It does not apply these mutations to c.peers. +func (c *Conn) UpdateNetmapDelta(muts []netmap.NodeMutation) { c.mu.Lock() defer c.mu.Unlock() - for _, m := range update.Mutations { + for _, m := range muts { nodeID := m.NodeIDBeingMutated() ep, ok := c.peerMap.endpointForNodeID(nodeID) if !ok { diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 3b7ceeaa23323..5fa177b3bce8b 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -171,7 +171,7 @@ type magicStack struct { } // newMagicStack builds and initializes an idle magicsock and -// friends. You need to call conn.onNodeViewsUpdate and dev.Reconfig +// friends. You need to call conn.SetNetworkMap and dev.Reconfig // before anything interesting happens. func newMagicStack(t testing.TB, logf logger.Logf, ln nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack { privateKey := key.NewNode() @@ -346,13 +346,9 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM for i, m := range ms { nm := buildNetmapLocked(i) - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) - peerSet := make(set.Set[key.NodePublic], len(nv.Peers)) - for _, peer := range nv.Peers { + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) + peerSet := make(set.Set[key.NodePublic], len(nm.Peers)) + for _, peer := range nm.Peers { peerSet.Add(peer.Key()) } m.conn.UpdatePeers(peerSet) @@ -1388,16 +1384,14 @@ func addTestEndpoint(tb testing.TB, conn *Conn, sendConn net.PacketConn) (key.No // codepath. discoKey := key.DiscoPublicFromRaw32(mem.B([]byte{31: 1})) nodeKey := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 31: 0})) - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews([]*tailcfg.Node{ - { - ID: 1, - Key: nodeKey, - DiscoKey: discoKey, - Endpoints: eps(sendConn.LocalAddr().String()), - }, - }), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews([]*tailcfg.Node{ + { + ID: 1, + Key: nodeKey, + DiscoKey: discoKey, + Endpoints: eps(sendConn.LocalAddr().String()), + }, + })) conn.SetPrivateKey(key.NodePrivateFromRaw32(mem.B([]byte{0: 1, 31: 0}))) _, err := conn.ParseEndpoint(nodeKey.UntypedHexString()) if err != nil { @@ -1581,7 +1575,7 @@ func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView { // doesn't change its disco key doesn't result in a broken state. // // https://github.com/tailscale/tailscale/issues/1391 -func TestOnNodeViewsUpdateChangingNodeKey(t *testing.T) { +func TestSetNetworkMapChangingNodeKey(t *testing.T) { conn := newTestConn(t) t.Cleanup(func() { conn.Close() }) var buf tstest.MemLogger @@ -1593,32 +1587,28 @@ func TestOnNodeViewsUpdateChangingNodeKey(t *testing.T) { nodeKey1 := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 2: '1', 31: 0})) nodeKey2 := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 2: '2', 31: 0})) - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews([]*tailcfg.Node{ - { - ID: 1, - Key: nodeKey1, - DiscoKey: discoKey, - Endpoints: eps("192.168.1.2:345"), - }, - }), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews([]*tailcfg.Node{ + { + ID: 1, + Key: nodeKey1, + DiscoKey: discoKey, + Endpoints: eps("192.168.1.2:345"), + }, + })) _, err := conn.ParseEndpoint(nodeKey1.UntypedHexString()) if err != nil { t.Fatal(err) } for range 3 { - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews([]*tailcfg.Node{ - { - ID: 2, - Key: nodeKey2, - DiscoKey: discoKey, - Endpoints: eps("192.168.1.2:345"), - }, - }), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews([]*tailcfg.Node{ + { + ID: 2, + Key: nodeKey2, + DiscoKey: discoKey, + Endpoints: eps("192.168.1.2:345"), + }, + })) } de, ok := conn.peerMap.endpointForNodeKey(nodeKey2) @@ -1932,7 +1922,7 @@ func eps(s ...string) []netip.AddrPort { return eps } -func TestStressOnNodeViewsUpdate(t *testing.T) { +func TestStressSetNetworkMap(t *testing.T) { t.Parallel() conn := newTestConn(t) @@ -1988,9 +1978,7 @@ func TestStressOnNodeViewsUpdate(t *testing.T) { } } // Set the node views. - conn.onNodeViewsUpdate(NodeViewsUpdate{ - Peers: nodeViews(peers), - }) + conn.SetNetworkMap(tailcfg.NodeView{}, nodeViews(peers)) // Check invariants. if err := conn.peerMap.validate(); err != nil { t.Error(err) @@ -2113,10 +2101,10 @@ func TestRebindingUDPConn(t *testing.T) { } // https://github.com/tailscale/tailscale/issues/6680: don't ignore -// onNodeViewsUpdate calls when there are no peers. (A too aggressive fast path was +// SetNetworkMap calls when there are no peers. (A too aggressive fast path was // previously bailing out early, thinking there were no changes since all zero // peers didn't change, but the node views has non-peer info in it too we shouldn't discard) -func TestOnNodeViewsUpdateWithNoPeers(t *testing.T) { +func TestSetNetworkMapWithNoPeers(t *testing.T) { var c Conn knobs := &controlknobs.Knobs{} c.logf = logger.Discard @@ -2125,9 +2113,7 @@ func TestOnNodeViewsUpdateWithNoPeers(t *testing.T) { for i := 1; i <= 3; i++ { v := !debugEnableSilentDisco() envknob.Setenv("TS_DEBUG_ENABLE_SILENT_DISCO", fmt.Sprint(v)) - nv := NodeViewsUpdate{} - c.onNodeViewsUpdate(nv) - t.Logf("ptr %d: %p", i, nv) + c.SetNetworkMap(tailcfg.NodeView{}, nil) if c.lastFlags.heartbeatDisabled != v { t.Fatalf("call %d: didn't store netmap", i) } @@ -2215,11 +2201,7 @@ func TestIsWireGuardOnlyPeer(t *testing.T) { }, }), } - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "") if err != nil { @@ -2280,11 +2262,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) { }, }), } - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "") if err != nil { @@ -2321,11 +2299,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) { // configures WG. func applyNetworkMap(t *testing.T, m *magicStack, nm *netmap.NetworkMap) { t.Helper() - nv := NodeViewsUpdate{ - SelfNode: nm.SelfNode, - Peers: nm.Peers, - } - m.conn.onNodeViewsUpdate(nv) + m.conn.SetNetworkMap(nm.SelfNode, nm.Peers) // Make sure we can't use v6 to avoid test failures. m.conn.noV6.Store(true) @@ -3590,7 +3564,7 @@ func Test_nodeHasCap(t *testing.T) { } } -func TestConn_onNodeViewsUpdate_updateRelayServersSet(t *testing.T) { +func TestConn_SetNetworkMap_updateRelayServersSet(t *testing.T) { peerNodeCandidateRelay := &tailcfg.Node{ Cap: 121, ID: 1, @@ -3752,10 +3726,7 @@ func TestConn_onNodeViewsUpdate_updateRelayServersSet(t *testing.T) { c.hasPeerRelayServers.Store(true) } - c.onNodeViewsUpdate(NodeViewsUpdate{ - SelfNode: tt.self, - Peers: tt.peers, - }) + c.SetNetworkMap(tt.self, tt.peers) got := c.relayManager.getServers() if !got.Equal(tt.wantRelayServers) { t.Fatalf("got: %v != want: %v", got, tt.wantRelayServers) From 6cbfc2f3babe5e6e55ddc589dee413801f663797 Mon Sep 17 00:00:00 2001 From: James Scott Date: Tue, 10 Feb 2026 13:24:00 -0800 Subject: [PATCH 089/202] logtail/filch: fix filch test panic (#18660) Updates rotateLocked so that we hold the activeStderrWriteForTest write lock around the dup2Stderr call, rather than acquiring it only after dup2 was already compelete. This ensures no stderrWriteForTest calls can race with the dup2 syscall. The now unused waitIdleStderrForTest has been removed. On macOS, dup2 and write on the same file descriptor are not atomic with respect to each other, when rotateLocked called dup2Stderr to redirect the stderr fd to a new file, concurrent goroutines calling stderrWriteForTest could observe the fd in a transiently invalid state, resulting in the bad file descripter. Fixes tailscale/corp#36953 Signed-off-by: James Scott --- logtail/filch/filch.go | 27 ++++++++++++++------------- logtail/filch/filch_test.go | 14 -------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/logtail/filch/filch.go b/logtail/filch/filch.go index 1bd82d8c41e8a..8ae9123060e73 100644 --- a/logtail/filch/filch.go +++ b/logtail/filch/filch.go @@ -297,12 +297,6 @@ func stderrWriteForTest(b []byte) int { return must.Get(os.Stderr.Write(b)) } -// waitIdleStderrForTest waits until there are no active stderrWriteForTest calls. -func waitIdleStderrForTest() { - activeStderrWriteForTest.Lock() - defer activeStderrWriteForTest.Unlock() -} - // rotateLocked swaps f.newer and f.older such that: // // - f.newer will be truncated and future writes will be appended to the end. @@ -350,8 +344,15 @@ func (f *Filch) rotateLocked() error { // Note that mutex does not prevent stderr writes. prevSize := f.newlyWrittenBytes + f.newlyFilchedBytes f.newlyWrittenBytes, f.newlyFilchedBytes = 0, 0 + + // Hold the write lock around dup2 to prevent concurrent + // stderrWriteForTest calls from racing with dup2 on the same fd. + // On macOS, dup2 and write are not atomic with respect to each other, + // so a concurrent write can observe a bad file descriptor. + activeStderrWriteForTest.Lock() if f.OrigStderr != nil { if err := dup2Stderr(f.newer); err != nil { + activeStderrWriteForTest.Unlock() return err } } @@ -369,15 +370,15 @@ func (f *Filch) rotateLocked() error { // In rare cases, it is possible that [Filch.TryReadLine] consumes // the entire older file before the write commits, // leading to dropped stderr lines. - waitIdleStderrForTest() - if fi, err := f.older.Stat(); err != nil { + fi, err := f.older.Stat() + activeStderrWriteForTest.Unlock() + if err != nil { return err - } else { - filchedBytes := max(0, fi.Size()-prevSize) - f.writeBytes.Add(filchedBytes) - f.filchedBytes.Add(filchedBytes) - f.storedBytes.Set(fi.Size()) // newer has been truncated, so only older matters } + filchedBytes := max(0, fi.Size()-prevSize) + f.writeBytes.Add(filchedBytes) + f.filchedBytes.Add(filchedBytes) + f.storedBytes.Set(fi.Size()) // newer has been truncated, so only older matters // Start reading from the start of older. if _, err := f.older.Seek(0, io.SeekStart); err != nil { diff --git a/logtail/filch/filch_test.go b/logtail/filch/filch_test.go index f2f9e9e3bcd6b..2538233cfd84c 100644 --- a/logtail/filch/filch_test.go +++ b/logtail/filch/filch_test.go @@ -5,7 +5,6 @@ package filch import ( "bytes" - "crypto/sha256" "encoding/json" "fmt" "io" @@ -121,19 +120,7 @@ func setupStderr(t *testing.T) { tstest.Replace(t, &os.Stderr, pipeW) } -func skipDarwin(t testing.TB) { - if runtime.GOOS != "darwin" { - return - } - src := must.Get(os.ReadFile("filch.go")) - if fmt.Sprintf("%x", sha256.Sum256(src)) != "a32da5e22034823c19ac7f29960e3646f540d67f85a0028832cab1f1557fc693" { - t.Errorf("filch.go has changed since this test was skipped; please delete this skip") - } - t.Skip("skipping known failing test on darwin; fixed in progress by https://github.com/tailscale/tailscale/pull/18660") -} - func TestConcurrentWriteAndRead(t *testing.T) { - skipDarwin(t) if replaceStderrSupportedForTest { setupStderr(t) } @@ -296,7 +283,6 @@ func TestMaxLineSize(t *testing.T) { } func TestMaxFileSize(t *testing.T) { - skipDarwin(t) if replaceStderrSupportedForTest { t.Run("ReplaceStderr:true", func(t *testing.T) { testMaxFileSize(t, true) }) } From 1172b2febd005af4545bfa2d0778001e9a873b8e Mon Sep 17 00:00:00 2001 From: License Updater Date: Wed, 11 Feb 2026 01:11:27 +0000 Subject: [PATCH 090/202] licenses: update license notices Signed-off-by: License Updater --- licenses/apple.md | 2 +- licenses/tailscale.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/licenses/apple.md b/licenses/apple.md index f61291c943cb8..4170a4c8bac49 100644 --- a/licenses/apple.md +++ b/licenses/apple.md @@ -34,7 +34,7 @@ See also the dependencies in the [Tailscale CLI][]. - [github.com/digitalocean/go-smbios/smbios](https://pkg.go.dev/github.com/digitalocean/go-smbios/smbios) ([Apache-2.0](https://github.com/digitalocean/go-smbios/blob/390a4f403a8e/LICENSE.md)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) - - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) + - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.26.1/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/4849db3c2f7e/LICENSE)) - [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/76236955d466/LICENSE)) - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) diff --git a/licenses/tailscale.md b/licenses/tailscale.md index 28eb73db42cc6..9ccc37adb22cc 100644 --- a/licenses/tailscale.md +++ b/licenses/tailscale.md @@ -44,7 +44,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - [github.com/fogleman/gg](https://pkg.go.dev/github.com/fogleman/gg) ([MIT](https://github.com/fogleman/gg/blob/v1.3.0/LICENSE.md)) - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) - - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) + - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.26.1/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/ebf49471dced/LICENSE)) - [github.com/go-ole/go-ole](https://pkg.go.dev/github.com/go-ole/go-ole) ([MIT](https://github.com/go-ole/go-ole/blob/v1.3.0/LICENSE)) - [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/76236955d466/LICENSE)) From 12188c0ade65d5617abd674c7010a5cca9f8519c Mon Sep 17 00:00:00 2001 From: Simon Law Date: Tue, 10 Feb 2026 18:14:32 -0800 Subject: [PATCH 091/202] ipn/ipnlocal: log traffic steering scores and suggested exit nodes (#18681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When traffic steering is enabled, some users are suggested an exit node that is inappropriately far from their location. This seems to happen right when the client connects to the control plane and the client eventually fixes itself. But whenever an affected client reconnects, its suggested exit node flaps, and this happens often enough to be noticeable because connections drop whenever the exit node is switched. This should not happen, since the map response that contains the list of suggested exit nodes that the client picks from, also contains the scores for those nodes. Since our current logging and diagnostic tools don’t give us enough insight into what is happening, this PR adds additional logging when: - traffic steering scores are used to suggest an exit node - an exit node is suggested, no matter how it was determined Updates: tailscale/corp#29964 Updates: tailscale/corp#36446 Signed-off-by: Simon Law --- ipn/ipnlocal/local.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 981e2df73a83b..27858484a7a0e 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -6,6 +6,7 @@ package ipnlocal import ( + "bufio" "cmp" "context" "crypto/sha256" @@ -7484,13 +7485,16 @@ func suggestExitNode(report *netcheck.Report, nb *nodeBackend, prevSuggestion ta switch { case nb.SelfHasCap(tailcfg.NodeAttrTrafficSteering): // The traffic-steering feature flag is enabled on this tailnet. - return suggestExitNodeUsingTrafficSteering(nb, allowList) + res, err = suggestExitNodeUsingTrafficSteering(nb, allowList) default: // The control plane will always strip the `traffic-steering` // node attribute if it isn’t enabled for this tailnet, even if // it is set in the policy file: tailscale/corp#34401 - return suggestExitNodeUsingDERP(report, nb, prevSuggestion, selectRegion, selectNode, allowList) + res, err = suggestExitNodeUsingDERP(report, nb, prevSuggestion, selectRegion, selectNode, allowList) } + name, _, _ := strings.Cut(res.Name, ".") + nb.logf("netmap: suggested exit node: %s (%s)", name, res.ID) + return res, err } // suggestExitNodeUsingDERP is the classic algorithm used to suggest exit nodes, @@ -7723,6 +7727,21 @@ func suggestExitNodeUsingTrafficSteering(nb *nodeBackend, allowed set.Set[tailcf pick = nodes[0] } + nb.logf("netmap: traffic steering: exit node scores: %v", logger.ArgWriter(func(bw *bufio.Writer) { + const max = 10 + for i, n := range nodes { + if i == max { + fmt.Fprintf(bw, "... +%d", len(nodes)-max) + return + } + if i > 0 { + bw.WriteString(", ") + } + name, _, _ := strings.Cut(n.Name(), ".") + fmt.Fprintf(bw, "%d:%s", score(n), name) + } + })) + if !pick.Valid() { return apitype.ExitNodeSuggestionResponse{}, nil } From 8e39a0aa0fd68a5cccccf2e984a118120d489aeb Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 10 Feb 2026 19:44:14 -0800 Subject: [PATCH 092/202] go.toolchain.next.rev: update to final Go 1.26.0 [next] This updates the TS_GO_NEXT=1 (testing) toolchain to Go 1.26.0 The default one is still Go 1.25.x. Updates #18682 Change-Id: I99747798c166ce162ee9eee74baa9ff6744a62f6 Signed-off-by: Brad Fitzpatrick --- go.toolchain.next.rev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.toolchain.next.rev b/go.toolchain.next.rev index abdc21022aa19..ea3d3c773f779 100644 --- a/go.toolchain.next.rev +++ b/go.toolchain.next.rev @@ -1 +1 @@ -5ba287c89a4cef2f4a419aed4e6bc3121c5c4dad +5b5cb0db47535a0a8d2f450cb1bf83af8e70f164 From 45db3691b9dcd4008157e3b7443bcd21fef85f1a Mon Sep 17 00:00:00 2001 From: Anton Tolchanov Date: Tue, 10 Feb 2026 19:25:50 +0000 Subject: [PATCH 093/202] prober: export a metric with the number of in-flight probes Updates tailscale/corp#37049 Signed-off-by: Anton Tolchanov --- prober/prober.go | 7 +++++++ prober/prober_test.go | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/prober/prober.go b/prober/prober.go index 16c262bc81c0d..3a43401a14ac3 100644 --- a/prober/prober.go +++ b/prober/prober.go @@ -161,6 +161,7 @@ func newProbe(p *Prober, name string, interval time.Duration, lg prometheus.Labe mEndTime: prometheus.NewDesc("end_secs", "Latest probe end time (seconds since epoch)", nil, lg), mLatency: prometheus.NewDesc("latency_millis", "Latest probe latency (ms)", nil, lg), mResult: prometheus.NewDesc("result", "Latest probe result (1 = success, 0 = failure)", nil, lg), + mInFlight: prometheus.NewDesc("in_flight", "Number of probes currently running", nil, lg), mAttempts: prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "attempts_total", Help: "Total number of probing attempts", ConstLabels: lg, }, []string{"status"}), @@ -261,10 +262,12 @@ type Probe struct { mEndTime *prometheus.Desc mLatency *prometheus.Desc mResult *prometheus.Desc + mInFlight *prometheus.Desc mAttempts *prometheus.CounterVec mSeconds *prometheus.CounterVec mu sync.Mutex + inFlight int // number of currently running probes start time.Time // last time doProbe started end time.Time // last time doProbe returned latency time.Duration // last successful probe latency @@ -392,11 +395,13 @@ func (p *Probe) run() (pi ProbeInfo, err error) { func (p *Probe) recordStart() { p.mu.Lock() p.start = p.prober.now() + p.inFlight++ p.mu.Unlock() } func (p *Probe) recordEndLocked(err error) { end := p.prober.now() + p.inFlight-- p.end = end p.succeeded = err == nil p.lastErr = err @@ -649,6 +654,7 @@ func (p *Probe) Describe(ch chan<- *prometheus.Desc) { ch <- p.mStartTime ch <- p.mEndTime ch <- p.mResult + ch <- p.mInFlight ch <- p.mLatency p.mAttempts.Describe(ch) p.mSeconds.Describe(ch) @@ -664,6 +670,7 @@ func (p *Probe) Collect(ch chan<- prometheus.Metric) { p.mu.Lock() defer p.mu.Unlock() ch <- prometheus.MustNewConstMetric(p.mInterval, prometheus.GaugeValue, p.interval.Seconds()) + ch <- prometheus.MustNewConstMetric(p.mInFlight, prometheus.GaugeValue, float64(p.inFlight)) if !p.start.IsZero() { ch <- prometheus.MustNewConstMetric(p.mStartTime, prometheus.GaugeValue, float64(p.start.Unix())) } diff --git a/prober/prober_test.go b/prober/prober_test.go index c945f617a6633..8da5127875859 100644 --- a/prober/prober_test.go +++ b/prober/prober_test.go @@ -213,6 +213,14 @@ func TestProberConcurrency(t *testing.T) { if got, want := ran.Load(), int64(3); got != want { return fmt.Errorf("expected %d probes to run concurrently, got %d", want, got) } + wantMetrics := ` + # HELP prober_in_flight Number of probes currently running + # TYPE prober_in_flight gauge + prober_in_flight{class="",name="foo"} 3 + ` + if err := testutil.GatherAndCompare(p.metrics, strings.NewReader(wantMetrics), "prober_in_flight"); err != nil { + return fmt.Errorf("unexpected metrics: %w", err) + } return nil }); err != nil { t.Fatal(err) @@ -308,9 +316,12 @@ probe_end_secs{class="",label="value",name="testprobe"} %d # HELP probe_result Latest probe result (1 = success, 0 = failure) # TYPE probe_result gauge probe_result{class="",label="value",name="testprobe"} 0 +# HELP probe_in_flight Number of probes currently running +# TYPE probe_in_flight gauge +probe_in_flight{class="",label="value",name="testprobe"} 0 `, probeInterval.Seconds(), epoch.Unix(), epoch.Add(aFewMillis).Unix()) return testutil.GatherAndCompare(p.metrics, strings.NewReader(want), - "probe_interval_secs", "probe_start_secs", "probe_end_secs", "probe_result") + "probe_interval_secs", "probe_start_secs", "probe_end_secs", "probe_result", "probe_in_flight") }) if err != nil { t.Fatal(err) @@ -338,9 +349,13 @@ probe_latency_millis{class="",label="value",name="testprobe"} %d # HELP probe_result Latest probe result (1 = success, 0 = failure) # TYPE probe_result gauge probe_result{class="",label="value",name="testprobe"} 1 +# HELP probe_in_flight Number of probes currently running +# TYPE probe_in_flight gauge +probe_in_flight{class="",label="value",name="testprobe"} 0 `, probeInterval.Seconds(), start.Unix(), end.Unix(), aFewMillis.Milliseconds()) return testutil.GatherAndCompare(p.metrics, strings.NewReader(want), - "probe_interval_secs", "probe_start_secs", "probe_end_secs", "probe_latency_millis", "probe_result") + "probe_interval_secs", "probe_start_secs", "probe_end_secs", + "probe_latency_millis", "probe_result", "probe_in_flight") }) if err != nil { t.Fatal(err) From 73d09316e20fe1ccf8a9613c34d80c0a82c5a490 Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Wed, 11 Feb 2026 13:47:48 -0500 Subject: [PATCH 094/202] tstest: update clock to always use UTC (#18663) Instead of relying on the local timezone, which may cause non-deterministic behavior in some CIs, we force timezone to be UTC on default created clocks. Fixes: tailscale/corp#37005 Signed-off-by: Fernando Serboncini --- tstest/clock.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tstest/clock.go b/tstest/clock.go index f11187a4a69e5..5742c6e5aeda1 100644 --- a/tstest/clock.go +++ b/tstest/clock.go @@ -17,8 +17,11 @@ import ( type ClockOpts struct { // Start is the starting time for the Clock. When FollowRealTime is false, // Start is also the value that will be returned by the first call - // to Clock.Now. + // to Clock.Now. If you are passing a value here, set an explicit + // timezone, otherwise the test may be non-deterministic when TZ environment + // variable is set to different values. The default time is in UTC. Start time.Time + // Step is the amount of time the Clock will advance whenever Clock.Now is // called. If set to zero, the Clock will only advance when Clock.Advance is // called and/or if FollowRealTime is true. @@ -119,7 +122,7 @@ func (c *Clock) init() { } if c.start.IsZero() { if c.realTime.IsZero() { - c.start = time.Now() + c.start = time.Now().UTC() } else { c.start = c.realTime } From 84ee5b640b2101af610a2a554808ec77adbf070e Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Mon, 9 Feb 2026 16:34:46 -0700 Subject: [PATCH 095/202] testcontrol: send updates for new DNS records or app capabilities Two methods were recently added to the testcontrol.Server type: AddDNSRecords and SetGlobalAppCaps. These two methods should trigger netmap updates for all nodes connected to the Server instance, the way that other state-change methods do (see SetNodeCapMap, for example). This will also allow us to get rid of Server.ForceNetmapUpdate, which was a band-aid fix to force the netmap updates which should have been triggered by the aforementioned methods. Fixes tailscale/corp#37102 Signed-off-by: Harry Harpham --- tsnet/tsnet_test.go | 6 +-- tstest/integration/testcontrol/testcontrol.go | 41 ++----------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 41d239e3b91be..0b6b61bd10061 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -1194,10 +1194,8 @@ func TestListenService(t *testing.T) { tt.extraSetup(t, control) } - // Force netmap updates to avoid race conditions. The nodes need to - // see our control updates before we can start the test. - must.Do(control.ForceNetmapUpdate(ctx, serviceHost.lb.NodeKey())) - must.Do(control.ForceNetmapUpdate(ctx, serviceClient.lb.NodeKey())) + // Wait until both nodes have up-to-date netmaps before + // proceeding with the test. netmapUpToDate := func(s *Server) bool { nm := s.lb.NetMap() return slices.ContainsFunc(nm.DNS.ExtraRecords, func(r tailcfg.DNSRecord) bool { diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go index f61d1b53a6d99..56664ba746204 100644 --- a/tstest/integration/testcontrol/testcontrol.go +++ b/tstest/integration/testcontrol/testcontrol.go @@ -299,43 +299,6 @@ func (s *Server) addDebugMessage(nodeKeyDst key.NodePublic, msg any) bool { return sendUpdate(oldUpdatesCh, updateDebugInjection) } -// ForceNetmapUpdate waits for the node to get stuck in a map poll and then -// sends the current netmap (which may result in a redundant netmap). The -// intended use case is ensuring state changes propagate before running tests. -// -// This should only be called for nodes connected as streaming clients. Calling -// this with a non-streaming node will result in non-deterministic behavior. -// -// This function cannot guarantee that the node has processed the issued update, -// so tests should confirm processing by querying the node. By example: -// -// if err := s.ForceNetmapUpdate(node.Key()); err != nil { -// // handle error -// } -// for !updatesPresent(node.NetMap()) { -// time.Sleep(10 * time.Millisecond) -// } -func (s *Server) ForceNetmapUpdate(ctx context.Context, nodeKey key.NodePublic) error { - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - if err := s.AwaitNodeInMapRequest(ctx, nodeKey); err != nil { - return fmt.Errorf("waiting for node to poll: %w", err) - } - mr, err := s.MapResponse(&tailcfg.MapRequest{NodeKey: nodeKey}) - if err != nil { - return fmt.Errorf("generating map response: %w", err) - } - if s.addDebugMessage(nodeKey, mr) { - return nil - } - // If we failed to send the map response, loop around and try again. - } -} - // Mark the Node key of every node as expired func (s *Server) SetExpireAllNodes(expired bool) { s.mu.Lock() @@ -589,8 +552,9 @@ func (s *Server) SetNodeCapMap(nodeKey key.NodePublic, capMap tailcfg.NodeCapMap // ] func (s *Server) SetGlobalAppCaps(appCaps tailcfg.PeerCapMap) { s.mu.Lock() + defer s.mu.Unlock() s.globalAppCaps = appCaps - s.mu.Unlock() + s.updateLocked("SetGlobalAppCaps", s.nodeIDsLocked(0)) } // AddDNSRecords adds records to the server's DNS config. @@ -601,6 +565,7 @@ func (s *Server) AddDNSRecords(records ...tailcfg.DNSRecord) { s.DNSConfig = new(tailcfg.DNSConfig) } s.DNSConfig.ExtraRecords = append(s.DNSConfig.ExtraRecords, records...) + s.updateLocked("AddDNSRecords", s.nodeIDsLocked(0)) } // nodeIDsLocked returns the node IDs of all nodes in the server, except From 0bac4223d140778ec8408d65882151371f76f2cd Mon Sep 17 00:00:00 2001 From: Michael Ben-Ami Date: Tue, 10 Feb 2026 12:58:21 -0500 Subject: [PATCH 096/202] tstun: add test for intercept ordering Fixes tailscale/corp#36999 Signed-off-by: Michael Ben-Ami --- net/tstun/wrap_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 8515cb8f0a4c0..1744fc30266a9 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -41,6 +41,7 @@ import ( "tailscale.com/util/must" "tailscale.com/util/usermetric" "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/netstack/gro" "tailscale.com/wgengine/wgcfg" ) @@ -991,3 +992,67 @@ func TestTSMPDisco(t *testing.T) { } }) } + +func TestInterceptOrdering(t *testing.T) { + bus := eventbustest.NewBus(t) + chtun, tun := newChannelTUN(t.Logf, bus, true) + defer tun.Close() + + var seq uint8 + orderedFilterFn := func(expected uint8) FilterFunc { + return func(_ *packet.Parsed, _ *Wrapper) filter.Response { + seq++ + if expected != seq { + t.Errorf("got sequence %d; want %d", seq, expected) + } + return filter.Accept + } + } + + ordereredGROFilterFn := func(expected uint8) GROFilterFunc { + return func(_ *packet.Parsed, _ *Wrapper, _ *gro.GRO) (filter.Response, *gro.GRO) { + seq++ + if expected != seq { + t.Errorf("got sequence %d; want %d", seq, expected) + } + return filter.Accept, nil + } + } + + // As the number of inbound intercepts change, + // this value should change. + numInboundIntercepts := uint8(3) + + tun.PreFilterPacketInboundFromWireGuard = orderedFilterFn(1) + tun.PostFilterPacketInboundFromWireGuardAppConnector = orderedFilterFn(2) + tun.PostFilterPacketInboundFromWireGuard = ordereredGROFilterFn(3) + + // Write the packet. + go func() { <-chtun.Inbound }() // Simulate tun device receiving. + packet := [][]byte{udp4("5.6.7.8", "1.2.3.4", 89, 89)} + tun.Write(packet, 0) + + if seq != numInboundIntercepts { + t.Errorf("got number of intercepts run in Write(): %d; want: %d", seq, numInboundIntercepts) + } + + // As the number of inbound intercepts change, + // this value should change. + numOutboundIntercepts := uint8(4) + + seq = 0 + tun.PreFilterPacketOutboundToWireGuardNetstackIntercept = ordereredGROFilterFn(1) + tun.PreFilterPacketOutboundToWireGuardEngineIntercept = orderedFilterFn(2) + tun.PreFilterPacketOutboundToWireGuardAppConnectorIntercept = orderedFilterFn(3) + tun.PostFilterPacketOutboundToWireGuard = orderedFilterFn(4) + + // Read the packet. + var buf [MaxPacketSize]byte + sizes := make([]int, 1) + chtun.Outbound <- udp4("1.2.3.4", "5.6.7.8", 98, 98) // Simulate tun device sending. + tun.Read([][]byte{buf[:]}, sizes, 0) + + if seq != numOutboundIntercepts { + t.Errorf("got number of intercepts run in Read(): %d; want: %d", seq, numOutboundIntercepts) + } +} From 36d359e585374358ef04ed28f54c5f1667b0c170 Mon Sep 17 00:00:00 2001 From: Will Hannah Date: Thu, 12 Feb 2026 14:49:52 -0500 Subject: [PATCH 097/202] clientupdate, cmd/tailscale/cli: support updating to release-candidates (#18632) Adds a new track for release candidates. Supports querying by track in version and updating to RCs in update for supported platforms. updates #18193 Signed-off-by: Will Hannah --- clientupdate/clientupdate.go | 23 ++++---- clientupdate/clientupdate_test.go | 95 +++++++++++++++++-------------- cmd/tailscale/cli/update.go | 9 ++- cmd/tailscale/cli/version.go | 6 +- 4 files changed, 74 insertions(+), 59 deletions(-) diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 09f9d0be1787d..1ed7894bf3d43 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -38,8 +38,9 @@ import ( ) const ( - StableTrack = "stable" - UnstableTrack = "unstable" + StableTrack = "stable" + UnstableTrack = "unstable" + ReleaseCandidateTrack = "release-candidate" ) var CurrentTrack = func() string { @@ -80,6 +81,8 @@ type Arguments struct { // running binary // - StableTrack and UnstableTrack will use the latest versions of the // corresponding tracks + // - ReleaseCandidateTrack will use the newest version from StableTrack + // and ReleaseCandidateTrack. // // Leaving this empty will use Version or fall back to CurrentTrack if both // Track and Version are empty. @@ -114,7 +117,7 @@ func (args Arguments) validate() error { return fmt.Errorf("only one of Version(%q) or Track(%q) can be set", args.Version, args.Track) } switch args.Track { - case StableTrack, UnstableTrack, "": + case StableTrack, UnstableTrack, ReleaseCandidateTrack, "": // All valid values. default: return fmt.Errorf("unsupported track %q", args.Track) @@ -496,10 +499,10 @@ func (up *Updater) updateDebLike() error { const aptSourcesFile = "/etc/apt/sources.list.d/tailscale.list" // updateDebianAptSourcesList updates the /etc/apt/sources.list.d/tailscale.list -// file to make sure it has the provided track (stable or unstable) in it. +// file to make sure it has the provided track (stable, unstable, or release-candidate) in it. // -// If it already has the right track (including containing both stable and -// unstable), it does nothing. +// If it already has the right track (including containing both stable, +// unstable, and release-candidate), it does nothing. func updateDebianAptSourcesList(dstTrack string) (rewrote bool, err error) { was, err := os.ReadFile(aptSourcesFile) if err != nil { @@ -522,7 +525,7 @@ func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent [] bs := bufio.NewScanner(bytes.NewReader(was)) hadCorrect := false commentLine := regexp.MustCompile(`^\s*\#`) - pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/((un)?stable)/`) + pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/(stable|unstable|release-candidate)/`) for bs.Scan() { line := bs.Bytes() if !commentLine.Match(line) { @@ -616,15 +619,15 @@ func (up *Updater) updateFedoraLike(packageManager string) func() error { } // updateYUMRepoTrack updates the repoFile file to make sure it has the -// provided track (stable or unstable) in it. +// provided track (stable, unstable, or release-candidate) in it. func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) { was, err := os.ReadFile(repoFile) if err != nil { return false, err } - urlRe := regexp.MustCompile(`^(baseurl|gpgkey)=https://pkgs\.tailscale\.com/(un)?stable/`) - urlReplacement := fmt.Sprintf("$1=https://pkgs.tailscale.com/%s/", dstTrack) + urlRe := regexp.MustCompile(`^(baseurl|gpgkey)=https://pkgs\.tailscale\.com/(stable|unstable|release-candidate)`) + urlReplacement := fmt.Sprintf("$1=https://pkgs.tailscale.com/%s", dstTrack) s := bufio.NewScanner(bytes.NewReader(was)) newContent := bytes.NewBuffer(make([]byte, 0, len(was))) diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index 089936a3120f1..7487026355326 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -86,29 +86,8 @@ func TestUpdateDebianAptSourcesListBytes(t *testing.T) { } } -func TestUpdateYUMRepoTrack(t *testing.T) { - tests := []struct { - desc string - before string - track string - after string - rewrote bool - wantErr bool - }{ - { - desc: "same track", - before: ` -[tailscale-stable] -name=Tailscale stable -baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch -enabled=1 -type=rpm -repo_gpgcheck=1 -gpgcheck=0 -gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg -`, - track: StableTrack, - after: ` +var YUMRepos = map[string]string{ + StableTrack: ` [tailscale-stable] name=Tailscale stable baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch @@ -118,35 +97,30 @@ repo_gpgcheck=1 gpgcheck=0 gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg `, - }, - { - desc: "change track", - before: ` -[tailscale-stable] -name=Tailscale stable -baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch + + UnstableTrack: ` +[tailscale-unstable] +name=Tailscale unstable +baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch enabled=1 type=rpm repo_gpgcheck=1 gpgcheck=0 -gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg +gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg `, - track: UnstableTrack, - after: ` -[tailscale-unstable] -name=Tailscale unstable -baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch + + ReleaseCandidateTrack: ` +[tailscale-release-candidate] +name=Tailscale release-candidate +baseurl=https://pkgs.tailscale.com/release-candidate/fedora/$basearch enabled=1 type=rpm repo_gpgcheck=1 gpgcheck=0 -gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg +gpgkey=https://pkgs.tailscale.com/release-candidate/fedora/repo.gpg `, - rewrote: true, - }, - { - desc: "non-tailscale repo file", - before: ` + + "FakeRepo": ` [fedora] name=Fedora $releasever - $basearch #baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/ @@ -158,8 +132,41 @@ repo_gpgcheck=0 type=rpm gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch -skip_if_unavailable=False -`, +skip_if_unavailable=False`, +} + +func TestUpdateYUMRepoTrack(t *testing.T) { + tests := []struct { + desc string + before string + track string + after string + rewrote bool + wantErr bool + }{ + { + desc: "same track", + before: YUMRepos[StableTrack], + track: StableTrack, + after: YUMRepos[StableTrack], + }, + { + desc: "change track", + before: YUMRepos[StableTrack], + track: UnstableTrack, + after: YUMRepos[UnstableTrack], + rewrote: true, + }, + { + desc: "change track RC", + before: YUMRepos[StableTrack], + track: ReleaseCandidateTrack, + after: YUMRepos[ReleaseCandidateTrack], + rewrote: true, + }, + { + desc: "non-tailscale repo file", + before: YUMRepos["FakeRepo"], track: StableTrack, wantErr: true, }, diff --git a/cmd/tailscale/cli/update.go b/cmd/tailscale/cli/update.go index 6d57e6d41f110..47177347dba85 100644 --- a/cmd/tailscale/cli/update.go +++ b/cmd/tailscale/cli/update.go @@ -22,8 +22,11 @@ import ( func init() { maybeUpdateCmd = func() *ffcli.Command { return updateCmd } - clientupdateLatestTailscaleVersion.Set(func() (string, error) { - return clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) + clientupdateLatestTailscaleVersion.Set(func(track string) (string, error) { + if track == "" { + return clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) + } + return clientupdate.LatestTailscaleVersion(track) }) } @@ -50,7 +53,7 @@ var updateCmd = &ffcli.Command{ distro.Get() != distro.Synology && runtime.GOOS != "freebsd" && runtime.GOOS != "darwin" { - fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable" or "unstable" (dev); empty means same as current`) + fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable", "release-candidate", or "unstable" (dev); empty means same as current`) fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`) } return fs diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index 2c6a3738bd36a..3d6590a39bf2e 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -24,6 +24,7 @@ var versionCmd = &ffcli.Command{ fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version") fs.BoolVar(&versionArgs.json, "json", false, "output in JSON format") fs.BoolVar(&versionArgs.upstream, "upstream", false, "fetch and print the latest upstream release version from pkgs.tailscale.com") + fs.StringVar(&versionArgs.track, "track", "", `which track to check for updates: "stable", "release-candidate", or "unstable" (dev); empty means same as current`) return fs })(), Exec: runVersion, @@ -33,9 +34,10 @@ var versionArgs struct { daemon bool // also check local node's daemon version json bool upstream bool + track string } -var clientupdateLatestTailscaleVersion feature.Hook[func() (string, error)] +var clientupdateLatestTailscaleVersion feature.Hook[func(string) (string, error)] func runVersion(ctx context.Context, args []string) error { if len(args) > 0 { @@ -57,7 +59,7 @@ func runVersion(ctx context.Context, args []string) error { if !ok { return fmt.Errorf("fetching latest version not supported in this build") } - upstreamVer, err = f() + upstreamVer, err = f(versionArgs.track) if err != nil { return err } From 068074c109a5a78de746b47b0f1bd2915936809c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 12 Feb 2026 13:03:22 -0800 Subject: [PATCH 098/202] portlist: also tb.Skip benchmarks (not just tests) on bad Linux kernels Updates #16966 Change-Id: I0269927bdf8e6c4e949fcf755ce7e5fd21386d7d Signed-off-by: Brad Fitzpatrick --- portlist/portlist_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/portlist/portlist_test.go b/portlist/portlist_test.go index 5e0964b248882..922cb7a1ef562 100644 --- a/portlist/portlist_test.go +++ b/portlist/portlist_test.go @@ -11,7 +11,7 @@ import ( "tailscale.com/tstest" ) -func maybeSkip(t *testing.T) { +func maybeSkip(t testing.TB) { if runtime.GOOS == "linux" { tstest.SkipOnKernelVersions(t, "https://github.com/tailscale/tailscale/issues/16966", @@ -214,6 +214,7 @@ func BenchmarkGetListIncremental(b *testing.B) { } func benchmarkGetList(b *testing.B, incremental bool) { + maybeSkip(b) b.ReportAllocs() var p Poller p.init() From d46887031097858c79b102a9fe3f2345bcea084a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 12 Feb 2026 13:15:24 -0800 Subject: [PATCH 099/202] .github/workflows: bump oss-fuzz builder hash Fixes #18710 Change-Id: I2ebad48b1227321233172beb9801087963ece4fa Signed-off-by: Brad Fitzpatrick --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 152ef7bce9008..cdf8f3f5f69d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -711,9 +711,9 @@ jobs: steps: - name: build fuzzers id: build - # As of 21 October 2025, this repo doesn't tag releases, so this commit + # As of 12 February 2026, this repo doesn't tag releases, so this commit # hash is just the tip of master. - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@1242ccb5b6352601e73c00f189ac2ae397242264 + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@f277aafb36f358582fdb24a41a9a52f2e097a2fd # continue-on-error makes steps.build.conclusion be 'success' even if # steps.build.outcome is 'failure'. This means this step does not # contribute to the job's overall pass/fail evaluation. @@ -743,9 +743,9 @@ jobs: # report a failure because TS_FUZZ_CURRENTLY_BROKEN is set to the wrong # value. if: steps.build.outcome == 'success' - # As of 21 October 2025, this repo doesn't tag releases, so this commit + # As of 12 February 2026, this repo doesn't tag releases, so this commit # hash is just the tip of master. - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@1242ccb5b6352601e73c00f189ac2ae397242264 + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@f277aafb36f358582fdb24a41a9a52f2e097a2fd with: oss-fuzz-project-name: 'tailscale' fuzz-seconds: 150 From a341eea00bc2f102dcf1bbd19db2fd4a1bebefac Mon Sep 17 00:00:00 2001 From: David Bond Date: Fri, 13 Feb 2026 16:04:34 +0000 Subject: [PATCH 100/202] k8s-operator,cmd/k8s-operator: define ProxyGroupPolicy CRD (#18614) This commit adds a new custom resource definition to the kubernetes operator named `ProxyGroupPolicy`. This resource is namespace scoped and is used as an allow list for which `ProxyGroup` resources can be used within its namespace. The `spec` contains two fields, `ingress` and `egress`. These should contain the names of `ProxyGroup` resources to denote which can be used as values in the `tailscale.com/proxy-group` annotation within `Service` and `Ingress` resources. The intention is for these policies to be merged within a namespace and produce a `ValidatingAdmissionPolicy` and `ValidatingAdmissionPolicyBinding` for both ingress and egress that prevents users from using names of `ProxyGroup` resources in those annotations. Closes: https://github.com/tailscale/corp/issues/36829 Signed-off-by: David Bond --- .../tailscale.com_proxygrouppolicies.yaml | 139 ++++++++++++++++++ k8s-operator/api.md | 77 ++++++++++ k8s-operator/apis/v1alpha1/register.go | 2 + .../apis/v1alpha1/types_proxygrouppolicy.go | 67 +++++++++ .../apis/v1alpha1/zz_generated.deepcopy.go | 106 +++++++++++++ 5 files changed, 391 insertions(+) create mode 100644 cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml create mode 100644 k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml new file mode 100644 index 0000000000000..51edcb56f039b --- /dev/null +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml @@ -0,0 +1,139 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: proxygrouppolicies.tailscale.com +spec: + group: tailscale.com + names: + kind: ProxyGroupPolicy + listKind: ProxyGroupPolicyList + plural: proxygrouppolicies + shortNames: + - pgp + singular: proxygrouppolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Status of the deployed ProxyGroupPolicy resources. + jsonPath: .status.conditions[?(@.type == "ProxyGroupPolicyReady")].reason + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the ProxyGroupPolicy. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + properties: + egress: + description: |- + Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list + denotes that no egress via ProxyGroups is allowed within this namespace. + type: array + items: + type: string + ingress: + description: |- + Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list + denotes that no ingress via ProxyGroups is allowed within this namespace. + type: array + items: + type: string + status: + description: |- + Status describes the status of the ProxyGroupPolicy. This is set + and managed by the Tailscale operator. + type: object + properties: + conditions: + type: array + items: + description: Condition contains details for one aspect of the current state of this API Resource. + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + type: string + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + served: true + storage: true + subresources: + status: {} diff --git a/k8s-operator/api.md b/k8s-operator/api.md index 51a354b925574..5a60f66e039d0 100644 --- a/k8s-operator/api.md +++ b/k8s-operator/api.md @@ -16,6 +16,8 @@ - [ProxyClassList](#proxyclasslist) - [ProxyGroup](#proxygroup) - [ProxyGroupList](#proxygrouplist) +- [ProxyGroupPolicy](#proxygrouppolicy) +- [ProxyGroupPolicyList](#proxygrouppolicylist) - [Recorder](#recorder) - [RecorderList](#recorderlist) - [Tailnet](#tailnet) @@ -725,6 +727,81 @@ _Appears in:_ | `items` _[ProxyGroup](#proxygroup) array_ | | | | +#### ProxyGroupPolicy + + + + + + + +_Appears in:_ +- [ProxyGroupPolicyList](#proxygrouppolicylist) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `ProxyGroupPolicy` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[ProxyGroupPolicySpec](#proxygrouppolicyspec)_ | Spec describes the desired state of the ProxyGroupPolicy.
More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status | | | +| `status` _[ProxyGroupPolicyStatus](#proxygrouppolicystatus)_ | Status describes the status of the ProxyGroupPolicy. This is set
and managed by the Tailscale operator. | | | + + +#### ProxyGroupPolicyList + + + + + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `tailscale.com/v1alpha1` | | | +| `kind` _string_ | `ProxyGroupPolicyList` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `items` _[ProxyGroupPolicy](#proxygrouppolicy) array_ | | | | + + +#### ProxyGroupPolicySpec + + + + + + + +_Appears in:_ +- [ProxyGroupPolicy](#proxygrouppolicy) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `ingress` _string array_ | Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list
denotes that no ingress via ProxyGroups is allowed within this namespace. | | | +| `egress` _string array_ | Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list
denotes that no egress via ProxyGroups is allowed within this namespace. | | | + + +#### ProxyGroupPolicyStatus + + + + + + + +_Appears in:_ +- [ProxyGroupPolicy](#proxygrouppolicy) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.3/#condition-v1-meta) array_ | | | | + + #### ProxyGroupSpec diff --git a/k8s-operator/apis/v1alpha1/register.go b/k8s-operator/apis/v1alpha1/register.go index ebdd2bae1f3ea..125d7419866ea 100644 --- a/k8s-operator/apis/v1alpha1/register.go +++ b/k8s-operator/apis/v1alpha1/register.go @@ -69,6 +69,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ProxyGroupList{}, &Tailnet{}, &TailnetList{}, + &ProxyGroupPolicy{}, + &ProxyGroupPolicyList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go b/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go new file mode 100644 index 0000000000000..dd06380c271f5 --- /dev/null +++ b/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go @@ -0,0 +1,67 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Code comments on these types should be treated as user facing documentation- +// they will appear on the ProxyGroupPolicy CRD i.e. if someone runs kubectl explain tailnet. + +var ProxyGroupPolicyKind = "ProxyGroupPolicy" + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,shortName=pgp +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "ProxyGroupPolicyReady")].reason`,description="Status of the deployed ProxyGroupPolicy resources." + +type ProxyGroupPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitzero"` + + // Spec describes the desired state of the ProxyGroupPolicy. + // More info: + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + Spec ProxyGroupPolicySpec `json:"spec"` + + // Status describes the status of the ProxyGroupPolicy. This is set + // and managed by the Tailscale operator. + // +optional + Status ProxyGroupPolicyStatus `json:"status"` +} + +// +kubebuilder:object:root=true + +type ProxyGroupPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []ProxyGroupPolicy `json:"items"` +} + +type ProxyGroupPolicySpec struct { + // Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list + // denotes that no ingress via ProxyGroups is allowed within this namespace. + // +optional + Ingress []string `json:"ingress,omitempty"` + + // Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list + // denotes that no egress via ProxyGroups is allowed within this namespace. + // +optional + Egress []string `json:"egress,omitempty"` +} + +type ProxyGroupPolicyStatus struct { + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions"` +} + +// ProxyGroupPolicyReady is set to True if the ProxyGroupPolicy is available for use by operator workloads. +const ProxyGroupPolicyReady ConditionType = "ProxyGroupPolicyReady" diff --git a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go index 1081c162c81bc..2528c89f364d6 100644 --- a/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go +++ b/k8s-operator/apis/v1alpha1/zz_generated.deepcopy.go @@ -832,6 +832,112 @@ func (in *ProxyGroupList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicy) DeepCopyInto(out *ProxyGroupPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicy. +func (in *ProxyGroupPolicy) DeepCopy() *ProxyGroupPolicy { + if in == nil { + return nil + } + out := new(ProxyGroupPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProxyGroupPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicyList) DeepCopyInto(out *ProxyGroupPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ProxyGroupPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicyList. +func (in *ProxyGroupPolicyList) DeepCopy() *ProxyGroupPolicyList { + if in == nil { + return nil + } + out := new(ProxyGroupPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProxyGroupPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicySpec) DeepCopyInto(out *ProxyGroupPolicySpec) { + *out = *in + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Egress != nil { + in, out := &in.Egress, &out.Egress + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicySpec. +func (in *ProxyGroupPolicySpec) DeepCopy() *ProxyGroupPolicySpec { + if in == nil { + return nil + } + out := new(ProxyGroupPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyGroupPolicyStatus) DeepCopyInto(out *ProxyGroupPolicyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyGroupPolicyStatus. +func (in *ProxyGroupPolicyStatus) DeepCopy() *ProxyGroupPolicyStatus { + if in == nil { + return nil + } + out := new(ProxyGroupPolicyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyGroupSpec) DeepCopyInto(out *ProxyGroupSpec) { *out = *in From afb065fb6842ccbe6efd2a26096b609e73a3f41b Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Thu, 12 Feb 2026 22:37:41 -0600 Subject: [PATCH 101/202] net/dns: write MagicDNS host names to the hosts file on domain-joined Windows machines On domain-joined Windows devices the primary search domain (the one the device is joined to) always takes precedence over other search domains. This breaks MagicDNS when we are the primary resolver on the device (see #18712). To work around this Windows behavior, we should write MagicDNS host names the hosts file just as we do when we're not the primary resolver. This commit does exactly that. Fixes #18712 Signed-off-by: Nick Khyl --- net/dns/manager_windows.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go index 118dd18dde14b..bc1e645606402 100644 --- a/net/dns/manager_windows.go +++ b/net/dns/manager_windows.go @@ -399,7 +399,15 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error { if err := m.setSplitDNS(resolvers, domains); err != nil { return err } - if err := m.setHosts(nil); err != nil { + var hosts []*HostEntry + if winenv.IsDomainJoined() { + // On domain-joined Windows devices the primary search domain (the one the device is joined to) + // always takes precedence over other search domains. This breaks MagicDNS when we are the primary + // resolver on the device (see #18712). To work around this Windows behavior, we should write MagicDNS + // host names the hosts file just as we do when we're not the primary resolver. + hosts = cfg.Hosts + } + if err := m.setHosts(hosts); err != nil { return err } if err := m.setPrimaryDNS(cfg.Nameservers, cfg.SearchDomains); err != nil { From 9741c1e846d0812f5b63c2ad9f6825b0f6753dcd Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Thu, 12 Feb 2026 22:38:54 -0600 Subject: [PATCH 102/202] control/controlknobs,net/dns,tailcfg: add a control knob that disables hosts file updates on Windows In the absence of a better mechanism, writing unqualified hostnames to the hosts file may be required for MagicDNS to work on some Windows environments, such as domain-joined machines. It can also improve MagicDNS performance on non-domain joined devices when we are not the device's primary DNS resolver. At the same time, updating the hosts file can be slow and expensive, especially when it already contains many entries, as was previously reported in #14327. It may also have negative side effects, such as interfering with the system's DNS resolution policies. Additionally, to fix #18712, we had to extend hosts file usage to domain-joined machines when we are not the primary DNS resolver. For the reasons above, this change may introduce risk. To allow customers to disable hosts file updates remotely without disabling MagicDNS entirely, whether on domain-joined machines or not, this PR introduces the `disable-hosts-file-updates` node attribute. Updates #18712 Updates #14327 Signed-off-by: Nick Khyl --- control/controlknobs/controlknobs.go | 8 ++++++++ net/dns/manager_windows.go | 21 ++++++++++++++------- tailcfg/tailcfg.go | 10 +++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index 708840155df45..0f85e82368dd9 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -107,6 +107,12 @@ type Knobs struct { // of queued netmap.NetworkMap between the controlclient and LocalBackend. // See tailscale/tailscale#14768. DisableSkipStatusQueue atomic.Bool + + // DisableHostsFileUpdates indicates that the node's DNS manager should not create + // hosts file entries when it normally would, such as when we're not the primary + // resolver on Windows or when the host is domain-joined and its primary domain + // takes precedence over MagicDNS. As of 2026-02-13, it is only used on Windows. + DisableHostsFileUpdates atomic.Bool } // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self @@ -137,6 +143,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { disableLocalDNSOverrideViaNRPT = has(tailcfg.NodeAttrDisableLocalDNSOverrideViaNRPT) disableCaptivePortalDetection = has(tailcfg.NodeAttrDisableCaptivePortalDetection) disableSkipStatusQueue = has(tailcfg.NodeAttrDisableSkipStatusQueue) + disableHostsFileUpdates = has(tailcfg.NodeAttrDisableHostsFileUpdates) ) if has(tailcfg.NodeAttrOneCGNATEnable) { @@ -163,6 +170,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT) k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection) k.DisableSkipStatusQueue.Store(disableSkipStatusQueue) + k.DisableHostsFileUpdates.Store(disableHostsFileUpdates) // If both attributes are present, then "enable" should win. This reflects // the history of seamless key renewal. diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go index bc1e645606402..1e412b2d20617 100644 --- a/net/dns/manager_windows.go +++ b/net/dns/manager_windows.go @@ -34,6 +34,7 @@ import ( "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/syspolicy/ptype" "tailscale.com/util/winutil" + "tailscale.com/util/winutil/winenv" ) const ( @@ -354,6 +355,10 @@ func (m *windowsManager) disableLocalDNSOverrideViaNRPT() bool { return m.knobs != nil && m.knobs.DisableLocalDNSOverrideViaNRPT.Load() } +func (m *windowsManager) disableHostsFileUpdates() bool { + return m.knobs != nil && m.knobs.DisableHostsFileUpdates.Load() +} + func (m *windowsManager) SetDNS(cfg OSConfig) error { // We can configure Windows DNS in one of two ways: // @@ -400,7 +405,7 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error { return err } var hosts []*HostEntry - if winenv.IsDomainJoined() { + if !m.disableHostsFileUpdates() && winenv.IsDomainJoined() { // On domain-joined Windows devices the primary search domain (the one the device is joined to) // always takes precedence over other search domains. This breaks MagicDNS when we are the primary // resolver on the device (see #18712). To work around this Windows behavior, we should write MagicDNS @@ -429,12 +434,14 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error { return err } - // As we are not the primary resolver in this setup, we need to - // explicitly set some single name hosts to ensure that we can resolve - // them quickly and get around the 2.3s delay that otherwise occurs due - // to multicast timeouts. - if err := m.setHosts(cfg.Hosts); err != nil { - return err + if !m.disableHostsFileUpdates() { + // As we are not the primary resolver in this setup, we need to + // explicitly set some single name hosts to ensure that we can resolve + // them quickly and get around the 2.3s delay that otherwise occurs due + // to multicast timeouts. + if err := m.setHosts(cfg.Hosts); err != nil { + return err + } } } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 171f88fd77b5c..69ca20a947735 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -178,7 +178,8 @@ type CapabilityVersion int // - 129: 2025-10-04: Fixed sleep/wake deadlock in magicsock when using peer relay (PR #17449) // - 130: 2025-10-06: client can send key.HardwareAttestationPublic and key.HardwareAttestationKeySignature in MapRequest // - 131: 2025-11-25: client respects [NodeAttrDefaultAutoUpdate] -const CurrentCapabilityVersion CapabilityVersion = 131 +// - 132: 2026-02-13: client respects [NodeAttrDisableHostsFileUpdates] +const CurrentCapabilityVersion CapabilityVersion = 132 // ID is an integer ID for a user, node, or login allocated by the // control plane. @@ -2740,6 +2741,13 @@ const ( // // The value of the key in [NodeCapMap] is a JSON boolean. NodeAttrDefaultAutoUpdate NodeCapability = "default-auto-update" + + // NodeAttrDisableHostsFileUpdates indicates that the node's DNS manager should + // not create hosts file entries when it normally would, such as when we're not + // the primary resolver on Windows or when the host is domain-joined and its + // primary domain takes precedence over MagicDNS. As of 2026-02-12, it is only + // used on Windows. + NodeAttrDisableHostsFileUpdates NodeCapability = "disable-hosts-file-updates" ) // SetDNSRequest is a request to add a DNS record. From be4449f6e08af0dd67bf702ce362cc6a895d3f9b Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Fri, 13 Feb 2026 13:30:48 -0500 Subject: [PATCH 103/202] util/clientmetric, wgengine/watchdog: report watchdog errors in user/client metrics (#18591) fixes tailscale/corp#36708 Sets up a set of metrics to report watchdog timeouts for wgengine and reports an event for any watchdog timeout. Signed-off-by: Jonathan Nobels --- wgengine/watchdog.go | 99 ++++++++++++++++++++++++++++++++------- wgengine/watchdog_test.go | 95 +++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 18 deletions(-) diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 18b36e0039d6d..f12b1c19e2764 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -22,12 +22,47 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/netmap" + "tailscale.com/util/clientmetric" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgint" ) +type watchdogEvent string + +const ( + Any watchdogEvent = "Any" + Reconfig watchdogEvent = "Reconfig" + ResetAndStop watchdogEvent = "ResetAndStop" + SetFilter watchdogEvent = "SetFilter" + SetJailedFilter watchdogEvent = "SetJailedFilter" + SetStatusCallback watchdogEvent = "SetStatusCallback" + UpdateStatus watchdogEvent = "UpdateStatus" + RequestStatus watchdogEvent = "RequestStatus" + SetNetworkMap watchdogEvent = "SetNetworkMap" + Ping watchdogEvent = "Ping" + Close watchdogEvent = "Close" + PeerForIPEvent watchdogEvent = "PeerForIP" +) + +var ( + watchdogMetrics = map[watchdogEvent]*clientmetric.Metric{ + Any: clientmetric.NewCounter("watchdog_timeout_any_total"), + Reconfig: clientmetric.NewCounter("watchdog_timeout_reconfig"), + ResetAndStop: clientmetric.NewCounter("watchdog_timeout_resetandstop"), + SetFilter: clientmetric.NewCounter("watchdog_timeout_setfilter"), + SetJailedFilter: clientmetric.NewCounter("watchdog_timeout_setjailedfilter"), + SetStatusCallback: clientmetric.NewCounter("watchdog_timeout_setstatuscallback"), + UpdateStatus: clientmetric.NewCounter("watchdog_timeout_updatestatus"), + RequestStatus: clientmetric.NewCounter("watchdog_timeout_requeststatus"), + SetNetworkMap: clientmetric.NewCounter("watchdog_timeout_setnetworkmap"), + Ping: clientmetric.NewCounter("watchdog_timeout_ping"), + Close: clientmetric.NewCounter("watchdog_timeout_close"), + PeerForIPEvent: clientmetric.NewCounter("watchdog_timeout_peerforipevent"), + } +) + // NewWatchdog wraps an Engine and makes sure that all methods complete // within a reasonable amount of time. // @@ -46,7 +81,7 @@ func NewWatchdog(e Engine) Engine { } type inFlightKey struct { - op string + op watchdogEvent ctr uint64 } @@ -62,12 +97,13 @@ type watchdogEngine struct { inFlightCtr uint64 } -func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { +func (e *watchdogEngine) watchdogErr(event watchdogEvent, fn func() error) error { // Track all in-flight operations so we can print more useful error // messages on watchdog failure e.inFlightMu.Lock() + key := inFlightKey{ - op: name, + op: event, ctr: e.inFlightCtr, } e.inFlightCtr++ @@ -93,7 +129,6 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { buf := new(strings.Builder) pprof.Lookup("goroutine").WriteTo(buf, 1) e.logf("wgengine watchdog stacks:\n%s", buf.String()) - // Collect the list of in-flight operations for debugging. var ( b []byte @@ -104,64 +139,92 @@ func (e *watchdogEngine) watchdogErr(name string, fn func() error) error { dur := now.Sub(t).Round(time.Millisecond) b = fmt.Appendf(b, "in-flight[%d]: name=%s duration=%v start=%s\n", k.ctr, k.op, dur, t.Format(time.RFC3339Nano)) } + e.recordEvent(event) e.inFlightMu.Unlock() // Print everything as a single string to avoid log // rate limits. e.logf("wgengine watchdog in-flight:\n%s", b) - e.fatalf("wgengine: watchdog timeout on %s", name) + e.fatalf("wgengine: watchdog timeout on %s", event) return nil } } -func (e *watchdogEngine) watchdog(name string, fn func()) { - e.watchdogErr(name, func() error { +func (e *watchdogEngine) recordEvent(event watchdogEvent) { + if watchdogMetrics == nil { + return + } + + mEvent, ok := watchdogMetrics[event] + if ok { + mEvent.Add(1) + } + mAny, ok := watchdogMetrics[Any] + if ok { + mAny.Add(1) + } +} + +func (e *watchdogEngine) watchdog(event watchdogEvent, fn func()) { + e.watchdogErr(event, func() error { fn() return nil }) } func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *dns.Config) error { - return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg, dnsCfg) }) + return e.watchdogErr(Reconfig, func() error { return e.wrap.Reconfig(cfg, routerCfg, dnsCfg) }) } + func (e *watchdogEngine) ResetAndStop() (st *Status, err error) { - e.watchdog("ResetAndStop", func() { + e.watchdog(ResetAndStop, func() { st, err = e.wrap.ResetAndStop() }) return st, err } + func (e *watchdogEngine) GetFilter() *filter.Filter { return e.wrap.GetFilter() } + func (e *watchdogEngine) SetFilter(filt *filter.Filter) { - e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) }) + e.watchdog(SetFilter, func() { e.wrap.SetFilter(filt) }) } + func (e *watchdogEngine) GetJailedFilter() *filter.Filter { return e.wrap.GetJailedFilter() } + func (e *watchdogEngine) SetJailedFilter(filt *filter.Filter) { - e.watchdog("SetJailedFilter", func() { e.wrap.SetJailedFilter(filt) }) + e.watchdog(SetJailedFilter, func() { e.wrap.SetJailedFilter(filt) }) } + func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) { - e.watchdog("SetStatusCallback", func() { e.wrap.SetStatusCallback(cb) }) + e.watchdog(SetStatusCallback, func() { e.wrap.SetStatusCallback(cb) }) } + func (e *watchdogEngine) UpdateStatus(sb *ipnstate.StatusBuilder) { - e.watchdog("UpdateStatus", func() { e.wrap.UpdateStatus(sb) }) + e.watchdog(UpdateStatus, func() { e.wrap.UpdateStatus(sb) }) } + func (e *watchdogEngine) RequestStatus() { - e.watchdog("RequestStatus", func() { e.wrap.RequestStatus() }) + e.watchdog(RequestStatus, func() { e.wrap.RequestStatus() }) } + func (e *watchdogEngine) SetNetworkMap(nm *netmap.NetworkMap) { - e.watchdog("SetNetworkMap", func() { e.wrap.SetNetworkMap(nm) }) + e.watchdog(SetNetworkMap, func() { e.wrap.SetNetworkMap(nm) }) } + func (e *watchdogEngine) Ping(ip netip.Addr, pingType tailcfg.PingType, size int, cb func(*ipnstate.PingResult)) { - e.watchdog("Ping", func() { e.wrap.Ping(ip, pingType, size, cb) }) + e.watchdog(Ping, func() { e.wrap.Ping(ip, pingType, size, cb) }) } + func (e *watchdogEngine) Close() { - e.watchdog("Close", e.wrap.Close) + e.watchdog(Close, e.wrap.Close) } + func (e *watchdogEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) { - e.watchdog("PeerForIP", func() { ret, ok = e.wrap.PeerForIP(ip) }) + e.watchdog(PeerForIPEvent, func() { ret, ok = e.wrap.PeerForIP(ip) }) return ret, ok } diff --git a/wgengine/watchdog_test.go b/wgengine/watchdog_test.go index 47f133373c445..8032339573e90 100644 --- a/wgengine/watchdog_test.go +++ b/wgengine/watchdog_test.go @@ -1,10 +1,13 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause +//go:build !js + package wgengine import ( "runtime" + "sync" "testing" "time" @@ -44,3 +47,95 @@ func TestWatchdog(t *testing.T) { e.Close() }) } + +func TestWatchdogMetrics(t *testing.T) { + tests := []struct { + name string + events []watchdogEvent + wantCounts map[watchdogEvent]int64 + }{ + { + name: "single event types", + events: []watchdogEvent{RequestStatus, PeerForIPEvent, Ping}, + wantCounts: map[watchdogEvent]int64{ + RequestStatus: 1, + PeerForIPEvent: 1, + Ping: 1, + }, + }, + { + name: "repeated events", + events: []watchdogEvent{RequestStatus, RequestStatus, Ping, RequestStatus}, + wantCounts: map[watchdogEvent]int64{ + RequestStatus: 3, + Ping: 1, + }, + }, + } + + // For swallowing fatalf calls and stack traces + logf := func(format string, args ...any) {} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clearMetrics(t) + bus := eventbustest.NewBus(t) + ht := health.NewTracker(bus) + reg := new(usermetric.Registry) + e, err := NewFakeUserspaceEngine(logf, 0, ht, reg, bus) + if err != nil { + t.Fatal(err) + } + e = NewWatchdog(e) + w := e.(*watchdogEngine) + w.maxWait = 1 * time.Microsecond + w.logf = logf + w.fatalf = logf + + var wg sync.WaitGroup + wg.Add(len(tt.events)) + + for _, ev := range tt.events { + blocked := make(chan struct{}) + w.watchdog(ev, func() { + defer wg.Done() + <-blocked + }) + close(blocked) + } + wg.Wait() + + // Check individual event counts + for ev, want := range tt.wantCounts { + m, ok := watchdogMetrics[ev] + if !ok { + t.Fatalf("no metric found for event %q", ev) + } + got := m.Value() + if got != want { + t.Errorf("got %d metric events for %q, want %d", got, ev, want) + } + } + + // Check total count for Any + m, ok := watchdogMetrics[Any] + if !ok { + t.Fatalf("no Any metric found") + } + got := m.Value() + if got != int64(len(tt.events)) { + t.Errorf("got %d metric events for Any, want %d", got, len(tt.events)) + } + }) + } +} + +func clearMetrics(t *testing.T) { + t.Helper() + if watchdogMetrics == nil { + return + } + for _, m := range watchdogMetrics { + m.Set(0) + } +} From 4f1406f05ae7a1ea4c79d12587bdb3156bb2e12e Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Fri, 13 Feb 2026 10:59:43 -0800 Subject: [PATCH 104/202] ipn/ipnlocal/netmapcache: include packet filters in the cache (#18715) Store packet filter rules in the cache. The match expressions are derived from the filter rules, so these do not need to be stored explicitly, but ensure they are properly reconstructed when the cache is read back. Update the tests to include these fields, and provide representative values. Updates #12639 Change-Id: I9bdb972a86d2c6387177d393ada1f54805a2448b Signed-off-by: M. J. Fromberger --- ipn/ipnlocal/netmapcache/netmapcache.go | 86 +++++++++++++------- ipn/ipnlocal/netmapcache/netmapcache_test.go | 55 ++++++++++++- ipn/ipnlocal/netmapcache/types.go | 7 ++ 3 files changed, 115 insertions(+), 33 deletions(-) diff --git a/ipn/ipnlocal/netmapcache/netmapcache.go b/ipn/ipnlocal/netmapcache/netmapcache.go index b12443b99f473..1b8347f0b8d18 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache.go +++ b/ipn/ipnlocal/netmapcache/netmapcache.go @@ -27,6 +27,7 @@ import ( "tailscale.com/types/netmap" "tailscale.com/util/mak" "tailscale.com/util/set" + "tailscale.com/wgengine/filter" ) var ( @@ -45,17 +46,17 @@ var ( type Cache struct { store Store - // wantKeys records the storage keys from the last write or load of a cached + // wantKeys records the cache keys from the last write or load of a cached // netmap. This is used to prune keys that are no longer referenced after an // update. - wantKeys set.Set[string] + wantKeys set.Set[cacheKey] // lastWrote records the last values written to each stored key. // // TODO(creachadair): This is meant to avoid disk writes, but I'm not // convinced we need it. Or maybe just track hashes of the content rather // than caching a complete copy. - lastWrote map[string]lastWrote + lastWrote map[cacheKey]lastWrote } // NewCache constructs a new empty [Cache] from the given [Store]. @@ -66,8 +67,8 @@ func NewCache(s Store) *Cache { } return &Cache{ store: s, - wantKeys: make(set.Set[string]), - lastWrote: make(map[string]lastWrote), + wantKeys: make(set.Set[cacheKey]), + lastWrote: make(map[cacheKey]lastWrote), } } @@ -76,7 +77,7 @@ type lastWrote struct { at time.Time } -func (c *Cache) writeJSON(ctx context.Context, key string, v any) error { +func (c *Cache) writeJSON(ctx context.Context, key cacheKey, v any) error { j, err := jsonv1.Marshal(v) if err != nil { return fmt.Errorf("JSON marshalling %q: %w", key, err) @@ -90,7 +91,7 @@ func (c *Cache) writeJSON(ctx context.Context, key string, v any) error { return nil } - if err := c.store.Store(ctx, key, j); err != nil { + if err := c.store.Store(ctx, string(key), j); err != nil { return err } @@ -110,11 +111,12 @@ func (c *Cache) removeUnwantedKeys(ctx context.Context) error { errs = append(errs, err) break } - if !c.wantKeys.Contains(key) { + ckey := cacheKey(key) + if !c.wantKeys.Contains(ckey) { if err := c.store.Remove(ctx, key); err != nil { errs = append(errs, fmt.Errorf("remove key %q: %w", key, err)) } - delete(c.lastWrote, key) // even if removal failed, we don't want it + delete(c.lastWrote, ckey) // even if removal failed, we don't want it } } return errors.Join(errs...) @@ -177,6 +179,20 @@ func (s FileStore) Remove(ctx context.Context, key string) error { return err } +// cacheKey is a type wrapper for strings used as cache keys. +type cacheKey string + +const ( + selfKey cacheKey = "self" + miscKey cacheKey = "msic" + dnsKey cacheKey = "dns" + derpMapKey cacheKey = "derpmap" + peerKeyPrefix cacheKey = "peer-" // + stable ID + userKeyPrefix cacheKey = "user-" // + profile ID + sshPolicyKey cacheKey = "ssh" + packetFilterKey cacheKey = "filter" +) + // Store records nm in the cache, replacing any previously-cached values. func (c *Cache) Store(ctx context.Context, nm *netmap.NetworkMap) error { if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached { @@ -187,7 +203,7 @@ func (c *Cache) Store(ctx context.Context, nm *netmap.NetworkMap) error { } clear(c.wantKeys) - if err := c.writeJSON(ctx, "misc", netmapMisc{ + if err := c.writeJSON(ctx, miscKey, netmapMisc{ MachineKey: &nm.MachineKey, CollectServices: &nm.CollectServices, DisplayMessages: &nm.DisplayMessages, @@ -198,33 +214,36 @@ func (c *Cache) Store(ctx context.Context, nm *netmap.NetworkMap) error { }); err != nil { return err } - if err := c.writeJSON(ctx, "dns", netmapDNS{DNS: &nm.DNS}); err != nil { + if err := c.writeJSON(ctx, dnsKey, netmapDNS{DNS: &nm.DNS}); err != nil { return err } - if err := c.writeJSON(ctx, "derpmap", netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { + if err := c.writeJSON(ctx, derpMapKey, netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { return err } - if err := c.writeJSON(ctx, "self", netmapNode{Node: &nm.SelfNode}); err != nil { + if err := c.writeJSON(ctx, selfKey, netmapNode{Node: &nm.SelfNode}); err != nil { return err // N.B. The NodeKey and AllCaps fields can be recovered from SelfNode on // load, and do not need to be stored separately. } for _, p := range nm.Peers { - key := fmt.Sprintf("peer-%s", p.StableID()) + key := peerKeyPrefix + cacheKey(p.StableID()) if err := c.writeJSON(ctx, key, netmapNode{Node: &p}); err != nil { return err } } for uid, u := range nm.UserProfiles { - key := fmt.Sprintf("user-%d", uid) - if err := c.writeJSON(ctx, key, netmapUserProfile{UserProfile: &u}); err != nil { + key := fmt.Sprintf("%s%d", userKeyPrefix, uid) + if err := c.writeJSON(ctx, cacheKey(key), netmapUserProfile{UserProfile: &u}); err != nil { return err } } + if err := c.writeJSON(ctx, packetFilterKey, netmapPacketFilter{Rules: &nm.PacketFilterRules}); err != nil { + return err + } if buildfeatures.HasSSH && nm.SSHPolicy != nil { - if err := c.writeJSON(ctx, "ssh", netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { + if err := c.writeJSON(ctx, sshPolicyKey, netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { return err } } @@ -244,12 +263,12 @@ func (c *Cache) Load(ctx context.Context) (*netmap.NetworkMap, error) { // At minimum, we require that the cache contain a "self" node, or the data // are not usable. - if self, err := c.store.Load(ctx, "self"); errors.Is(err, ErrKeyNotFound) { + if self, err := c.store.Load(ctx, string(selfKey)); errors.Is(err, ErrKeyNotFound) { return nil, ErrCacheNotAvailable } else if err := jsonv1.Unmarshal(self, &netmapNode{Node: &nm.SelfNode}); err != nil { return nil, err } - c.wantKeys.Add("self") + c.wantKeys.Add(selfKey) // If we successfully recovered a SelfNode, pull out its related fields. if s := nm.SelfNode; s.Valid() { @@ -266,7 +285,7 @@ func (c *Cache) Load(ctx context.Context) (*netmap.NetworkMap, error) { // Unmarshal the contents of each specified cache entry directly into the // fields of the output. See the comment in types.go for more detail. - if err := c.readJSON(ctx, "misc", &netmapMisc{ + if err := c.readJSON(ctx, miscKey, &netmapMisc{ MachineKey: &nm.MachineKey, CollectServices: &nm.CollectServices, DisplayMessages: &nm.DisplayMessages, @@ -278,43 +297,52 @@ func (c *Cache) Load(ctx context.Context) (*netmap.NetworkMap, error) { return nil, err } - if err := c.readJSON(ctx, "dns", &netmapDNS{DNS: &nm.DNS}); err != nil { + if err := c.readJSON(ctx, dnsKey, &netmapDNS{DNS: &nm.DNS}); err != nil { return nil, err } - if err := c.readJSON(ctx, "derpmap", &netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { + if err := c.readJSON(ctx, derpMapKey, &netmapDERPMap{DERPMap: &nm.DERPMap}); err != nil { return nil, err } - for key, err := range c.store.List(ctx, "peer-") { + for key, err := range c.store.List(ctx, string(peerKeyPrefix)) { if err != nil { return nil, err } var peer tailcfg.NodeView - if err := c.readJSON(ctx, key, &netmapNode{Node: &peer}); err != nil { + if err := c.readJSON(ctx, cacheKey(key), &netmapNode{Node: &peer}); err != nil { return nil, err } nm.Peers = append(nm.Peers, peer) } slices.SortFunc(nm.Peers, func(a, b tailcfg.NodeView) int { return cmp.Compare(a.ID(), b.ID()) }) - for key, err := range c.store.List(ctx, "user-") { + for key, err := range c.store.List(ctx, string(userKeyPrefix)) { if err != nil { return nil, err } var up tailcfg.UserProfileView - if err := c.readJSON(ctx, key, &netmapUserProfile{UserProfile: &up}); err != nil { + if err := c.readJSON(ctx, cacheKey(key), &netmapUserProfile{UserProfile: &up}); err != nil { return nil, err } mak.Set(&nm.UserProfiles, up.ID(), up) } - if err := c.readJSON(ctx, "ssh", &netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { + if err := c.readJSON(ctx, sshPolicyKey, &netmapSSH{SSHPolicy: &nm.SSHPolicy}); err != nil { return nil, err } + if err := c.readJSON(ctx, packetFilterKey, &netmapPacketFilter{Rules: &nm.PacketFilterRules}); err != nil { + return nil, err + } else if r := nm.PacketFilterRules; r.Len() != 0 { + // Reconstitute packet match expressions from the filter rules, + nm.PacketFilter, err = filter.MatchesFromFilterRules(r.AsSlice()) + if err != nil { + return nil, err + } + } return &nm, nil } -func (c *Cache) readJSON(ctx context.Context, key string, value any) error { - data, err := c.store.Load(ctx, key) +func (c *Cache) readJSON(ctx context.Context, key cacheKey, value any) error { + data, err := c.store.Load(ctx, string(key)) if errors.Is(err, ErrKeyNotFound) { return nil } else if err != nil { diff --git a/ipn/ipnlocal/netmapcache/netmapcache_test.go b/ipn/ipnlocal/netmapcache/netmapcache_test.go index b31db2d5eb8b5..b5a46d2982a04 100644 --- a/ipn/ipnlocal/netmapcache/netmapcache_test.go +++ b/ipn/ipnlocal/netmapcache/netmapcache_test.go @@ -11,6 +11,7 @@ import ( "fmt" "iter" "maps" + "net/netip" "os" "reflect" "slices" @@ -23,10 +24,13 @@ import ( "tailscale.com/ipn/ipnlocal/netmapcache" "tailscale.com/tailcfg" "tailscale.com/tka" + "tailscale.com/types/ipproto" "tailscale.com/types/key" "tailscale.com/types/netmap" "tailscale.com/types/views" "tailscale.com/util/set" + "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/filter/filtertype" ) // Input values for valid-looking placeholder values for keys, hashes, etc. @@ -68,6 +72,27 @@ func init() { panic(fmt.Sprintf("invalid test AUM hash %q: %v", testAUMHashString, err)) } + pfRules := []tailcfg.FilterRule{ + { + SrcIPs: []string{"192.168.0.0/16"}, + DstPorts: []tailcfg.NetPortRange{{ + IP: "*", + Ports: tailcfg.PortRange{First: 2000, Last: 9999}, + }}, + IPProto: []int{1, 6, 17}, // ICMPv4, TCP, UDP + CapGrant: []tailcfg.CapGrant{{ + Dsts: []netip.Prefix{netip.MustParsePrefix("192.168.4.0/24")}, + CapMap: tailcfg.PeerCapMap{ + "tailscale.com/testcap": []tailcfg.RawMessage{`"apple"`, `"pear"`}, + }, + }}, + }, + } + pfMatch, err := filter.MatchesFromFilterRules(pfRules) + if err != nil { + panic(fmt.Sprintf("invalid packet filter rules: %v", err)) + } + // The following network map must have a non-zero non-empty value for every // field that is to be stored in the cache. The test checks for this using // reflection, as a way to ensure that new fields added to the type are @@ -79,8 +104,9 @@ func init() { testMap = &netmap.NetworkMap{ Cached: false, // not cached, this is metadata for the cache machinery - PacketFilter: nil, // not cached - PacketFilterRules: views.Slice[tailcfg.FilterRule]{}, // not cached + // These two fields must contain compatible data. + PacketFilterRules: views.SliceOf(pfRules), + PacketFilter: pfMatch, // Fields stored under the "self" key. // Note that SelfNode must have a valid user in order to be considered @@ -235,7 +261,7 @@ func TestInvalidCache(t *testing.T) { // skippedMapFields are the names of fields that should not be considered by // network map caching, and thus skipped when comparing test results. var skippedMapFields = []string{ - "Cached", "PacketFilter", "PacketFilterRules", + "Cached", } // checkFieldCoverage logs an error in t if any of the fields of nm are zero @@ -366,6 +392,27 @@ func (t testStore) Remove(_ context.Context, key string) error { delete(t, key); func diffNetMaps(got, want *netmap.NetworkMap) string { return cmp.Diff(got, want, cmpopts.IgnoreFields(netmap.NetworkMap{}, skippedMapFields...), - cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}), + cmpopts.IgnoreFields(filtertype.Match{}, "SrcsContains"), // function pointer + cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}, netip.Prefix{}), + cmp.Comparer(eqViewsSlice(eqFilterRules)), + cmp.Comparer(eqViewsSlice(func(a, b ipproto.Proto) bool { return a == b })), ) } + +func eqViewsSlice[T any](eqVal func(x, y T) bool) func(a, b views.Slice[T]) bool { + return func(a, b views.Slice[T]) bool { + if a.Len() != b.Len() { + return false + } + for i := range a.Len() { + if !eqVal(a.At(i), b.At(i)) { + return false + } + } + return true + } +} + +func eqFilterRules(a, b tailcfg.FilterRule) bool { + return cmp.Equal(a, b, cmpopts.EquateComparable(netip.Prefix{})) +} diff --git a/ipn/ipnlocal/netmapcache/types.go b/ipn/ipnlocal/netmapcache/types.go index 2fb5a1575f1b3..c9f9efc1e3b21 100644 --- a/ipn/ipnlocal/netmapcache/types.go +++ b/ipn/ipnlocal/netmapcache/types.go @@ -7,6 +7,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/tka" "tailscale.com/types/key" + "tailscale.com/types/views" ) // The fields in the following wrapper types are all pointers, even when their @@ -50,3 +51,9 @@ type netmapNode struct { type netmapUserProfile struct { UserProfile *tailcfg.UserProfileView } + +type netmapPacketFilter struct { + Rules *views.Slice[tailcfg.FilterRule] + + // Match expressions are derived from the rules. +} From 3cc7f897d3dec55b87dc290d22e76f50139f59d7 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Fri, 13 Feb 2026 12:51:51 -0600 Subject: [PATCH 105/202] health: always include control health messages in the current state (*health.Tracker).CurrentState() returns an empty state when there are no client-side warnables, even when there are control-health messages, which is incorrect. This fixes it. Updates tailscale/corp#37275 Signed-off-by: Nick Khyl --- health/state.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/health/state.go b/health/state.go index 91e30b75e796d..61d36797ceaf0 100644 --- a/health/state.go +++ b/health/state.go @@ -11,6 +11,7 @@ import ( "tailscale.com/feature/buildfeatures" "tailscale.com/tailcfg" + "tailscale.com/util/mak" ) // State contains the health status of the backend, and is @@ -128,11 +129,7 @@ func (t *Tracker) CurrentState() *State { t.mu.Lock() defer t.mu.Unlock() - if t.warnableVal == nil || len(t.warnableVal) == 0 { - return &State{} - } - - wm := map[WarnableCode]UnhealthyState{} + var wm map[WarnableCode]UnhealthyState for w, ws := range t.warnableVal { if !w.IsVisible(ws, t.now) { @@ -145,7 +142,7 @@ func (t *Tracker) CurrentState() *State { continue } state := w.unhealthyState(ws) - wm[w.Code] = state.withETag() + mak.Set(&wm, w.Code, state.withETag()) } for id, msg := range t.lastNotifiedControlMessages { @@ -165,7 +162,7 @@ func (t *Tracker) CurrentState() *State { } } - wm[state.WarnableCode] = state.withETag() + mak.Set(&wm, state.WarnableCode, state.withETag()) } return &State{ From 371d6369cd25afb7e3dd170873f232ad6dba9a02 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 8 Feb 2026 02:13:45 +0000 Subject: [PATCH 106/202] gokrazy: use monorepo for gokrazy appliance builds (monogok) This switches our gokrazy builds to use a new variant of cmd/gok called opinionated about using monorepos: https://github.com/bradfitz/monogok And with that, we can get rid of all the go.mod files and builddir forests under gokrazy/**. Updates #13038 Updates gokrazy/gokrazy#361 Change-Id: I9f18fbe59b8792286abc1e563d686ea9472c622d Signed-off-by: Brad Fitzpatrick --- .github/workflows/natlab-integrationtest.yml | 2 +- flake.nix | 2 +- go.mod | 20 +- go.mod.sri | 2 +- go.sum | 49 +++- gokrazy/build.go | 13 +- gokrazy/go.mod | 19 -- gokrazy/go.sum | 33 --- gokrazy/gok | 8 - gokrazy/monogok | 8 + .../gokrazy/gokrazy/cmd/dhcp/go.mod | 18 -- .../gokrazy/gokrazy/cmd/dhcp/go.sum | 39 --- .../github.com/gokrazy/gokrazy/go.mod | 15 - .../github.com/gokrazy/gokrazy/go.sum | 23 -- .../github.com/gokrazy/kernel.arm64/go.mod | 5 - .../github.com/gokrazy/kernel.arm64/go.sum | 2 - .../github.com/gokrazy/serial-busybox/go.mod | 5 - .../github.com/gokrazy/serial-busybox/go.sum | 26 -- .../tailscale/gokrazy-kernel/go.mod | 5 - .../tailscale/gokrazy-kernel/go.sum | 4 - .../builddir/tailscale.com/go.mod | 7 - .../builddir/tailscale.com/go.sum | 266 ----------------- .../gokrazy/gokrazy/cmd/dhcp/go.mod | 18 -- .../gokrazy/gokrazy/cmd/dhcp/go.sum | 39 --- .../github.com/gokrazy/gokrazy/go.mod | 15 - .../github.com/gokrazy/gokrazy/go.sum | 23 -- .../github.com/gokrazy/serial-busybox/go.mod | 5 - .../github.com/gokrazy/serial-busybox/go.sum | 26 -- .../tailscale/gokrazy-kernel/go.mod | 5 - .../tailscale/gokrazy-kernel/go.sum | 4 - .../natlabapp/builddir/tailscale.com/go.mod | 7 - .../natlabapp/builddir/tailscale.com/go.sum | 268 ------------------ gokrazy/natlabapp/gokrazydeps.go | 16 ++ gokrazy/tidy-deps.go | 2 +- .../github.com/gokrazy/breakglass/go.mod | 19 -- .../github.com/gokrazy/breakglass/go.sum | 46 --- .../gokrazy/gokrazy/cmd/dhcp/go.mod | 18 -- .../gokrazy/gokrazy/cmd/dhcp/go.sum | 39 --- .../github.com/gokrazy/gokrazy/cmd/ntp/go.mod | 5 - .../github.com/gokrazy/gokrazy/cmd/ntp/go.sum | 8 - .../github.com/gokrazy/gokrazy/go.mod | 15 - .../github.com/gokrazy/gokrazy/go.sum | 23 -- .../github.com/gokrazy/rpi-eeprom/go.mod | 5 - .../github.com/gokrazy/rpi-eeprom/go.sum | 3 - .../github.com/gokrazy/serial-busybox/go.mod | 5 - .../github.com/gokrazy/serial-busybox/go.sum | 26 -- .../tailscale/gokrazy-kernel/go.mod | 5 - .../tailscale/gokrazy-kernel/go.sum | 4 - gokrazy/tsapp/builddir/tailscale.com/go.mod | 7 - gokrazy/tsapp/builddir/tailscale.com/go.sum | 262 ----------------- gokrazy/tsapp/gokrazydeps.go | 18 ++ pkgdoc_test.go | 4 + shell.nix | 2 +- tstest/integration/nat/nat_test.go | 2 +- 54 files changed, 124 insertions(+), 1391 deletions(-) delete mode 100644 gokrazy/go.mod delete mode 100644 gokrazy/go.sum delete mode 100755 gokrazy/gok create mode 100755 gokrazy/monogok delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod delete mode 100644 gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum delete mode 100644 gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod delete mode 100644 gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum delete mode 100644 gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod delete mode 100644 gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum delete mode 100644 gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod delete mode 100644 gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum delete mode 100644 gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod delete mode 100644 gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum delete mode 100644 gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod delete mode 100644 gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum delete mode 100644 gokrazy/natlabapp/builddir/tailscale.com/go.mod delete mode 100644 gokrazy/natlabapp/builddir/tailscale.com/go.sum create mode 100644 gokrazy/natlabapp/gokrazydeps.go delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum delete mode 100644 gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod delete mode 100644 gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum delete mode 100644 gokrazy/tsapp/builddir/tailscale.com/go.mod delete mode 100644 gokrazy/tsapp/builddir/tailscale.com/go.sum create mode 100644 gokrazy/tsapp/gokrazydeps.go diff --git a/.github/workflows/natlab-integrationtest.yml b/.github/workflows/natlab-integrationtest.yml index 3e87ba4345180..e10d879c3daa5 100644 --- a/.github/workflows/natlab-integrationtest.yml +++ b/.github/workflows/natlab-integrationtest.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Install qemu run: | - sudo rm /var/lib/man-db/auto-update + sudo rm -f /var/lib/man-db/auto-update sudo apt-get -y update sudo apt-get -y remove man-db sudo apt-get install -y qemu-system-x86 qemu-utils diff --git a/flake.nix b/flake.nix index b29d45aacf43b..bbd1f8b48be0c 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-5A6EShJ33yHQdr6tgsNCRFLvNUUjIKXDv5DvzsiUwFI= +# nix-direnv cache busting line: sha256-e5fAO7gye8B5FGBTxLNVTKq6dp8By9iDEw72M1/y4ZE= diff --git a/go.mod b/go.mod index c69f4f1edc5aa..bc356a19c9b59 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd + github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032 github.com/bramvdbogaerde/go-scp v1.4.0 github.com/cilium/ebpf v0.16.0 github.com/coder/websocket v1.8.12 @@ -43,6 +44,9 @@ require ( github.com/go-ole/go-ole v1.3.0 github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 + github.com/gokrazy/breakglass v0.0.0-20251229072214-9dbc0478d486 + github.com/gokrazy/gokrazy v0.0.0-20260123094004-294c93fa173c + github.com/gokrazy/serial-busybox v0.0.0-20250119153030-ac58ba7574e7 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 github.com/golang/snappy v0.0.4 github.com/golangci/golangci-lint v1.57.1 @@ -87,6 +91,7 @@ require ( github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e github.com/tailscale/depaware v0.0.0-20251001183927-9c2ad255ef3f github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 + github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a github.com/tailscale/mkctr v0.0.0-20260107121656-ea857e3e500b @@ -148,6 +153,7 @@ require ( github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/beevik/ntp v0.3.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/boltdb/bolt v1.3.1 // indirect github.com/bombsimon/wsl/v4 v4.2.1 // indirect @@ -177,11 +183,15 @@ require ( github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect + github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775 // indirect + github.com/gokrazy/internal v0.0.0-20251208203110-3c1aa9087c82 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-github/v66 v66.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/renameio/v2 v2.0.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect @@ -194,13 +204,18 @@ require ( github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/jjti/go-spancheck v0.5.3 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.0.8 // indirect + github.com/kenshaw/evdev v0.1.0 // indirect + github.com/kr/pty v1.1.8 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/macabu/inamedparam v0.1.3 // indirect + github.com/mdlayher/packet v1.1.2 // indirect + github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/buildkit v0.20.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -212,11 +227,13 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/puzpuzpuz/xsync v1.5.2 // indirect + github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/stacklok/frizbee v0.1.7 // indirect + github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect @@ -231,6 +248,7 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 // indirect golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect @@ -431,7 +449,7 @@ require ( github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/spf13/viper v1.16.0 // indirect diff --git a/go.mod.sri b/go.mod.sri index 4edd4e7acabad..de11cbc71e1b7 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-5A6EShJ33yHQdr6tgsNCRFLvNUUjIKXDv5DvzsiUwFI= +sha256-e5fAO7gye8B5FGBTxLNVTKq6dp8By9iDEw72M1/y4ZE= diff --git a/go.sum b/go.sum index e925fcc3d4371..c924e5e6e1a12 100644 --- a/go.sum +++ b/go.sum @@ -183,6 +183,9 @@ github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ= github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= +github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= +github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw= +github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -201,6 +204,8 @@ github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFi github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd h1:1Df3FBmfyUCIQ4eKzAPXIWTfewY89L0fWPWO56zWCyI= github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd/go.mod h1:2+xptBAd0m2kZ1wLO4AYZhldLEFPy+KeGwmnlXLvy+w= +github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032 h1:xDomVqO85ss/98Ky5zxM/g86bXDNBLebM2I9G/fu6uA= +github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032/go.mod h1:TG1HbU9fRVDnNgXncVkKz9GdvjIvqquXjH6QZSEVmY4= github.com/bramvdbogaerde/go-scp v1.4.0 h1:jKMwpwCbcX1KyvDbm/PDJuXcMuNVlLGi0Q0reuzjyKY= github.com/bramvdbogaerde/go-scp v1.4.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ= github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= @@ -277,6 +282,7 @@ github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7u github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008= github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc= github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -473,6 +479,18 @@ github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gokrazy/breakglass v0.0.0-20251229072214-9dbc0478d486 h1:QBELQyXGy+eCEcWtvSfslJk3y7nUPZldOwBqIz1tkXc= +github.com/gokrazy/breakglass v0.0.0-20251229072214-9dbc0478d486/go.mod h1:PFPkRFcazBmCZKo+sBaGjsWouTtfDvg13nCDm0tFOCA= +github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775 h1:f5+2UMRRbr3+e/gdWCBNn48chS/KMMljfbmlSSHfRBA= +github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775/go.mod h1:q9mIV8al0wqmqFXJhKiO3SOHkL9/7Q4kIMynqUQWhgU= +github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= +github.com/gokrazy/gokrazy v0.0.0-20260123094004-294c93fa173c h1:grjqEMf6dPJzZxf+gdo8rjx6bcyseO5p9hierlVkhXQ= +github.com/gokrazy/gokrazy v0.0.0-20260123094004-294c93fa173c/go.mod h1:NtMkrFeDGnwldKLi0dLdd2ipNwoVa7TI4HTxsy7lFRg= +github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= +github.com/gokrazy/internal v0.0.0-20251208203110-3c1aa9087c82 h1:4ghNfD9NaZLpFrqQiBF6mPVFeMYXJSky38ubVA4ic2E= +github.com/gokrazy/internal v0.0.0-20251208203110-3c1aa9087c82/go.mod h1:dQY4EMkD4L5ZjYJ0SPtpgYbV7MIUMCxNIXiOfnZ6jP4= +github.com/gokrazy/serial-busybox v0.0.0-20250119153030-ac58ba7574e7 h1:gurTGc4sL7Ik+IKZ29rhGgHNZQTXPtEXLw+aM9E+/HE= +github.com/gokrazy/serial-busybox v0.0.0-20250119153030-ac58ba7574e7/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -556,6 +574,7 @@ github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:E github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4= @@ -574,6 +593,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= +github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A= github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -704,6 +725,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= @@ -727,6 +750,8 @@ github.com/karamaru-alpha/copyloopvar v1.0.8 h1:gieLARwuByhEMxRwM3GRS/juJqFbLraf github.com/karamaru-alpha/copyloopvar v1.0.8/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= +github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -750,6 +775,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -812,10 +839,15 @@ github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy5 github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= +github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 h1:80FAK3TW5lVymfHu3kvB1QvTZvy9Kmx1lx6sT5Ep16s= +github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5/go.mod h1:z0QjVpjpK4jksEkffQwS3+abQ3XFTm1bnimyDzWyUk0= github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= @@ -986,6 +1018,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= +github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= +github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -1043,8 +1078,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1092,6 +1127,8 @@ github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8 github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= +github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= +github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM= github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= @@ -1153,7 +1190,10 @@ github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvni github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= +github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350 h1:w5OI+kArIBVksl8UGn6ARQshtPCQvDsbuA9NQie3GIg= +github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -1263,12 +1303,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ= +golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1409,6 +1452,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1445,6 +1489,7 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= diff --git a/gokrazy/build.go b/gokrazy/build.go index ea54cc829d1f1..f92edb1a34abb 100644 --- a/gokrazy/build.go +++ b/gokrazy/build.go @@ -137,25 +137,24 @@ func buildImage() error { // Build the tsapp.img var buf bytes.Buffer cmd := exec.Command("go", "run", - "github.com/gokrazy/tools/cmd/gok", - "--parent_dir="+dir, - "--instance="+*app, + "github.com/bradfitz/monogok/cmd/monogok", "overwrite", - "--full", *app+".img", + "--full", filepath.Join(dir, *app+".img"), "--target_storage_bytes=1258299392") + cmd.Dir = filepath.Join(dir, *app) cmd.Stdout = io.MultiWriter(os.Stdout, &buf) cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return err } - // gok overwrite emits a line of text saying how to run mkfs.ext4 + // monogok overwrite emits a line of text saying how to run mkfs.ext4 // to create the ext4 /perm filesystem. Parse that and run it. // The regexp is tight to avoid matching if the command changes, // to force us to check it's still correct/safe. But it shouldn't - // change on its own because we pin the gok version in our go.mod. + // change on its own because we pin the monogok version in our go.mod. // - // TODO(bradfitz): emit this in a machine-readable way from gok. + // TODO(bradfitz): emit this in a machine-readable way from monogok. rx := regexp.MustCompile(`(?m)/mkfs.ext4 (-F) (-E) (offset=\d+) (\S+) (\d+)\s*?$`) m := rx.FindStringSubmatch(buf.String()) if m == nil { diff --git a/gokrazy/go.mod b/gokrazy/go.mod deleted file mode 100644 index f7483f41d5d46..0000000000000 --- a/gokrazy/go.mod +++ /dev/null @@ -1,19 +0,0 @@ -module tailscale.com/gokrazy - -go 1.23 - -require github.com/gokrazy/tools v0.0.0-20250128200151-63160424957c - -require ( - github.com/breml/rootcerts v0.2.10 // indirect - github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 // indirect - github.com/gokrazy/internal v0.0.0-20250126213949-423a5b587b57 // indirect - github.com/gokrazy/updater v0.0.0-20230215172637-813ccc7f21e2 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.6.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.28.0 // indirect -) diff --git a/gokrazy/go.sum b/gokrazy/go.sum deleted file mode 100644 index 170d15b3db19c..0000000000000 --- a/gokrazy/go.sum +++ /dev/null @@ -1,33 +0,0 @@ -github.com/breml/rootcerts v0.2.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8= -github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= -github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= -github.com/gokrazy/internal v0.0.0-20250126213949-423a5b587b57 h1:f5bEvO4we3fbfiBkECrrUgWQ8OH6J3SdB2Dwxid/Yx4= -github.com/gokrazy/internal v0.0.0-20250126213949-423a5b587b57/go.mod h1:SJG1KwuJQXFEoBgryaNCkMbdISyovDgZd0xmXJRZmiw= -github.com/gokrazy/tools v0.0.0-20250128200151-63160424957c h1:iEbS8GrNOn671ze8J/AfrYFEVzf8qMx8aR5K0VxPK2w= -github.com/gokrazy/tools v0.0.0-20250128200151-63160424957c/go.mod h1:f2vZhnaPzy92+Bjpx1iuZHK7VuaJx6SNCWQWmu23HZA= -github.com/gokrazy/updater v0.0.0-20230215172637-813ccc7f21e2 h1:kBY5R1tSf+EYZ+QaSrofLaVJtBqYsVNVBWkdMq3Smcg= -github.com/gokrazy/updater v0.0.0-20230215172637-813ccc7f21e2/go.mod h1:PYOvzGOL4nlBmuxu7IyKQTFLaxr61+WPRNRzVtuYOHw= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gokrazy/gok b/gokrazy/gok deleted file mode 100755 index 13111dab28f6a..0000000000000 --- a/gokrazy/gok +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -# This is a wrapper around gok that sets --parent_dir. - -dir=$(dirname "${BASH_SOURCE[0]}") - -cd $dir -$dir/../tool/go run github.com/gokrazy/tools/cmd/gok --parent_dir="$dir" "$@" diff --git a/gokrazy/monogok b/gokrazy/monogok new file mode 100755 index 0000000000000..2e09a6918fb5a --- /dev/null +++ b/gokrazy/monogok @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# This is a wrapper around monogok that sets the working directory. + +dir=$(dirname "${BASH_SOURCE[0]}") + +cd $dir +$dir/../tool/go run github.com/bradfitz/monogok/cmd/monogok "$@" diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod deleted file mode 100644 index c56dede46ed65..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/mdlayher/packet v1.0.0 // indirect - github.com/mdlayher/socket v0.2.3 // indirect - github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect - github.com/vishvananda/netlink v1.1.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum deleted file mode 100644 index 3cd002ae782b1..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= -github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod deleted file mode 100644 index 33656efeea7d7..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240802144848-676865a4e84f // indirect - github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.20.0 // indirect -) - -replace github.com/gokrazy/gokrazy => github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum deleted file mode 100644 index 479eb1cef1ca7..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/gokrazy/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a h1:FKeN678rNpKTpWRdFbAhYL9mWzPu57R5XPXCR3WmXdI= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 h1:XDklMxV0pE5jWiNaoo5TzvWfqdoiRRScmr4ZtDzE4Uw= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b/go.mod h1:bmoJUS6qOA3uKFvF3KVuhf7mU1KQirzQMeHXtPyKEqg= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a h1:7dnA8x14JihQmKbPr++Y5CCN/XSyDmOB6cXUxcIj6VQ= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f h1:ZSAGWpgs+6dK2oIz5OR+HUul3oJbnhFn8YNgcZ3d9SQ= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 h1:2B8/FbIRqmVgRUulQ4iu1EojniufComYe5Yj4BtIn1c= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -golang.org/x/sys v0.0.0-20201005065044-765f4ea38db3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod deleted file mode 100644 index d4708bf4628ff..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/natlabapp.arm64 - -go 1.23.1 - -require github.com/gokrazy/kernel.arm64 v0.0.0-20240830035047-cdba87a9eb0e // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum deleted file mode 100644 index 5084da5c5990c..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/kernel.arm64/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/gokrazy/kernel.arm64 v0.0.0-20240830035047-cdba87a9eb0e h1:D9QYleJ7CI4p7gpgUT1mPgAlWMi5au6yOiE8/qC5PhE= -github.com/gokrazy/kernel.arm64 v0.0.0-20240830035047-cdba87a9eb0e/go.mod h1:WWx72LXHEesuJxbopusRfSoKJQ6ffdwkT0DZditdrLo= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod deleted file mode 100644 index de52e181b9c3c..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum deleted file mode 100644 index 8135f60c3e791..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/gokrazy/serial-busybox/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904 h1:eqfH4A/LLgxv5RvqEXwVoFvfmpRa8+TokRjB5g6xBkk= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9 h1:x5jR/nNo4/kMSoNo/nwa2xbL7PN1an8S3oIn4OZJdec= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca h1:x0eSjuFy8qsRctVHeWm3EC474q3xm4h3OOOrYpcqyyA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= -github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod b/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod deleted file mode 100644 index ec4d9c64fc93e..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum b/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum deleted file mode 100644 index d32d5460bf29c..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/github.com/tailscale/gokrazy-kernel/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2 h1:xzf+cMvBJBcA/Av7OTWBa0Tjrbfcy00TeatJeJt6zrY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= diff --git a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod b/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod deleted file mode 100644 index da21a143975e9..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gokrazy/build/tsapp - -go 1.23.1 - -replace tailscale.com => ../../../.. - -require tailscale.com v0.0.0-00010101000000-000000000000 // indirect diff --git a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum b/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum deleted file mode 100644 index ae814f31698f4..0000000000000 --- a/gokrazy/natlabapp.arm64/builddir/tailscale.com/go.sum +++ /dev/null @@ -1,266 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= -github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= -github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= -github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= -github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -github.com/illarion/gonotify/v2 v2.0.2 h1:oDH5yvxq9oiQGWUeut42uShcWzOy/hsT9E7pvO95+kQ= -github.com/illarion/gonotify/v2 v2.0.2/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1 h1:ycpNCSYwzZ7x4G4ioPNtKQmIY0G/3o4pVf8wCZq6blY= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07 h1:Z+Zg+aXJYq6f4TK2E4H+vZkQ4dJAWnInXDR6hM9znxo= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod deleted file mode 100644 index c56dede46ed65..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/mdlayher/packet v1.0.0 // indirect - github.com/mdlayher/socket v0.2.3 // indirect - github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect - github.com/vishvananda/netlink v1.1.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum deleted file mode 100644 index 3cd002ae782b1..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= -github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod deleted file mode 100644 index 33656efeea7d7..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240802144848-676865a4e84f // indirect - github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.20.0 // indirect -) - -replace github.com/gokrazy/gokrazy => github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum b/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum deleted file mode 100644 index 479eb1cef1ca7..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/gokrazy/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a h1:FKeN678rNpKTpWRdFbAhYL9mWzPu57R5XPXCR3WmXdI= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 h1:XDklMxV0pE5jWiNaoo5TzvWfqdoiRRScmr4ZtDzE4Uw= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b/go.mod h1:bmoJUS6qOA3uKFvF3KVuhf7mU1KQirzQMeHXtPyKEqg= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a h1:7dnA8x14JihQmKbPr++Y5CCN/XSyDmOB6cXUxcIj6VQ= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f h1:ZSAGWpgs+6dK2oIz5OR+HUul3oJbnhFn8YNgcZ3d9SQ= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 h1:2B8/FbIRqmVgRUulQ4iu1EojniufComYe5Yj4BtIn1c= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -golang.org/x/sys v0.0.0-20201005065044-765f4ea38db3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod b/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod deleted file mode 100644 index de52e181b9c3c..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca // indirect diff --git a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum b/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum deleted file mode 100644 index 8135f60c3e791..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/gokrazy/serial-busybox/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904 h1:eqfH4A/LLgxv5RvqEXwVoFvfmpRa8+TokRjB5g6xBkk= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9 h1:x5jR/nNo4/kMSoNo/nwa2xbL7PN1an8S3oIn4OZJdec= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca h1:x0eSjuFy8qsRctVHeWm3EC474q3xm4h3OOOrYpcqyyA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= -github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod b/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod deleted file mode 100644 index ec4d9c64fc93e..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e // indirect diff --git a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum b/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum deleted file mode 100644 index d32d5460bf29c..0000000000000 --- a/gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2 h1:xzf+cMvBJBcA/Av7OTWBa0Tjrbfcy00TeatJeJt6zrY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= diff --git a/gokrazy/natlabapp/builddir/tailscale.com/go.mod b/gokrazy/natlabapp/builddir/tailscale.com/go.mod deleted file mode 100644 index 53bc11f9bd3f8..0000000000000 --- a/gokrazy/natlabapp/builddir/tailscale.com/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gokrazy/build/tsapp - -go 1.25.5 - -replace tailscale.com => ../../../.. - -require tailscale.com v0.0.0-00010101000000-000000000000 // indirect diff --git a/gokrazy/natlabapp/builddir/tailscale.com/go.sum b/gokrazy/natlabapp/builddir/tailscale.com/go.sum deleted file mode 100644 index 25f15059d3af6..0000000000000 --- a/gokrazy/natlabapp/builddir/tailscale.com/go.sum +++ /dev/null @@ -1,268 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= -github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= -github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= -github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= -github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -github.com/illarion/gonotify/v2 v2.0.2 h1:oDH5yvxq9oiQGWUeut42uShcWzOy/hsT9E7pvO95+kQ= -github.com/illarion/gonotify/v2 v2.0.2/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1 h1:ycpNCSYwzZ7x4G4ioPNtKQmIY0G/3o4pVf8wCZq6blY= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc= -github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07 h1:Z+Zg+aXJYq6f4TK2E4H+vZkQ4dJAWnInXDR6hM9znxo= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/gokrazy/natlabapp/gokrazydeps.go b/gokrazy/natlabapp/gokrazydeps.go new file mode 100644 index 0000000000000..c5d2b32a3d543 --- /dev/null +++ b/gokrazy/natlabapp/gokrazydeps.go @@ -0,0 +1,16 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build for_go_mod_tidy_only + +package gokrazydeps + +import ( + _ "github.com/gokrazy/gokrazy" + _ "github.com/gokrazy/gokrazy/cmd/dhcp" + _ "github.com/gokrazy/serial-busybox" + _ "github.com/tailscale/gokrazy-kernel" + _ "tailscale.com/cmd/tailscale" + _ "tailscale.com/cmd/tailscaled" + _ "tailscale.com/cmd/tta" +) diff --git a/gokrazy/tidy-deps.go b/gokrazy/tidy-deps.go index 8f99f333302b2..3b9dbea7c73af 100644 --- a/gokrazy/tidy-deps.go +++ b/gokrazy/tidy-deps.go @@ -6,5 +6,5 @@ package gokrazy import ( - _ "github.com/gokrazy/tools/cmd/gok" + _ "github.com/bradfitz/monogok/cmd/monogok" ) diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod deleted file mode 100644 index fc809b8f7c130..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.mod +++ /dev/null @@ -1,19 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/creack/pty v1.1.18 // indirect - github.com/gokrazy/breakglass v0.0.0-20240604170121-09eeab3321d6 // indirect - github.com/gokrazy/gokrazy v0.0.0-20230812092215-346db1998f83 // indirect - github.com/gokrazy/internal v0.0.0-20230211171410-9608422911d0 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/kr/fs v0.1.0 // indirect - github.com/kr/pty v1.1.8 // indirect - github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 // indirect - github.com/pkg/sftp v1.13.5 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect -) diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum deleted file mode 100644 index 99e0622742caf..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/breakglass/go.sum +++ /dev/null @@ -1,46 +0,0 @@ -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/breakglass v0.0.0-20240529175905-44b3fe64f19c h1:cWzgXJIluB6jAQ0HcnvA1yExLawmtDSssk9H4fLv3yM= -github.com/gokrazy/breakglass v0.0.0-20240529175905-44b3fe64f19c/go.mod h1:4Yffo2Z5w3q2eDvo3HDR8eDnmkDpMAkX0Tn7b/9upgs= -github.com/gokrazy/breakglass v0.0.0-20240604170121-09eeab3321d6 h1:38JB1lVPx+ihCzlWZdbH1LoNmu0KR+jRSmNFR7aMVTg= -github.com/gokrazy/breakglass v0.0.0-20240604170121-09eeab3321d6/go.mod h1:4Yffo2Z5w3q2eDvo3HDR8eDnmkDpMAkX0Tn7b/9upgs= -github.com/gokrazy/gokrazy v0.0.0-20230812092215-346db1998f83 h1:Y4sADvUYd/c0eqnqebipHHl0GMpAxOQeTzPnwI4ievM= -github.com/gokrazy/gokrazy v0.0.0-20230812092215-346db1998f83/go.mod h1:9q5Tg+q+YvRjC3VG0gfMFut46dhbhtAnvUEp4lPjc6c= -github.com/gokrazy/internal v0.0.0-20230211171410-9608422911d0 h1:QTi0skQ/OM7he/5jEWA9k/DYgdwGAhw3hrUoiPGGZHM= -github.com/gokrazy/internal v0.0.0-20230211171410-9608422911d0/go.mod h1:ddHcxXZ/VVQOSAWcRBbkYY58+QOw4L145ye6phyDmRA= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5 h1:80FAK3TW5lVymfHu3kvB1QvTZvy9Kmx1lx6sT5Ep16s= -github.com/mdlayher/watchdog v0.0.0-20221003142519-49be0df7b3b5/go.mod h1:z0QjVpjpK4jksEkffQwS3+abQ3XFTm1bnimyDzWyUk0= -github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= -github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tailscale/breakglass v0.0.0-20240529174846-0d8ebfc2c652 h1:36TB+ZuYaA8OTdMoPnygC9CJuQmTWxMEmn+a+9XTOgk= -github.com/tailscale/breakglass v0.0.0-20240529174846-0d8ebfc2c652/go.mod h1:4Yffo2Z5w3q2eDvo3HDR8eDnmkDpMAkX0Tn7b/9upgs= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod deleted file mode 100644 index c56dede46ed65..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect - github.com/mdlayher/packet v1.0.0 // indirect - github.com/mdlayher/socket v0.2.3 // indirect - github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 // indirect - github.com/vishvananda/netlink v1.1.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum deleted file mode 100644 index 3cd002ae782b1..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/dhcp/go.sum +++ /dev/null @@ -1,39 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8= -github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46 h1:3psQveH4RUiv5yc3p7kRySilf1nSXLQhAvJFwg4fgnE= -github.com/rtr7/dhcp4 v0.0.0-20220302171438-18c84d089b46/go.mod h1:Ng1F/s+z0zCMsbEFEneh+30LJa9DrTfmA+REbEqcTPk= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod deleted file mode 100644 index d851081bbc660..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 // indirect diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum deleted file mode 100644 index d3dc288edf218..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/cmd/ntp/go.sum +++ /dev/null @@ -1,8 +0,0 @@ -github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw= -github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod deleted file mode 100644 index 33656efeea7d7..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require ( - github.com/gokrazy/gokrazy v0.0.0-20240802144848-676865a4e84f // indirect - github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 // indirect - github.com/google/renameio/v2 v2.0.0 // indirect - github.com/kenshaw/evdev v0.1.0 // indirect - github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.20.0 // indirect -) - -replace github.com/gokrazy/gokrazy => github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum deleted file mode 100644 index 479eb1cef1ca7..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/gokrazy/go.sum +++ /dev/null @@ -1,23 +0,0 @@ -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803 h1:gdGRW/wXHPJuZgZD931Lh75mdJfzEEXrL+Dvi97Ck3A= -github.com/gokrazy/gokrazy v0.0.0-20240525065858-dedadaf38803/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a h1:FKeN678rNpKTpWRdFbAhYL9mWzPu57R5XPXCR3WmXdI= -github.com/gokrazy/internal v0.0.0-20240510165500-68dd68393b7a/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5 h1:XDklMxV0pE5jWiNaoo5TzvWfqdoiRRScmr4ZtDzE4Uw= -github.com/gokrazy/internal v0.0.0-20240629150625-a0f1dee26ef5/go.mod h1:t3ZirVhcs9bH+fPAJuGh51rzT7sVCZ9yfXvszf0ZjF0= -github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= -github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/kenshaw/evdev v0.1.0 h1:wmtceEOFfilChgdNT+c/djPJ2JineVsQ0N14kGzFRUo= -github.com/kenshaw/evdev v0.1.0/go.mod h1:B/fErKCihUyEobz0mjn2qQbHgyJKFQAxkXSvkeeA/Wo= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= -github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b/go.mod h1:bmoJUS6qOA3uKFvF3KVuhf7mU1KQirzQMeHXtPyKEqg= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a h1:7dnA8x14JihQmKbPr++Y5CCN/XSyDmOB6cXUxcIj6VQ= -github.com/tailscale/gokrazy v0.0.0-20240602215456-7b9b6bbf726a/go.mod h1:NHROeDlzn0icUl3f+tEYvGGpcyBDMsr3AvKLHOWRe5M= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f h1:ZSAGWpgs+6dK2oIz5OR+HUul3oJbnhFn8YNgcZ3d9SQ= -github.com/tailscale/gokrazy v0.0.0-20240802144848-676865a4e84f/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678 h1:2B8/FbIRqmVgRUulQ4iu1EojniufComYe5Yj4BtIn1c= -github.com/tailscale/gokrazy v0.0.0-20240812224643-6b21ddf64678/go.mod h1:+/WWMckeuQt+DG6690A6H8IgC+HpBFq2fmwRKcSbxdk= -golang.org/x/sys v0.0.0-20201005065044-765f4ea38db3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod deleted file mode 100644 index 613104a7f6469..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/rpi-eeprom v0.0.0-20240518032756-37da22ee9608 // indirect diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum deleted file mode 100644 index b037c105633fb..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/rpi-eeprom/go.sum +++ /dev/null @@ -1,3 +0,0 @@ -github.com/gokrazy/rpi-eeprom v0.0.0-20240518032756-37da22ee9608 h1:8uderKR+8eXR0nRcyBugql1YPoJQjpjoltHqX9yl2DI= -github.com/gokrazy/rpi-eeprom v0.0.0-20240518032756-37da22ee9608/go.mod h1:vabxV1M+i6S3rGuWoFieHxCJW3jlob3rqe0KV82j+0o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod b/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod deleted file mode 100644 index de52e181b9c3c..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca // indirect diff --git a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum b/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum deleted file mode 100644 index 8135f60c3e791..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/gokrazy/serial-busybox/go.sum +++ /dev/null @@ -1,26 +0,0 @@ -github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904 h1:eqfH4A/LLgxv5RvqEXwVoFvfmpRa8+TokRjB5g6xBkk= -github.com/gokrazy/gokrazy v0.0.0-20200501080617-f3445e01a904/go.mod h1:pq6rGHqxMRPSaTXaCMzIZy0wLDusAJyoVNyNo05RLs0= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9 h1:x5jR/nNo4/kMSoNo/nwa2xbL7PN1an8S3oIn4OZJdec= -github.com/gokrazy/internal v0.0.0-20200407075822-660ad467b7c9/go.mod h1:LA5TQy7LcvYGQOy75tkrYkFUhbV2nl5qEBP47PSi2JA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca h1:x0eSjuFy8qsRctVHeWm3EC474q3xm4h3OOOrYpcqyyA= -github.com/gokrazy/serial-busybox v0.0.0-20220918193710-d728912733ca/go.mod h1:OYcG5tSb+QrelmUOO4EZVUFcIHyyZb0QDbEbZFUp1TA= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= -github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4 h1:c1Sgqkh8v6ZxafNGG64r8C8UisIW2TKMJN8P86tKjr0= -golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod b/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod deleted file mode 100644 index ec4d9c64fc93e..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module gokrazy/build/tsapp - -go 1.22.2 - -require github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e // indirect diff --git a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum b/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum deleted file mode 100644 index d32d5460bf29c..0000000000000 --- a/gokrazy/tsapp/builddir/github.com/tailscale/gokrazy-kernel/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2 h1:xzf+cMvBJBcA/Av7OTWBa0Tjrbfcy00TeatJeJt6zrY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240530042707-3f95c886bcf2/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e h1:tyUUgeRPGHjCZWycRnhdx8Lx9DRkjl3WsVUxYMrVBOw= -github.com/tailscale/gokrazy-kernel v0.0.0-20240728225134-3d23beabda2e/go.mod h1:7Mth+m9bq2IHusSsexMNyupHWPL8RxwOuSvBlSGtgDY= diff --git a/gokrazy/tsapp/builddir/tailscale.com/go.mod b/gokrazy/tsapp/builddir/tailscale.com/go.mod deleted file mode 100644 index 53bc11f9bd3f8..0000000000000 --- a/gokrazy/tsapp/builddir/tailscale.com/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gokrazy/build/tsapp - -go 1.25.5 - -replace tailscale.com => ../../../.. - -require tailscale.com v0.0.0-00010101000000-000000000000 // indirect diff --git a/gokrazy/tsapp/builddir/tailscale.com/go.sum b/gokrazy/tsapp/builddir/tailscale.com/go.sum deleted file mode 100644 index 2ffef7bf7ba22..0000000000000 --- a/gokrazy/tsapp/builddir/tailscale.com/go.sum +++ /dev/null @@ -1,262 +0,0 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= -github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= -github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw= -github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU= -github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= -github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= -github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= -github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= -github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= -github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= -github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= -github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= -github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= -github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= -github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= -github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= -github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= -github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= -github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w= -github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= -github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g= -github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= -github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1 h1:ycpNCSYwzZ7x4G4ioPNtKQmIY0G/3o4pVf8wCZq6blY= -github.com/tailscale/wireguard-go v0.0.0-20240705152531-2f5d148bcfe1/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98 h1:RNpJrXfI5u6e+uzyIzvmnXbhmhdRkVf//90sMBH3lso= -github.com/tailscale/wireguard-go v0.0.0-20240731203015-71393c576b98/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw= -github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9 h1:81P7rjnikHKTJ75EkjppvbwUfKHDHYk6LJpO5PZy8pA= -github.com/tailscale/xnet v0.0.0-20240117122442-62b9a7c569f9/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= -github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= -github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs= -github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= -github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= -github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= -go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= -go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= -go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07 h1:Z+Zg+aXJYq6f4TK2E4H+vZkQ4dJAWnInXDR6hM9znxo= -golang.org/x/crypto v0.32.1-0.20250118192723-a8ea4be81f07/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= -golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 h1:/8/t5pz/mgdRXhYOIeqqYhFAQLE4DDGegc0Y4ZjyFJM= -gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8= -gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= -nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= -software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/gokrazy/tsapp/gokrazydeps.go b/gokrazy/tsapp/gokrazydeps.go new file mode 100644 index 0000000000000..931080647f8e5 --- /dev/null +++ b/gokrazy/tsapp/gokrazydeps.go @@ -0,0 +1,18 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build for_go_mod_tidy_only + +package gokrazydeps + +import ( + _ "github.com/gokrazy/breakglass" + _ "github.com/gokrazy/gokrazy" + _ "github.com/gokrazy/gokrazy/cmd/dhcp" + _ "github.com/gokrazy/gokrazy/cmd/ntp" + _ "github.com/gokrazy/gokrazy/cmd/randomd" + _ "github.com/gokrazy/serial-busybox" + _ "github.com/tailscale/gokrazy-kernel" + _ "tailscale.com/cmd/tailscale" + _ "tailscale.com/cmd/tailscaled" +) diff --git a/pkgdoc_test.go b/pkgdoc_test.go index b3a902bf41f4b..60b2d4856d6c7 100644 --- a/pkgdoc_test.go +++ b/pkgdoc_test.go @@ -71,6 +71,10 @@ func TestPackageDocs(t *testing.T) { t.Logf("multiple files with package doc in %s: %q", dir, ff) } if len(ff) == 0 { + if strings.HasPrefix(dir, "gokrazy/") { + // Ignore gokrazy appliances. Their *.go file is only for deps. + continue + } t.Errorf("no package doc in %s", dir) } } diff --git a/shell.nix b/shell.nix index ff44b9b89631b..0c51f59c00d3b 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-5A6EShJ33yHQdr6tgsNCRFLvNUUjIKXDv5DvzsiUwFI= +# nix-direnv cache busting line: sha256-e5fAO7gye8B5FGBTxLNVTKq6dp8By9iDEw72M1/y4ZE= diff --git a/tstest/integration/nat/nat_test.go b/tstest/integration/nat/nat_test.go index 2aea7c296701b..56d602222cbe9 100644 --- a/tstest/integration/nat/nat_test.go +++ b/tstest/integration/nat/nat_test.go @@ -81,7 +81,7 @@ func newNatTest(tb testing.TB) *natTest { } } - nt.kernel, err = findKernelPath(filepath.Join(modRoot, "gokrazy/natlabapp/builddir/github.com/tailscale/gokrazy-kernel/go.mod")) + nt.kernel, err = findKernelPath(filepath.Join(modRoot, "go.mod")) if err != nil { tb.Skipf("skipping test; kernel not found: %v", err) } From 6854d2982b0619e6bfe0dcff0b20f12fe3a72d01 Mon Sep 17 00:00:00 2001 From: Simon Law Date: Fri, 13 Feb 2026 18:19:27 -0800 Subject: [PATCH 107/202] ipn/ipnlocal: log errors when suggesting exit nodes (#18728) In PR #18681, we started logging which exit nodes were being suggested. However, we did not log if there were errors encountered. This patch corrects this oversight. Updates: tailscale/corp#29964 Updates: tailscale/corp#36446 Signed-off-by: Simon Law --- ipn/ipnlocal/local.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 27858484a7a0e..e9222cde35b50 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -7492,8 +7492,12 @@ func suggestExitNode(report *netcheck.Report, nb *nodeBackend, prevSuggestion ta // it is set in the policy file: tailscale/corp#34401 res, err = suggestExitNodeUsingDERP(report, nb, prevSuggestion, selectRegion, selectNode, allowList) } - name, _, _ := strings.Cut(res.Name, ".") - nb.logf("netmap: suggested exit node: %s (%s)", name, res.ID) + if err != nil { + nb.logf("netmap: suggested exit node: %v", err) + } else { + name, _, _ := strings.Cut(res.Name, ".") + nb.logf("netmap: suggested exit node: %s (%s)", name, res.ID) + } return res, err } From 3f3af841afe5637996b45272e3460a8f095fb151 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 16 Feb 2026 01:05:32 +0000 Subject: [PATCH 108/202] tool/gocross: respect TS_GO_NEXT=1 in gocross too The gocross-wrapper.sh bash script already checks TS_GO_NEXT (as of a374cc344e48) to select go.toolchain.next.rev over go.toolchain.rev, but when TS_USE_GOCROSS=1 the Go binary itself was hardcoded to read go.toolchain.rev. This makes gocross also respect the TS_GO_NEXT=1 environment variable. Updates tailscale/corp#36382 Change-Id: I04bef25a34e7ed3ccb1bfdb33a3a1f896236c6ee Signed-off-by: Brad Fitzpatrick --- tool/gocross/toolchain.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tool/gocross/toolchain.go b/tool/gocross/toolchain.go index 2eb675861bbce..8086d96976e92 100644 --- a/tool/gocross/toolchain.go +++ b/tool/gocross/toolchain.go @@ -43,7 +43,11 @@ findTopLevel: } } - return readRevFile(filepath.Join(d, "go.toolchain.rev")) + revFile := "go.toolchain.rev" + if os.Getenv("TS_GO_NEXT") == "1" { + revFile = "go.toolchain.next.rev" + } + return readRevFile(filepath.Join(d, revFile)) } func readRevFile(path string) (string, error) { From bfc15cb57c0ce2ee809434db77f1c3ac7c107b29 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 16 Feb 2026 06:26:39 -1000 Subject: [PATCH 109/202] cmd/cigocacher: remove Windows-specific disk code moved upstream (#18697) Updates tailscale/corp#10808 Updates bradfitz/go-tool-cache#27 Change-Id: I27a2af63d882d916998933521f17e410692255ca Signed-off-by: Brad Fitzpatrick Signed-off-by: Tom Proctor --- cmd/cigocacher/cigocacher.go | 134 ++++++++++-------------------- cmd/cigocacher/disk.go | 88 -------------------- cmd/cigocacher/disk_notwindows.go | 44 ---------- cmd/cigocacher/disk_windows.go | 102 ----------------------- cmd/cigocacher/http.go | 109 ------------------------ flake.nix | 2 +- go.mod | 4 +- go.mod.sri | 2 +- go.sum | 8 +- shell.nix | 2 +- 10 files changed, 55 insertions(+), 440 deletions(-) delete mode 100644 cmd/cigocacher/disk.go delete mode 100644 cmd/cigocacher/disk_notwindows.go delete mode 100644 cmd/cigocacher/disk_windows.go delete mode 100644 cmd/cigocacher/http.go diff --git a/cmd/cigocacher/cigocacher.go b/cmd/cigocacher/cigocacher.go index b308afd06d688..1e4326ebcb6be 100644 --- a/cmd/cigocacher/cigocacher.go +++ b/cmd/cigocacher/cigocacher.go @@ -12,10 +12,8 @@ package main import ( - "bytes" "context" jsonv1 "encoding/json" - "errors" "flag" "fmt" "io" @@ -103,13 +101,7 @@ func main() { if tk == "" { log.Fatal("--token is empty; cannot fetch stats") } - c := &gocachedClient{ - baseURL: *srvURL, - cl: httpClient(srvHost, *srvHostDial), - accessToken: tk, - verbose: *verbose, - } - stats, err := c.fetchStats() + stats, err := fetchStats(httpClient(srvHost, *srvHostDial), *srvURL, tk) if err != nil { log.Fatalf("error fetching gocached stats: %v", err) } @@ -140,11 +132,13 @@ func main() { if *verbose { log.Printf("Using cigocached at %s", *srvURL) } - c.gocached = &gocachedClient{ - baseURL: *srvURL, - cl: httpClient(srvHost, *srvHostDial), - accessToken: *token, - verbose: *verbose, + c.remote = &cachers.HTTPClient{ + BaseURL: *srvURL, + Disk: c.disk, + HTTPClient: httpClient(srvHost, *srvHostDial), + AccessToken: *token, + Verbose: *verbose, + BestEffortHTTP: true, } } var p *cacheproc.Process @@ -186,9 +180,9 @@ func httpClient(srvHost, srvHostDial string) *http.Client { } type cigocacher struct { - disk *cachers.DiskCache - gocached *gocachedClient - verbose bool + disk *cachers.DiskCache + remote *cachers.HTTPClient // nil if no remote server + verbose bool getNanos atomic.Int64 // total nanoseconds spent in gets putNanos atomic.Int64 // total nanoseconds spent in puts @@ -209,39 +203,33 @@ func (c *cigocacher) get(ctx context.Context, actionID string) (outputID, diskPa defer func() { c.getNanos.Add(time.Since(t0).Nanoseconds()) }() - if c.gocached == nil { - return c.disk.Get(ctx, actionID) - } outputID, diskPath, err = c.disk.Get(ctx, actionID) - if err == nil && outputID != "" { - return outputID, diskPath, nil + if c.remote == nil || (err == nil && outputID != "") { + return outputID, diskPath, err } + // Disk miss; try remote. HTTPClient.Get handles the HTTP fetch + // (including lz4 decompression) and writes to disk for us. c.getHTTP.Add(1) t0HTTP := time.Now() defer func() { c.getHTTPNanos.Add(time.Since(t0HTTP).Nanoseconds()) }() - outputID, res, err := c.gocached.get(ctx, actionID) + outputID, diskPath, err = c.remote.Get(ctx, actionID) if err != nil { c.getHTTPErrors.Add(1) return "", "", nil } - if outputID == "" || res == nil { + if outputID == "" { c.getHTTPMisses.Add(1) return "", "", nil } - defer res.Body.Close() - - diskPath, err = put(c.disk, actionID, outputID, res.ContentLength, res.Body) - if err != nil { - return "", "", fmt.Errorf("error filling disk cache from HTTP: %w", err) - } - c.getHTTPHits.Add(1) - c.getHTTPBytes.Add(res.ContentLength) + if fi, err := os.Stat(diskPath); err == nil { + c.getHTTPBytes.Add(fi.Size()) + } return outputID, diskPath, nil } @@ -250,56 +238,25 @@ func (c *cigocacher) put(ctx context.Context, actionID, outputID string, size in defer func() { c.putNanos.Add(time.Since(t0).Nanoseconds()) }() - if c.gocached == nil { - return put(c.disk, actionID, outputID, size, r) - } - c.putHTTP.Add(1) - var diskReader, httpReader io.Reader - tee := &bestEffortTeeReader{r: r} - if size == 0 { - // Special case the empty file so NewRequest sets "Content-Length: 0", - // as opposed to thinking we didn't set it and not being able to sniff its size - // from the type. - diskReader, httpReader = bytes.NewReader(nil), bytes.NewReader(nil) - } else { - pr, pw := io.Pipe() - defer pw.Close() - // The diskReader is in the driving seat. We will try to forward data - // to httpReader as well, but only best-effort. - diskReader = tee - tee.w = pw - httpReader = pr + if c.remote == nil { + return c.disk.Put(ctx, actionID, outputID, size, r) } - httpErrCh := make(chan error) - go func() { - t0HTTP := time.Now() - defer func() { - c.putHTTPNanos.Add(time.Since(t0HTTP).Nanoseconds()) - }() - httpErrCh <- c.gocached.put(ctx, actionID, outputID, size, httpReader) - }() - diskPath, err = put(c.disk, actionID, outputID, size, diskReader) + c.putHTTP.Add(1) + diskPath, err = c.remote.Put(ctx, actionID, outputID, size, r) + c.putHTTPNanos.Add(time.Since(t0).Nanoseconds()) if err != nil { - return "", fmt.Errorf("error writing to disk cache: %w", errors.Join(err, tee.err)) - } - - select { - case err := <-httpErrCh: - if err != nil { - c.putHTTPErrors.Add(1) - } else { - c.putHTTPBytes.Add(size) - } - case <-ctx.Done(): + c.putHTTPErrors.Add(1) + } else { + c.putHTTPBytes.Add(size) } - return diskPath, nil + return diskPath, err } func (c *cigocacher) close() error { - if !c.verbose || c.gocached == nil { + if !c.verbose || c.remote == nil { return nil } @@ -307,7 +264,7 @@ func (c *cigocacher) close() error { c.getHTTP.Load(), float64(c.getHTTPBytes.Load())/float64(1<<20), float64(c.getHTTPNanos.Load())/float64(time.Second), c.getHTTPHits.Load(), c.getHTTPMisses.Load(), c.getHTTPErrors.Load(), c.putHTTP.Load(), float64(c.putHTTPBytes.Load())/float64(1<<20), float64(c.putHTTPNanos.Load())/float64(time.Second), c.putHTTPErrors.Load()) - stats, err := c.gocached.fetchStats() + stats, err := fetchStats(c.remote.HTTPClient, c.remote.BaseURL, c.remote.AccessToken) if err != nil { log.Printf("error fetching gocached stats: %v", err) } else { @@ -354,19 +311,20 @@ func fetchAccessToken(cl *http.Client, idTokenURL, idTokenRequestToken, gocached return accessToken.AccessToken, nil } -type bestEffortTeeReader struct { - r io.Reader - w io.WriteCloser - err error -} - -func (t *bestEffortTeeReader) Read(p []byte) (int, error) { - n, err := t.r.Read(p) - if n > 0 && t.w != nil { - if _, err := t.w.Write(p[:n]); err != nil { - t.err = errors.Join(err, t.w.Close()) - t.w = nil - } +func fetchStats(cl *http.Client, baseURL, accessToken string) (string, error) { + req, _ := http.NewRequest("GET", baseURL+"/session/stats", nil) + req.Header.Set("Authorization", "Bearer "+accessToken) + resp, err := cl.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("fetching stats: %s", resp.Status) + } + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", err } - return n, err + return string(b), nil } diff --git a/cmd/cigocacher/disk.go b/cmd/cigocacher/disk.go deleted file mode 100644 index e04dac0509300..0000000000000 --- a/cmd/cigocacher/disk.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Tailscale Inc & contributors -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "log" - "os" - "path/filepath" - "time" - - "github.com/bradfitz/go-tool-cache/cachers" -) - -// indexEntry is the metadata that DiskCache stores on disk for an ActionID. -type indexEntry struct { - Version int `json:"v"` - OutputID string `json:"o"` - Size int64 `json:"n"` - TimeNanos int64 `json:"t"` -} - -func validHex(x string) bool { - if len(x) < 4 || len(x) > 100 { - return false - } - for _, b := range x { - if b >= '0' && b <= '9' || b >= 'a' && b <= 'f' { - continue - } - return false - } - return true -} - -// put is like dc.Put but refactored to support safe concurrent writes on Windows. -// TODO(tomhjp): upstream these changes to go-tool-cache once they look stable. -func put(dc *cachers.DiskCache, actionID, outputID string, size int64, body io.Reader) (diskPath string, _ error) { - if len(actionID) < 4 || len(outputID) < 4 { - return "", fmt.Errorf("actionID and outputID must be at least 4 characters long") - } - if !validHex(actionID) { - log.Printf("diskcache: got invalid actionID %q", actionID) - return "", errors.New("actionID must be hex") - } - if !validHex(outputID) { - log.Printf("diskcache: got invalid outputID %q", outputID) - return "", errors.New("outputID must be hex") - } - - actionFile := dc.ActionFilename(actionID) - outputFile := dc.OutputFilename(outputID) - actionDir := filepath.Dir(actionFile) - outputDir := filepath.Dir(outputFile) - - if err := os.MkdirAll(actionDir, 0755); err != nil { - return "", fmt.Errorf("failed to create action directory: %w", err) - } - if err := os.MkdirAll(outputDir, 0755); err != nil { - return "", fmt.Errorf("failed to create output directory: %w", err) - } - - wrote, err := writeOutputFile(outputFile, body, size, outputID) - if err != nil { - return "", err - } - if wrote != size { - return "", fmt.Errorf("wrote %d bytes, expected %d", wrote, size) - } - - ij, err := json.Marshal(indexEntry{ - Version: 1, - OutputID: outputID, - Size: size, - TimeNanos: time.Now().UnixNano(), - }) - if err != nil { - return "", err - } - if err := writeActionFile(dc.ActionFilename(actionID), ij); err != nil { - return "", fmt.Errorf("atomic write failed: %w", err) - } - return outputFile, nil -} diff --git a/cmd/cigocacher/disk_notwindows.go b/cmd/cigocacher/disk_notwindows.go deleted file mode 100644 index 353b734ab9ce7..0000000000000 --- a/cmd/cigocacher/disk_notwindows.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Tailscale Inc & contributors -// SPDX-License-Identifier: BSD-3-Clause - -//go:build !windows - -package main - -import ( - "bytes" - "io" - "os" - "path/filepath" -) - -func writeActionFile(dest string, b []byte) error { - _, err := writeAtomic(dest, bytes.NewReader(b)) - return err -} - -func writeOutputFile(dest string, r io.Reader, _ int64, _ string) (int64, error) { - return writeAtomic(dest, r) -} - -func writeAtomic(dest string, r io.Reader) (int64, error) { - tf, err := os.CreateTemp(filepath.Dir(dest), filepath.Base(dest)+".*") - if err != nil { - return 0, err - } - size, err := io.Copy(tf, r) - if err != nil { - tf.Close() - os.Remove(tf.Name()) - return 0, err - } - if err := tf.Close(); err != nil { - os.Remove(tf.Name()) - return 0, err - } - if err := os.Rename(tf.Name(), dest); err != nil { - os.Remove(tf.Name()) - return 0, err - } - return size, nil -} diff --git a/cmd/cigocacher/disk_windows.go b/cmd/cigocacher/disk_windows.go deleted file mode 100644 index 686bcf2b0d68b..0000000000000 --- a/cmd/cigocacher/disk_windows.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Tailscale Inc & contributors -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "crypto/sha256" - "errors" - "fmt" - "io" - "os" -) - -// The functions in this file are based on go's own cache in -// cmd/go/internal/cache/cache.go, particularly putIndexEntry and copyFile. - -// writeActionFile writes the indexEntry metadata for an ActionID to disk. It -// may be called for the same actionID concurrently from multiple processes, -// and the outputID for a specific actionID may change from time to time due -// to non-deterministic builds. It makes a best-effort to delete the file if -// anything goes wrong. -func writeActionFile(dest string, b []byte) (retErr error) { - f, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, 0o666) - if err != nil { - return err - } - defer func() { - cerr := f.Close() - if retErr != nil || cerr != nil { - retErr = errors.Join(retErr, cerr, os.Remove(dest)) - } - }() - - _, err = f.Write(b) - if err != nil { - return err - } - - // Truncate the file only *after* writing it. - // (This should be a no-op, but truncate just in case of previous corruption.) - // - // This differs from os.WriteFile, which truncates to 0 *before* writing - // via os.O_TRUNC. Truncating only after writing ensures that a second write - // of the same content to the same file is idempotent, and does not - even - // temporarily! - undo the effect of the first write. - return f.Truncate(int64(len(b))) -} - -// writeOutputFile writes content to be cached to disk. The outputID is the -// sha256 hash of the content, and each file should only be written ~once, -// assuming no sha256 hash collisions. It may be written multiple times if -// concurrent processes are both populating the same output. The file is opened -// with FILE_SHARE_READ|FILE_SHARE_WRITE, which means both processes can write -// the same contents concurrently without conflict. -// -// It makes a best effort to clean up if anything goes wrong, but the file may -// be left in an inconsistent state in the event of disk-related errors such as -// another process taking file locks, or power loss etc. -func writeOutputFile(dest string, r io.Reader, size int64, outputID string) (_ int64, retErr error) { - info, err := os.Stat(dest) - if err == nil && info.Size() == size { - // Already exists, check the hash. - if f, err := os.Open(dest); err == nil { - h := sha256.New() - io.Copy(h, f) - f.Close() - if fmt.Sprintf("%x", h.Sum(nil)) == outputID { - // Still drain the reader to ensure associated resources are released. - return io.Copy(io.Discard, r) - } - } - } - - // Didn't successfully find the pre-existing file, write it. - mode := os.O_WRONLY | os.O_CREATE - if err == nil && info.Size() > size { - mode |= os.O_TRUNC // Should never happen, but self-heal. - } - f, err := os.OpenFile(dest, mode, 0644) - if err != nil { - return 0, fmt.Errorf("failed to open output file %q: %w", dest, err) - } - defer func() { - cerr := f.Close() - if retErr != nil || cerr != nil { - retErr = errors.Join(retErr, cerr, os.Remove(dest)) - } - }() - - // Copy file to f, but also into h to double-check hash. - h := sha256.New() - w := io.MultiWriter(f, h) - n, err := io.Copy(w, r) - if err != nil { - return 0, err - } - if fmt.Sprintf("%x", h.Sum(nil)) != outputID { - return 0, errors.New("file content changed underfoot") - } - - return n, nil -} diff --git a/cmd/cigocacher/http.go b/cmd/cigocacher/http.go deleted file mode 100644 index 16d0ae899acbc..0000000000000 --- a/cmd/cigocacher/http.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Tailscale Inc & contributors -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "context" - "fmt" - "io" - "log" - "net/http" -) - -type gocachedClient struct { - baseURL string // base URL of the cacher server, like "http://localhost:31364". - cl *http.Client // http.Client to use. - accessToken string // Bearer token to use in the Authorization header. - verbose bool -} - -// drainAndClose reads and throws away a small bounded amount of data. This is a -// best-effort attempt to allow connection reuse; Go's HTTP/1 Transport won't -// reuse a TCP connection unless you fully consume HTTP responses. -func drainAndClose(body io.ReadCloser) { - io.CopyN(io.Discard, body, 4<<10) - body.Close() -} - -func tryReadErrorMessage(res *http.Response) []byte { - msg, _ := io.ReadAll(io.LimitReader(res.Body, 4<<10)) - return msg -} - -func (c *gocachedClient) get(ctx context.Context, actionID string) (outputID string, resp *http.Response, err error) { - req, _ := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/action/"+actionID, nil) - req.Header.Set("Want-Object", "1") // opt in to single roundtrip protocol - if c.accessToken != "" { - req.Header.Set("Authorization", "Bearer "+c.accessToken) - } - - res, err := c.cl.Do(req) - if err != nil { - return "", nil, err - } - defer func() { - if resp == nil { - drainAndClose(res.Body) - } - }() - if res.StatusCode == http.StatusNotFound { - return "", nil, nil - } - if res.StatusCode != http.StatusOK { - msg := tryReadErrorMessage(res) - if c.verbose { - log.Printf("error GET /action/%s: %v, %s", actionID, res.Status, msg) - } - return "", nil, fmt.Errorf("unexpected GET /action/%s status %v", actionID, res.Status) - } - - outputID = res.Header.Get("Go-Output-Id") - if outputID == "" { - return "", nil, fmt.Errorf("missing Go-Output-Id header in response") - } - if res.ContentLength == -1 { - return "", nil, fmt.Errorf("no Content-Length from server") - } - return outputID, res, nil -} - -func (c *gocachedClient) put(ctx context.Context, actionID, outputID string, size int64, body io.Reader) error { - req, _ := http.NewRequestWithContext(ctx, "PUT", c.baseURL+"/"+actionID+"/"+outputID, body) - req.ContentLength = size - if c.accessToken != "" { - req.Header.Set("Authorization", "Bearer "+c.accessToken) - } - res, err := c.cl.Do(req) - if err != nil { - if c.verbose { - log.Printf("error PUT /%s/%s: %v", actionID, outputID, err) - } - return err - } - defer res.Body.Close() - if res.StatusCode != http.StatusNoContent { - msg := tryReadErrorMessage(res) - if c.verbose { - log.Printf("error PUT /%s/%s: %v, %s", actionID, outputID, res.Status, msg) - } - return fmt.Errorf("unexpected PUT /%s/%s status %v", actionID, outputID, res.Status) - } - - return nil -} - -func (c *gocachedClient) fetchStats() (string, error) { - req, _ := http.NewRequest("GET", c.baseURL+"/session/stats", nil) - req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.cl.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - return string(b), nil -} diff --git a/flake.nix b/flake.nix index bbd1f8b48be0c..6fc0ff28a906f 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-e5fAO7gye8B5FGBTxLNVTKq6dp8By9iDEw72M1/y4ZE= +# nix-direnv cache busting line: sha256-JD1PZPZT5clhRWIAQO8skBRN59QPiyfTc7nPYTvGbd8= diff --git a/go.mod b/go.mod index bc356a19c9b59..7b062afbf4ccf 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.75.3 github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 - github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd + github.com/bradfitz/go-tool-cache v0.0.0-20260216153636-9e5201344fe5 github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032 github.com/bramvdbogaerde/go-scp v1.4.0 github.com/cilium/ebpf v0.16.0 @@ -419,7 +419,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pierrec/lz4/v4 v4.1.25 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/go.mod.sri b/go.mod.sri index de11cbc71e1b7..e5d18033ae976 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-e5fAO7gye8B5FGBTxLNVTKq6dp8By9iDEw72M1/y4ZE= +sha256-JD1PZPZT5clhRWIAQO8skBRN59QPiyfTc7nPYTvGbd8= diff --git a/go.sum b/go.sum index c924e5e6e1a12..299fe95cd84ad 100644 --- a/go.sum +++ b/go.sum @@ -202,8 +202,8 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd h1:1Df3FBmfyUCIQ4eKzAPXIWTfewY89L0fWPWO56zWCyI= -github.com/bradfitz/go-tool-cache v0.0.0-20251113223507-0124e698e0bd/go.mod h1:2+xptBAd0m2kZ1wLO4AYZhldLEFPy+KeGwmnlXLvy+w= +github.com/bradfitz/go-tool-cache v0.0.0-20260216153636-9e5201344fe5 h1:0sG3c7afYdBNlc3QyhckvZ4bV9iqlfqCQM1i+mWm0eE= +github.com/bradfitz/go-tool-cache v0.0.0-20260216153636-9e5201344fe5/go.mod h1:78ZLITnBUCDJeU01+wYYJKaPYYgsDzJPRfxeI8qFh5g= github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032 h1:xDomVqO85ss/98Ky5zxM/g86bXDNBLebM2I9G/fu6uA= github.com/bradfitz/monogok v0.0.0-20260208031948-2219c393d032/go.mod h1:TG1HbU9fRVDnNgXncVkKz9GdvjIvqquXjH6QZSEVmY4= github.com/bramvdbogaerde/go-scp v1.4.0 h1:jKMwpwCbcX1KyvDbm/PDJuXcMuNVlLGi0Q0reuzjyKY= @@ -935,8 +935,8 @@ github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkM github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= +github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= diff --git a/shell.nix b/shell.nix index 0c51f59c00d3b..9fab641722dca 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-e5fAO7gye8B5FGBTxLNVTKq6dp8By9iDEw72M1/y4ZE= +# nix-direnv cache busting line: sha256-JD1PZPZT5clhRWIAQO8skBRN59QPiyfTc7nPYTvGbd8= From 4044e05dfdd6445a2baabea2c35e34b49c1d37df Mon Sep 17 00:00:00 2001 From: Will Norris Date: Mon, 16 Feb 2026 11:41:53 -0800 Subject: [PATCH 110/202] client/systray: set consistent ID for StatusNotifierItem Fixes #18736 Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris --- client/systray/systray.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/systray/systray.go b/client/systray/systray.go index 8c30dbf05ef3e..7018f0f3be2be 100644 --- a/client/systray/systray.go +++ b/client/systray/systray.go @@ -171,6 +171,11 @@ tailscale systray See https://tailscale.com/kb/1597/linux-systray for more information.`) } setAppIcon(disconnected) + + // set initial title, which is used by the systray package as the ID of the StatusNotifierItem. + // This value will get overwritten later as the client status changes. + systray.SetTitle("tailscale") + menu.rebuild() menu.mu.Lock() From a8204568d88897292d7146d3ceda03071f6067fb Mon Sep 17 00:00:00 2001 From: Will Norris Date: Thu, 16 Feb 2023 10:46:52 -0800 Subject: [PATCH 111/202] all: replace UserVisibleError with vizerror package Updates tailscale/corp#9025 Signed-off-by: Will Norris --- cmd/tailscaled/depaware.txt | 6 +++--- control/controlclient/client.go | 6 ------ control/controlclient/direct.go | 3 ++- ipn/ipnlocal/local.go | 6 +++--- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 71a1df1d4c6c2..7f2e6f7681751 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -150,7 +150,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/klauspost/compress from github.com/klauspost/compress/zstd github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0 github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd - github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/huff0+ + github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+ 💣 github.com/klauspost/compress/internal/le from github.com/klauspost/compress/huff0+ github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd from tailscale.com/util/zstdframe @@ -472,7 +472,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/testenv from tailscale.com/ipn/ipnlocal+ tailscale.com/util/truncate from tailscale.com/logtail tailscale.com/util/usermetric from tailscale.com/health+ - tailscale.com/util/vizerror from tailscale.com/tailcfg+ + tailscale.com/util/vizerror from tailscale.com/tsweb+ 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+ W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate+ W 💣 tailscale.com/util/winutil/gp from tailscale.com/net/dns+ @@ -480,7 +480,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+ tailscale.com/util/zstdframe from tailscale.com/control/controlclient+ tailscale.com/version from tailscale.com/client/web+ - tailscale.com/version/distro from tailscale.com/client/web+ + tailscale.com/version/distro from tailscale.com/hostinfo+ W tailscale.com/wf from tailscale.com/cmd/tailscaled tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ diff --git a/control/controlclient/client.go b/control/controlclient/client.go index 3bc53ed5a24fc..a57c6940a88c4 100644 --- a/control/controlclient/client.go +++ b/control/controlclient/client.go @@ -91,9 +91,3 @@ type Client interface { // distinguish one client from another. ClientID() int64 } - -// UserVisibleError is an error that should be shown to users. -type UserVisibleError string - -func (e UserVisibleError) Error() string { return string(e) } -func (e UserVisibleError) UserVisibleError() string { return string(e) } diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index a368d6f858384..6f3393b18dfdf 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -59,6 +59,7 @@ import ( "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/testenv" + "tailscale.com/util/vizerror" "tailscale.com/util/zstdframe" ) @@ -743,7 +744,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new resp.NodeKeyExpired, resp.MachineAuthorized, resp.AuthURL != "") if resp.Error != "" { - return false, "", nil, UserVisibleError(resp.Error) + return false, "", nil, vizerror.New(resp.Error) } if len(resp.NodeKeySignature) > 0 { return true, "", resp.NodeKeySignature, nil diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index e9222cde35b50..bf0651ac97bd1 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -99,6 +99,7 @@ import ( "tailscale.com/util/syspolicy/ptype" "tailscale.com/util/testenv" "tailscale.com/util/usermetric" + "tailscale.com/util/vizerror" "tailscale.com/version" "tailscale.com/version/distro" "tailscale.com/wgengine" @@ -1583,9 +1584,8 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control return } b.logf("Received error: %v", st.Err) - var uerr controlclient.UserVisibleError - if errors.As(st.Err, &uerr) { - s := uerr.UserVisibleError() + if vizerr, ok := vizerror.As(st.Err); ok { + s := vizerr.Error() b.sendLocked(ipn.Notify{ErrMessage: &s}) } return From a6390ca008b580ef41e65d10bb1dfc811ebf3aa9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 13 Feb 2026 05:07:45 +0000 Subject: [PATCH 112/202] ssh/tailssh: fix data race on conn auth state in OnPolicyChange OnPolicyChange can observe a conn in activeConns before authentication completes. The previous `c.info == nil` guard was itself a data race against clientAuth writing c.info, and even when c.info appeared non-nil, c.localUser could still be nil, causing a nil pointer dereference at c.localUser.Username. Add an authCompleted atomic.Bool to conn, stored true after all auth fields are written in clientAuth. OnPolicyChange checks this atomic instead of c.info, which provides the memory barrier guaranteeing all prior writes are visible to the concurrent reader. Updates tailscale/corp#36268 (fixes, but we might want to cherry-pick) Co-authored-by: Gesa Stupperich Change-Id: I4c69843541f5f9f04add9bf431e320c65a203a39 Signed-off-by: Brad Fitzpatrick --- ssh/tailssh/tailssh.go | 21 +++++++++-- ssh/tailssh/tailssh_test.go | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 9d5a7d2a880db..cb56f701b5e68 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -192,9 +192,9 @@ func (srv *server) OnPolicyChange() { srv.mu.Lock() defer srv.mu.Unlock() for c := range srv.activeConns { - if c.info == nil { - // c.info is nil when the connection hasn't been authenticated yet. - // In that case, the connection will be terminated when it is. + if !c.authCompleted.Load() { + // The connection hasn't completed authentication yet. + // In that case, the connection will be terminated when it does. continue } go c.checkStillValid() @@ -236,14 +236,26 @@ type conn struct { // Banners cannot be sent after auth completes. spac gossh.ServerPreAuthConn + // The following fields are set during clientAuth and are used for policy + // evaluation and session management. They are immutable after clientAuth + // completes. They must not be read from other goroutines until + // authCompleted is set to true. + action0 *tailcfg.SSHAction // set by clientAuth finalAction *tailcfg.SSHAction // set by clientAuth - info *sshConnInfo // set by setInfo + info *sshConnInfo // set by setInfo (during clientAuth) localUser *userMeta // set by clientAuth userGroupIDs []string // set by clientAuth acceptEnv []string + // authCompleted is set to true after clientAuth has finished writing + // all authentication state fields (info, localUser, action0, + // finalAction, userGroupIDs, acceptEnv). It provides a memory + // barrier so that concurrent readers (e.g. OnPolicyChange) see + // fully-initialized values. + authCompleted atomic.Bool + // mu protects the following fields. // // srv.mu should be acquired prior to mu. @@ -369,6 +381,7 @@ func (c *conn) clientAuth(cm gossh.ConnMetadata) (perms *gossh.Permissions, retE } } c.finalAction = action + c.authCompleted.Store(true) return &gossh.Permissions{}, nil case action.Reject: metricTerminalReject.Add(1) diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index 44db0cc000beb..6d9d859a22d91 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -31,6 +31,7 @@ import ( "sync" "sync/atomic" "testing" + "testing/synctest" "time" gossh "golang.org/x/crypto/ssh" @@ -1111,6 +1112,7 @@ func TestSSH(t *testing.T) { } sc.action0 = &tailcfg.SSHAction{Accept: true} sc.finalAction = sc.action0 + sc.authCompleted.Store(true) sc.Handler = func(s ssh.Session) { sc.newSSHSession(s).run() @@ -1320,6 +1322,79 @@ func TestStdOsUserUserAssumptions(t *testing.T) { } } +func TestOnPolicyChangeSkipsPreAuthConns(t *testing.T) { + tests := []struct { + name string + sshRule *tailcfg.SSHRule + wantCancel bool + }{ + { + name: "accept-after-auth", + sshRule: newSSHRule(&tailcfg.SSHAction{Accept: true}), + wantCancel: false, + }, + { + name: "reject-after-auth", + sshRule: newSSHRule(&tailcfg.SSHAction{Reject: true}), + wantCancel: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + srv := &server{ + logf: tstest.WhileTestRunningLogger(t), + lb: &localState{ + sshEnabled: true, + matchingRule: tt.sshRule, + }, + } + c := &conn{ + srv: srv, + info: &sshConnInfo{ + sshUser: "alice", + src: netip.MustParseAddrPort("1.2.3.4:30343"), + dst: netip.MustParseAddrPort("100.100.100.102:22"), + }, + localUser: &userMeta{User: user.User{Username: currentUser}}, + } + srv.activeConns = map[*conn]bool{c: true} + ctx, cancel := context.WithCancelCause(context.Background()) + ss := &sshSession{ctx: ctx, cancelCtx: cancel} + c.sessions = []*sshSession{ss} + + // Before authCompleted is set, OnPolicyChange should skip + // the conn entirely — no goroutine spawned. + srv.OnPolicyChange() + synctest.Wait() + select { + case <-ctx.Done(): + t.Fatal("session canceled before auth completed") + default: + } + + // Mark auth as completed. Now OnPolicyChange should + // evaluate the policy and act accordingly. + c.authCompleted.Store(true) + + srv.OnPolicyChange() + synctest.Wait() + select { + case <-ctx.Done(): + if !tt.wantCancel { + t.Fatal("valid session should not have been canceled") + } + default: + if tt.wantCancel { + t.Fatal("invalid session should have been canceled") + } + } + }) + }) + } +} + func mockRecordingServer(t *testing.T, handleRecord http.HandlerFunc) *httptest.Server { t.Helper() mux := http.NewServeMux() From a7a864419d3238756c4c15a532408fa475c9f992 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 16 Feb 2026 18:56:51 -1000 Subject: [PATCH 113/202] net/dns: make MagicDNS IPv6 registration opt-out now, not opt-in This adds a new ControlKnob to make MagicDNS IPv6 registration (telling systemd/etc) opt-out rather than opt-in. Updates #15404 Change-Id: If008e1cb046b792c6aff7bb1d7c58638f7d650b1 Signed-off-by: Brad Fitzpatrick --- control/controlknobs/controlknobs.go | 16 +++++ net/dns/config.go | 8 +-- net/dns/manager_tcp_test.go | 4 +- net/dns/manager_test.go | 88 +++++++++++++++++++--------- tailcfg/tailcfg.go | 9 ++- 5 files changed, 91 insertions(+), 34 deletions(-) diff --git a/control/controlknobs/controlknobs.go b/control/controlknobs/controlknobs.go index 0f85e82368dd9..1861a122e2f9e 100644 --- a/control/controlknobs/controlknobs.go +++ b/control/controlknobs/controlknobs.go @@ -113,6 +113,14 @@ type Knobs struct { // resolver on Windows or when the host is domain-joined and its primary domain // takes precedence over MagicDNS. As of 2026-02-13, it is only used on Windows. DisableHostsFileUpdates atomic.Bool + + // ForceRegisterMagicDNSIPv4Only is whether the node should only register + // its IPv4 MagicDNS service IP and not its IPv6 one. The IPv6 one, + // tsaddr.TailscaleServiceIPv6String, still works in either case. This knob + // controls only whether we tell systemd/etc about the IPv6 one. + // See https://github.com/tailscale/tailscale/issues/15404. + // TODO(bradfitz): remove this a few releases after 2026-02-16. + ForceRegisterMagicDNSIPv4Only atomic.Bool } // UpdateFromNodeAttributes updates k (if non-nil) based on the provided self @@ -144,6 +152,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { disableCaptivePortalDetection = has(tailcfg.NodeAttrDisableCaptivePortalDetection) disableSkipStatusQueue = has(tailcfg.NodeAttrDisableSkipStatusQueue) disableHostsFileUpdates = has(tailcfg.NodeAttrDisableHostsFileUpdates) + forceRegisterMagicDNSIPv4Only = has(tailcfg.NodeAttrForceRegisterMagicDNSIPv4Only) ) if has(tailcfg.NodeAttrOneCGNATEnable) { @@ -171,6 +180,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) { k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection) k.DisableSkipStatusQueue.Store(disableSkipStatusQueue) k.DisableHostsFileUpdates.Store(disableHostsFileUpdates) + k.ForceRegisterMagicDNSIPv4Only.Store(forceRegisterMagicDNSIPv4Only) // If both attributes are present, then "enable" should win. This reflects // the history of seamless key renewal. @@ -210,3 +220,9 @@ func (k *Knobs) AsDebugJSON() map[string]any { } return ret } + +// ShouldForceRegisterMagicDNSIPv4Only reports the value of +// ForceRegisterMagicDNSIPv4Only, or false if k is nil. +func (k *Knobs) ShouldForceRegisterMagicDNSIPv4Only() bool { + return k != nil && k.ForceRegisterMagicDNSIPv4Only.Load() +} diff --git a/net/dns/config.go b/net/dns/config.go index 47fac83c2df48..0b09fe1a8f609 100644 --- a/net/dns/config.go +++ b/net/dns/config.go @@ -73,11 +73,9 @@ func (c *Config) serviceIPs(knobs *controlknobs.Knobs) []netip.Addr { return []netip.Addr{tsaddr.TailscaleServiceIPv6()} } - // TODO(bradfitz,mikeodr,raggi): include IPv6 here too; tailscale/tailscale#15404 - // And add a controlknobs knob to disable dual stack. - // - // For now, opt-in for testing. - if magicDNSDualStack() { + // See https://github.com/tailscale/tailscale/issues/15404 for the background + // on the opt-in debug knob and the controlknob opt-out. + if magicDNSDualStack() || !knobs.ShouldForceRegisterMagicDNSIPv4Only() { return []netip.Addr{ tsaddr.TailscaleServiceIP(), tsaddr.TailscaleServiceIPv6(), diff --git a/net/dns/manager_tcp_test.go b/net/dns/manager_tcp_test.go index bdd5cc7bb314b..67d6d15cd42ed 100644 --- a/net/dns/manager_tcp_test.go +++ b/net/dns/manager_tcp_test.go @@ -15,6 +15,7 @@ import ( "github.com/google/go-cmp/cmp" dns "golang.org/x/net/dns/dnsmessage" + "tailscale.com/control/controlknobs" "tailscale.com/health" "tailscale.com/net/netmon" "tailscale.com/net/tsdial" @@ -93,7 +94,8 @@ func TestDNSOverTCP(t *testing.T) { bus := eventbustest.NewBus(t) dialer := tsdial.NewDialer(netmon.NewStatic()) dialer.SetBus(bus) - m := NewManager(t.Logf, &f, health.NewTracker(bus), dialer, nil, nil, "", bus) + cknobs := &controlknobs.Knobs{} + m := NewManager(t.Logf, &f, health.NewTracker(bus), dialer, nil, cknobs, "", bus) m.resolver.TestOnlySetHook(f.SetResolver) m.Set(Config{ Hosts: hosts( diff --git a/net/dns/manager_test.go b/net/dns/manager_test.go index cf0c2458e395f..8a67aca5cd545 100644 --- a/net/dns/manager_test.go +++ b/net/dns/manager_test.go @@ -28,6 +28,7 @@ import ( "tailscale.com/net/dns/publicdns" "tailscale.com/net/dns/resolver" "tailscale.com/net/netmon" + "tailscale.com/net/tsaddr" "tailscale.com/net/tsdial" "tailscale.com/tstest" "tailscale.com/types/dnstype" @@ -172,6 +173,8 @@ func TestCompileHostEntries(t *testing.T) { } } +var serviceAddr46 = []netip.Addr{tsaddr.TailscaleServiceIP(), tsaddr.TailscaleServiceIPv6()} + func TestManager(t *testing.T) { if runtime.GOOS == "windows" { t.Skipf("test's assumptions break because of https://github.com/tailscale/corp/issues/1662") @@ -189,6 +192,7 @@ func TestManager(t *testing.T) { split bool bs OSConfig os OSConfig + knobs *controlknobs.Knobs rs resolver.Config goos string // empty means "linux" }{ @@ -231,7 +235,7 @@ func TestManager(t *testing.T) { "bar.tld.", "2.3.4.5"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, }, rs: resolver.Config{ Hosts: hosts( @@ -317,7 +321,7 @@ func TestManager(t *testing.T) { "bradfitz.ts.com.", "2.3.4.5"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -340,7 +344,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -359,7 +363,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("tailscale.com", "universe.tf"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -377,7 +381,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -386,6 +390,33 @@ func TestManager(t *testing.T) { "corp.com.", "2.2.2.2"), }, }, + { + name: "controlknob-disable-v6-registration", + in: Config{ + DefaultResolvers: mustRes("1.1.1.1", "9.9.9.9"), + SearchDomains: fqdns("tailscale.com", "universe.tf"), + Routes: upstreams("ts.com", ""), + Hosts: hosts( + "dave.ts.com.", "1.2.3.4", + "bradfitz.ts.com.", "2.3.4.5"), + }, + knobs: (func() *controlknobs.Knobs { + k := new(controlknobs.Knobs) + k.ForceRegisterMagicDNSIPv4Only.Store(true) + return k + })(), + os: OSConfig{ + Nameservers: mustIPs("100.100.100.100"), // without IPv6 + SearchDomains: fqdns("tailscale.com", "universe.tf"), + }, + rs: resolver.Config{ + Routes: upstreams(".", "1.1.1.1", "9.9.9.9"), + Hosts: hosts( + "dave.ts.com.", "1.2.3.4", + "bradfitz.ts.com.", "2.3.4.5"), + LocalDomains: fqdns("ts.com."), + }, + }, { name: "routes", in: Config{ @@ -397,7 +428,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -432,7 +463,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -452,7 +483,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), MatchDomains: fqdns("bigco.net", "corp.com"), }, @@ -477,7 +508,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -502,7 +533,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -527,7 +558,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -549,7 +580,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), MatchDomains: fqdns("ts.com"), }, @@ -575,7 +606,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -601,7 +632,7 @@ func TestManager(t *testing.T) { }, split: false, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -627,7 +658,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("coffee.shop"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"), }, rs: resolver.Config{ @@ -653,7 +684,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), MatchDomains: fqdns("corp.com", "ts.com"), }, @@ -683,7 +714,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -715,7 +746,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -740,7 +771,7 @@ func TestManager(t *testing.T) { SearchDomains: fqdns("tailscale.com", "universe.tf"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("tailscale.com", "universe.tf"), }, rs: resolver.Config{ @@ -768,7 +799,7 @@ func TestManager(t *testing.T) { DefaultResolvers: mustRes("2a07:a8c0::c3:a884"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, }, rs: resolver.Config{ Routes: upstreams(".", "2a07:a8c0::c3:a884"), @@ -780,7 +811,7 @@ func TestManager(t *testing.T) { DefaultResolvers: mustRes("https://dns.nextdns.io/c3a884"), }, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, }, rs: resolver.Config{ Routes: upstreams(".", "https://dns.nextdns.io/c3a884"), @@ -796,7 +827,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("optimistic-display.ts.net"), MatchDomains: fqdns("ts.net"), }, @@ -821,7 +852,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("optimistic-display.ts.net"), }, rs: resolver.Config{ @@ -844,7 +875,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("optimistic-display.ts.net"), }, rs: resolver.Config{ @@ -885,7 +916,7 @@ func TestManager(t *testing.T) { }, }, }, - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("ts.com", "universe.tf"), MatchDomains: fqdns("corp.com", "ts.com"), }, @@ -912,7 +943,7 @@ func TestManager(t *testing.T) { }, split: true, os: OSConfig{ - Nameservers: mustIPs("100.100.100.100"), + Nameservers: serviceAddr46, SearchDomains: fqdns("ts.com", "universe.tf"), MatchDomains: fqdns("corp.com", "ts.com"), }, @@ -946,7 +977,10 @@ func TestManager(t *testing.T) { if goos == "" { goos = "linux" } - knobs := &controlknobs.Knobs{} + knobs := test.knobs + if knobs == nil { + knobs = &controlknobs.Knobs{} + } bus := eventbustest.NewBus(t) dialer := tsdial.NewDialer(netmon.NewStatic()) dialer.SetBus(bus) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 69ca20a947735..b49791be6fb39 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -179,7 +179,8 @@ type CapabilityVersion int // - 130: 2025-10-06: client can send key.HardwareAttestationPublic and key.HardwareAttestationKeySignature in MapRequest // - 131: 2025-11-25: client respects [NodeAttrDefaultAutoUpdate] // - 132: 2026-02-13: client respects [NodeAttrDisableHostsFileUpdates] -const CurrentCapabilityVersion CapabilityVersion = 132 +// - 133: 2026-02-17: client understands [NodeAttrForceRegisterMagicDNSIPv4Only]; MagicDNS IPv6 registered w/ OS by default +const CurrentCapabilityVersion CapabilityVersion = 133 // ID is an integer ID for a user, node, or login allocated by the // control plane. @@ -2748,6 +2749,12 @@ const ( // primary domain takes precedence over MagicDNS. As of 2026-02-12, it is only // used on Windows. NodeAttrDisableHostsFileUpdates NodeCapability = "disable-hosts-file-updates" + + // NodeAttrForceRegisterMagicDNSIPv4Only forces the client to only register + // its MagicDNS IPv4 address with systemd/etc, and not both its IPv4 and IPv6 addresses. + // See https://github.com/tailscale/tailscale/issues/15404. + // TODO(bradfitz): remove this a few releases after 2026-02-16. + NodeAttrForceRegisterMagicDNSIPv4Only NodeCapability = "force-register-magicdns-ipv4-only" ) // SetDNSRequest is a request to add a DNS record. From fbbf0d6669fe2b305f5bad5dd638e8b5db5c14bc Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Fri, 13 Feb 2026 09:11:15 -0700 Subject: [PATCH 114/202] tsconsensus: fix race condition in TestOnlyTaggedPeersCanBeDialed TestOnlyTaggedPeersCanBeDialed has a race condition: - The test untags ps[2] and waits until ps[0] sees this tag dropped from ps[2] in the netmap. - Later the test tries to dial ps[2] from ps[0] and expects the dial to fail as authorization to dial relies on the presence of the tag, now removed from ps[2]. - However, the authorization layer caches the status used to consult peer tags. When the dial happens before the cache times out, the test fails. - Due to a bug in testcontrol.Server.UpdateNode, which the test uses to remove the tag, netmap updates are not immediately triggered. The test has to wait for the next natural set of netmap updates, which on my machine takes about 22 seconds. As a result, the cache in the authorization layer times out and the test passes. - If one fixes the bug in UpdateNode, then netmap updates happen immediately, the cache is no longer timed out when the dial occurs, and the test fails. Fixes #18720 Updates #18703 Signed-off-by: Harry Harpham --- tsconsensus/authorization.go | 21 +++++++++++++++++++-- tsconsensus/tsconsensus_test.go | 6 ++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/tsconsensus/authorization.go b/tsconsensus/authorization.go index 6261a8f1debb6..017c9e80721b9 100644 --- a/tsconsensus/authorization.go +++ b/tsconsensus/authorization.go @@ -17,6 +17,10 @@ import ( "tailscale.com/util/set" ) +// defaultStatusCacheTimeout is the duration after which cached status will be +// disregarded. See tailscaleStatusGetter.cacheTimeout. +const defaultStatusCacheTimeout = time.Second + type statusGetter interface { getStatus(context.Context) (*ipnstate.Status, error) } @@ -24,6 +28,10 @@ type statusGetter interface { type tailscaleStatusGetter struct { ts *tsnet.Server + // cacheTimeout is used to determine when the cached status should be + // disregarded and a new status fetched. Zero means ignore the cache. + cacheTimeout time.Duration + mu sync.Mutex // protects the following lastStatus *ipnstate.Status lastStatusTime time.Time @@ -40,7 +48,7 @@ func (sg *tailscaleStatusGetter) fetchStatus(ctx context.Context) (*ipnstate.Sta func (sg *tailscaleStatusGetter) getStatus(ctx context.Context) (*ipnstate.Status, error) { sg.mu.Lock() defer sg.mu.Unlock() - if sg.lastStatus != nil && time.Since(sg.lastStatusTime) < 1*time.Second { + if sg.lastStatus != nil && time.Since(sg.lastStatusTime) < sg.cacheTimeout { return sg.lastStatus, nil } status, err := sg.fetchStatus(ctx) @@ -61,14 +69,23 @@ type authorization struct { } func newAuthorization(ts *tsnet.Server, tag string) *authorization { + return newAuthorizationWithCacheTimeout(ts, tag, defaultStatusCacheTimeout) +} + +func newAuthorizationWithCacheTimeout(ts *tsnet.Server, tag string, cacheTimeout time.Duration) *authorization { return &authorization{ sg: &tailscaleStatusGetter{ - ts: ts, + ts: ts, + cacheTimeout: cacheTimeout, }, tag: tag, } } +func newAuthorizationForTest(ts *tsnet.Server, tag string) *authorization { + return newAuthorizationWithCacheTimeout(ts, tag, 0) +} + func (a *authorization) Refresh(ctx context.Context) error { tStatus, err := a.sg.getStatus(ctx) if err != nil { diff --git a/tsconsensus/tsconsensus_test.go b/tsconsensus/tsconsensus_test.go index 2199a0c6b9441..8897db119c467 100644 --- a/tsconsensus/tsconsensus_test.go +++ b/tsconsensus/tsconsensus_test.go @@ -642,7 +642,7 @@ func TestOnlyTaggedPeersCanBeDialed(t *testing.T) { // make a StreamLayer for ps[0] ts := ps[0].ts - auth := newAuthorization(ts, clusterTag) + auth := newAuthorizationForTest(ts, clusterTag) port := 19841 lns := make([]net.Listener, 3) @@ -692,10 +692,12 @@ func TestOnlyTaggedPeersCanBeDialed(t *testing.T) { conn.Close() _, err = sl.Dial(a2, 2*time.Second) + if err == nil { + t.Fatal("expected dial error to untagged node, got none") + } if err.Error() != "dial: peer is not allowed" { t.Fatalf("expected dial: peer is not allowed, got: %v", err) } - } func TestOnlyTaggedPeersCanJoin(t *testing.T) { From f4aea70f7a199e5ac155e225fe7ad4de374e9dea Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Tue, 17 Feb 2026 14:51:54 -0800 Subject: [PATCH 115/202] ipn/ipnlocal: add basic support for netmap caching (#18530) This commit is based on ff0978ab, and extends #18497 to connect network map caching to the LocalBackend. As implemented, only "whole" netmap values are stored, and we do not yet handle incremental updates. As-written, the feature must be explicitly enabled via the TS_USE_CACHED_NETMAP envknob, and must be considered experimental. Updates #12639 Co-Authored-by: Brad Fitzpatrick Change-Id: I48a1e92facfbf7fb3a8e67cff7f2c9ab4ed62c83 Signed-off-by: M. J. Fromberger --- cmd/k8s-operator/depaware.txt | 1 + cmd/tailscaled/depaware-min.txt | 1 + cmd/tailscaled/depaware-minbox.txt | 1 + cmd/tailscaled/depaware.txt | 1 + cmd/tsidp/depaware.txt | 1 + ipn/ipnlocal/diskcache.go | 56 ++++++++++++++++ ipn/ipnlocal/local.go | 38 ++++++++--- ipn/ipnlocal/local_test.go | 100 +++++++++++++++++++++++++++++ tsnet/depaware.txt | 1 + 9 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 ipn/ipnlocal/diskcache.go diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index 5565ec01921bb..677891ad71d3f 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -821,6 +821,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ 💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/tsnet tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index b7df3a48a6b1e..f97e0368c0d12 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -71,6 +71,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index ca029194c101e..8dfa00af75a68 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -85,6 +85,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 7f2e6f7681751..aa25fd75f9e9e 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -318,6 +318,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn/ipnext from tailscale.com/ipn/auditlog+ tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver+ diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index 4dfb831b59f43..ae13b20449b41 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -240,6 +240,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar 💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/tsnet tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ diff --git a/ipn/ipnlocal/diskcache.go b/ipn/ipnlocal/diskcache.go new file mode 100644 index 0000000000000..0b1b7b4487bd1 --- /dev/null +++ b/ipn/ipnlocal/diskcache.go @@ -0,0 +1,56 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package ipnlocal + +import ( + "tailscale.com/feature/buildfeatures" + "tailscale.com/ipn/ipnlocal/netmapcache" + "tailscale.com/types/netmap" +) + +// diskCache is the state netmap caching to disk. +type diskCache struct { + // all fields guarded by LocalBackend.mu + + dir string // active profile cache directory + cache *netmapcache.Cache +} + +func (b *LocalBackend) writeNetmapToDiskLocked(nm *netmap.NetworkMap) error { + if !buildfeatures.HasCacheNetMap || nm == nil || nm.Cached { + return nil + } + b.logf("writing netmap to disk cache") + + dir, err := b.profileMkdirAllLocked(b.pm.CurrentProfile().ID(), "netmap-cache") + if err != nil { + return err + } + if c := b.diskCache; c.cache == nil || c.dir != dir { + b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir)) + b.diskCache.dir = dir + } + return b.diskCache.cache.Store(b.currentNode().Context(), nm) +} + +func (b *LocalBackend) loadDiskCacheLocked() (om *netmap.NetworkMap, ok bool) { + if !buildfeatures.HasCacheNetMap { + return nil, false + } + dir, err := b.profileMkdirAllLocked(b.pm.CurrentProfile().ID(), "netmap-cache") + if err != nil { + b.logf("profile data directory: %v", err) + return nil, false + } + if c := b.diskCache; c.cache == nil || c.dir != dir { + b.diskCache.cache = netmapcache.NewCache(netmapcache.FileStore(dir)) + b.diskCache.dir = dir + } + nm, err := b.diskCache.cache.Load(b.currentNode().Context()) + if err != nil { + b.logf("load netmap from cache: %v", err) + return nil, false + } + return nm, true +} diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index bf0651ac97bd1..4221b45e5615a 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -271,6 +271,7 @@ type LocalBackend struct { // of [LocalBackend]'s own state that is not tied to the node context. currentNodeAtomic atomic.Pointer[nodeBackend] + diskCache diskCache conf *conffile.Config // latest parsed config, or nil if not in declarative mode pm *profileManager // mu guards access lastFilterInputs *filterInputs @@ -1573,7 +1574,13 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control } b.mu.Lock() defer b.mu.Unlock() + b.setControlClientStatusLocked(c, st) +} +// setControlClientStatusLocked is the locked version of SetControlClientStatus. +// +// b.mu must be held. +func (b *LocalBackend) setControlClientStatusLocked(c controlclient.Client, st controlclient.Status) { if b.cc != c { b.logf("Ignoring SetControlClientStatus from old client") return @@ -2414,6 +2421,14 @@ func (b *LocalBackend) initOnce() { b.extHost.Init() } +func (b *LocalBackend) controlDebugFlags() []string { + debugFlags := controlDebugFlags + if b.sys.IsNetstackRouter() { + return append([]string{"netstack"}, debugFlags...) + } + return debugFlags +} + // Start applies the configuration specified in opts, and starts the // state machine. // @@ -2570,14 +2585,18 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { persistv = new(persist.Persist) } - discoPublic := b.MagicConn().DiscoPublicKey() - - isNetstack := b.sys.IsNetstackRouter() - debugFlags := controlDebugFlags - if isNetstack { - debugFlags = append([]string{"netstack"}, debugFlags...) + if envknob.Bool("TS_USE_CACHED_NETMAP") { + if nm, ok := b.loadDiskCacheLocked(); ok { + logf("loaded netmap from disk cache; %d peers", len(nm.Peers)) + b.setControlClientStatusLocked(nil, controlclient.Status{ + NetMap: nm, + LoggedIn: true, // sure + }) + } } + discoPublic := b.MagicConn().DiscoPublicKey() + var ccShutdownCbs []func() ccShutdown := func() { for _, cb := range ccShutdownCbs { @@ -2603,7 +2622,7 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { Hostinfo: b.hostInfoWithServicesLocked(), HTTPTestClient: httpTestClient, DiscoPublicKey: discoPublic, - DebugFlags: debugFlags, + DebugFlags: b.controlDebugFlags(), HealthTracker: b.health, PolicyClient: b.sys.PolicyClientOrDefault(), Pinger: b, @@ -2619,7 +2638,7 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error { // Don't warn about broken Linux IP forwarding when // netstack is being used. - SkipIPForwardingCheck: isNetstack, + SkipIPForwardingCheck: b.sys.IsNetstackRouter(), }) if err != nil { return err @@ -6248,6 +6267,9 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { var login string if nm != nil { login = cmp.Or(profileFromView(nm.UserProfiles[nm.User()]).LoginName, "") + if err := b.writeNetmapToDiskLocked(nm); err != nil { + b.logf("write netmap to cache: %v", err) + } } b.currentNode().SetNetMap(nm) if ms, ok := b.sys.MagicSock.GetOK(); ok { diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index cd44acdd1fecf..259e4b6b28a83 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -41,6 +41,7 @@ import ( "tailscale.com/ipn" "tailscale.com/ipn/conffile" "tailscale.com/ipn/ipnauth" + "tailscale.com/ipn/ipnlocal/netmapcache" "tailscale.com/ipn/store/mem" "tailscale.com/net/netcheck" "tailscale.com/net/netmon" @@ -611,6 +612,105 @@ func makeExitNode(id tailcfg.NodeID, opts ...peerOptFunc) tailcfg.NodeView { return makePeer(id, append([]peerOptFunc{withCap(26), withSuggest(), withExitRoutes()}, opts...)...) } +func TestLoadCachedNetMap(t *testing.T) { + t.Setenv("TS_USE_CACHED_NETMAP", "1") + + // Write a small network map into a cache, and verify we can load it. + varRoot := t.TempDir() + cacheDir := filepath.Join(varRoot, "profile-data", "id0", "netmap-cache") + if err := os.MkdirAll(cacheDir, 0700); err != nil { + t.Fatalf("Create cache directory: %v", err) + } + + testMap := &netmap.NetworkMap{ + SelfNode: (&tailcfg.Node{ + Name: "example.ts.net", + User: tailcfg.UserID(1), + Addresses: []netip.Prefix{ + netip.MustParsePrefix("100.2.3.4/32"), + }, + }).View(), + UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{ + tailcfg.UserID(1): (&tailcfg.UserProfile{ + ID: 1, + LoginName: "amelie@example.com", + DisplayName: "Amelie du Pangoline", + }).View(), + }, + Peers: []tailcfg.NodeView{ + (&tailcfg.Node{ + ID: 601, + StableID: "n601FAKE", + ComputedName: "some-peer", + User: tailcfg.UserID(1), + Key: makeNodeKeyFromID(601), + Addresses: []netip.Prefix{ + netip.MustParsePrefix("100.2.3.5/32"), + }, + }).View(), + (&tailcfg.Node{ + ID: 602, + StableID: "n602FAKE", + ComputedName: "some-tagged-peer", + Tags: []string{"tag:server", "tag:test"}, + User: tailcfg.UserID(1), + Key: makeNodeKeyFromID(602), + Addresses: []netip.Prefix{ + netip.MustParsePrefix("100.2.3.6/32"), + }, + }).View(), + }, + } + dc := netmapcache.NewCache(netmapcache.FileStore(cacheDir)) + if err := dc.Store(t.Context(), testMap); err != nil { + t.Fatalf("Store netmap in cache: %v", err) + } + + // Now make a new backend and hook it up to have access to the cache created + // above, then start it to pull in the cached netmap. + sys := tsd.NewSystem() + e, err := wgengine.NewFakeUserspaceEngine(logger.Discard, + sys.Set, + sys.HealthTracker.Get(), + sys.UserMetricsRegistry(), + sys.Bus.Get(), + ) + if err != nil { + t.Fatalf("Make userspace engine: %v", err) + } + t.Cleanup(e.Close) + sys.Set(e) + sys.Set(new(mem.Store)) + + logf := tstest.WhileTestRunningLogger(t) + clb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0) + if err != nil { + t.Fatalf("Make local backend: %v", err) + } + t.Cleanup(clb.Shutdown) + clb.SetVarRoot(varRoot) + + pm := must.Get(newProfileManager(new(mem.Store), logf, health.NewTracker(sys.Bus.Get()))) + pm.currentProfile = (&ipn.LoginProfile{ID: "id0"}).View() + clb.pm = pm + + // Start up the node. We can't actually log in, because we have no + // controlplane, but verify that we got a network map. + if err := clb.Start(ipn.Options{}); err != nil { + t.Fatalf("Start local backend: %v", err) + } + + // Check that the network map the backend wound up with is the one we + // stored, modulo uncached fields. + nm := clb.currentNode().NetMap() + if diff := cmp.Diff(nm, testMap, + cmpopts.IgnoreFields(netmap.NetworkMap{}, "Cached", "PacketFilter", "PacketFilterRules"), + cmpopts.EquateComparable(key.NodePublic{}, key.MachinePublic{}), + ); diff != "" { + t.Error(diff) + } +} + func TestConfigureExitNode(t *testing.T) { controlURL := "https://localhost:1/" exitNode1 := makeExitNode(1, withName("node-1"), withDERP(1), withAddresses(netip.MustParsePrefix("100.64.1.1/32"))) diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index 46acadd1dd750..bcc00590a4d2c 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -236,6 +236,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) 💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnext+ tailscale.com/ipn/ipnext from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+ + tailscale.com/ipn/ipnlocal/netmapcache from tailscale.com/ipn/ipnlocal tailscale.com/ipn/ipnstate from tailscale.com/client/local+ tailscale.com/ipn/localapi from tailscale.com/tsnet tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+ From eb3d35c8b59c853eec0e66a48e22faacc7a82859 Mon Sep 17 00:00:00 2001 From: David Bond Date: Wed, 18 Feb 2026 09:34:55 +0000 Subject: [PATCH 116/202] cmd/k8s-operator,k8s-operator: define ProxyGroupPolicy reconciler (#18654) This commit implements a reconciler for the new `ProxyGroupPolicy` custom resource. When created, all `ProxyGroupPolicy` resources within the same namespace are merged into two `ValidatingAdmissionPolicy` resources, one for egress and one for ingress. These policies use CEL expressions to limit the usage of the "tailscale.com/proxy-group" annotation on `Service` and `Ingress` resources on create & update. Included here is also a new e2e test that ensures that resources that violate the policy return an error on creation, and that once the policy is changed to allow them they can be created. Closes: https://github.com/tailscale/corp/issues/36830 Signed-off-by: David Bond --- cmd/k8s-operator/depaware.txt | 1 + .../deploy/chart/templates/.gitignore | 1 + .../deploy/chart/templates/operator-rbac.yaml | 6 + .../tailscale.com_proxygrouppolicies.yaml | 4 - .../deploy/manifests/operator.yaml | 158 +++++++ cmd/k8s-operator/e2e/doc.go | 2 +- cmd/k8s-operator/e2e/ingress_test.go | 1 + cmd/k8s-operator/e2e/main_test.go | 19 +- cmd/k8s-operator/e2e/pebble.go | 1 + cmd/k8s-operator/e2e/proxy_test.go | 1 + cmd/k8s-operator/e2e/proxygrouppolicy_test.go | 161 ++++++++ cmd/k8s-operator/e2e/setup.go | 1 + cmd/k8s-operator/e2e/ssh.go | 1 + cmd/k8s-operator/generate/main.go | 33 +- cmd/k8s-operator/operator.go | 9 + .../apis/v1alpha1/types_proxygrouppolicy.go | 4 - .../proxygrouppolicy/proxygrouppolicy.go | 391 ++++++++++++++++++ .../proxygrouppolicy/proxygrouppolicy_test.go | 217 ++++++++++ 18 files changed, 987 insertions(+), 24 deletions(-) create mode 100644 cmd/k8s-operator/e2e/proxygrouppolicy_test.go create mode 100644 k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go create mode 100644 k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index 677891ad71d3f..cd87d49872028 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -832,6 +832,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ tailscale.com/k8s-operator/apis from tailscale.com/k8s-operator/apis/v1alpha1 tailscale.com/k8s-operator/apis/v1alpha1 from tailscale.com/cmd/k8s-operator+ tailscale.com/k8s-operator/reconciler from tailscale.com/k8s-operator/reconciler/tailnet + tailscale.com/k8s-operator/reconciler/proxygrouppolicy from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/reconciler/tailnet from tailscale.com/cmd/k8s-operator tailscale.com/k8s-operator/sessionrecording from tailscale.com/k8s-operator/api-proxy tailscale.com/k8s-operator/sessionrecording/spdy from tailscale.com/k8s-operator/sessionrecording diff --git a/cmd/k8s-operator/deploy/chart/templates/.gitignore b/cmd/k8s-operator/deploy/chart/templates/.gitignore index f480bb57d5f18..185ea9e2be316 100644 --- a/cmd/k8s-operator/deploy/chart/templates/.gitignore +++ b/cmd/k8s-operator/deploy/chart/templates/.gitignore @@ -9,3 +9,4 @@ /proxygroup.yaml /recorder.yaml /tailnet.yaml +/proxygrouppolicy.yaml diff --git a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml index 92decef17aab4..4d59b4aad077d 100644 --- a/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml +++ b/cmd/k8s-operator/deploy/chart/templates/operator-rbac.yaml @@ -40,6 +40,9 @@ rules: - apiGroups: ["tailscale.com"] resources: ["tailnets", "tailnets/status"] verbs: ["get", "list", "watch", "update"] +- apiGroups: ["tailscale.com"] + resources: ["proxygrouppolicies", "proxygrouppolicies/status"] + verbs: ["get", "list", "watch", "update"] - apiGroups: ["tailscale.com"] resources: ["recorders", "recorders/status"] verbs: ["get", "list", "watch", "update"] @@ -47,6 +50,9 @@ rules: resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] resourceNames: ["servicemonitors.monitoring.coreos.com"] +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingadmissionpolicies", "validatingadmissionpolicybindings"] + verbs: ["list", "create", "delete", "update", "get", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml index 51edcb56f039b..d1425fba80165 100644 --- a/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml +++ b/cmd/k8s-operator/deploy/crds/tailscale.com_proxygrouppolicies.yaml @@ -19,10 +19,6 @@ spec: - jsonPath: .metadata.creationTimestamp name: Age type: date - - description: Status of the deployed ProxyGroupPolicy resources. - jsonPath: .status.conditions[?(@.type == "ProxyGroupPolicyReady")].reason - name: Status - type: string name: v1alpha1 schema: openAPIV3Schema: diff --git a/cmd/k8s-operator/deploy/manifests/operator.yaml b/cmd/k8s-operator/deploy/manifests/operator.yaml index b31e45eb7befc..597641bdefecf 100644 --- a/cmd/k8s-operator/deploy/manifests/operator.yaml +++ b/cmd/k8s-operator/deploy/manifests/operator.yaml @@ -3290,6 +3290,142 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: proxygrouppolicies.tailscale.com +spec: + group: tailscale.com + names: + kind: ProxyGroupPolicy + listKind: ProxyGroupPolicyList + plural: proxygrouppolicies + shortNames: + - pgp + singular: proxygrouppolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Spec describes the desired state of the ProxyGroupPolicy. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + egress: + description: |- + Names of ProxyGroup resources that can be used by Service resources within this namespace. An empty list + denotes that no egress via ProxyGroups is allowed within this namespace. + items: + type: string + type: array + ingress: + description: |- + Names of ProxyGroup resources that can be used by Ingress resources within this namespace. An empty list + denotes that no ingress via ProxyGroups is allowed within this namespace. + items: + type: string + type: array + type: object + status: + description: |- + Status describes the status of the ProxyGroupPolicy. This is set + and managed by the Tailscale operator. + properties: + conditions: + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.0 @@ -5318,6 +5454,16 @@ rules: - list - watch - update + - apiGroups: + - tailscale.com + resources: + - proxygrouppolicies + - proxygrouppolicies/status + verbs: + - get + - list + - watch + - update - apiGroups: - tailscale.com resources: @@ -5338,6 +5484,18 @@ rules: - get - list - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingadmissionpolicies + - validatingadmissionpolicybindings + verbs: + - list + - create + - delete + - update + - get + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/cmd/k8s-operator/e2e/doc.go b/cmd/k8s-operator/e2e/doc.go index c0cc363160f70..27d10e637c8c2 100644 --- a/cmd/k8s-operator/e2e/doc.go +++ b/cmd/k8s-operator/e2e/doc.go @@ -24,5 +24,5 @@ // // * go // * container runtime with the docker daemon API available -// * devcontrol: ./tool/go run ./cmd/devcontrol --generate-test-devices=k8s-operator-e2e --scenario-output-dir=/tmp/k8s-operator-e2e --test-dns=http://localhost:8055 +// * devcontrol: ./tool/go run --tags=tailscale_saas ./cmd/devcontrol --generate-test-devices=k8s-operator-e2e --scenario-output-dir=/tmp/k8s-operator-e2e --test-dns=http://localhost:8055 package e2e diff --git a/cmd/k8s-operator/e2e/ingress_test.go b/cmd/k8s-operator/e2e/ingress_test.go index eb05efa0cd1b8..5339b05836388 100644 --- a/cmd/k8s-operator/e2e/ingress_test.go +++ b/cmd/k8s-operator/e2e/ingress_test.go @@ -14,6 +14,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + kube "tailscale.com/k8s-operator" "tailscale.com/tstest" "tailscale.com/types/ptr" diff --git a/cmd/k8s-operator/e2e/main_test.go b/cmd/k8s-operator/e2e/main_test.go index cb5c35c0054b2..02f614014dbee 100644 --- a/cmd/k8s-operator/e2e/main_test.go +++ b/cmd/k8s-operator/e2e/main_test.go @@ -54,12 +54,29 @@ func createAndCleanup(t *testing.T, cl client.Client, obj client.Object) { t.Cleanup(func() { // Use context.Background() for cleanup, as t.Context() is cancelled // just before cleanup functions are called. - if err := cl.Delete(context.Background(), obj); err != nil { + if err = cl.Delete(context.Background(), obj); err != nil { t.Errorf("error cleaning up %s %s/%s: %s", obj.GetObjectKind().GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err) } }) } +func createAndCleanupErr(t *testing.T, cl client.Client, obj client.Object) error { + t.Helper() + + err := cl.Create(t.Context(), obj) + if err != nil { + return err + } + + t.Cleanup(func() { + if err = cl.Delete(context.Background(), obj); err != nil { + t.Errorf("error cleaning up %s %s/%s: %s", obj.GetObjectKind().GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err) + } + }) + + return nil +} + func get(ctx context.Context, cl client.Client, obj client.Object) error { return cl.Get(ctx, client.ObjectKeyFromObject(obj), obj) } diff --git a/cmd/k8s-operator/e2e/pebble.go b/cmd/k8s-operator/e2e/pebble.go index a3ccb50cd0493..5fcb35e057c3d 100644 --- a/cmd/k8s-operator/e2e/pebble.go +++ b/cmd/k8s-operator/e2e/pebble.go @@ -12,6 +12,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + "tailscale.com/types/ptr" ) diff --git a/cmd/k8s-operator/e2e/proxy_test.go b/cmd/k8s-operator/e2e/proxy_test.go index f7d11d278ef77..2d4fa53cc2589 100644 --- a/cmd/k8s-operator/e2e/proxy_test.go +++ b/cmd/k8s-operator/e2e/proxy_test.go @@ -16,6 +16,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + "tailscale.com/ipn" "tailscale.com/tstest" ) diff --git a/cmd/k8s-operator/e2e/proxygrouppolicy_test.go b/cmd/k8s-operator/e2e/proxygrouppolicy_test.go new file mode 100644 index 0000000000000..f8126499b0db0 --- /dev/null +++ b/cmd/k8s-operator/e2e/proxygrouppolicy_test.go @@ -0,0 +1,161 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package e2e + +import ( + "strings" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/types/ptr" +) + +// See [TestMain] for test requirements. +func TestProxyGroupPolicy(t *testing.T) { + if tnClient == nil { + t.Skip("TestProxyGroupPolicy requires a working tailnet client") + } + + // Apply deny-all policy + denyAllPolicy := &tsapi.ProxyGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{}, + Egress: []string{}, + }, + } + + createAndCleanup(t, kubeClient, denyAllPolicy) + <-time.After(time.Second * 2) + + // Attempt to create an egress Service within the default namespace, the above policy should + // reject it. + egressService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "egress-to-proxy-group", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "tailscale.com/tailnet-fqdn": "test.something.ts.net", + "tailscale.com/proxy-group": "test", + }, + }, + Spec: corev1.ServiceSpec{ + ExternalName: "placeholder", + Type: corev1.ServiceTypeExternalName, + Ports: []corev1.ServicePort{ + { + Port: 8080, + Protocol: corev1.ProtocolTCP, + Name: "http", + }, + }, + }, + } + + err := createAndCleanupErr(t, kubeClient, egressService) + switch { + case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): + case err != nil: + t.Fatalf("expected forbidden error, got: %v", err) + default: + t.Fatal("expected error when creating egress service") + } + + // Attempt to create an ingress Service within the default namespace, the above policy should + // reject it. + ingressService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-to-proxy-group", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "tailscale.com/proxy-group": "test", + }, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + LoadBalancerClass: ptr.To("tailscale"), + Ports: []corev1.ServicePort{ + { + Port: 8080, + Protocol: corev1.ProtocolTCP, + Name: "http", + }, + }, + }, + } + + err = createAndCleanupErr(t, kubeClient, ingressService) + switch { + case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): + case err != nil: + t.Fatalf("expected forbidden error, got: %v", err) + default: + t.Fatal("expected error when creating ingress service") + } + + // Attempt to create an Ingress within the default namespace, the above policy should reject it + ingress := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-to-proxy-group", + Namespace: metav1.NamespaceDefault, + Annotations: map[string]string{ + "tailscale.com/proxy-group": "test", + }, + }, + Spec: networkingv1.IngressSpec{ + IngressClassName: ptr.To("tailscale"), + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "nginx", + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"nginx"}, + }, + }, + }, + } + + err = createAndCleanupErr(t, kubeClient, ingress) + switch { + case err != nil && strings.Contains(err.Error(), "ValidatingAdmissionPolicy"): + case err != nil: + t.Fatalf("expected forbidden error, got: %v", err) + default: + t.Fatal("expected error when creating ingress") + } + + // Add policy to allow ingress/egress using the "test" proxy-group. This should be merged with the deny-all + // policy so they do not conflict. + allowTestPolicy := &tsapi.ProxyGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-test", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{"test"}, + Egress: []string{"test"}, + }, + } + + createAndCleanup(t, kubeClient, allowTestPolicy) + <-time.After(time.Second * 2) + + // With this policy in place, the above ingress/egress resources should be allowed to be created. + createAndCleanup(t, kubeClient, egressService) + createAndCleanup(t, kubeClient, ingressService) + createAndCleanup(t, kubeClient, ingress) +} diff --git a/cmd/k8s-operator/e2e/setup.go b/cmd/k8s-operator/e2e/setup.go index baf763ac61a60..845a591453b64 100644 --- a/cmd/k8s-operator/e2e/setup.go +++ b/cmd/k8s-operator/e2e/setup.go @@ -52,6 +52,7 @@ import ( "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/cmd" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" "tailscale.com/ipn/store/mem" diff --git a/cmd/k8s-operator/e2e/ssh.go b/cmd/k8s-operator/e2e/ssh.go index 8000d13267262..371c44f9d4544 100644 --- a/cmd/k8s-operator/e2e/ssh.go +++ b/cmd/k8s-operator/e2e/ssh.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + tailscaleroot "tailscale.com" "tailscale.com/types/ptr" ) diff --git a/cmd/k8s-operator/generate/main.go b/cmd/k8s-operator/generate/main.go index 9a910da3eb945..840812ea3b248 100644 --- a/cmd/k8s-operator/generate/main.go +++ b/cmd/k8s-operator/generate/main.go @@ -20,20 +20,22 @@ import ( ) const ( - operatorDeploymentFilesPath = "cmd/k8s-operator/deploy" - connectorCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_connectors.yaml" - proxyClassCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxyclasses.yaml" - dnsConfigCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_dnsconfigs.yaml" - recorderCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_recorders.yaml" - proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml" - tailnetCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_tailnets.yaml" - helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates" - connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml" - proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml" - dnsConfigCRDHelmTemplatePath = helmTemplatesPath + "/dnsconfig.yaml" - recorderCRDHelmTemplatePath = helmTemplatesPath + "/recorder.yaml" - proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml" - tailnetCRDHelmTemplatePath = helmTemplatesPath + "/tailnet.yaml" + operatorDeploymentFilesPath = "cmd/k8s-operator/deploy" + connectorCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_connectors.yaml" + proxyClassCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxyclasses.yaml" + dnsConfigCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_dnsconfigs.yaml" + recorderCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_recorders.yaml" + proxyGroupCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygroups.yaml" + tailnetCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_tailnets.yaml" + proxyGroupPolicyCRDPath = operatorDeploymentFilesPath + "/crds/tailscale.com_proxygrouppolicies.yaml" + helmTemplatesPath = operatorDeploymentFilesPath + "/chart/templates" + connectorCRDHelmTemplatePath = helmTemplatesPath + "/connector.yaml" + proxyClassCRDHelmTemplatePath = helmTemplatesPath + "/proxyclass.yaml" + dnsConfigCRDHelmTemplatePath = helmTemplatesPath + "/dnsconfig.yaml" + recorderCRDHelmTemplatePath = helmTemplatesPath + "/recorder.yaml" + proxyGroupCRDHelmTemplatePath = helmTemplatesPath + "/proxygroup.yaml" + tailnetCRDHelmTemplatePath = helmTemplatesPath + "/tailnet.yaml" + proxyGroupPolicyCRDHelmTemplatePath = helmTemplatesPath + "/proxygrouppolicy.yaml" helmConditionalStart = "{{ if .Values.installCRDs -}}\n" helmConditionalEnd = "{{- end -}}" @@ -157,6 +159,7 @@ func generate(baseDir string) error { {recorderCRDPath, recorderCRDHelmTemplatePath}, {proxyGroupCRDPath, proxyGroupCRDHelmTemplatePath}, {tailnetCRDPath, tailnetCRDHelmTemplatePath}, + {proxyGroupPolicyCRDPath, proxyGroupPolicyCRDHelmTemplatePath}, } { if err := addCRDToHelm(crd.crdPath, crd.templatePath); err != nil { return fmt.Errorf("error adding %s CRD to Helm templates: %w", crd.crdPath, err) @@ -173,6 +176,8 @@ func cleanup(baseDir string) error { dnsConfigCRDHelmTemplatePath, recorderCRDHelmTemplatePath, proxyGroupCRDHelmTemplatePath, + tailnetCRDHelmTemplatePath, + proxyGroupPolicyCRDHelmTemplatePath, } { if err := os.Remove(filepath.Join(baseDir, path)); err != nil && !os.IsNotExist(err) { return fmt.Errorf("error cleaning up %s: %w", path, err) diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index 4f48c1812643a..81f62d4775671 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -54,6 +54,7 @@ import ( "tailscale.com/ipn/store/kubestore" apiproxy "tailscale.com/k8s-operator/api-proxy" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/proxygrouppolicy" "tailscale.com/k8s-operator/reconciler/tailnet" "tailscale.com/kube/kubetypes" "tailscale.com/tsnet" @@ -337,6 +338,14 @@ func runReconcilers(opts reconcilerOpts) { startlog.Fatalf("could not register tailnet reconciler: %v", err) } + proxyGroupPolicyOptions := proxygrouppolicy.ReconcilerOptions{ + Client: mgr.GetClient(), + } + + if err = proxygrouppolicy.NewReconciler(proxyGroupPolicyOptions).Register(mgr); err != nil { + startlog.Fatalf("could not register proxygrouppolicy reconciler: %v", err) + } + svcFilter := handler.EnqueueRequestsFromMapFunc(serviceHandler) svcChildFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("svc")) // If a ProxyClass changes, enqueue all Services labeled with that diff --git a/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go b/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go index dd06380c271f5..551811693f7c8 100644 --- a/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go +++ b/k8s-operator/apis/v1alpha1/types_proxygrouppolicy.go @@ -18,7 +18,6 @@ var ProxyGroupPolicyKind = "ProxyGroupPolicy" // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Namespaced,shortName=pgp // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "ProxyGroupPolicyReady")].reason`,description="Status of the deployed ProxyGroupPolicy resources." type ProxyGroupPolicy struct { metav1.TypeMeta `json:",inline"` @@ -62,6 +61,3 @@ type ProxyGroupPolicyStatus struct { // +optional Conditions []metav1.Condition `json:"conditions"` } - -// ProxyGroupPolicyReady is set to True if the ProxyGroupPolicy is available for use by operator workloads. -const ProxyGroupPolicyReady ConditionType = "ProxyGroupPolicyReady" diff --git a/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go new file mode 100644 index 0000000000000..0541a5cf3691b --- /dev/null +++ b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy.go @@ -0,0 +1,391 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !plan9 + +// Package proxygrouppolicy provides reconciliation logic for the ProxyGroupPolicy custom resource definition. It is +// responsible for generating ValidatingAdmissionPolicy resources that limit users to a set number of ProxyGroup +// names that can be used within Service and Ingress resources via the "tailscale.com/proxy-group" annotation. +package proxygrouppolicy + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + + admr "k8s.io/api/admissionregistration/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/types/ptr" + "tailscale.com/util/set" +) + +type ( + // The Reconciler type is a reconcile.TypedReconciler implementation used to manage the reconciliation of + // ProxyGroupPolicy custom resources. + Reconciler struct { + client.Client + } + + // The ReconcilerOptions type contains configuration values for the Reconciler. + ReconcilerOptions struct { + // The client for interacting with the Kubernetes API. + Client client.Client + } +) + +const reconcilerName = "proxygrouppolicy-reconciler" + +// NewReconciler returns a new instance of the Reconciler type. It watches specifically for changes to ProxyGroupPolicy +// custom resources. The ReconcilerOptions can be used to modify the behaviour of the Reconciler. +func NewReconciler(options ReconcilerOptions) *Reconciler { + return &Reconciler{ + Client: options.Client, + } +} + +// Register the Reconciler onto the given manager.Manager implementation. +func (r *Reconciler) Register(mgr manager.Manager) error { + return builder. + ControllerManagedBy(mgr). + For(&tsapi.ProxyGroupPolicy{}). + Named(reconcilerName). + Complete(r) +} + +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + // Rather than working on a single ProxyGroupPolicy resource, we list all that exist within the + // same namespace as the one we're reconciling so that we can merge them into a single pair of + // ValidatingAdmissionPolicy resources. + var policies tsapi.ProxyGroupPolicyList + if err := r.List(ctx, &policies, client.InNamespace(req.Namespace)); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to list ProxyGroupPolicy resources %q: %w", req.NamespacedName, err) + } + + if len(policies.Items) == 0 { + // If we've got no items in the list, we go and delete any policies and bindings that + // may exist. + return r.delete(ctx, req.Namespace) + } + + return r.createOrUpdate(ctx, req.Namespace, policies) +} + +func (r *Reconciler) delete(ctx context.Context, namespace string) (reconcile.Result, error) { + ingress := "ts-ingress-" + namespace + egress := "ts-egress-" + namespace + + objects := []client.Object{ + &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingress, + }, + }, + &admr.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingress, + }, + }, + &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: egress, + }, + }, + &admr.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: egress, + }, + }, + } + + for _, obj := range objects { + err := r.Delete(ctx, obj) + switch { + case apierrors.IsNotFound(err): + // A resource may have already been deleted in a previous reconciliation that failed for + // some reason, so we'll ignore it if it doesn't exist. + continue + case err != nil: + return reconcile.Result{}, fmt.Errorf("failed to delete %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) + } + } + + return reconcile.Result{}, nil +} + +func (r *Reconciler) createOrUpdate(ctx context.Context, namespace string, policies tsapi.ProxyGroupPolicyList) (reconcile.Result, error) { + ingressNames := set.Set[string]{} + egressNames := set.Set[string]{} + + // If this namespace has multiple ProxyGroupPolicy resources, we'll reduce them down to just their distinct + // egress/ingress names. + for _, policy := range policies.Items { + ingressNames.AddSlice(policy.Spec.Ingress) + egressNames.AddSlice(policy.Spec.Egress) + } + + ingress, err := r.generateIngressPolicy(ctx, namespace, ingressNames) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate ingress policy: %w", err) + } + + ingressBinding, err := r.generatePolicyBinding(ctx, namespace, ingress) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate ingress policy binding: %w", err) + } + + egress, err := r.generateEgressPolicy(ctx, namespace, egressNames) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate egress policy: %w", err) + } + + egressBinding, err := r.generatePolicyBinding(ctx, namespace, egress) + if err != nil { + return reconcile.Result{}, fmt.Errorf("failed to generate egress policy binding: %w", err) + } + + objects := []client.Object{ + ingress, + ingressBinding, + egress, + egressBinding, + } + + for _, obj := range objects { + // Attempt to perform an update first as we'll only create these once and continually update them, so it's + // more likely that an update is needed instead of creation. If the resource does not exist, we'll + // create it. + err = r.Update(ctx, obj) + switch { + case apierrors.IsNotFound(err): + if err = r.Create(ctx, obj); err != nil { + return reconcile.Result{}, fmt.Errorf("failed to create %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) + } + case err != nil: + return reconcile.Result{}, fmt.Errorf("failed to update %s %q: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) + } + } + + return reconcile.Result{}, nil +} + +const ( + // ingressCEL enforces proxy-group annotation rules for Ingress resources. + // + // Logic: + // + // - If the object is NOT an Ingress → allow (this validation is irrelevant) + // - If the annotation is absent → allow (annotation is optional) + // - If the annotation is present → its value must be in the allowlist + // + // Empty allowlist behavior: + // If the list is empty, any present annotation will fail membership, + // effectively acting as "deny-all". + ingressCEL = `request.kind.kind != "Ingress" || !("tailscale.com/proxy-group" in object.metadata.annotations) || object.metadata.annotations["tailscale.com/proxy-group"] in [%s]` + + // ingressServiceCEL enforces proxy-group annotation rules for Services + // that are using the tailscale load balancer. + // + // Logic: + // + // - If the object is NOT a Service → allow + // - If Service does NOT use loadBalancerClass "tailscale" → allow + // (egress policy will handle those) + // - If annotation is absent → allow + // - If annotation is present → must be in allowlist + // + // This makes ingress policy apply ONLY to tailscale Services. + ingressServiceCEL = `request.kind.kind != "Service" || !((has(object.spec.loadBalancerClass) && object.spec.loadBalancerClass == "tailscale") || ("tailscale.com/expose" in object.metadata.annotations && object.metadata.annotations["tailscale.com/expose"] == "true")) || (!("tailscale.com/proxy-group" in object.metadata.annotations) || object.metadata.annotations["tailscale.com/proxy-group"] in [%s])` + // egressCEL enforces proxy-group annotation rules for Services that + // are NOT using the tailscale load balancer. + // + // Logic: + // + // - If Service uses loadBalancerClass "tailscale" → allow + // (ingress policy handles those) + // - If Service uses "tailscale.com/expose" → allow + // (ingress policy handles those) + // - If annotation is absent → allow + // - If annotation is present → must be in allowlist + // + // Empty allowlist behavior: + // Any present annotation is rejected ("deny-all"). + // + // This expression is mutually exclusive with ingressServiceCEL, + // preventing policy conflicts. + egressCEL = `((has(object.spec.loadBalancerClass) && object.spec.loadBalancerClass == "tailscale") || ("tailscale.com/expose" in object.metadata.annotations && object.metadata.annotations["tailscale.com/expose"] == "true")) || !("tailscale.com/proxy-group" in object.metadata.annotations) || object.metadata.annotations["tailscale.com/proxy-group"] in [%s]` +) + +func (r *Reconciler) generateIngressPolicy(ctx context.Context, namespace string, names set.Set[string]) (*admr.ValidatingAdmissionPolicy, error) { + name := "ts-ingress-" + namespace + + var policy admr.ValidatingAdmissionPolicy + err := r.Get(ctx, client.ObjectKey{Name: name}, &policy) + switch { + case apierrors.IsNotFound(err): + // If it's not found, we can create a new one. We only want the existing one for + // its resource version. + case err != nil: + return nil, fmt.Errorf("failed to get ValidatingAdmissionPolicy %q: %w", name, err) + } + + return &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: policy.ResourceVersion, + }, + Spec: admr.ValidatingAdmissionPolicySpec{ + FailurePolicy: ptr.To(admr.Fail), + MatchConstraints: &admr.MatchResources{ + // The operator allows ingress via Ingress resources & Service resources (that use the "tailscale" load + // balancer class), so we have two resource rules here with multiple validation expressions that attempt + // to keep out of each other's way. + ResourceRules: []admr.NamedRuleWithOperations{ + { + RuleWithOperations: admr.RuleWithOperations{ + Operations: []admr.OperationType{ + admr.Create, + admr.Update, + }, + Rule: admr.Rule{ + APIGroups: []string{"networking.k8s.io"}, + APIVersions: []string{"*"}, + Resources: []string{"ingresses"}, + }, + }, + }, + { + RuleWithOperations: admr.RuleWithOperations{ + Operations: []admr.OperationType{ + admr.Create, + admr.Update, + }, + Rule: admr.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"services"}, + }, + }, + }, + }, + }, + Validations: []admr.Validation{ + generateValidation(names, ingressCEL), + generateValidation(names, ingressServiceCEL), + }, + }, + }, nil +} + +func (r *Reconciler) generateEgressPolicy(ctx context.Context, namespace string, names set.Set[string]) (*admr.ValidatingAdmissionPolicy, error) { + name := "ts-egress-" + namespace + + var policy admr.ValidatingAdmissionPolicy + err := r.Get(ctx, client.ObjectKey{Name: name}, &policy) + switch { + case apierrors.IsNotFound(err): + // If it's not found, we can create a new one. We only want the existing one for + // its resource version. + case err != nil: + return nil, fmt.Errorf("failed to get ValidatingAdmissionPolicy %q: %w", name, err) + } + + return &admr.ValidatingAdmissionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: policy.ResourceVersion, + }, + Spec: admr.ValidatingAdmissionPolicySpec{ + FailurePolicy: ptr.To(admr.Fail), + MatchConstraints: &admr.MatchResources{ + ResourceRules: []admr.NamedRuleWithOperations{ + { + RuleWithOperations: admr.RuleWithOperations{ + Operations: []admr.OperationType{ + admr.Create, + admr.Update, + }, + Rule: admr.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"services"}, + }, + }, + }, + }, + }, + Validations: []admr.Validation{ + generateValidation(names, egressCEL), + }, + }, + }, nil +} + +const ( + denyMessage = `Annotation "tailscale.com/proxy-group" cannot be used on this resource in this namespace` + messageFormat = `If set, annotation "tailscale.com/proxy-group" must be one of [%s]` +) + +func generateValidation(names set.Set[string], format string) admr.Validation { + values := names.Slice() + + // We use a sort here so that the order of the proxy-group names are consistent + // across reconciliation loops. + sort.Strings(values) + + quoted := make([]string, len(values)) + for i, v := range values { + quoted[i] = strconv.Quote(v) + } + + joined := strings.Join(quoted, ",") + message := fmt.Sprintf(messageFormat, strings.Join(values, ", ")) + if len(values) == 0 { + message = denyMessage + } + + return admr.Validation{ + Expression: fmt.Sprintf(format, joined), + Message: message, + } +} + +func (r *Reconciler) generatePolicyBinding(ctx context.Context, namespace string, policy *admr.ValidatingAdmissionPolicy) (*admr.ValidatingAdmissionPolicyBinding, error) { + var binding admr.ValidatingAdmissionPolicyBinding + err := r.Get(ctx, client.ObjectKey{Name: policy.Name}, &binding) + switch { + case apierrors.IsNotFound(err): + // If it's not found, we can create a new one. We only want the existing one for + // its resource version. + case err != nil: + return nil, fmt.Errorf("failed to get ValidatingAdmissionPolicyBinding %q: %w", policy.Name, err) + } + + return &admr.ValidatingAdmissionPolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: policy.Name, + ResourceVersion: binding.ResourceVersion, + }, + Spec: admr.ValidatingAdmissionPolicyBindingSpec{ + PolicyName: policy.Name, + ValidationActions: []admr.ValidationAction{ + admr.Deny, + }, + MatchResources: &admr.MatchResources{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "kubernetes.io/metadata.name": namespace, + }, + }, + }, + }, + }, nil +} diff --git a/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go new file mode 100644 index 0000000000000..6710eac7406d6 --- /dev/null +++ b/k8s-operator/reconciler/proxygrouppolicy/proxygrouppolicy_test.go @@ -0,0 +1,217 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package proxygrouppolicy_test + +import ( + "slices" + "strings" + "testing" + + admr "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + tsapi "tailscale.com/k8s-operator/apis/v1alpha1" + "tailscale.com/k8s-operator/reconciler/proxygrouppolicy" +) + +func TestReconciler_Reconcile(t *testing.T) { + t.Parallel() + + tt := []struct { + Name string + Request reconcile.Request + ExpectedPolicyCount int + ExistingResources []client.Object + ExpectsError bool + }{ + { + Name: "single policy, denies all", + ExpectedPolicyCount: 2, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + }, + ExistingResources: []client.Object{ + &tsapi.ProxyGroupPolicy{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{}, + Egress: []string{}, + }, + }, + }, + }, + { + Name: "multiple policies merged", + ExpectedPolicyCount: 2, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + }, + ExistingResources: []client.Object{ + &tsapi.ProxyGroupPolicy{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{}, + Egress: []string{}, + }, + }, + &tsapi.ProxyGroupPolicy{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-one", + Namespace: metav1.NamespaceDefault, + }, + Spec: tsapi.ProxyGroupPolicySpec{ + Ingress: []string{ + "test-ingress", + }, + Egress: []string{}, + }, + }, + }, + }, + { + Name: "no policies, no child resources", + ExpectedPolicyCount: 0, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "deny-all", + Namespace: metav1.NamespaceDefault, + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + bldr := fake.NewClientBuilder().WithScheme(tsapi.GlobalScheme) + bldr = bldr.WithObjects(tc.ExistingResources...) + + fc := bldr.Build() + opts := proxygrouppolicy.ReconcilerOptions{ + Client: fc, + } + + reconciler := proxygrouppolicy.NewReconciler(opts) + _, err := reconciler.Reconcile(t.Context(), tc.Request) + if tc.ExpectsError && err == nil { + t.Fatalf("expected error, got none") + } + + if !tc.ExpectsError && err != nil { + t.Fatalf("expected no error, got %v", err) + } + + var policies admr.ValidatingAdmissionPolicyList + if err = fc.List(t.Context(), &policies); err != nil { + t.Fatal(err) + } + + if len(policies.Items) != tc.ExpectedPolicyCount { + t.Fatalf("expected %d ValidatingAdmissionPolicy resources, got %d", tc.ExpectedPolicyCount, len(policies.Items)) + } + + var bindings admr.ValidatingAdmissionPolicyBindingList + if err = fc.List(t.Context(), &bindings); err != nil { + t.Fatal(err) + } + + if len(bindings.Items) != tc.ExpectedPolicyCount { + t.Fatalf("expected %d ValidatingAdmissionPolicyBinding resources, got %d", tc.ExpectedPolicyCount, len(bindings.Items)) + } + + for _, binding := range bindings.Items { + actual, ok := binding.Spec.MatchResources.NamespaceSelector.MatchLabels["kubernetes.io/metadata.name"] + if !ok || actual != metav1.NamespaceDefault { + t.Fatalf("expected binding to be for default namespace, got %v", actual) + } + + if !slices.Contains(binding.Spec.ValidationActions, admr.Deny) { + t.Fatalf("expected binding to be deny, got %v", binding.Spec.ValidationActions) + } + } + + for _, policy := range policies.Items { + // Each ValidatingAdmissionPolicy must be set to fail (rejecting resources). + if policy.Spec.FailurePolicy == nil || *policy.Spec.FailurePolicy != admr.Fail { + t.Fatalf("expected fail policy, got %v", *policy.Spec.FailurePolicy) + } + + // Each ValidatingAdmissionPolicy must have a matching ValidatingAdmissionPolicyBinding + bound := slices.ContainsFunc(bindings.Items, func(obj admr.ValidatingAdmissionPolicyBinding) bool { + return obj.Spec.PolicyName == policy.Name + }) + if !bound { + t.Fatalf("expected policy %s to be bound, but wasn't", policy.Name) + } + + // Each ValidatingAdmissionPolicy must be set to evaluate on creation and update of resources. + for _, rule := range policy.Spec.MatchConstraints.ResourceRules { + if !slices.Contains(rule.Operations, admr.Update) { + t.Fatal("expected ingress rule to act on update, but doesn't") + } + + if !slices.Contains(rule.Operations, admr.Create) { + t.Fatal("expected ingress rule to act on create, but doesn't") + } + } + + // Egress policies should only act on Service resources. + if strings.Contains(policy.Name, "egress") { + if len(policy.Spec.MatchConstraints.ResourceRules) != 1 { + t.Fatalf("expected exactly one matching resource, got %d", len(policy.Spec.MatchConstraints.ResourceRules)) + } + + rule := policy.Spec.MatchConstraints.ResourceRules[0] + + if !slices.Contains(rule.Resources, "services") { + t.Fatal("expected egress rule to act on services, but doesn't") + } + + if len(policy.Spec.Validations) != 1 { + t.Fatalf("expected exactly one validation, got %d", len(policy.Spec.Validations)) + } + } + + // Ingress policies should act on both Ingress and Service resources. + if strings.Contains(policy.Name, "ingress") { + if len(policy.Spec.MatchConstraints.ResourceRules) != 2 { + t.Fatalf("expected exactly two matching resources, got %d", len(policy.Spec.MatchConstraints.ResourceRules)) + } + + ingressRule := policy.Spec.MatchConstraints.ResourceRules[0] + if !slices.Contains(ingressRule.Resources, "ingresses") { + t.Fatal("expected ingress rule to act on ingresses, but doesn't") + } + + serviceRule := policy.Spec.MatchConstraints.ResourceRules[1] + if !slices.Contains(serviceRule.Resources, "services") { + t.Fatal("expected ingress rule to act on services, but doesn't") + } + + if len(policy.Spec.Validations) != 2 { + t.Fatalf("expected exactly two validations, got %d", len(policy.Spec.Validations)) + } + } + } + }) + } +} From 299f1bf581886a6d9051d6ac60efc770db5321ab Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Thu, 12 Feb 2026 16:38:18 -0700 Subject: [PATCH 117/202] testcontrol: ensure Server.UpdateNode triggers netmap updates Updating a node on a testcontrol server should trigger netmap updates to all connected streaming clients. This was not the case previous to this change and consequently caused race conditions in tests. It was possible for a test to call UpdateNode and for connected nodes to never see the update propagate. Updates #16340 Fixes #18703 Signed-off-by: Harry Harpham --- tstest/integration/testcontrol/testcontrol.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go index 56664ba746204..1e24414903ae9 100644 --- a/tstest/integration/testcontrol/testcontrol.go +++ b/tstest/integration/testcontrol/testcontrol.go @@ -1075,9 +1075,7 @@ func sendUpdate(dst chan<- updateType, updateType updateType) bool { } } -func (s *Server) UpdateNode(n *tailcfg.Node) (peersToUpdate []tailcfg.NodeID) { - s.mu.Lock() - defer s.mu.Unlock() +func (s *Server) updateNodeLocked(n *tailcfg.Node) (peersToUpdate []tailcfg.NodeID) { if n.Key.IsZero() { panic("zero nodekey") } @@ -1085,6 +1083,15 @@ func (s *Server) UpdateNode(n *tailcfg.Node) (peersToUpdate []tailcfg.NodeID) { return s.nodeIDsLocked(n.ID) } +// UpdateNode updates or adds the input node, then triggers a netmap update for +// all attached streaming clients. +func (s *Server) UpdateNode(n *tailcfg.Node) { + s.mu.Lock() + defer s.mu.Unlock() + s.updateNodeLocked(n) + s.updateLocked("UpdateNode", s.nodeIDsLocked(0)) +} + func (s *Server) incrInServeMap(delta int) { s.mu.Lock() defer s.mu.Unlock() @@ -1143,7 +1150,9 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi } } } - peersToUpdate = s.UpdateNode(node) + s.mu.Lock() + peersToUpdate = s.updateNodeLocked(node) + s.mu.Unlock() } nodeID := node.ID From 976aa940ec2b4600b1a6dc53364da8388df25ef6 Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Wed, 18 Feb 2026 11:54:09 -0500 Subject: [PATCH 118/202] ipn/ipnlocal, cmd/tailscale: use wildcard. prefix for cert filenames (#18748) Stop stripping the "*." prefix from wildcard domains when used as storage keys. Instead, replace "*" with "wildcard_" only at the filesystem boundary in certFile and keyFile. This prevents wildcard and non-wildcard certs from colliding in storage. Updates #1196 Updates #7081 Signed-off-by: Fernando Serboncini --- cmd/tailscale/cli/cert.go | 5 +++-- ipn/ipnlocal/cert.go | 32 ++++++++++++++++---------------- ipn/ipnlocal/cert_test.go | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/cmd/tailscale/cli/cert.go b/cmd/tailscale/cli/cert.go index f38ddbacf1804..6d78a8d8abf5f 100644 --- a/cmd/tailscale/cli/cert.go +++ b/cmd/tailscale/cli/cert.go @@ -108,8 +108,9 @@ func runCert(ctx context.Context, args []string) error { log.SetFlags(0) } if certArgs.certFile == "" && certArgs.keyFile == "" { - certArgs.certFile = domain + ".crt" - certArgs.keyFile = domain + ".key" + fileBase := strings.Replace(domain, "*.", "wildcard_.", 1) + certArgs.certFile = fileBase + ".crt" + certArgs.keyFile = fileBase + ".key" } certPEM, keyPEM, err := localClient.CertPairWithValidity(ctx, domain, certArgs.minValidity) if err != nil { diff --git a/ipn/ipnlocal/cert.go b/ipn/ipnlocal/cert.go index 027e7c810778b..efab9db7aad6e 100644 --- a/ipn/ipnlocal/cert.go +++ b/ipn/ipnlocal/cert.go @@ -130,8 +130,6 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string if err != nil { return nil, err } - storageKey := strings.TrimPrefix(certDomain, "*.") - logf := logger.WithPrefix(b.logf, fmt.Sprintf("cert(%q): ", domain)) now := b.clock.Now() traceACME := func(v any) { @@ -147,13 +145,13 @@ func (b *LocalBackend) GetCertPEMWithValidity(ctx context.Context, domain string return nil, err } - if pair, err := getCertPEMCached(cs, storageKey, now); err == nil { + if pair, err := getCertPEMCached(cs, certDomain, now); err == nil { if envknob.IsCertShareReadOnlyMode() { return pair, nil } // If we got here, we have a valid unexpired cert. // Check whether we should start an async renewal. - shouldRenew, err := b.shouldStartDomainRenewal(cs, storageKey, now, pair, minValidity) + shouldRenew, err := b.shouldStartDomainRenewal(cs, certDomain, now, pair, minValidity) if err != nil { logf("error checking for certificate renewal: %v", err) // Renewal check failed, but the current cert is valid and not @@ -501,8 +499,12 @@ func (kp TLSCertKeyPair) parseCertificate() (*x509.Certificate, error) { return x509.ParseCertificate(block.Bytes) } -func keyFile(dir, domain string) string { return filepath.Join(dir, domain+".key") } -func certFile(dir, domain string) string { return filepath.Join(dir, domain+".crt") } +func keyFile(dir, domain string) string { + return filepath.Join(dir, strings.Replace(domain, "*.", "wildcard_.", 1)+".key") +} +func certFile(dir, domain string) string { + return filepath.Join(dir, strings.Replace(domain, "*.", "wildcard_.", 1)+".crt") +} // getCertPEMCached returns a non-nil keyPair if a cached keypair for domain // exists on disk in dir that is valid at the provided now time. @@ -525,18 +527,16 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l acmeMu.Lock() defer acmeMu.Unlock() - // storageKey is used for file storage and renewal tracking. - // For wildcards, "*.node.ts.net" -> "node.ts.net" - storageKey, isWildcard := strings.CutPrefix(domain, "*.") + baseDomain, isWildcard := strings.CutPrefix(domain, "*.") // In case this method was triggered multiple times in parallel (when // serving incoming requests), check whether one of the other goroutines // already renewed the cert before us. - previous, err := getCertPEMCached(cs, storageKey, now) + previous, err := getCertPEMCached(cs, domain, now) if err == nil { // shouldStartDomainRenewal caches its result so it's OK to call this // frequently. - shouldRenew, err := b.shouldStartDomainRenewal(cs, storageKey, now, previous, minValidity) + shouldRenew, err := b.shouldStartDomainRenewal(cs, domain, now, previous, minValidity) if err != nil { logf("error checking for certificate renewal: %v", err) } else if !shouldRenew { @@ -598,7 +598,7 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l if isWildcard { authzIDs = []acme.AuthzID{ {Type: "dns", Value: domain}, - {Type: "dns", Value: storageKey}, + {Type: "dns", Value: baseDomain}, } } else { authzIDs = []acme.AuthzID{{Type: "dns", Value: domain}} @@ -697,10 +697,10 @@ var getCertPEM = func(ctx context.Context, b *LocalBackend, cs certStore, logf l return nil, err } } - if err := cs.WriteTLSCertAndKey(storageKey, certPEM.Bytes(), privPEM.Bytes()); err != nil { + if err := cs.WriteTLSCertAndKey(domain, certPEM.Bytes(), privPEM.Bytes()); err != nil { return nil, err } - b.domainRenewed(storageKey) + b.domainRenewed(domain) return &TLSCertKeyPair{CertPEM: certPEM.Bytes(), KeyPEM: privPEM.Bytes()}, nil } @@ -924,7 +924,7 @@ func (b *LocalBackend) resolveCertDomain(domain string) (string, error) { return "", fmt.Errorf("wildcard certificates are not enabled for this node") } if !slices.Contains(certDomains, base) { - return "", fmt.Errorf("invalid domain %q; parent domain must be one of %q", domain, certDomains) + return "", fmt.Errorf("invalid domain %q; wildcard certificates are not enabled for this domain", domain) } return domain, nil } @@ -951,7 +951,7 @@ func handleC2NTLSCertStatus(b *LocalBackend, w http.ResponseWriter, r *http.Requ return } - domain := strings.TrimPrefix(r.FormValue("domain"), "*.") + domain := r.FormValue("domain") if domain == "" { http.Error(w, "no 'domain'", http.StatusBadRequest) return diff --git a/ipn/ipnlocal/cert_test.go b/ipn/ipnlocal/cert_test.go index b8acb710ac7d3..cc9146ae1e055 100644 --- a/ipn/ipnlocal/cert_test.go +++ b/ipn/ipnlocal/cert_test.go @@ -139,7 +139,7 @@ func TestResolveCertDomain(t *testing.T) { domain: "*.unrelated.ts.net", certDomains: []string{"node.ts.net"}, hasCap: true, - wantErr: `invalid domain "*.unrelated.ts.net"; parent domain must be one of ["node.ts.net"]`, + wantErr: `invalid domain "*.unrelated.ts.net"; wildcard certificates are not enabled for this domain`, }, { name: "subdomain_unrelated_rejected", From 9acf22f9dfd993acd23d33003e05bc68124f9187 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Wed, 18 Feb 2026 14:12:16 -0500 Subject: [PATCH 119/202] netmon: use State AnyInterfaceUp in ChangeDelta (#18752) fixes tailscale/corp#37048 We're duplicating logic in AnyInterfaceUp in the ChangeDelta and we're duplicating it wrong. The new State has the logic for this based on the HaveV6 and HaveV4 flags. Signed-off-by: Jonathan Nobels --- net/netmon/netmon.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/net/netmon/netmon.go b/net/netmon/netmon.go index c30010ee407da..1d51379d86e31 100644 --- a/net/netmon/netmon.go +++ b/net/netmon/netmon.go @@ -201,12 +201,7 @@ func (cd *ChangeDelta) AnyInterfaceUp() bool { if cd.new == nil { return false } - for _, ifi := range cd.new.Interface { - if ifi.IsUp() { - return true - } - } - return false + return cd.new.AnyInterfaceUp() } // isInterestingInterfaceChange reports whether any interfaces have changed in a meaningful way. From 7fb61e176575ce0e5c148f01b09105ea4c661429 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Thu, 19 Feb 2026 16:06:12 +0000 Subject: [PATCH 120/202] cmd/cigocacher: make --stats flag best-effort (#18761) --auth is already best-effort, but we saw some CI failures due to failing to fetch stats when cigocached was overwhelmed recently. Make sure it fails more gracefully in the absence of cigocached like the rest of cigocacher already does. Updates tailscale/corp#37059 Change-Id: I0703b30b1c5a7f8c649879a87e6bcd2278610208 Signed-off-by: Tom Proctor --- cmd/cigocacher/cigocacher.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cmd/cigocacher/cigocacher.go b/cmd/cigocacher/cigocacher.go index 1e4326ebcb6be..74ed083679743 100644 --- a/cmd/cigocacher/cigocacher.go +++ b/cmd/cigocacher/cigocacher.go @@ -103,9 +103,19 @@ func main() { } stats, err := fetchStats(httpClient(srvHost, *srvHostDial), *srvURL, tk) if err != nil { - log.Fatalf("error fetching gocached stats: %v", err) + // Errors that are not due to misconfiguration are non-fatal so we + // don't fail builds if e.g. cigocached is down. + // + // Print error as JSON so it can still be piped through jq. + statsErr := map[string]any{ + "error": fmt.Sprintf("fetching gocached stats: %v", err), + } + b, _ := jsonv1.Marshal(statsErr) + fmt.Println(string(b)) + } else { + fmt.Println(stats) } - fmt.Println(stats) + return } From c208ba2615bd4df47fb7dada25dc6bd28a94358d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 09:31:50 -0800 Subject: [PATCH 121/202] .github: Bump actions/setup-go from 5.5.0 to 6.2.0 (#18455) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.5.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5.5.0...7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e66d6454a9847..9363facaa2aa2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -49,7 +49,7 @@ jobs: # Install a more recent Go that understands modern go.mod content. - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version-file: go.mod diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 684a094e26560..22d9d3c467ad9 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdf8f3f5f69d1..57a638d2977da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -245,7 +245,7 @@ jobs: path: ${{ github.workspace }}/src - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version-file: ${{ github.workspace }}/src/go.mod cache: false From 6e76db73a9830ac414b9e69195d30ffe62d8293e Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Thu, 19 Feb 2026 10:01:33 -0800 Subject: [PATCH 122/202] go.mod: bump filippo.io/edwards25519 (#18765) Pick up a fix for CVE-2026-26958. Fixes #18756 Signed-off-by: Andrew Lytvynov --- flake.nix | 2 +- go.mod | 2 +- go.mod.sri | 2 +- go.sum | 3 ++- shell.nix | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index 6fc0ff28a906f..af860c09ce627 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-JD1PZPZT5clhRWIAQO8skBRN59QPiyfTc7nPYTvGbd8= +# nix-direnv cache busting line: sha256-A3mjdGE6B5t6sdkHieZZGVYlCyvhdrcqpNaHxISAPuk= diff --git a/go.mod b/go.mod index 7b062afbf4ccf..468b085a180a0 100644 --- a/go.mod +++ b/go.mod @@ -270,7 +270,7 @@ require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect dario.cat/mergo v1.0.1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/edwards25519 v1.2.0 // indirect github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/AlekSi/pointer v1.2.0 github.com/Antonboom/errname v0.1.12 // indirect diff --git a/go.mod.sri b/go.mod.sri index e5d18033ae976..7cab0c4024e7e 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-JD1PZPZT5clhRWIAQO8skBRN59QPiyfTc7nPYTvGbd8= +sha256-A3mjdGE6B5t6sdkHieZZGVYlCyvhdrcqpNaHxISAPuk= diff --git a/go.sum b/go.sum index 299fe95cd84ad..10febf73a81b8 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= fyne.io/systray v1.11.1-0.20250812065214-4856ac3adc3c h1:km4PIleGtbbF1oxmFQuO93CyNCldwuRTPB8WlzNWNZs= diff --git a/shell.nix b/shell.nix index 9fab641722dca..652e41a94469b 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-JD1PZPZT5clhRWIAQO8skBRN59QPiyfTc7nPYTvGbd8= +# nix-direnv cache busting line: sha256-A3mjdGE6B5t6sdkHieZZGVYlCyvhdrcqpNaHxISAPuk= From f1509d27cc1ed6c02b8e69f183ad478c135b729f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:22:22 -0800 Subject: [PATCH 123/202] build(deps): bump lodash from 4.17.21 to 4.17.23 in /client/web (#18476) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- client/web/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/web/yarn.lock b/client/web/yarn.lock index e8e5f5bb66450..106a104b98d26 100644 --- a/client/web/yarn.lock +++ b/client/web/yarn.lock @@ -4088,9 +4088,9 @@ lodash.merge@^4.6.2: integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" From c0446aa4e17d288085e98503cadb200e15018dc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:56:39 -0800 Subject: [PATCH 124/202] .github: Bump DeterminateSystems/nix-installer-action from 20 to 21 (#18453) Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 20 to 21. - [Release notes](https://github.com/determinatesystems/nix-installer-action/releases) - [Commits](https://github.com/determinatesystems/nix-installer-action/compare/786fff0690178f1234e4e1fe9b536e94f5433196...c5a866b6ab867e88becbed4467b93592bce69f8a) --- updated-dependencies: - dependency-name: DeterminateSystems/nix-installer-action dependency-version: '21' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/flakehub-publish-tagged.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flakehub-publish-tagged.yml b/.github/workflows/flakehub-publish-tagged.yml index 8b3f44338026a..798e1708a1c2a 100644 --- a/.github/workflows/flakehub-publish-tagged.yml +++ b/.github/workflows/flakehub-publish-tagged.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}" - - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 # v20 + - uses: DeterminateSystems/nix-installer-action@c5a866b6ab867e88becbed4467b93592bce69f8a # v21 - uses: DeterminateSystems/flakehub-push@71f57208810a5d299fc6545350981de98fdbc860 # v6 with: visibility: "public" From f5d1202988556d52c4a07a1083e36a2171a061b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:14:58 -0800 Subject: [PATCH 125/202] build(deps): bump postcss from 8.4.14 to 8.4.31 in /cmd/tsconnect (#9698) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.14 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.14...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cmd/tsconnect/yarn.lock | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock index d9d9db32f66a0..46d86c1ee4df1 100644 --- a/cmd/tsconnect/yarn.lock +++ b/cmd/tsconnect/yarn.lock @@ -348,10 +348,10 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -nanoid@^3.3.4: - version "3.3.8" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" - integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -397,6 +397,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -457,13 +462,13 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.14: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" preact@^10.10.0: version "10.10.0" @@ -540,10 +545,10 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" From 03d0f6c356886e4ab4e7f0bc9a5a8c0d270f97a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:22:09 -0800 Subject: [PATCH 126/202] build(deps): bump github.com/go-git/go-git/v5 from 5.13.1 to 5.16.5 (#18667) Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.13.1 to 5.16.5. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.13.1...v5.16.5) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-version: 5.16.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: Andrew Lytvynov Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- flake.nix | 2 +- go.mod | 8 ++++---- go.mod.sri | 2 +- go.sum | 20 ++++++++++---------- shell.nix | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/flake.nix b/flake.nix index af860c09ce627..726d9b7762284 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-A3mjdGE6B5t6sdkHieZZGVYlCyvhdrcqpNaHxISAPuk= +# nix-direnv cache busting line: sha256-8J1iLhnLzrLrh6MFLcCyO+iYT0jjczxNDW3O6a6f+xM= diff --git a/go.mod b/go.mod index 468b085a180a0..7d9684a129920 100644 --- a/go.mod +++ b/go.mod @@ -281,7 +281,7 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect @@ -334,7 +334,7 @@ require ( github.com/go-critic/go-critic v0.11.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.13.1 // indirect + github.com/go-git/go-git/v5 v5.16.5 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect @@ -420,7 +420,7 @@ require ( github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.25 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.4.8 // indirect @@ -444,7 +444,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.7.1 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect diff --git a/go.mod.sri b/go.mod.sri index 7cab0c4024e7e..b92d32a95a925 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-A3mjdGE6B5t6sdkHieZZGVYlCyvhdrcqpNaHxISAPuk= +sha256-8J1iLhnLzrLrh6MFLcCyO+iYT0jjczxNDW3O6a6f+xM= diff --git a/go.sum b/go.sum index 10febf73a81b8..ca3d0ca21abe0 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= @@ -337,8 +337,8 @@ github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= github.com/elastic/crd-ref-docs v0.0.12 h1:F3seyncbzUz3rT3d+caeYWhumb5ojYQ6Bl0Z+zOp16M= github.com/elastic/crd-ref-docs v0.0.12/go.mod h1:X83mMBdJt05heJUYiS3T0yJ/JkCuliuhSUNav5Gjo/U= -github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= -github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -398,8 +398,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= -github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= +github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -940,8 +940,8 @@ github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0 github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1063,8 +1063,8 @@ github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+W github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= diff --git a/shell.nix b/shell.nix index 652e41a94469b..b5c3b82cae751 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-A3mjdGE6B5t6sdkHieZZGVYlCyvhdrcqpNaHxISAPuk= +# nix-direnv cache busting line: sha256-8J1iLhnLzrLrh6MFLcCyO+iYT0jjczxNDW3O6a6f+xM= From 03247a35d5e3d811a547214f0b336a999d603743 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:22:34 -0800 Subject: [PATCH 127/202] .github: Bump actions/create-github-app-token from 2.0.6 to 2.2.1 (#18388) Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2.0.6 to 2.2.1. - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/df432ceedc7162793a195dd1713ff69aefc7379e...29824e69f54612133e76f7eaac726eef6c875baf) --- updated-dependencies: - dependency-name: actions/create-github-app-token dependency-version: 2.2.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/request-dataplane-review.yml | 2 +- .github/workflows/update-flake.yml | 2 +- .github/workflows/update-webclient-prebuilt.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/request-dataplane-review.yml b/.github/workflows/request-dataplane-review.yml index 7ca3b98022ce7..2e30ba06d4629 100644 --- a/.github/workflows/request-dataplane-review.yml +++ b/.github/workflows/request-dataplane-review.yml @@ -18,7 +18,7 @@ jobs: - name: Check out code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Get access token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token with: # Get token for app: https://github.com/apps/change-visibility-bot diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 69c954384e9bc..0c40758543458 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -27,7 +27,7 @@ jobs: run: ./update-flake.sh - name: Get access token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token with: # Get token for app: https://github.com/apps/tailscale-code-updater diff --git a/.github/workflows/update-webclient-prebuilt.yml b/.github/workflows/update-webclient-prebuilt.yml index c302e4f2091ca..2f4f676c5d354 100644 --- a/.github/workflows/update-webclient-prebuilt.yml +++ b/.github/workflows/update-webclient-prebuilt.yml @@ -23,7 +23,7 @@ jobs: ./tool/go mod tidy - name: Get access token - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token with: # Get token for app: https://github.com/apps/tailscale-code-updater From 9e31a68547eb3d729c11b3a7efaca48cafba5104 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:35:59 -0800 Subject: [PATCH 128/202] build(deps): bump micromatch from 4.0.5 to 4.0.8 in /cmd/tsconnect (#13335) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cmd/tsconnect/yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock index 46d86c1ee4df1..5ab282dcb2ead 100644 --- a/cmd/tsconnect/yarn.lock +++ b/cmd/tsconnect/yarn.lock @@ -89,7 +89,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -336,11 +336,11 @@ merge2@^1.3.0: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" minimist@^1.2.6: From c38d1badba578e41da4c10d3b4d2e2da61326950 Mon Sep 17 00:00:00 2001 From: Amal Bansode Date: Thu, 19 Feb 2026 11:39:16 -0800 Subject: [PATCH 129/202] cmd/tailscale/cli: add bind-address and bind-port flags to netcheck command (#18621) Add more explicit `--bind-address` and `--bind-port` flags to the `tailscale netcheck` CLI to give users control over UDP probes' source IP and UDP port. This was already supported in a less documented manner via the` TS_DEBUG_NETCHECK_UDP_BIND` environment variable. The environment variable reference is preserved and used as a fallback value in the absence of these new CLI flags. Updates tailscale/corp#36833 Signed-off-by: Amal Bansode --- cmd/tailscale/cli/netcheck.go | 87 ++++++++++++++++++++--- cmd/tailscale/cli/netcheck_test.go | 108 +++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 cmd/tailscale/cli/netcheck_test.go diff --git a/cmd/tailscale/cli/netcheck.go b/cmd/tailscale/cli/netcheck.go index c9cbce29a32cf..5e45445c79cd5 100644 --- a/cmd/tailscale/cli/netcheck.go +++ b/cmd/tailscale/cli/netcheck.go @@ -10,7 +10,9 @@ import ( "fmt" "io" "log" + "math" "net/http" + "net/netip" "sort" "strings" "time" @@ -26,6 +28,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/util/eventbus" + "tailscale.com/util/set" // The "netcheck" command also wants the portmapper linked. // @@ -41,19 +44,25 @@ var netcheckCmd = &ffcli.Command{ ShortUsage: "tailscale netcheck", ShortHelp: "Print an analysis of local network conditions", Exec: runNetcheck, - FlagSet: (func() *flag.FlagSet { - fs := newFlagSet("netcheck") - fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`) - fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency") - fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs") - return fs - })(), + FlagSet: netcheckFlagSet, } +var netcheckFlagSet = func() *flag.FlagSet { + fs := newFlagSet("netcheck") + fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`) + fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency") + fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs") + fs.StringVar(&netcheckArgs.bindAddress, "bind-address", "", "send and receive connectivity probes using this locally bound IP address; default: OS-assigned") + fs.IntVar(&netcheckArgs.bindPort, "bind-port", 0, "send and receive connectivity probes using this UDP port; default: OS-assigned") + return fs +}() + var netcheckArgs struct { - format string - every time.Duration - verbose bool + format string + every time.Duration + verbose bool + bindAddress string + bindPort int } func runNetcheck(ctx context.Context, args []string) error { @@ -73,6 +82,11 @@ func runNetcheck(ctx context.Context, args []string) error { defer pm.Close() } + flagsProvided := set.Set[string]{} + netcheckFlagSet.Visit(func(f *flag.Flag) { + flagsProvided.Add(f.Name) + }) + c := &netcheck.Client{ NetMon: netMon, PortMapper: pm, @@ -89,7 +103,17 @@ func runNetcheck(ctx context.Context, args []string) error { fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface") } - if err := c.Standalone(ctx, envknob.String("TS_DEBUG_NETCHECK_UDP_BIND")); err != nil { + bind, err := createNetcheckBindString( + netcheckArgs.bindAddress, + flagsProvided.Contains("bind-address"), + netcheckArgs.bindPort, + flagsProvided.Contains("bind-port"), + envknob.String("TS_DEBUG_NETCHECK_UDP_BIND")) + if err != nil { + return err + } + + if err := c.Standalone(ctx, bind); err != nil { fmt.Fprintln(Stderr, "netcheck: UDP test failure:", err) } @@ -265,3 +289,44 @@ func prodDERPMap(ctx context.Context, httpc *http.Client) (*tailcfg.DERPMap, err } return &derpMap, nil } + +// createNetcheckBindString determines the netcheck socket bind "address:port" string based +// on the CLI args and environment variable values used to invoke the netcheck CLI. +// Arguments cliAddressIsSet and cliPortIsSet explicitly indicate whether the +// corresponding cliAddress and cliPort were set in CLI args, instead of relying +// on in-band sentinel values. +func createNetcheckBindString(cliAddress string, cliAddressIsSet bool, cliPort int, cliPortIsSet bool, envBind string) (string, error) { + // Default to port number 0 but overwrite with a valid CLI value, if set. + var port uint16 = 0 + if cliPortIsSet { + // 0 is valid, results in OS picking port. + if cliPort >= 0 && cliPort <= math.MaxUint16 { + port = uint16(cliPort) + } else { + return "", fmt.Errorf("invalid bind port number: %d", cliPort) + } + } + + // Use CLI address, if set. + if cliAddressIsSet { + addr, err := netip.ParseAddr(cliAddress) + if err != nil { + return "", fmt.Errorf("invalid bind address: %q", cliAddress) + } + return netip.AddrPortFrom(addr, port).String(), nil + } else { + // No CLI address set, but port is set. + if cliPortIsSet { + return fmt.Sprintf(":%d", port), nil + } + } + + // Fall back to the environment variable. + // Intentionally skipping input validation here to avoid breaking legacy usage method. + if envBind != "" { + return envBind, nil + } + + // OS picks both address and port. + return ":0", nil +} diff --git a/cmd/tailscale/cli/netcheck_test.go b/cmd/tailscale/cli/netcheck_test.go new file mode 100644 index 0000000000000..b2c2bceb39dc9 --- /dev/null +++ b/cmd/tailscale/cli/netcheck_test.go @@ -0,0 +1,108 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "testing" +) + +func TestCreateBindStr(t *testing.T) { + // Test all combinations of CLI arg address, CLI arg port, and env var string + // as inputs to create netcheck bind string. + tests := []struct { + name string + cliAddress string + cliAddressIsSet bool + cliPort int + cliPortIsSet bool + envBind string + want string + wantError string + }{ + { + name: "noAddr-noPort-noEnv", + want: ":0", + }, + { + name: "yesAddrv4-noPort-noEnv", + cliAddress: "100.123.123.123", + cliAddressIsSet: true, + want: "100.123.123.123:0", + }, + { + name: "yesAddrv6-noPort-noEnv", + cliAddress: "dead::beef", + cliAddressIsSet: true, + want: "[dead::beef]:0", + }, + { + name: "yesAddr-yesPort-noEnv", + cliAddress: "100.123.123.123", + cliAddressIsSet: true, + cliPort: 456, + cliPortIsSet: true, + want: "100.123.123.123:456", + }, + { + name: "yesAddr-yesPort-yesEnv", + cliAddress: "100.123.123.123", + cliAddressIsSet: true, + cliPort: 456, + cliPortIsSet: true, + envBind: "55.55.55.55:789", + want: "100.123.123.123:456", + }, + { + name: "noAddr-yesPort-noEnv", + cliPort: 456, + cliPortIsSet: true, + want: ":456", + }, + { + name: "noAddr-yesPort-yesEnv", + cliPort: 456, + cliPortIsSet: true, + envBind: "55.55.55.55:789", + want: ":456", + }, + { + name: "noAddr-noPort-yesEnv", + envBind: "55.55.55.55:789", + want: "55.55.55.55:789", + }, + { + name: "badAddr-noPort-noEnv-1", + cliAddress: "678.678.678.678", + cliAddressIsSet: true, + wantError: `invalid bind address: "678.678.678.678"`, + }, + { + name: "badAddr-noPort-noEnv-2", + cliAddress: "lorem ipsum", + cliAddressIsSet: true, + wantError: `invalid bind address: "lorem ipsum"`, + }, + { + name: "noAddr-badPort-noEnv", + cliPort: -1, + cliPortIsSet: true, + wantError: "invalid bind port number: -1", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotErr := createNetcheckBindString(tt.cliAddress, tt.cliAddressIsSet, tt.cliPort, tt.cliPortIsSet, tt.envBind) + var gotErrStr string + if gotErr != nil { + gotErrStr = gotErr.Error() + } + if gotErrStr != tt.wantError { + t.Errorf("got error %q; want error %q", gotErrStr, tt.wantError) + } + if got != tt.want { + t.Errorf("got result %q; want result %q", got, tt.want) + } + }) + } +} From d9d95db0bbde36b57099fa684e439c2cfbf3b20b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:51:42 -0800 Subject: [PATCH 130/202] build(deps): bump github.com/go-viper/mapstructure/v2 (#16914) Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.0.0-alpha.1 to 2.4.0. - [Release notes](https://github.com/go-viper/mapstructure/releases) - [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-viper/mapstructure/compare/v2.0.0-alpha.1...v2.4.0) --- updated-dependencies: - dependency-name: github.com/go-viper/mapstructure/v2 dependency-version: 2.4.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- flake.nix | 2 +- go.mod | 2 +- go.mod.sri | 2 +- go.sum | 4 ++-- shell.nix | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 726d9b7762284..37c971f1111ce 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-8J1iLhnLzrLrh6MFLcCyO+iYT0jjczxNDW3O6a6f+xM= +# nix-direnv cache busting line: sha256-phgPg9fDR/rTJaVItwxAaqNCUR3CAkTVBxnuRRt3Kts= diff --git a/go.mod b/go.mod index 7d9684a129920..507275ba1595e 100644 --- a/go.mod +++ b/go.mod @@ -180,7 +180,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/gokrazy/gokapi v0.0.0-20250222071133-506fdb322775 // indirect diff --git a/go.mod.sri b/go.mod.sri index b92d32a95a925..a587699c5e39c 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-8J1iLhnLzrLrh6MFLcCyO+iYT0jjczxNDW3O6a6f+xM= +sha256-phgPg9fDR/rTJaVItwxAaqNCUR3CAkTVBxnuRRt3Kts= diff --git a/go.sum b/go.sum index ca3d0ca21abe0..18db5ef22d475 100644 --- a/go.sum +++ b/go.sum @@ -461,8 +461,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo= diff --git a/shell.nix b/shell.nix index b5c3b82cae751..fa19d968228ef 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-8J1iLhnLzrLrh6MFLcCyO+iYT0jjczxNDW3O6a6f+xM= +# nix-direnv cache busting line: sha256-phgPg9fDR/rTJaVItwxAaqNCUR3CAkTVBxnuRRt3Kts= From 2a60d0a007ea5803e6576b05367a207586993441 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:54:06 -0800 Subject: [PATCH 131/202] .github: Bump github/codeql-action from 3.29.8 to 4.31.10 (#18454) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.8 to 4.31.10. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/76621b61decf072c1cee8dd1ce2d2a82d33c17ed...cdefb33c0f6224e58673d9004f47f7cb3e328b89) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.10 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9363facaa2aa2..39133dc40c3dd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 From cae54e204640b785a4a95619f714ee441997aa3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:05:15 -0800 Subject: [PATCH 132/202] build(deps): bump github.com/docker/docker (#13081) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.4+incompatible to 26.1.5+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v26.1.4...v26.1.5) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- flake.nix | 2 +- go.mod | 15 ++++++++------- go.mod.sri | 2 +- go.sum | 34 ++++++++++++++++++++-------------- shell.nix | 2 +- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/flake.nix b/flake.nix index 37c971f1111ce..e15eeca6a664f 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-phgPg9fDR/rTJaVItwxAaqNCUR3CAkTVBxnuRRt3Kts= +# nix-direnv cache busting line: sha256-4orp8iQekVbhCFpt7DXLvj6dediKxo1qkWr1oe7+RaE= diff --git a/go.mod b/go.mod index 507275ba1595e..80b453cd5a9f7 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/golang/snappy v0.0.4 github.com/golangci/golangci-lint v1.57.1 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.20.3 + github.com/google/go-containerregistry v0.20.7 github.com/google/go-tpm v0.9.4 github.com/google/gopacket v1.1.19 github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 @@ -114,7 +114,7 @@ require ( golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/mod v0.30.0 golang.org/x/net v0.48.0 - golang.org/x/oauth2 v0.32.0 + golang.org/x/oauth2 v0.33.0 golang.org/x/sync v0.19.0 golang.org/x/sys v0.40.0 golang.org/x/term v0.38.0 @@ -164,6 +164,7 @@ require ( github.com/ckaznocha/intrange v0.1.0 // indirect github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v1.0.0-rc.2 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect @@ -313,15 +314,15 @@ require ( github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.12.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/docker/cli v27.5.1+incompatible // indirect + github.com/docker/cli v29.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v27.5.1+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/ettle/strcase v0.2.0 // indirect @@ -471,7 +472,7 @@ require ( github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.0 // indirect github.com/uudashr/gocognit v1.1.2 // indirect - github.com/vbatts/tar-split v0.11.6 // indirect + github.com/vbatts/tar-split v0.12.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/yagipy/maintidx v1.0.0 // indirect diff --git a/go.mod.sri b/go.mod.sri index a587699c5e39c..feea9b11b1ab0 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-phgPg9fDR/rTJaVItwxAaqNCUR3CAkTVBxnuRRt3Kts= +sha256-4orp8iQekVbhCFpt7DXLvj6dediKxo1qkWr1oe7+RaE= diff --git a/go.sum b/go.sum index 18db5ef22d475..ab4f3303623c8 100644 --- a/go.sum +++ b/go.sum @@ -262,12 +262,14 @@ github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9 github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= -github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= -github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= +github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= @@ -317,14 +319,14 @@ github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v27.5.1+incompatible h1:JB9cieUT9YNiMITtIsguaN55PLOHhBSz3LKVc6cqWaY= -github.com/docker/cli v27.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= +github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= -github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= @@ -562,8 +564,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= -github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= +github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -871,6 +873,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1189,8 +1195,8 @@ github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZ github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= -github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= -github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350 h1:w5OI+kArIBVksl8UGn6ARQshtPCQvDsbuA9NQie3GIg= github.com/vishvananda/netlink v1.3.1-0.20240922070040-084abd93d350/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= @@ -1409,8 +1415,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/shell.nix b/shell.nix index fa19d968228ef..07d3c1ad53bad 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-phgPg9fDR/rTJaVItwxAaqNCUR3CAkTVBxnuRRt3Kts= +# nix-direnv cache busting line: sha256-4orp8iQekVbhCFpt7DXLvj6dediKxo1qkWr1oe7+RaE= From 3b737edbf182631f380e6e0e2663de6df73b4bba Mon Sep 17 00:00:00 2001 From: Fran Bull Date: Wed, 28 Jan 2026 14:07:08 -0800 Subject: [PATCH 133/202] appc,feature/conn25,net: Add DNS response interception for conn25 The new version of app connector (conn25) needs to read DNS responses for domains it is interested in and store and swap out IP addresses. Add a hook to dns manager to enable this. Give the conn25 updated netmaps so that it knows when to assign connecting addresses and from what pool. Assign an address when we see a DNS response for a domain we are interested in, but don't do anything with the address yet. Updates tailscale/corp#34252 Signed-off-by: Fran Bull --- appc/conn25.go | 101 ----- appc/conn25_test.go | 178 --------- cmd/derper/depaware.txt | 2 +- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- feature/conn25/conn25.go | 471 ++++++++++++++++++++++- feature/conn25/conn25_test.go | 490 ++++++++++++++++++++++++ {appc => feature/conn25}/ippool.go | 2 +- {appc => feature/conn25}/ippool_test.go | 2 +- net/dns/manager.go | 27 +- types/appctype/appconnector.go | 15 + 11 files changed, 1000 insertions(+), 292 deletions(-) create mode 100644 feature/conn25/conn25_test.go rename {appc => feature/conn25}/ippool.go (99%) rename {appc => feature/conn25}/ippool_test.go (98%) diff --git a/appc/conn25.go b/appc/conn25.go index 08ca651fda7e9..08b2a1ade6826 100644 --- a/appc/conn25.go +++ b/appc/conn25.go @@ -5,9 +5,7 @@ package appc import ( "cmp" - "net/netip" "slices" - "sync" "tailscale.com/tailcfg" "tailscale.com/types/appctype" @@ -15,105 +13,6 @@ import ( "tailscale.com/util/set" ) -// Conn25 holds the developing state for the as yet nascent next generation app connector. -// There is currently (2025-12-08) no actual app connecting functionality. -type Conn25 struct { - mu sync.Mutex - transitIPs map[tailcfg.NodeID]map[netip.Addr]netip.Addr -} - -const dupeTransitIPMessage = "Duplicate transit address in ConnectorTransitIPRequest" - -// HandleConnectorTransitIPRequest creates a ConnectorTransitIPResponse in response to a ConnectorTransitIPRequest. -// It updates the connectors mapping of TransitIP->DestinationIP per peer (tailcfg.NodeID). -// If a peer has stored this mapping in the connector Conn25 will route traffic to TransitIPs to DestinationIPs for that peer. -func (c *Conn25) HandleConnectorTransitIPRequest(nid tailcfg.NodeID, ctipr ConnectorTransitIPRequest) ConnectorTransitIPResponse { - resp := ConnectorTransitIPResponse{} - seen := map[netip.Addr]bool{} - for _, each := range ctipr.TransitIPs { - if seen[each.TransitIP] { - resp.TransitIPs = append(resp.TransitIPs, TransitIPResponse{ - Code: OtherFailure, - Message: dupeTransitIPMessage, - }) - continue - } - tipresp := c.handleTransitIPRequest(nid, each) - seen[each.TransitIP] = true - resp.TransitIPs = append(resp.TransitIPs, tipresp) - } - return resp -} - -func (c *Conn25) handleTransitIPRequest(nid tailcfg.NodeID, tipr TransitIPRequest) TransitIPResponse { - c.mu.Lock() - defer c.mu.Unlock() - if c.transitIPs == nil { - c.transitIPs = make(map[tailcfg.NodeID]map[netip.Addr]netip.Addr) - } - peerMap, ok := c.transitIPs[nid] - if !ok { - peerMap = make(map[netip.Addr]netip.Addr) - c.transitIPs[nid] = peerMap - } - peerMap[tipr.TransitIP] = tipr.DestinationIP - return TransitIPResponse{} -} - -func (c *Conn25) transitIPTarget(nid tailcfg.NodeID, tip netip.Addr) netip.Addr { - c.mu.Lock() - defer c.mu.Unlock() - return c.transitIPs[nid][tip] -} - -// TransitIPRequest details a single TransitIP allocation request from a client to a -// connector. -type TransitIPRequest struct { - // TransitIP is the intermediate destination IP that will be received at this - // connector and will be replaced by DestinationIP when performing DNAT. - TransitIP netip.Addr `json:"transitIP,omitzero"` - - // DestinationIP is the final destination IP that connections to the TransitIP - // should be mapped to when performing DNAT. - DestinationIP netip.Addr `json:"destinationIP,omitzero"` -} - -// ConnectorTransitIPRequest is the request body for a PeerAPI request to -// /connector/transit-ip and can include zero or more TransitIP allocation requests. -type ConnectorTransitIPRequest struct { - // TransitIPs is the list of requested mappings. - TransitIPs []TransitIPRequest `json:"transitIPs,omitempty"` -} - -// TransitIPResponseCode appears in TransitIPResponse and signifies success or failure status. -type TransitIPResponseCode int - -const ( - // OK indicates that the mapping was created as requested. - OK TransitIPResponseCode = 0 - - // OtherFailure indicates that the mapping failed for a reason that does not have - // another relevant [TransitIPResponsecode]. - OtherFailure TransitIPResponseCode = 1 -) - -// TransitIPResponse is the response to a TransitIPRequest -type TransitIPResponse struct { - // Code is an error code indicating success or failure of the [TransitIPRequest]. - Code TransitIPResponseCode `json:"code,omitzero"` - // Message is an error message explaining what happened, suitable for logging but - // not necessarily suitable for displaying in a UI to non-technical users. It - // should be empty when [Code] is [OK]. - Message string `json:"message,omitzero"` -} - -// ConnectorTransitIPResponse is the response to a ConnectorTransitIPRequest -type ConnectorTransitIPResponse struct { - // TransitIPs is the list of outcomes for each requested mapping. Elements - // correspond to the order of [ConnectorTransitIPRequest.TransitIPs]. - TransitIPs []TransitIPResponse `json:"transitIPs,omitempty"` -} - const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental" // PickSplitDNSPeers looks at the netmap peers capabilities and finds which peers diff --git a/appc/conn25_test.go b/appc/conn25_test.go index 33f89749ca748..a9cb0fb7ebf9c 100644 --- a/appc/conn25_test.go +++ b/appc/conn25_test.go @@ -5,7 +5,6 @@ package appc import ( "encoding/json" - "net/netip" "reflect" "testing" @@ -14,183 +13,6 @@ import ( "tailscale.com/types/opt" ) -// TestHandleConnectorTransitIPRequestZeroLength tests that if sent a -// ConnectorTransitIPRequest with 0 TransitIPRequests, we respond with a -// ConnectorTransitIPResponse with 0 TransitIPResponses. -func TestHandleConnectorTransitIPRequestZeroLength(t *testing.T) { - c := &Conn25{} - req := ConnectorTransitIPRequest{} - nid := tailcfg.NodeID(1) - - resp := c.HandleConnectorTransitIPRequest(nid, req) - if len(resp.TransitIPs) != 0 { - t.Fatalf("n TransitIPs in response: %d, want 0", len(resp.TransitIPs)) - } -} - -// TestHandleConnectorTransitIPRequestStoresAddr tests that if sent a -// request with a transit addr and a destination addr we store that mapping -// and can retrieve it. If sent another req with a different dst for that transit addr -// we store that instead. -func TestHandleConnectorTransitIPRequestStoresAddr(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - dip := netip.MustParseAddr("1.2.3.4") - dip2 := netip.MustParseAddr("1.2.3.5") - mr := func(t, d netip.Addr) ConnectorTransitIPRequest { - return ConnectorTransitIPRequest{ - TransitIPs: []TransitIPRequest{ - {TransitIP: t, DestinationIP: d}, - }, - } - } - - resp := c.HandleConnectorTransitIPRequest(nid, mr(tip, dip)) - if len(resp.TransitIPs) != 1 { - t.Fatalf("n TransitIPs in response: %d, want 1", len(resp.TransitIPs)) - } - got := resp.TransitIPs[0].Code - if got != TransitIPResponseCode(0) { - t.Fatalf("TransitIP Code: %d, want 0", got) - } - gotAddr := c.transitIPTarget(nid, tip) - if gotAddr != dip { - t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip) - } - - // mapping can be overwritten - resp2 := c.HandleConnectorTransitIPRequest(nid, mr(tip, dip2)) - if len(resp2.TransitIPs) != 1 { - t.Fatalf("n TransitIPs in response: %d, want 1", len(resp2.TransitIPs)) - } - got2 := resp.TransitIPs[0].Code - if got2 != TransitIPResponseCode(0) { - t.Fatalf("TransitIP Code: %d, want 0", got2) - } - gotAddr2 := c.transitIPTarget(nid, tip) - if gotAddr2 != dip2 { - t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip2) - } -} - -// TestHandleConnectorTransitIPRequestMultipleTIP tests that we can -// get a req with multiple mappings and we store them all. Including -// multiple transit addrs for the same destination. -func TestHandleConnectorTransitIPRequestMultipleTIP(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - tip2 := netip.MustParseAddr("0.0.0.2") - tip3 := netip.MustParseAddr("0.0.0.3") - dip := netip.MustParseAddr("1.2.3.4") - dip2 := netip.MustParseAddr("1.2.3.5") - req := ConnectorTransitIPRequest{ - TransitIPs: []TransitIPRequest{ - {TransitIP: tip, DestinationIP: dip}, - {TransitIP: tip2, DestinationIP: dip2}, - // can store same dst addr for multiple transit addrs - {TransitIP: tip3, DestinationIP: dip}, - }, - } - resp := c.HandleConnectorTransitIPRequest(nid, req) - if len(resp.TransitIPs) != 3 { - t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) - } - - for i := 0; i < 3; i++ { - got := resp.TransitIPs[i].Code - if got != TransitIPResponseCode(0) { - t.Fatalf("i=%d TransitIP Code: %d, want 0", i, got) - } - } - gotAddr1 := c.transitIPTarget(nid, tip) - if gotAddr1 != dip { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) - } - gotAddr2 := c.transitIPTarget(nid, tip2) - if gotAddr2 != dip2 { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip2) - } - gotAddr3 := c.transitIPTarget(nid, tip3) - if gotAddr3 != dip { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip3, gotAddr3, dip) - } -} - -// TestHandleConnectorTransitIPRequestSameTIP tests that if we get -// a req that has more than one TransitIPRequest for the same transit addr -// only the first is stored, and the subsequent ones get an error code and -// message in the response. -func TestHandleConnectorTransitIPRequestSameTIP(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - tip2 := netip.MustParseAddr("0.0.0.2") - dip := netip.MustParseAddr("1.2.3.4") - dip2 := netip.MustParseAddr("1.2.3.5") - dip3 := netip.MustParseAddr("1.2.3.6") - req := ConnectorTransitIPRequest{ - TransitIPs: []TransitIPRequest{ - {TransitIP: tip, DestinationIP: dip}, - // cannot have dupe TransitIPs in one ConnectorTransitIPRequest - {TransitIP: tip, DestinationIP: dip2}, - {TransitIP: tip2, DestinationIP: dip3}, - }, - } - - resp := c.HandleConnectorTransitIPRequest(nid, req) - if len(resp.TransitIPs) != 3 { - t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) - } - - got := resp.TransitIPs[0].Code - if got != TransitIPResponseCode(0) { - t.Fatalf("i=0 TransitIP Code: %d, want 0", got) - } - msg := resp.TransitIPs[0].Message - if msg != "" { - t.Fatalf("i=0 TransitIP Message: \"%s\", want \"%s\"", msg, "") - } - got1 := resp.TransitIPs[1].Code - if got1 != TransitIPResponseCode(1) { - t.Fatalf("i=1 TransitIP Code: %d, want 1", got1) - } - msg1 := resp.TransitIPs[1].Message - if msg1 != dupeTransitIPMessage { - t.Fatalf("i=1 TransitIP Message: \"%s\", want \"%s\"", msg1, dupeTransitIPMessage) - } - got2 := resp.TransitIPs[2].Code - if got2 != TransitIPResponseCode(0) { - t.Fatalf("i=2 TransitIP Code: %d, want 0", got2) - } - msg2 := resp.TransitIPs[2].Message - if msg2 != "" { - t.Fatalf("i=2 TransitIP Message: \"%s\", want \"%s\"", msg, "") - } - - gotAddr1 := c.transitIPTarget(nid, tip) - if gotAddr1 != dip { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) - } - gotAddr2 := c.transitIPTarget(nid, tip2) - if gotAddr2 != dip3 { - t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip3) - } -} - -// TestGetDstIPUnknownTIP tests that unknown transit addresses can be looked up without problem. -func TestTransitIPTargetUnknownTIP(t *testing.T) { - c := &Conn25{} - nid := tailcfg.NodeID(1) - tip := netip.MustParseAddr("0.0.0.1") - got := c.transitIPTarget(nid, tip) - want := netip.Addr{} - if got != want { - t.Fatalf("Unknown transit addr, want: %v, got %v", want, got) - } -} - func TestPickSplitDNSPeers(t *testing.T) { getBytesForAttr := func(name string, domains []string, tags []string) []byte { attr := appctype.AppConnectorAttr{ diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 7695cf598b694..d04c66eba118e 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -45,7 +45,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa github.com/tailscale/setec/types/api from github.com/tailscale/setec/client/setec github.com/x448/float16 from github.com/fxamacker/cbor/v2 💣 go4.org/mem from tailscale.com/client/local+ - go4.org/netipx from tailscale.com/net/tsaddr + go4.org/netipx from tailscale.com/net/tsaddr+ W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/netmon+ google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+ diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 58f9e1c0bfb83..8cef9725847a3 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -149,7 +149,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/x448/float16 from github.com/fxamacker/cbor/v2 go.yaml.in/yaml/v2 from sigs.k8s.io/yaml 💣 go4.org/mem from tailscale.com/client/local+ - go4.org/netipx from tailscale.com/net/tsaddr + go4.org/netipx from tailscale.com/net/tsaddr+ W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/netmon+ k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli sigs.k8s.io/yaml from tailscale.com/cmd/tailscale/cli diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index aa25fd75f9e9e..4128ecc4ce972 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -249,7 +249,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal 💣 tailscale.com/atomicfile from tailscale.com/ipn+ LD tailscale.com/chirp from tailscale.com/cmd/tailscaled tailscale.com/client/local from tailscale.com/client/web+ diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index 33ba0e486abe3..02bec132dc10c 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -9,13 +9,24 @@ package conn25 import ( "encoding/json" + "errors" "net/http" + "net/netip" + "strings" + "sync" - "tailscale.com/appc" + "go4.org/netipx" + "golang.org/x/net/dns/dnsmessage" "tailscale.com/feature" "tailscale.com/ipn/ipnext" "tailscale.com/ipn/ipnlocal" + "tailscale.com/net/dns" + "tailscale.com/tailcfg" + "tailscale.com/types/appctype" "tailscale.com/types/logger" + "tailscale.com/util/dnsname" + "tailscale.com/util/mak" + "tailscale.com/util/set" ) // featureName is the name of the feature implemented by this package. @@ -26,7 +37,8 @@ func init() { feature.Register(featureName) newExtension := func(logf logger.Logf, sb ipnext.SafeBackend) (ipnext.Extension, error) { e := &extension{ - conn: &appc.Conn25{}, + conn25: newConn25(logger.WithPrefix(logf, "conn25: ")), + backend: sb, } return e, nil } @@ -46,7 +58,11 @@ func handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.ResponseWriter, // extension is an [ipnext.Extension] managing the connector on platforms // that import this package. type extension struct { - conn *appc.Conn25 + conn25 *Conn25 // safe for concurrent access and only set at creation + backend ipnext.SafeBackend // safe for concurrent access and only set at creation + + mu sync.Mutex // protects the fields below + isDNSHookRegistered bool } // Name implements [ipnext.Extension]. @@ -56,6 +72,7 @@ func (e *extension) Name() string { // Init implements [ipnext.Extension]. func (e *extension) Init(host ipnext.Host) error { + host.Hooks().OnSelfChange.Add(e.onSelfChange) return nil } @@ -71,13 +88,13 @@ func (e *extension) handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.R http.Error(w, "Method should be POST", http.StatusMethodNotAllowed) return } - var req appc.ConnectorTransitIPRequest + var req ConnectorTransitIPRequest err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxBodyBytes+1)).Decode(&req) if err != nil { http.Error(w, "Error decoding JSON", http.StatusBadRequest) return } - resp := e.conn.HandleConnectorTransitIPRequest(h.Peer().ID(), req) + resp := e.conn25.handleConnectorTransitIPRequest(h.Peer().ID(), req) bs, err := json.Marshal(resp) if err != nil { http.Error(w, "Error encoding JSON", http.StatusInternalServerError) @@ -85,3 +102,447 @@ func (e *extension) handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.R } w.Write(bs) } + +func (e *extension) onSelfChange(selfNode tailcfg.NodeView) { + err := e.conn25.reconfig(selfNode) + if err != nil { + e.conn25.client.logf("error during Reconfig onSelfChange: %v", err) + return + } + + if e.conn25.isConfigured() { + err = e.registerDNSHook() + } else { + err = e.unregisterDNSHook() + } + if err != nil { + e.conn25.client.logf("error managing DNS hook onSelfChange: %v", err) + } +} + +func (e *extension) registerDNSHook() error { + e.mu.Lock() + defer e.mu.Unlock() + if e.isDNSHookRegistered { + return nil + } + err := e.setDNSHookLocked(e.conn25.mapDNSResponse) + if err == nil { + e.isDNSHookRegistered = true + } + return err +} + +func (e *extension) unregisterDNSHook() error { + e.mu.Lock() + defer e.mu.Unlock() + if !e.isDNSHookRegistered { + return nil + } + err := e.setDNSHookLocked(nil) + if err == nil { + e.isDNSHookRegistered = false + } + return err +} + +func (e *extension) setDNSHookLocked(fx dns.ResponseMapper) error { + dnsManager, ok := e.backend.Sys().DNSManager.GetOK() + if !ok || dnsManager == nil { + return errors.New("couldn't get DNSManager from sys") + } + dnsManager.SetQueryResponseMapper(fx) + return nil +} + +type appAddr struct { + app string + addr netip.Addr +} + +// Conn25 holds state for routing traffic for a domain via a connector. +type Conn25 struct { + client *client + connector *connector +} + +func (c *Conn25) isConfigured() bool { + return c.client.isConfigured() +} + +func newConn25(logf logger.Logf) *Conn25 { + c := &Conn25{ + client: &client{logf: logf}, + connector: &connector{logf: logf}, + } + return c +} + +func ipSetFromIPRanges(rs []netipx.IPRange) (*netipx.IPSet, error) { + b := &netipx.IPSetBuilder{} + for _, r := range rs { + b.AddRange(r) + } + return b.IPSet() +} + +func (c *Conn25) reconfig(selfNode tailcfg.NodeView) error { + cfg, err := configFromNodeView(selfNode) + if err != nil { + return err + } + if err := c.client.reconfig(cfg); err != nil { + return err + } + if err := c.connector.reconfig(cfg); err != nil { + return err + } + return nil +} + +// mapDNSResponse parses and inspects the DNS response, and uses the +// contents to assign addresses for connecting. It does not yet modify +// the response. +func (c *Conn25) mapDNSResponse(buf []byte) []byte { + return c.client.mapDNSResponse(buf) +} + +const dupeTransitIPMessage = "Duplicate transit address in ConnectorTransitIPRequest" + +// handleConnectorTransitIPRequest creates a ConnectorTransitIPResponse in response to a ConnectorTransitIPRequest. +// It updates the connectors mapping of TransitIP->DestinationIP per peer (tailcfg.NodeID). +// If a peer has stored this mapping in the connector Conn25 will route traffic to TransitIPs to DestinationIPs for that peer. +func (c *Conn25) handleConnectorTransitIPRequest(nid tailcfg.NodeID, ctipr ConnectorTransitIPRequest) ConnectorTransitIPResponse { + resp := ConnectorTransitIPResponse{} + seen := map[netip.Addr]bool{} + for _, each := range ctipr.TransitIPs { + if seen[each.TransitIP] { + resp.TransitIPs = append(resp.TransitIPs, TransitIPResponse{ + Code: OtherFailure, + Message: dupeTransitIPMessage, + }) + continue + } + tipresp := c.connector.handleTransitIPRequest(nid, each) + seen[each.TransitIP] = true + resp.TransitIPs = append(resp.TransitIPs, tipresp) + } + return resp +} + +func (s *connector) handleTransitIPRequest(nid tailcfg.NodeID, tipr TransitIPRequest) TransitIPResponse { + s.mu.Lock() + defer s.mu.Unlock() + if s.transitIPs == nil { + s.transitIPs = make(map[tailcfg.NodeID]map[netip.Addr]appAddr) + } + peerMap, ok := s.transitIPs[nid] + if !ok { + peerMap = make(map[netip.Addr]appAddr) + s.transitIPs[nid] = peerMap + } + peerMap[tipr.TransitIP] = appAddr{addr: tipr.DestinationIP, app: tipr.App} + return TransitIPResponse{} +} + +func (s *connector) transitIPTarget(nid tailcfg.NodeID, tip netip.Addr) netip.Addr { + s.mu.Lock() + defer s.mu.Unlock() + return s.transitIPs[nid][tip].addr +} + +// TransitIPRequest details a single TransitIP allocation request from a client to a +// connector. +type TransitIPRequest struct { + // TransitIP is the intermediate destination IP that will be received at this + // connector and will be replaced by DestinationIP when performing DNAT. + TransitIP netip.Addr `json:"transitIP,omitzero"` + + // DestinationIP is the final destination IP that connections to the TransitIP + // should be mapped to when performing DNAT. + DestinationIP netip.Addr `json:"destinationIP,omitzero"` + + // App is the name of the connector application from the tailnet + // configuration. + App string `json:"app,omitzero"` +} + +// ConnectorTransitIPRequest is the request body for a PeerAPI request to +// /connector/transit-ip and can include zero or more TransitIP allocation requests. +type ConnectorTransitIPRequest struct { + // TransitIPs is the list of requested mappings. + TransitIPs []TransitIPRequest `json:"transitIPs,omitempty"` +} + +// TransitIPResponseCode appears in TransitIPResponse and signifies success or failure status. +type TransitIPResponseCode int + +const ( + // OK indicates that the mapping was created as requested. + OK TransitIPResponseCode = 0 + + // OtherFailure indicates that the mapping failed for a reason that does not have + // another relevant [TransitIPResponsecode]. + OtherFailure TransitIPResponseCode = 1 +) + +// TransitIPResponse is the response to a TransitIPRequest +type TransitIPResponse struct { + // Code is an error code indicating success or failure of the [TransitIPRequest]. + Code TransitIPResponseCode `json:"code,omitzero"` + // Message is an error message explaining what happened, suitable for logging but + // not necessarily suitable for displaying in a UI to non-technical users. It + // should be empty when [Code] is [OK]. + Message string `json:"message,omitzero"` +} + +// ConnectorTransitIPResponse is the response to a ConnectorTransitIPRequest +type ConnectorTransitIPResponse struct { + // TransitIPs is the list of outcomes for each requested mapping. Elements + // correspond to the order of [ConnectorTransitIPRequest.TransitIPs]. + TransitIPs []TransitIPResponse `json:"transitIPs,omitempty"` +} + +const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental" + +// config holds the config from the policy and lookups derived from that. +// config is not safe for concurrent use. +type config struct { + isConfigured bool + apps []appctype.Conn25Attr + appsByDomain map[string][]string + selfRoutedDomains set.Set[string] +} + +func configFromNodeView(n tailcfg.NodeView) (config, error) { + apps, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.Conn25Attr](n.CapMap(), AppConnectorsExperimentalAttrName) + if err != nil { + return config{}, err + } + if len(apps) == 0 { + return config{}, nil + } + selfTags := set.SetOf(n.Tags().AsSlice()) + cfg := config{ + isConfigured: true, + apps: apps, + appsByDomain: map[string][]string{}, + selfRoutedDomains: set.Set[string]{}, + } + for _, app := range apps { + selfMatchesTags := false + for _, tag := range app.Connectors { + if selfTags.Contains(tag) { + selfMatchesTags = true + break + } + } + for _, d := range app.Domains { + fqdn, err := dnsname.ToFQDN(d) + if err != nil { + return config{}, err + } + key := fqdn.WithTrailingDot() + mak.Set(&cfg.appsByDomain, key, append(cfg.appsByDomain[key], app.Name)) + if selfMatchesTags { + cfg.selfRoutedDomains.Add(key) + } + } + } + return cfg, nil +} + +// client performs the conn25 functionality for clients of connectors +// It allocates magic and transit IP addresses and communicates them with +// connectors. +// It's safe for concurrent use. +type client struct { + logf logger.Logf + + mu sync.Mutex // protects the fields below + magicIPPool *ippool + transitIPPool *ippool + // map of magic IP -> (transit IP, app) + magicIPs map[netip.Addr]appAddr + config config +} + +func (c *client) isConfigured() bool { + c.mu.Lock() + defer c.mu.Unlock() + return c.config.isConfigured +} + +func (c *client) reconfig(newCfg config) error { + c.mu.Lock() + defer c.mu.Unlock() + + c.config = newCfg + + // TODO(fran) this is not the correct way to manage the pools and changes to the pools. + // We probably want to: + // * check the pools haven't changed + // * reset the whole connector if the pools change? or just if they've changed to exclude + // addresses we have in use? + // * have config separate from the apps for this (rather than multiple potentially conflicting places) + // but this works while we are just getting started here. + for _, app := range c.config.apps { + if c.magicIPPool != nil { // just take the first config and never reconfig + break + } + if app.MagicIPPool == nil { + continue + } + mipp, err := ipSetFromIPRanges(app.MagicIPPool) + if err != nil { + return err + } + tipp, err := ipSetFromIPRanges(app.TransitIPPool) + if err != nil { + return err + } + c.magicIPPool = newIPPool(mipp) + c.transitIPPool = newIPPool(tipp) + } + return nil +} + +func (c *client) setMagicIP(magicAddr, transitAddr netip.Addr, app string) { + c.mu.Lock() + defer c.mu.Unlock() + mak.Set(&c.magicIPs, magicAddr, appAddr{addr: transitAddr, app: app}) +} + +func (c *client) isConnectorDomain(domain string) bool { + c.mu.Lock() + defer c.mu.Unlock() + appNames, ok := c.config.appsByDomain[domain] + return ok && len(appNames) > 0 +} + +// reserveAddresses tries to make an assignment of addrs from the address pools +// for this domain+dst address, so that this client can use conn25 connectors. +// It checks that this domain should be routed and that this client is not itself a connector for the domain +// and generally if it is valid to make the assignment. +func (c *client) reserveAddresses(domain string, dst netip.Addr) (addrs, error) { + c.mu.Lock() + defer c.mu.Unlock() + appNames, _ := c.config.appsByDomain[domain] + // only reserve for first app + app := appNames[0] + mip, err := c.magicIPPool.next() + if err != nil { + return addrs{}, err + } + tip, err := c.transitIPPool.next() + if err != nil { + return addrs{}, err + } + addrs := addrs{ + dst: dst, + magic: mip, + transit: tip, + app: app, + } + return addrs, nil +} + +func (c *client) enqueueAddressAssignment(addrs addrs) { + c.setMagicIP(addrs.magic, addrs.transit, addrs.app) + // TODO(fran) 2026-02-03 asynchronously send peerapi req to connector to + // allocate these addresses for us. +} + +func (c *client) mapDNSResponse(buf []byte) []byte { + var p dnsmessage.Parser + if _, err := p.Start(buf); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + if err := p.SkipAllQuestions(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + for { + h, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + break + } + if err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + + if h.Class != dnsmessage.ClassINET { + if err := p.SkipAnswer(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + continue + } + + switch h.Type { + case dnsmessage.TypeA: + domain := strings.ToLower(h.Name.String()) + if len(domain) == 0 || !c.isConnectorDomain(domain) { + if err := p.SkipAnswer(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + continue + } + r, err := p.AResource() + if err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + addrs, err := c.reserveAddresses(domain, netip.AddrFrom4(r.A)) + if err != nil { + c.logf("error assigning connector addresses: %v", err) + return buf + } + if !addrs.isValid() { + c.logf("assigned connector addresses unexpectedly empty: %v", err) + return buf + } + c.enqueueAddressAssignment(addrs) + default: + if err := p.SkipAnswer(); err != nil { + c.logf("error parsing dns response: %v", err) + return buf + } + continue + } + } + + // TODO(fran) 2026-01-21 return a dns response with addresses + // swapped out for the magic IPs to make conn25 work. + return buf +} + +type connector struct { + logf logger.Logf + + mu sync.Mutex // protects the fields below + // transitIPs is a map of connector client peer NodeID -> client transitIPs that we update as connector client peers instruct us to, and then use to route traffic to its destination on behalf of connector clients. + transitIPs map[tailcfg.NodeID]map[netip.Addr]appAddr + config config +} + +func (s *connector) reconfig(newCfg config) error { + s.mu.Lock() + defer s.mu.Unlock() + s.config = newCfg + return nil +} + +type addrs struct { + dst netip.Addr + magic netip.Addr + transit netip.Addr + app string +} + +func (c addrs) isValid() bool { + return c.dst.IsValid() +} diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go new file mode 100644 index 0000000000000..0489b22a14e4d --- /dev/null +++ b/feature/conn25/conn25_test.go @@ -0,0 +1,490 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package conn25 + +import ( + "encoding/json" + "net/netip" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "go4.org/netipx" + "golang.org/x/net/dns/dnsmessage" + "tailscale.com/tailcfg" + "tailscale.com/types/appctype" + "tailscale.com/types/logger" + "tailscale.com/util/set" +) + +func mustIPSetFromPrefix(s string) *netipx.IPSet { + b := &netipx.IPSetBuilder{} + b.AddPrefix(netip.MustParsePrefix(s)) + set, err := b.IPSet() + if err != nil { + panic(err) + } + return set +} + +// TestHandleConnectorTransitIPRequestZeroLength tests that if sent a +// ConnectorTransitIPRequest with 0 TransitIPRequests, we respond with a +// ConnectorTransitIPResponse with 0 TransitIPResponses. +func TestHandleConnectorTransitIPRequestZeroLength(t *testing.T) { + c := newConn25(logger.Discard) + req := ConnectorTransitIPRequest{} + nid := tailcfg.NodeID(1) + + resp := c.handleConnectorTransitIPRequest(nid, req) + if len(resp.TransitIPs) != 0 { + t.Fatalf("n TransitIPs in response: %d, want 0", len(resp.TransitIPs)) + } +} + +// TestHandleConnectorTransitIPRequestStoresAddr tests that if sent a +// request with a transit addr and a destination addr we store that mapping +// and can retrieve it. If sent another req with a different dst for that transit addr +// we store that instead. +func TestHandleConnectorTransitIPRequestStoresAddr(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + dip := netip.MustParseAddr("1.2.3.4") + dip2 := netip.MustParseAddr("1.2.3.5") + mr := func(t, d netip.Addr) ConnectorTransitIPRequest { + return ConnectorTransitIPRequest{ + TransitIPs: []TransitIPRequest{ + {TransitIP: t, DestinationIP: d}, + }, + } + } + + resp := c.handleConnectorTransitIPRequest(nid, mr(tip, dip)) + if len(resp.TransitIPs) != 1 { + t.Fatalf("n TransitIPs in response: %d, want 1", len(resp.TransitIPs)) + } + got := resp.TransitIPs[0].Code + if got != TransitIPResponseCode(0) { + t.Fatalf("TransitIP Code: %d, want 0", got) + } + gotAddr := c.connector.transitIPTarget(nid, tip) + if gotAddr != dip { + t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip) + } + + // mapping can be overwritten + resp2 := c.handleConnectorTransitIPRequest(nid, mr(tip, dip2)) + if len(resp2.TransitIPs) != 1 { + t.Fatalf("n TransitIPs in response: %d, want 1", len(resp2.TransitIPs)) + } + got2 := resp.TransitIPs[0].Code + if got2 != TransitIPResponseCode(0) { + t.Fatalf("TransitIP Code: %d, want 0", got2) + } + gotAddr2 := c.connector.transitIPTarget(nid, tip) + if gotAddr2 != dip2 { + t.Fatalf("Connector stored destination for tip: %v, want %v", gotAddr, dip2) + } +} + +// TestHandleConnectorTransitIPRequestMultipleTIP tests that we can +// get a req with multiple mappings and we store them all. Including +// multiple transit addrs for the same destination. +func TestHandleConnectorTransitIPRequestMultipleTIP(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + tip2 := netip.MustParseAddr("0.0.0.2") + tip3 := netip.MustParseAddr("0.0.0.3") + dip := netip.MustParseAddr("1.2.3.4") + dip2 := netip.MustParseAddr("1.2.3.5") + req := ConnectorTransitIPRequest{ + TransitIPs: []TransitIPRequest{ + {TransitIP: tip, DestinationIP: dip}, + {TransitIP: tip2, DestinationIP: dip2}, + // can store same dst addr for multiple transit addrs + {TransitIP: tip3, DestinationIP: dip}, + }, + } + resp := c.handleConnectorTransitIPRequest(nid, req) + if len(resp.TransitIPs) != 3 { + t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) + } + + for i := 0; i < 3; i++ { + got := resp.TransitIPs[i].Code + if got != TransitIPResponseCode(0) { + t.Fatalf("i=%d TransitIP Code: %d, want 0", i, got) + } + } + gotAddr1 := c.connector.transitIPTarget(nid, tip) + if gotAddr1 != dip { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) + } + gotAddr2 := c.connector.transitIPTarget(nid, tip2) + if gotAddr2 != dip2 { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip2) + } + gotAddr3 := c.connector.transitIPTarget(nid, tip3) + if gotAddr3 != dip { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip3, gotAddr3, dip) + } +} + +// TestHandleConnectorTransitIPRequestSameTIP tests that if we get +// a req that has more than one TransitIPRequest for the same transit addr +// only the first is stored, and the subsequent ones get an error code and +// message in the response. +func TestHandleConnectorTransitIPRequestSameTIP(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + tip2 := netip.MustParseAddr("0.0.0.2") + dip := netip.MustParseAddr("1.2.3.4") + dip2 := netip.MustParseAddr("1.2.3.5") + dip3 := netip.MustParseAddr("1.2.3.6") + req := ConnectorTransitIPRequest{ + TransitIPs: []TransitIPRequest{ + {TransitIP: tip, DestinationIP: dip}, + // cannot have dupe TransitIPs in one ConnectorTransitIPRequest + {TransitIP: tip, DestinationIP: dip2}, + {TransitIP: tip2, DestinationIP: dip3}, + }, + } + + resp := c.handleConnectorTransitIPRequest(nid, req) + if len(resp.TransitIPs) != 3 { + t.Fatalf("n TransitIPs in response: %d, want 3", len(resp.TransitIPs)) + } + + got := resp.TransitIPs[0].Code + if got != TransitIPResponseCode(0) { + t.Fatalf("i=0 TransitIP Code: %d, want 0", got) + } + msg := resp.TransitIPs[0].Message + if msg != "" { + t.Fatalf("i=0 TransitIP Message: \"%s\", want \"%s\"", msg, "") + } + got1 := resp.TransitIPs[1].Code + if got1 != TransitIPResponseCode(1) { + t.Fatalf("i=1 TransitIP Code: %d, want 1", got1) + } + msg1 := resp.TransitIPs[1].Message + if msg1 != dupeTransitIPMessage { + t.Fatalf("i=1 TransitIP Message: \"%s\", want \"%s\"", msg1, dupeTransitIPMessage) + } + got2 := resp.TransitIPs[2].Code + if got2 != TransitIPResponseCode(0) { + t.Fatalf("i=2 TransitIP Code: %d, want 0", got2) + } + msg2 := resp.TransitIPs[2].Message + if msg2 != "" { + t.Fatalf("i=2 TransitIP Message: \"%s\", want \"%s\"", msg, "") + } + + gotAddr1 := c.connector.transitIPTarget(nid, tip) + if gotAddr1 != dip { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip, gotAddr1, dip) + } + gotAddr2 := c.connector.transitIPTarget(nid, tip2) + if gotAddr2 != dip3 { + t.Fatalf("Connector stored destination for tip(%v): %v, want %v", tip2, gotAddr2, dip3) + } +} + +// TestGetDstIPUnknownTIP tests that unknown transit addresses can be looked up without problem. +func TestTransitIPTargetUnknownTIP(t *testing.T) { + c := newConn25(logger.Discard) + nid := tailcfg.NodeID(1) + tip := netip.MustParseAddr("0.0.0.1") + got := c.connector.transitIPTarget(nid, tip) + want := netip.Addr{} + if got != want { + t.Fatalf("Unknown transit addr, want: %v, got %v", want, got) + } +} + +func TestSetMagicIP(t *testing.T) { + c := newConn25(logger.Discard) + mip := netip.MustParseAddr("0.0.0.1") + tip := netip.MustParseAddr("0.0.0.2") + app := "a" + c.client.setMagicIP(mip, tip, app) + val, ok := c.client.magicIPs[mip] + if !ok { + t.Fatal("expected there to be a value stored for the magic IP") + } + if val.addr != tip { + t.Fatalf("want %v, got %v", tip, val.addr) + } + if val.app != app { + t.Fatalf("want %s, got %s", app, val.app) + } +} + +func TestReserveIPs(t *testing.T) { + c := newConn25(logger.Discard) + c.client.magicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24")) + c.client.transitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24")) + mbd := map[string][]string{} + mbd["example.com."] = []string{"a"} + c.client.config.appsByDomain = mbd + + dst := netip.MustParseAddr("0.0.0.1") + con, err := c.client.reserveAddresses("example.com.", dst) + if err != nil { + t.Fatal(err) + } + + wantDst := netip.MustParseAddr("0.0.0.1") // same as dst we pass in + wantMagic := netip.MustParseAddr("100.64.0.0") // first from magic pool + wantTransit := netip.MustParseAddr("169.254.0.0") // first from transit pool + wantApp := "a" // the app name related to example.com. + + if wantDst != con.dst { + t.Errorf("want %v, got %v", wantDst, con.dst) + } + if wantMagic != con.magic { + t.Errorf("want %v, got %v", wantMagic, con.magic) + } + if wantTransit != con.transit { + t.Errorf("want %v, got %v", wantTransit, con.transit) + } + if wantApp != con.app { + t.Errorf("want %s, got %s", wantApp, con.app) + } +} + +func TestReconfig(t *testing.T) { + rawCfg := `{"name":"app1","connectors":["tag:woo"],"domains":["example.com"]}` + capMap := tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ + tailcfg.RawMessage(rawCfg), + }, + } + + c := newConn25(logger.Discard) + sn := (&tailcfg.Node{ + CapMap: capMap, + }).View() + + err := c.reconfig(sn) + if err != nil { + t.Fatal(err) + } + + if len(c.client.config.apps) != 1 || c.client.config.apps[0].Name != "app1" { + t.Fatalf("want apps to have one entry 'app1', got %v", c.client.config.apps) + } +} + +func TestConfigReconfig(t *testing.T) { + for _, tt := range []struct { + name string + rawCfg string + cfg []appctype.Conn25Attr + tags []string + wantErr bool + wantAppsByDomain map[string][]string + wantSelfRoutedDomains set.Set[string] + }{ + { + name: "bad-config", + rawCfg: `bad`, + wantErr: true, + }, + { + name: "simple", + cfg: []appctype.Conn25Attr{ + {Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}}, + {Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}}, + }, + tags: []string{"tag:one"}, + wantAppsByDomain: map[string][]string{ + "a.example.com.": {"one"}, + "b.example.com.": {"two"}, + }, + wantSelfRoutedDomains: set.SetOf([]string{"a.example.com."}), + }, + { + name: "more-complex", + cfg: []appctype.Conn25Attr{ + {Name: "one", Domains: []string{"1.a.example.com", "1.b.example.com"}, Connectors: []string{"tag:one", "tag:onea"}}, + {Name: "two", Domains: []string{"2.b.example.com", "2.c.example.com"}, Connectors: []string{"tag:two", "tag:twoa"}}, + {Name: "three", Domains: []string{"1.b.example.com", "1.c.example.com"}, Connectors: []string{}}, + {Name: "four", Domains: []string{"4.b.example.com", "4.d.example.com"}, Connectors: []string{"tag:four"}}, + }, + tags: []string{"tag:onea", "tag:four", "tag:unrelated"}, + wantAppsByDomain: map[string][]string{ + "1.a.example.com.": {"one"}, + "1.b.example.com.": {"one", "three"}, + "1.c.example.com.": {"three"}, + "2.b.example.com.": {"two"}, + "2.c.example.com.": {"two"}, + "4.b.example.com.": {"four"}, + "4.d.example.com.": {"four"}, + }, + wantSelfRoutedDomains: set.SetOf([]string{"1.a.example.com.", "1.b.example.com.", "4.b.example.com.", "4.d.example.com."}), + }, + } { + t.Run(tt.name, func(t *testing.T) { + cfg := []tailcfg.RawMessage{tailcfg.RawMessage(tt.rawCfg)} + if tt.cfg != nil { + cfg = []tailcfg.RawMessage{} + for _, attr := range tt.cfg { + bs, err := json.Marshal(attr) + if err != nil { + t.Fatalf("unexpected error in test setup: %v", err) + } + cfg = append(cfg, tailcfg.RawMessage(bs)) + } + } + capMap := tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg, + } + sn := (&tailcfg.Node{ + CapMap: capMap, + Tags: tt.tags, + }).View() + c, err := configFromNodeView(sn) + if (err != nil) != tt.wantErr { + t.Fatalf("wantErr: %t, err: %v", tt.wantErr, err) + } + if diff := cmp.Diff(tt.wantAppsByDomain, c.appsByDomain); diff != "" { + t.Errorf("appsByDomain diff (-want, +got):\n%s", diff) + } + if diff := cmp.Diff(tt.wantSelfRoutedDomains, c.selfRoutedDomains); diff != "" { + t.Errorf("selfRoutedDomains diff (-want, +got):\n%s", diff) + } + }) + } +} + +func makeSelfNode(t *testing.T, attr appctype.Conn25Attr, tags []string) tailcfg.NodeView { + t.Helper() + bs, err := json.Marshal(attr) + if err != nil { + t.Fatalf("unexpected error in test setup: %v", err) + } + cfg := []tailcfg.RawMessage{tailcfg.RawMessage(bs)} + capMap := tailcfg.NodeCapMap{ + tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg, + } + return (&tailcfg.Node{ + CapMap: capMap, + Tags: tags, + }).View() +} + +func rangeFrom(from, to string) netipx.IPRange { + return netipx.IPRangeFrom( + netip.MustParseAddr("100.64.0."+from), + netip.MustParseAddr("100.64.0."+to), + ) +} + +func TestMapDNSResponse(t *testing.T) { + makeDNSResponse := func(domain string, addrs []dnsmessage.AResource) []byte { + b := dnsmessage.NewBuilder(nil, + dnsmessage.Header{ + ID: 1, + Response: true, + Authoritative: true, + RCode: dnsmessage.RCodeSuccess, + }) + b.EnableCompression() + + if err := b.StartQuestions(); err != nil { + t.Fatal(err) + } + + if err := b.Question(dnsmessage.Question{ + Name: dnsmessage.MustNewName(domain), + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + }); err != nil { + t.Fatal(err) + } + + if err := b.StartAnswers(); err != nil { + t.Fatal(err) + } + + for _, addr := range addrs { + b.AResource( + dnsmessage.ResourceHeader{ + Name: dnsmessage.MustNewName(domain), + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + }, + addr, + ) + } + + outbs, err := b.Finish() + if err != nil { + t.Fatal(err) + } + return outbs + } + + for _, tt := range []struct { + name string + domain string + addrs []dnsmessage.AResource + wantMagicIPs map[netip.Addr]appAddr + }{ + { + name: "one-ip-matches", + domain: "example.com.", + addrs: []dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}}, + // these are 'expected' because they are the beginning of the provided pools + wantMagicIPs: map[netip.Addr]appAddr{ + netip.MustParseAddr("100.64.0.0"): {app: "app1", addr: netip.MustParseAddr("100.64.0.40")}, + }, + }, + { + name: "multiple-ip-matches", + domain: "example.com.", + addrs: []dnsmessage.AResource{ + {A: [4]byte{1, 0, 0, 0}}, + {A: [4]byte{2, 0, 0, 0}}, + }, + wantMagicIPs: map[netip.Addr]appAddr{ + netip.MustParseAddr("100.64.0.0"): {app: "app1", addr: netip.MustParseAddr("100.64.0.40")}, + netip.MustParseAddr("100.64.0.1"): {app: "app1", addr: netip.MustParseAddr("100.64.0.41")}, + }, + }, + { + name: "no-domain-match", + domain: "x.example.com.", + addrs: []dnsmessage.AResource{ + {A: [4]byte{1, 0, 0, 0}}, + {A: [4]byte{2, 0, 0, 0}}, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + dnsResp := makeDNSResponse(tt.domain, tt.addrs) + sn := makeSelfNode(t, appctype.Conn25Attr{ + Name: "app1", + Connectors: []string{"tag:woo"}, + Domains: []string{"example.com"}, + MagicIPPool: []netipx.IPRange{rangeFrom("0", "10"), rangeFrom("20", "30")}, + TransitIPPool: []netipx.IPRange{rangeFrom("40", "50")}, + }, []string{}) + c := newConn25(logger.Discard) + c.reconfig(sn) + + bs := c.mapDNSResponse(dnsResp) + if !reflect.DeepEqual(dnsResp, bs) { + t.Fatal("shouldn't be changing the bytes (yet)") + } + if diff := cmp.Diff(tt.wantMagicIPs, c.client.magicIPs, cmpopts.EquateComparable(appAddr{}, netip.Addr{})); diff != "" { + t.Errorf("magicIPs diff (-want, +got):\n%s", diff) + } + }) + } +} diff --git a/appc/ippool.go b/feature/conn25/ippool.go similarity index 99% rename from appc/ippool.go rename to feature/conn25/ippool.go index 702f79ddef8d8..e50186d880914 100644 --- a/appc/ippool.go +++ b/feature/conn25/ippool.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package appc +package conn25 import ( "errors" diff --git a/appc/ippool_test.go b/feature/conn25/ippool_test.go similarity index 98% rename from appc/ippool_test.go rename to feature/conn25/ippool_test.go index 8ac457c117475..ccfaad3eb71e1 100644 --- a/appc/ippool_test.go +++ b/feature/conn25/ippool_test.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -package appc +package conn25 import ( "errors" diff --git a/net/dns/manager.go b/net/dns/manager.go index c052055654f1d..889c542cf1f1d 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -46,6 +46,11 @@ var ( // be running. const maxActiveQueries = 256 +// ResponseMapper is a function that accepts the bytes representing +// a DNS response and returns bytes representing a DNS response. +// Used to observe and/or mutate DNS responses managed by this manager. +type ResponseMapper func([]byte) []byte + // We use file-ignore below instead of ignore because on some platforms, // the lint exception is necessary and on others it is not, // and plain ignore complains if the exception is unnecessary. @@ -67,8 +72,9 @@ type Manager struct { knobs *controlknobs.Knobs // or nil goos string // if empty, gets set to runtime.GOOS - mu sync.Mutex // guards following - config *Config // Tracks the last viable DNS configuration set by Set. nil on failures other than compilation failures or if set has never been called. + mu sync.Mutex // guards following + config *Config // Tracks the last viable DNS configuration set by Set. nil on failures other than compilation failures or if set has never been called. + queryResponseMapper ResponseMapper } // NewManager created a new manager from the given config. @@ -467,7 +473,16 @@ func (m *Manager) Query(ctx context.Context, bs []byte, family string, from neti return nil, errFullQueue } defer atomic.AddInt32(&m.activeQueriesAtomic, -1) - return m.resolver.Query(ctx, bs, family, from) + outbs, err := m.resolver.Query(ctx, bs, family, from) + if err != nil { + return outbs, err + } + m.mu.Lock() + defer m.mu.Unlock() + if m.queryResponseMapper != nil { + outbs = m.queryResponseMapper(outbs) + } + return outbs, err } const ( @@ -653,3 +668,9 @@ func CleanUp(logf logger.Logf, netMon *netmon.Monitor, bus *eventbus.Bus, health } var metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue") + +func (m *Manager) SetQueryResponseMapper(fx ResponseMapper) { + m.mu.Lock() + defer m.mu.Unlock() + m.queryResponseMapper = fx +} diff --git a/types/appctype/appconnector.go b/types/appctype/appconnector.go index 5442e8290cb8a..0af5db4c38672 100644 --- a/types/appctype/appconnector.go +++ b/types/appctype/appconnector.go @@ -8,6 +8,7 @@ package appctype import ( "net/netip" + "go4.org/netipx" "tailscale.com/tailcfg" ) @@ -93,3 +94,17 @@ type RouteUpdate struct { Advertise []netip.Prefix Unadvertise []netip.Prefix } + +type Conn25Attr struct { + // Name is the name of this collection of domains. + Name string `json:"name,omitempty"` + // Domains enumerates the domains serviced by the specified app connectors. + // Domains can be of the form: example.com, or *.example.com. + Domains []string `json:"domains,omitempty"` + // Connectors enumerates the app connectors which service these domains. + // These can either be "*" to match any advertising connector, or a + // tag of the form tag:. + Connectors []string `json:"connectors,omitempty"` + MagicIPPool []netipx.IPRange `json:"magicIPPool,omitempty"` + TransitIPPool []netipx.IPRange `json:"transitIPPool,omitempty"` +} From 2d64c0dab3fcf355de78bd00b856b2fa7101bf94 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Fri, 20 Feb 2026 18:06:07 +0000 Subject: [PATCH 134/202] cmd/k8s-operator/e2e: mark TestIngress flaky (#18773) --- cmd/k8s-operator/e2e/ingress_test.go | 67 ++++++++++++++++++++++++++++ cmd/k8s-operator/e2e/setup.go | 3 +- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/cmd/k8s-operator/e2e/ingress_test.go b/cmd/k8s-operator/e2e/ingress_test.go index 5339b05836388..47a838414d449 100644 --- a/cmd/k8s-operator/e2e/ingress_test.go +++ b/cmd/k8s-operator/e2e/ingress_test.go @@ -5,6 +5,7 @@ package e2e import ( "context" + "encoding/json" "fmt" "net/http" "testing" @@ -14,7 +15,11 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + "tailscale.com/cmd/testwrapper/flakytest" kube "tailscale.com/k8s-operator" "tailscale.com/tstest" "tailscale.com/types/ptr" @@ -23,6 +28,7 @@ import ( // See [TestMain] for test requirements. func TestIngress(t *testing.T) { + flakytest.Mark(t, "https://github.com/tailscale/corp/issues/37533") if tnClient == nil { t.Skip("TestIngress requires a working tailnet client") } @@ -85,8 +91,68 @@ func TestIngress(t *testing.T) { } createAndCleanup(t, kubeClient, svc) + // TODO(tomhjp): Delete once we've reproduced the flake with this extra info. + t0 := time.Now() + watcherCtx, cancelWatcher := context.WithCancel(t.Context()) + defer cancelWatcher() + go func() { + // client-go client for logs. + clientGoKubeClient, err := kubernetes.NewForConfig(restCfg) + if err != nil { + t.Logf("error creating client-go Kubernetes client: %v", err) + return + } + + for { + select { + case <-watcherCtx.Done(): + t.Logf("stopping watcher after %v", time.Since(t0)) + return + case <-time.After(time.Minute): + t.Logf("dumping info after %v elapsed", time.Since(t0)) + // Service itself. + svc := &corev1.Service{ObjectMeta: objectMeta("default", "test-ingress")} + err := get(watcherCtx, kubeClient, svc) + svcYaml, _ := yaml.Marshal(svc) + t.Logf("Service: %s, error: %v\n%s", svc.Name, err, string(svcYaml)) + + // Pods in tailscale namespace. + var pods corev1.PodList + if err := kubeClient.List(watcherCtx, &pods, client.InNamespace("tailscale")); err != nil { + t.Logf("error listing Pods in tailscale namespace: %v", err) + } else { + t.Logf("%d Pods", len(pods.Items)) + for _, pod := range pods.Items { + podYaml, _ := yaml.Marshal(pod) + t.Logf("Pod: %s\n%s", pod.Name, string(podYaml)) + logs := clientGoKubeClient.CoreV1().Pods("tailscale").GetLogs(pod.Name, &corev1.PodLogOptions{}).Do(watcherCtx) + logData, err := logs.Raw() + if err != nil { + t.Logf("error reading logs for Pod %s: %v", pod.Name, err) + continue + } + t.Logf("Logs for Pod %s:\n%s", pod.Name, string(logData)) + } + } + + // Tailscale status on the tailnet. + lc, err := tnClient.LocalClient() + if err != nil { + t.Logf("error getting tailnet local client: %v", err) + } else { + status, err := lc.Status(watcherCtx) + statusJSON, _ := json.MarshalIndent(status, "", " ") + t.Logf("Tailnet status: %s, error: %v", string(statusJSON), err) + } + } + } + }() + // TODO: instead of timing out only when test times out, cancel context after 60s or so. if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { + if time.Since(t0) > time.Minute { + t.Logf("%v elapsed waiting for Service default/test-ingress to become Ready", time.Since(t0)) + } maybeReadySvc := &corev1.Service{ObjectMeta: objectMeta("default", "test-ingress")} if err := get(ctx, kubeClient, maybeReadySvc); err != nil { return false, err @@ -99,6 +165,7 @@ func TestIngress(t *testing.T) { }); err != nil { t.Fatalf("error waiting for the Service to become Ready: %v", err) } + cancelWatcher() var resp *http.Response if err := tstest.WaitFor(time.Minute, func() error { diff --git a/cmd/k8s-operator/e2e/setup.go b/cmd/k8s-operator/e2e/setup.go index 845a591453b64..c4fd45d3e4125 100644 --- a/cmd/k8s-operator/e2e/setup.go +++ b/cmd/k8s-operator/e2e/setup.go @@ -70,6 +70,7 @@ const ( var ( tsClient *tailscale.Client // For API calls to control. tnClient *tsnet.Server // For testing real tailnet traffic. + restCfg *rest.Config // For constructing a client-go client if necessary. kubeClient client.WithWatch // For k8s API calls. //go:embed certs/pebble.minica.crt @@ -141,7 +142,7 @@ func runTests(m *testing.M) (int, error) { } // Cluster client setup. - restCfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + restCfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return 0, fmt.Errorf("error loading kubeconfig: %w", err) } From 8890c3c413d6422c7810719efe4ff3e8c994afa9 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Fri, 20 Feb 2026 15:52:34 -0800 Subject: [PATCH 135/202] cmd/containerboot,kube: enable autoadvertisement of Tailscale services on containerboot (#18527) * cmd/containerboot,kube/services: support the ability to automatically advertise services on startup Updates #17769 Signed-off-by: chaosinthecrd * cmd/containerboot: don't assume we want to use kube state store if in kubernetes Fixes #8188 Signed-off-by: chaosinthecrd --------- Signed-off-by: chaosinthecrd --- cmd/containerboot/main.go | 27 +++++- cmd/containerboot/main_test.go | 70 ++++++++++++--- cmd/containerboot/serve.go | 103 +++++++++++++++------- cmd/containerboot/serve_test.go | 125 ++++++++++++++++++++++++--- cmd/containerboot/settings.go | 7 +- cmd/containerboot/tailscaled.go | 2 +- cmd/k8s-operator/proxygroup_specs.go | 4 + cmd/k8s-operator/sts.go | 4 + cmd/k8s-operator/testutils_test.go | 2 + kube/localclient/fake-client.go | 23 +++++ kube/localclient/local-client.go | 10 +++ kube/services/services.go | 37 ++++++++ 12 files changed, 349 insertions(+), 65 deletions(-) diff --git a/cmd/containerboot/main.go b/cmd/containerboot/main.go index 9d8d3f02328e8..6b192b41605f1 100644 --- a/cmd/containerboot/main.go +++ b/cmd/containerboot/main.go @@ -101,6 +101,10 @@ // cluster using the same hostname (in this case, the MagicDNS name of the ingress proxy) // as a non-cluster workload on tailnet. // This is only meant to be configured by the Kubernetes operator. +// - TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT: If set to true and if this +// containerboot instance is not running in Kubernetes, autoadvertise any services +// defined in the devices serve config, and unadvertise on shutdown. Defaults +// to `true`, but can be disabled to allow user specific advertisement configuration. // // When running on Kubernetes, containerboot defaults to storing state in the // "tailscale" kube secret. To store state on local disk instead, set @@ -137,6 +141,7 @@ import ( kubeutils "tailscale.com/k8s-operator" healthz "tailscale.com/kube/health" "tailscale.com/kube/kubetypes" + klc "tailscale.com/kube/localclient" "tailscale.com/kube/metrics" "tailscale.com/kube/services" "tailscale.com/tailcfg" @@ -155,6 +160,10 @@ func newNetfilterRunner(logf logger.Logf) (linuxfw.NetfilterRunner, error) { return linuxfw.New(logf, "") } +func getAutoAdvertiseBool() bool { + return defaultBool("TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", true) +} + func main() { if err := run(); err != nil && !errors.Is(err, context.Canceled) { log.Fatal(err) @@ -199,7 +208,7 @@ func run() error { defer cancel() var kc *kubeClient - if cfg.InKubernetes { + if cfg.KubeSecret != "" { kc, err = newKubeClient(cfg.Root, cfg.KubeSecret) if err != nil { return fmt.Errorf("error initializing kube client: %w", err) @@ -229,6 +238,7 @@ func run() error { ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) defer cancel() + // we are shutting down, we always want to unadvertise here if err := services.EnsureServicesNotAdvertised(ctx, client, log.Printf); err != nil { log.Printf("Error ensuring services are not advertised: %v", err) } @@ -652,9 +662,22 @@ runLoop: healthCheck.Update(len(addrs) != 0) } + var prevServeConfig *ipn.ServeConfig + if getAutoAdvertiseBool() { + prevServeConfig, err = client.GetServeConfig(ctx) + if err != nil { + return fmt.Errorf("autoadvertisement: failed to get serve config: %w", err) + } + + err = refreshAdvertiseServices(ctx, prevServeConfig, klc.New(client)) + if err != nil { + return fmt.Errorf("autoadvertisement: failed to refresh advertise services: %w", err) + } + } + if cfg.ServeConfigPath != "" { triggerWatchServeConfigChanges.Do(func() { - go watchServeConfigChanges(ctx, certDomainChanged, certDomain, client, kc, cfg) + go watchServeConfigChanges(ctx, certDomainChanged, certDomain, client, kc, cfg, prevServeConfig) }) } diff --git a/cmd/containerboot/main_test.go b/cmd/containerboot/main_test.go index 6eeb59c9b2e7e..1970fb4bfa449 100644 --- a/cmd/containerboot/main_test.go +++ b/cmd/containerboot/main_test.go @@ -1009,6 +1009,25 @@ func TestContainerBoot(t *testing.T) { }, } }, + "serve_config_with_service_auto_advertisement": func(env *testEnv) testCase { + return testCase{ + Env: map[string]string{ + "TS_SERVE_CONFIG": filepath.Join(env.d, "etc/tailscaled/serve-config-with-services.json"), + "TS_AUTHKEY": "tskey-key", + }, + Phases: []phase{ + { + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=mem: --statedir=/tmp --tun=userspace-networking", + "/usr/bin/tailscale --socket=/tmp/tailscaled.sock up --accept-dns=false --authkey=tskey-key", + }, + }, + { + Notify: runningNotify, + }, + }, + } + }, "kube_shutdown_during_state_write": func(env *testEnv) testCase { return testCase{ Env: map[string]string{ @@ -1159,7 +1178,7 @@ func TestContainerBoot(t *testing.T) { return nil }) if err != nil { - t.Fatalf("phase %d: %v", i, err) + t.Fatalf("test: %q phase %d: %v", name, i, err) } err = tstest.WaitFor(2*time.Second, func() error { for path, want := range p.WantFiles { @@ -1340,10 +1359,16 @@ func (lc *localAPI) Notify(n *ipn.Notify) { func (lc *localAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/localapi/v0/serve-config": - if r.Method != "POST" { + switch r.Method { + case "GET": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&ipn.ServeConfig{}) + return + case "POST": + return + default: panic(fmt.Sprintf("unsupported method %q", r.Method)) } - return case "/localapi/v0/watch-ipn-bus": if r.Method != "GET" { panic(fmt.Sprintf("unsupported method %q", r.Method)) @@ -1355,10 +1380,19 @@ func (lc *localAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("fake metrics")) return case "/localapi/v0/prefs": - if r.Method != "GET" { + switch r.Method { + case "GET": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&ipn.Prefs{}) + return + case "PATCH": + // EditPrefs - just return empty prefs + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&ipn.Prefs{}) + return + default: panic(fmt.Sprintf("unsupported method %q", r.Method)) } - return default: panic(fmt.Sprintf("unsupported path %q", r.URL.Path)) } @@ -1635,6 +1669,13 @@ func newTestEnv(t *testing.T) testEnv { tailscaledConf := &ipn.ConfigVAlpha{AuthKey: ptr.To("foo"), Version: "alpha0"} serveConf := ipn.ServeConfig{TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}} + serveConfWithServices := ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:test-service-1": {}, + "svc:test-service-2": {}, + }, + } egressCfg := egressSvcConfig("foo", "foo.tailnetxyz.ts.net") dirs := []string{ @@ -1652,15 +1693,16 @@ func newTestEnv(t *testing.T) testEnv { } } files := map[string][]byte{ - "usr/bin/tailscaled": fakeTailscaled, - "usr/bin/tailscale": fakeTailscale, - "usr/bin/iptables": fakeTailscale, - "usr/bin/ip6tables": fakeTailscale, - "dev/net/tun": []byte(""), - "proc/sys/net/ipv4/ip_forward": []byte("0"), - "proc/sys/net/ipv6/conf/all/forwarding": []byte("0"), - "etc/tailscaled/cap-95.hujson": mustJSON(t, tailscaledConf), - "etc/tailscaled/serve-config.json": mustJSON(t, serveConf), + "usr/bin/tailscaled": fakeTailscaled, + "usr/bin/tailscale": fakeTailscale, + "usr/bin/iptables": fakeTailscale, + "usr/bin/ip6tables": fakeTailscale, + "dev/net/tun": []byte(""), + "proc/sys/net/ipv4/ip_forward": []byte("0"), + "proc/sys/net/ipv6/conf/all/forwarding": []byte("0"), + "etc/tailscaled/cap-95.hujson": mustJSON(t, tailscaledConf), + "etc/tailscaled/serve-config.json": mustJSON(t, serveConf), + "etc/tailscaled/serve-config-with-services.json": mustJSON(t, serveConfWithServices), filepath.Join("etc/tailscaled/", egressservices.KeyEgressServices): mustJSON(t, egressCfg), filepath.Join("etc/tailscaled/", egressservices.KeyHEPPings): []byte("4"), } diff --git a/cmd/containerboot/serve.go b/cmd/containerboot/serve.go index bc154c7e9f258..f64d2d24f681f 100644 --- a/cmd/containerboot/serve.go +++ b/cmd/containerboot/serve.go @@ -9,6 +9,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "log" "os" "path/filepath" @@ -22,6 +23,7 @@ import ( "tailscale.com/kube/certs" "tailscale.com/kube/kubetypes" klc "tailscale.com/kube/localclient" + "tailscale.com/kube/services" "tailscale.com/types/netmap" ) @@ -29,8 +31,9 @@ import ( // the serve config from it, replacing ${TS_CERT_DOMAIN} with certDomain, and // applies it to lc. It exits when ctx is canceled. cdChanged is a channel that // is written to when the certDomain changes, causing the serve config to be -// re-read and applied. -func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDomainAtomic *atomic.Pointer[string], lc *local.Client, kc *kubeClient, cfg *settings) { +// re-read and applied. prevServeConfig is the serve config that was fetched +// during startup. This will be refreshed by the goroutine when serve config changes. +func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDomainAtomic *atomic.Pointer[string], lc *local.Client, kc *kubeClient, cfg *settings, prevServeConfig *ipn.ServeConfig) { if certDomainAtomic == nil { panic("certDomainAtomic must not be nil") } @@ -53,11 +56,18 @@ func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDom } var certDomain string - var prevServeConfig *ipn.ServeConfig var cm *certs.CertManager if cfg.CertShareMode == "rw" { cm = certs.NewCertManager(klc.New(lc), log.Printf) } + + var err error + if prevServeConfig == nil { + prevServeConfig, err = lc.GetServeConfig(ctx) + if err != nil { + log.Fatalf("serve proxy: failed to get serve config: %v", err) + } + } for { select { case <-ctx.Done(): @@ -70,35 +80,68 @@ func watchServeConfigChanges(ctx context.Context, cdChanged <-chan bool, certDom // k8s handles these mounts. So just re-read the file and apply it // if it's changed. } - sc, err := readServeConfig(cfg.ServeConfigPath, certDomain) - if err != nil { - log.Fatalf("serve proxy: failed to read serve config: %v", err) - } - if sc == nil { - log.Printf("serve proxy: no serve config at %q, skipping", cfg.ServeConfigPath) - continue - } - if prevServeConfig != nil && reflect.DeepEqual(sc, prevServeConfig) { - continue - } - if err := updateServeConfig(ctx, sc, certDomain, lc); err != nil { - log.Fatalf("serve proxy: error updating serve config: %v", err) - } - if kc != nil && kc.canPatch { - if err := kc.storeHTTPSEndpoint(ctx, certDomain); err != nil { - log.Fatalf("serve proxy: error storing HTTPS endpoint: %v", err) + + var sc *ipn.ServeConfig + if cfg.ServeConfigPath != "" { + sc, err := readServeConfig(cfg.ServeConfigPath, certDomain) + if err != nil { + log.Fatalf("serve proxy: failed to read serve config: %v", err) } + if sc == nil { + log.Printf("serve proxy: no serve config at %q, skipping", cfg.ServeConfigPath) + continue + } + if prevServeConfig != nil && reflect.DeepEqual(sc, prevServeConfig) { + continue + } + if err := updateServeConfig(ctx, sc, certDomain, klc.New(lc)); err != nil { + log.Fatalf("serve proxy: error updating serve config: %v", err) + } + if kc != nil && kc.canPatch { + if err := kc.storeHTTPSEndpoint(ctx, certDomain); err != nil { + log.Fatalf("serve proxy: error storing HTTPS endpoint: %v", err) + } + } + prevServeConfig = sc + if cfg.CertShareMode != "rw" { + continue + } + if err := cm.EnsureCertLoops(ctx, sc); err != nil { + log.Fatalf("serve proxy: error ensuring cert loops: %v", err) + } + } else { + log.Printf("serve config path not provided.") + sc = prevServeConfig } - prevServeConfig = sc - if cfg.CertShareMode != "rw" { - continue - } - if err := cm.EnsureCertLoops(ctx, sc); err != nil { - log.Fatalf("serve proxy: error ensuring cert loops: %v", err) + + // if we are running in kubernetes, we want to leave advertisement to the operator + // to do (by updating the serve config) + if getAutoAdvertiseBool() { + if err := refreshAdvertiseServices(ctx, sc, klc.New(lc)); err != nil { + log.Fatalf("error refreshing advertised services: %v", err) + } } } } +func refreshAdvertiseServices(ctx context.Context, sc *ipn.ServeConfig, lc klc.LocalClient) error { + if sc == nil || len(sc.Services) == 0 { + return nil + } + + var svcs []string + for svc := range sc.Services { + svcs = append(svcs, svc.String()) + } + + err := services.EnsureServicesAdvertised(ctx, svcs, lc, log.Printf) + if err != nil { + return fmt.Errorf("failed to ensure services advertised: %w", err) + } + + return nil +} + func certDomainFromNetmap(nm *netmap.NetworkMap) string { if len(nm.DNS.CertDomains) == 0 { return "" @@ -106,13 +149,7 @@ func certDomainFromNetmap(nm *netmap.NetworkMap) string { return nm.DNS.CertDomains[0] } -// localClient is a subset of [local.Client] that can be mocked for testing. -type localClient interface { - SetServeConfig(context.Context, *ipn.ServeConfig) error - CertPair(context.Context, string) ([]byte, []byte, error) -} - -func updateServeConfig(ctx context.Context, sc *ipn.ServeConfig, certDomain string, lc localClient) error { +func updateServeConfig(ctx context.Context, sc *ipn.ServeConfig, certDomain string, lc klc.LocalClient) error { if !isValidHTTPSConfig(certDomain, sc) { return nil } diff --git a/cmd/containerboot/serve_test.go b/cmd/containerboot/serve_test.go index 0683346f7159a..5da5ef5f737c3 100644 --- a/cmd/containerboot/serve_test.go +++ b/cmd/containerboot/serve_test.go @@ -12,9 +12,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "tailscale.com/client/local" "tailscale.com/ipn" "tailscale.com/kube/kubetypes" + "tailscale.com/kube/localclient" + "tailscale.com/tailcfg" ) func TestUpdateServeConfig(t *testing.T) { @@ -65,13 +66,13 @@ func TestUpdateServeConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fakeLC := &fakeLocalClient{} + fakeLC := &localclient.FakeLocalClient{} err := updateServeConfig(context.Background(), tt.sc, tt.certDomain, fakeLC) if err != nil { t.Errorf("updateServeConfig() error = %v", err) } - if fakeLC.setServeCalled != tt.wantCall { - t.Errorf("SetServeConfig() called = %v, want %v", fakeLC.setServeCalled, tt.wantCall) + if fakeLC.SetServeCalled != tt.wantCall { + t.Errorf("SetServeConfig() called = %v, want %v", fakeLC.SetServeCalled, tt.wantCall) } }) } @@ -196,18 +197,114 @@ func TestReadServeConfig(t *testing.T) { } } -type fakeLocalClient struct { - *local.Client - setServeCalled bool -} +func TestRefreshAdvertiseServices(t *testing.T) { + tests := []struct { + name string + sc *ipn.ServeConfig + wantServices []string + wantEditPrefsCalled bool + wantErr bool + }{ + { + name: "nil_serve_config", + sc: nil, + wantEditPrefsCalled: false, + }, + { + name: "empty_serve_config", + sc: &ipn.ServeConfig{}, + wantEditPrefsCalled: false, + }, + { + name: "no_services_defined", + sc: &ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{ + 80: {HTTP: true}, + }, + }, + wantEditPrefsCalled: false, + }, + { + name: "single_service", + sc: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:my-service": {}, + }, + }, + wantServices: []string{"svc:my-service"}, + wantEditPrefsCalled: true, + }, + { + name: "multiple_services", + sc: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:service-a": {}, + "svc:service-b": {}, + "svc:service-c": {}, + }, + }, + wantServices: []string{"svc:service-a", "svc:service-b", "svc:service-c"}, + wantEditPrefsCalled: true, + }, + { + name: "services_with_tcp_and_web", + sc: &ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{ + 80: {HTTP: true}, + }, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "example.com:443": {}, + }, + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:frontend": {}, + "svc:backend": {}, + }, + }, + wantServices: []string{"svc:frontend", "svc:backend"}, + wantEditPrefsCalled: true, + }, + } -func (m *fakeLocalClient) SetServeConfig(ctx context.Context, cfg *ipn.ServeConfig) error { - m.setServeCalled = true - return nil -} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeLC := &localclient.FakeLocalClient{} + err := refreshAdvertiseServices(context.Background(), tt.sc, fakeLC) -func (m *fakeLocalClient) CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) { - return nil, nil, nil + if (err != nil) != tt.wantErr { + t.Errorf("refreshAdvertiseServices() error = %v, wantErr %v", err, tt.wantErr) + } + + if tt.wantEditPrefsCalled != (len(fakeLC.EditPrefsCalls) > 0) { + t.Errorf("EditPrefs called = %v, want %v", len(fakeLC.EditPrefsCalls) > 0, tt.wantEditPrefsCalled) + } + + if tt.wantEditPrefsCalled { + if len(fakeLC.EditPrefsCalls) != 1 { + t.Fatalf("expected 1 EditPrefs call, got %d", len(fakeLC.EditPrefsCalls)) + } + + mp := fakeLC.EditPrefsCalls[0] + if !mp.AdvertiseServicesSet { + t.Error("AdvertiseServicesSet should be true") + } + + if len(mp.AdvertiseServices) != len(tt.wantServices) { + t.Errorf("AdvertiseServices length = %d, want %d", len(mp.Prefs.AdvertiseServices), len(tt.wantServices)) + } + + advertised := make(map[string]bool) + for _, svc := range mp.AdvertiseServices { + advertised[svc] = true + } + + for _, want := range tt.wantServices { + if !advertised[want] { + t.Errorf("expected service %q to be advertised, but it wasn't", want) + } + } + } + }) + } } func TestHasHTTPSEndpoint(t *testing.T) { diff --git a/cmd/containerboot/settings.go b/cmd/containerboot/settings.go index 181a94dd71114..e6147717bb39a 100644 --- a/cmd/containerboot/settings.go +++ b/cmd/containerboot/settings.go @@ -107,7 +107,12 @@ func configFromEnv() (*settings, error) { UserspaceMode: defaultBool("TS_USERSPACE", true), StateDir: defaultEnv("TS_STATE_DIR", ""), AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"), - KubeSecret: defaultEnv("TS_KUBE_SECRET", "tailscale"), + KubeSecret: func() string { + if os.Getenv("KUBERNETES_SERVICE_HOST") != "" { + return defaultEnv("TS_KUBE_SECRET", "tailscale") + } + return defaultEnv("TS_KUBE_SECRET", "") + }(), SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""), HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""), Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"), diff --git a/cmd/containerboot/tailscaled.go b/cmd/containerboot/tailscaled.go index 9990600c84c65..6f4ed77e76d72 100644 --- a/cmd/containerboot/tailscaled.go +++ b/cmd/containerboot/tailscaled.go @@ -69,7 +69,7 @@ func startTailscaled(ctx context.Context, cfg *settings) (*local.Client, *os.Pro func tailscaledArgs(cfg *settings) []string { args := []string{"--socket=" + cfg.Socket} switch { - case cfg.InKubernetes && cfg.KubeSecret != "": + case cfg.KubeSecret != "": args = append(args, "--state=kube:"+cfg.KubeSecret) if cfg.StateDir == "" { cfg.StateDir = "/tmp" diff --git a/cmd/k8s-operator/proxygroup_specs.go b/cmd/k8s-operator/proxygroup_specs.go index 6bce004eaa88d..05e0ed0b26013 100644 --- a/cmd/k8s-operator/proxygroup_specs.go +++ b/cmd/k8s-operator/proxygroup_specs.go @@ -173,6 +173,10 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode string Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)", }, + { + Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", + Value: "false", + }, { // TODO(tomhjp): This is tsrecorder-specific and does nothing. Delete. Name: "TS_STATE", diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index e81fe2d66f6ed..85aab2e8a0d2a 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -692,6 +692,10 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)", }, + corev1.EnvVar{ + Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", + Value: "false", + }, corev1.EnvVar{ Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR", Value: "/etc/tsconfig/$(POD_NAME)", diff --git a/cmd/k8s-operator/testutils_test.go b/cmd/k8s-operator/testutils_test.go index 0e4a3eee40e73..54b7ead55f7ff 100644 --- a/cmd/k8s-operator/testutils_test.go +++ b/cmd/k8s-operator/testutils_test.go @@ -91,6 +91,7 @@ func expectedSTS(t *testing.T, cl client.Client, opts configOpts) *appsv1.Statef {Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.name"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "POD_UID", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.uid"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)"}, + {Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", Value: "false"}, {Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR", Value: "/etc/tsconfig/$(POD_NAME)"}, {Name: "TS_DEBUG_ACME_FORCE_RENEWAL", Value: "true"}, }, @@ -287,6 +288,7 @@ func expectedSTSUserspace(t *testing.T, cl client.Client, opts configOpts) *apps {Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.name"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "POD_UID", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "", FieldPath: "metadata.uid"}, ResourceFieldRef: nil, ConfigMapKeyRef: nil, SecretKeyRef: nil}}, {Name: "TS_KUBE_SECRET", Value: "$(POD_NAME)"}, + {Name: "TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", Value: "false"}, {Name: "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR", Value: "/etc/tsconfig/$(POD_NAME)"}, {Name: "TS_DEBUG_ACME_FORCE_RENEWAL", Value: "true"}, {Name: "TS_SERVE_CONFIG", Value: "/etc/tailscaled/$(POD_NAME)/serve-config"}, diff --git a/kube/localclient/fake-client.go b/kube/localclient/fake-client.go index 1bce4bef00d6f..a244ce31a10c9 100644 --- a/kube/localclient/fake-client.go +++ b/kube/localclient/fake-client.go @@ -12,6 +12,29 @@ import ( type FakeLocalClient struct { FakeIPNBusWatcher + SetServeCalled bool + EditPrefsCalls []*ipn.MaskedPrefs + GetPrefsResult *ipn.Prefs +} + +func (m *FakeLocalClient) SetServeConfig(ctx context.Context, cfg *ipn.ServeConfig) error { + m.SetServeCalled = true + return nil +} + +func (m *FakeLocalClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) { + m.EditPrefsCalls = append(m.EditPrefsCalls, mp) + if m.GetPrefsResult == nil { + return &ipn.Prefs{}, nil + } + return m.GetPrefsResult, nil +} + +func (m *FakeLocalClient) GetPrefs(ctx context.Context) (*ipn.Prefs, error) { + if m.GetPrefsResult == nil { + return &ipn.Prefs{}, nil + } + return m.GetPrefsResult, nil } func (f *FakeLocalClient) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (IPNBusWatcher, error) { diff --git a/kube/localclient/local-client.go b/kube/localclient/local-client.go index 8cc0d41ffe473..b8d40f4067c0e 100644 --- a/kube/localclient/local-client.go +++ b/kube/localclient/local-client.go @@ -17,6 +17,8 @@ import ( // for easier testing. type LocalClient interface { WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (IPNBusWatcher, error) + SetServeConfig(context.Context, *ipn.ServeConfig) error + EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) CertIssuer } @@ -40,6 +42,14 @@ type localClient struct { lc *local.Client } +func (lc *localClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error { + return lc.lc.SetServeConfig(ctx, config) +} + +func (lc *localClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) { + return lc.lc.EditPrefs(ctx, mp) +} + func (lc *localClient) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (IPNBusWatcher, error) { return lc.lc.WatchIPNBus(ctx, mask) } diff --git a/kube/services/services.go b/kube/services/services.go index 36566c2855a9f..0c27f888f5f7d 100644 --- a/kube/services/services.go +++ b/kube/services/services.go @@ -12,9 +12,46 @@ import ( "tailscale.com/client/local" "tailscale.com/ipn" + "tailscale.com/kube/localclient" "tailscale.com/types/logger" ) +// EnsureServicesAdvertised is a function that gets called on containerboot +// startup and ensures that Services get advertised if they exist. +func EnsureServicesAdvertised(ctx context.Context, services []string, lc localclient.LocalClient, logf logger.Logf) error { + if _, err := lc.EditPrefs(ctx, &ipn.MaskedPrefs{ + AdvertiseServicesSet: true, + Prefs: ipn.Prefs{ + AdvertiseServices: services, + }, + }); err != nil { + // EditPrefs only returns an error if it fails _set_ its local prefs. + // If it fails to _persist_ the prefs in state, we don't get an error + // and we continue waiting below, as control will failover as usual. + return fmt.Errorf("error setting prefs AdvertiseServices: %w", err) + } + + // Services use the same (failover XOR regional routing) mechanism that + // HA subnet routers use. Unfortunately we don't yet get a reliable signal + // from control that it's responded to our unadvertisement, so the best we + // can do is wait for 20 seconds, where 15s is the approximate maximum time + // it should take for control to choose a new primary, and 5s is for buffer. + // + // Note: There is no guarantee that clients have been _informed_ of the new + // primary no matter how long we wait. We would need a mechanism to await + // netmap updates for peers to know for sure. + // + // See https://tailscale.com/kb/1115/high-availability for more details. + // TODO(tomhjp): Wait for a netmap update instead of sleeping when control + // supports that. + select { + case <-ctx.Done(): + return nil + case <-time.After(20 * time.Second): + return nil + } +} + // EnsureServicesNotAdvertised is a function that gets called on containerboot // or k8s-proxy termination and ensures that any currently advertised Services // get unadvertised to give clients time to switch to another node before this From c48b7364591d43f5760a9e4cd75828b36dd8262b Mon Sep 17 00:00:00 2001 From: License Updater Date: Mon, 23 Feb 2026 15:15:24 +0000 Subject: [PATCH 136/202] licenses: update license notices Signed-off-by: License Updater --- licenses/apple.md | 4 ++-- licenses/tailscale.md | 6 +++--- licenses/windows.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/licenses/apple.md b/licenses/apple.md index 4170a4c8bac49..93afd9385cdbe 100644 --- a/licenses/apple.md +++ b/licenses/apple.md @@ -11,7 +11,7 @@ See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.1/LICENSE)) - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.32.5/config/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.19.5/credentials/LICENSE.txt)) @@ -57,7 +57,7 @@ See also the dependencies in the [Tailscale CLI][]. - [github.com/mdlayher/sdnotify](https://pkg.go.dev/github.com/mdlayher/sdnotify) ([MIT](https://github.com/mdlayher/sdnotify/blob/v1.0.0/LICENSE.md)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md)) - - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.22/LICENSE)) + - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.25/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - [github.com/prometheus-community/pro-bing](https://pkg.go.dev/github.com/prometheus-community/pro-bing) ([MIT](https://github.com/prometheus-community/pro-bing/blob/v0.4.0/LICENSE)) - [github.com/safchain/ethtool](https://pkg.go.dev/github.com/safchain/ethtool) ([Apache-2.0](https://github.com/safchain/ethtool/blob/v0.3.0/LICENSE)) diff --git a/licenses/tailscale.md b/licenses/tailscale.md index 9ccc37adb22cc..521b6ff9ce887 100644 --- a/licenses/tailscale.md +++ b/licenses/tailscale.md @@ -13,7 +13,7 @@ well as an [option for macOS][]. Some packages may only be included on certain architectures or operating systems. - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [fyne.io/systray](https://pkg.go.dev/fyne.io/systray) ([Apache-2.0](https://github.com/fyne-io/systray/blob/4856ac3adc3c/LICENSE)) - [github.com/Kodeworks/golang-image-ico](https://pkg.go.dev/github.com/Kodeworks/golang-image-ico) ([BSD-3-Clause](https://github.com/Kodeworks/golang-image-ico/blob/73f0f4cfade9/LICENSE)) - [github.com/akutz/memconn](https://pkg.go.dev/github.com/akutz/memconn) ([Apache-2.0](https://github.com/akutz/memconn/blob/v0.1.0/LICENSE)) @@ -68,7 +68,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - [github.com/mitchellh/go-ps](https://pkg.go.dev/github.com/mitchellh/go-ps) ([MIT](https://github.com/mitchellh/go-ps/blob/v1.0.0/LICENSE.md)) - [github.com/peterbourgon/ff/v3](https://pkg.go.dev/github.com/peterbourgon/ff/v3) ([Apache-2.0](https://github.com/peterbourgon/ff/blob/v3.4.0/LICENSE)) - - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.21/LICENSE)) + - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.25/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - [github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) ([BSD-2-Clause](https://github.com/pkg/sftp/blob/v1.13.6/LICENSE)) - [github.com/prometheus-community/pro-bing](https://pkg.go.dev/github.com/prometheus-community/pro-bing) ([MIT](https://github.com/prometheus-community/pro-bing/blob/v0.4.0/LICENSE)) @@ -90,7 +90,7 @@ Some packages may only be included on certain architectures or operating systems - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/b7579e27:LICENSE)) - [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.27.0:LICENSE)) - [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE)) - - [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.32.0:LICENSE)) + - [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE)) - [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE)) - [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.40.0:LICENSE)) - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE)) diff --git a/licenses/windows.md b/licenses/windows.md index 03d0ce40ef717..29581566c68ba 100644 --- a/licenses/windows.md +++ b/licenses/windows.md @@ -9,7 +9,7 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.1/LICENSE)) - [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE)) - [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE)) - [github.com/beorn7/perks/quantile](https://pkg.go.dev/github.com/beorn7/perks/quantile) ([MIT](https://github.com/beorn7/perks/blob/v1.0.1/LICENSE)) From 0ea55d37e3d3c42ef4631e8f9237fae80f089fdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:00:20 +0000 Subject: [PATCH 137/202] .github: bump peter-evans/create-pull-request from 8.0.0 to 8.1.0 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/98357b18bf14b5342f975ff684046ec3b2a07725...c0f553fe549906ede9cf27b5156039d195d2ece0) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/update-flake.yml | 2 +- .github/workflows/update-webclient-prebuilt.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 0c40758543458..22ee80397fb3c 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -35,7 +35,7 @@ jobs: private-key: ${{ secrets.CODE_UPDATER_APP_PRIVATE_KEY }} - name: Send pull request - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0 with: token: ${{ steps.generate-token.outputs.token }} author: Flakes Updater diff --git a/.github/workflows/update-webclient-prebuilt.yml b/.github/workflows/update-webclient-prebuilt.yml index 2f4f676c5d354..24e6535dcc385 100644 --- a/.github/workflows/update-webclient-prebuilt.yml +++ b/.github/workflows/update-webclient-prebuilt.yml @@ -32,7 +32,7 @@ jobs: - name: Send pull request id: pull-request - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 #v8.0.0 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0 with: token: ${{ steps.generate-token.outputs.token }} author: OSS Updater From 98c77ac2f5b16f7f3a3c8b5396154ab2980909d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:00:30 +0000 Subject: [PATCH 138/202] .github: bump actions/cache from 4.2.4 to 5.0.3 Bumps [actions/cache](https://github.com/actions/cache) from 4.2.4 to 5.0.3. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0400d5f644dc74513175e3cd8d07132dd4860809...cdf6c1fa76f9f475f3d7449005a359c84ca0f306) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: Mario Minardi --- .github/workflows/test.yml | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 57a638d2977da..5610bda10f9b8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,7 @@ jobs: # See if the cache entry already exists to avoid downloading it # and doing the cache write again. - id: check-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache # relative to workspace; see env note at top of file key: ${{ steps.hash.outputs.key }} @@ -70,7 +70,7 @@ jobs: run: go mod download - name: Cache Go modules if: steps.check-cache.outputs.cache-hit != 'true' - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache # relative to workspace; see env note at top of file key: ${{ steps.hash.outputs.key }} @@ -93,7 +93,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -131,14 +131,14 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -209,7 +209,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -251,7 +251,7 @@ jobs: cache: false - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -309,14 +309,14 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/Library/Caches/go-build key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}-${{ github.run_id }} @@ -352,7 +352,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/Library/Caches/go-build key: ${{ runner.os }}-go-test-${{ hashFiles('**/go.sum') }}-${{ github.job }}-${{ github.run_id }} @@ -369,7 +369,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -392,7 +392,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -448,14 +448,14 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -490,7 +490,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -509,7 +509,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -547,14 +547,14 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -582,7 +582,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -607,7 +607,7 @@ jobs: # some Android breakages early. # TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482 - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -628,14 +628,14 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache id: restore-cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only restoring the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -668,7 +668,7 @@ jobs: - name: Save Cache # Save cache even on failure, but only on cache miss and main branch to avoid thrashing. if: always() && steps.restore-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main' - uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: # Note: this is only saving the build cache. Mod cache is shared amongst # all jobs in the workflow. @@ -686,7 +686,7 @@ jobs: - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -773,7 +773,7 @@ jobs: - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -791,7 +791,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -815,7 +815,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -837,7 +837,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} @@ -891,7 +891,7 @@ jobs: with: path: src - name: Restore Go module cache - uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} From 3d2bb5baa82ed0bdbcc29e4283030653f47a88e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:59:46 +0000 Subject: [PATCH 139/202] .github: bump actions/download-artifact from 6.0.0 to 7.0.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/018cc2cf5baa6db3ef3c5f8a56943fffe632ef53...37930b1c2abaa49bbe596cd826c3c89aef350131) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/cigocacher.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cigocacher.yml b/.github/workflows/cigocacher.yml index c4dd0c3c509a5..cc2fb87faf3b0 100644 --- a/.github/workflows/cigocacher.yml +++ b/.github/workflows/cigocacher.yml @@ -36,7 +36,7 @@ jobs: contents: write steps: - name: Download all artifacts - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: pattern: 'cigocacher-*' merge-multiple: true From 8be5affa6da48f56435abc3f55f565d362282d5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:00:13 +0000 Subject: [PATCH 140/202] .github: bump actions/checkout from 6.0.1 to 6.0.2 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8e8c483db84b4bee98b60c0593521ed34d9990e8...de0fac2e4500dabe0009e67214ff5f5447ce83dd) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/checklocks.yml | 2 +- .github/workflows/cigocacher.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/docker-base.yml | 2 +- .github/workflows/docker-file-build.yml | 2 +- .github/workflows/flakehub-publish-tagged.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/installer.yml | 2 +- .github/workflows/kubemanifests.yaml | 2 +- .github/workflows/natlab-integrationtest.yml | 2 +- .github/workflows/pin-github-actions.yml | 2 +- .../workflows/request-dataplane-review.yml | 2 +- .github/workflows/ssh-integrationtest.yml | 2 +- .github/workflows/test.yml | 38 +++++++++---------- .github/workflows/update-flake.yml | 2 +- .../workflows/update-webclient-prebuilt.yml | 2 +- .github/workflows/vet.yml | 2 +- .github/workflows/webclient.yml | 2 +- 19 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/workflows/checklocks.yml b/.github/workflows/checklocks.yml index ee950b4fc9212..5768cf05af634 100644 --- a/.github/workflows/checklocks.yml +++ b/.github/workflows/checklocks.yml @@ -18,7 +18,7 @@ jobs: runs-on: [ ubuntu-latest ] steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build checklocks run: ./tool/go build -o /tmp/checklocks gvisor.dev/gvisor/tools/checklocks/cmd/checklocks diff --git a/.github/workflows/cigocacher.yml b/.github/workflows/cigocacher.yml index cc2fb87faf3b0..f19e004d3e726 100644 --- a/.github/workflows/cigocacher.yml +++ b/.github/workflows/cigocacher.yml @@ -17,7 +17,7 @@ jobs: GOARCH: "${{ matrix.GOARCH }}" CGO_ENABLED: "0" steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build run: | OUT="cigocacher$(./tool/go env GOEXE)" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 39133dc40c3dd..49657de707f17 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,7 +45,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Install a more recent Go that understands modern go.mod content. - name: Install Go diff --git a/.github/workflows/docker-base.yml b/.github/workflows/docker-base.yml index a47669f6ade8a..a3eac2c24e691 100644 --- a/.github/workflows/docker-base.yml +++ b/.github/workflows/docker-base.yml @@ -9,7 +9,7 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "build and test" run: | set -e diff --git a/.github/workflows/docker-file-build.yml b/.github/workflows/docker-file-build.yml index 9a56fd05758a9..7ee2468682695 100644 --- a/.github/workflows/docker-file-build.yml +++ b/.github/workflows/docker-file-build.yml @@ -8,6 +8,6 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "Build Docker image" run: docker build . diff --git a/.github/workflows/flakehub-publish-tagged.yml b/.github/workflows/flakehub-publish-tagged.yml index 798e1708a1c2a..c781e30e5154f 100644 --- a/.github/workflows/flakehub-publish-tagged.yml +++ b/.github/workflows/flakehub-publish-tagged.yml @@ -17,7 +17,7 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}" - uses: DeterminateSystems/nix-installer-action@c5a866b6ab867e88becbed4467b93592bce69f8a # v21 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 22d9d3c467ad9..dbabb361e14fa 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -27,7 +27,7 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index c99cb11d3eff7..2b46aa9b06e57 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Check out code into the Go module directory - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install govulncheck run: ./tool/go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index d7db30782470b..6fc8913c4e19c 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -99,7 +99,7 @@ jobs: contains(matrix.image, 'parrotsec') || contains(matrix.image, 'kalilinux') - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: run installer run: scripts/installer.sh env: diff --git a/.github/workflows/kubemanifests.yaml b/.github/workflows/kubemanifests.yaml index 6812b69d6e702..40734a015dad3 100644 --- a/.github/workflows/kubemanifests.yaml +++ b/.github/workflows/kubemanifests.yaml @@ -17,7 +17,7 @@ jobs: runs-on: [ ubuntu-latest ] steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build and lint Helm chart run: | eval `./tool/go run ./cmd/mkversion` diff --git a/.github/workflows/natlab-integrationtest.yml b/.github/workflows/natlab-integrationtest.yml index e10d879c3daa5..c3821db17f22f 100644 --- a/.github/workflows/natlab-integrationtest.yml +++ b/.github/workflows/natlab-integrationtest.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install qemu run: | sudo rm -f /var/lib/man-db/auto-update diff --git a/.github/workflows/pin-github-actions.yml b/.github/workflows/pin-github-actions.yml index 7c1816d134cd6..836ae46dbfa89 100644 --- a/.github/workflows/pin-github-actions.yml +++ b/.github/workflows/pin-github-actions.yml @@ -22,7 +22,7 @@ jobs: name: pin-github-actions runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: pin run: make pin-github-actions - name: check for changed workflow files diff --git a/.github/workflows/request-dataplane-review.yml b/.github/workflows/request-dataplane-review.yml index 2e30ba06d4629..2b66fc7899428 100644 --- a/.github/workflows/request-dataplane-review.yml +++ b/.github/workflows/request-dataplane-review.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Get access token uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 id: generate-token diff --git a/.github/workflows/ssh-integrationtest.yml b/.github/workflows/ssh-integrationtest.yml index 342b8e9362c30..afe2dd2f74683 100644 --- a/.github/workflows/ssh-integrationtest.yml +++ b/.github/workflows/ssh-integrationtest.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run SSH integration tests run: | make sshintegrationtest \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5610bda10f9b8..3cd71097f66ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: cache-key: ${{ steps.hash.outputs.key }} steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Compute cache key from go.{mod,sum} @@ -89,7 +89,7 @@ jobs: - shard: '4/4' steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -127,7 +127,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -240,7 +240,7 @@ jobs: shard: "2/2" steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: ${{ github.workspace }}/src @@ -293,7 +293,7 @@ jobs: name: Windows (win-tool-go) steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: test-tool-go @@ -305,7 +305,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -365,7 +365,7 @@ jobs: options: --privileged steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -388,7 +388,7 @@ jobs: if: github.repository == 'tailscale/tailscale' steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -444,7 +444,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -505,7 +505,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -543,7 +543,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -599,7 +599,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src # Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed @@ -624,7 +624,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -682,7 +682,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache @@ -767,7 +767,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Set GOMODCACHE env @@ -787,7 +787,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -811,7 +811,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -833,7 +833,7 @@ jobs: needs: gomod-cache steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache @@ -887,7 +887,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src - name: Restore Go module cache diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 22ee80397fb3c..4c0da7831b5ba 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run update-flakes run: ./update-flake.sh diff --git a/.github/workflows/update-webclient-prebuilt.yml b/.github/workflows/update-webclient-prebuilt.yml index 24e6535dcc385..a3d78e1a5b4a8 100644 --- a/.github/workflows/update-webclient-prebuilt.yml +++ b/.github/workflows/update-webclient-prebuilt.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run go get run: | diff --git a/.github/workflows/vet.yml b/.github/workflows/vet.yml index c85e3ec86a67f..574852e62beee 100644 --- a/.github/workflows/vet.yml +++ b/.github/workflows/vet.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: src diff --git a/.github/workflows/webclient.yml b/.github/workflows/webclient.yml index 4fc19901d0ef6..1a65eacf56414 100644 --- a/.github/workflows/webclient.yml +++ b/.github/workflows/webclient.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Check out code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install deps run: ./tool/yarn --cwd client/web - name: Run lint From 980e1c9d5c1db2c5172a1f014fa265b164630e1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:00:34 +0000 Subject: [PATCH 141/202] .github: bump actions/upload-artifact from 4.6.2 to 6.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 6.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.6.2...b7c566a772e6b6bfb58ed0dc250532a479d7789f) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/cigocacher.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cigocacher.yml b/.github/workflows/cigocacher.yml index f19e004d3e726..15aec8af90904 100644 --- a/.github/workflows/cigocacher.yml +++ b/.github/workflows/cigocacher.yml @@ -24,7 +24,7 @@ jobs: ./tool/go build -o "${OUT}" ./cmd/cigocacher/ tar -zcf cigocacher-${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz "${OUT}" - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: cigocacher-${{ matrix.GOOS }}-${{ matrix.GOARCH }} path: cigocacher-${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3cd71097f66ba..862420f70f98d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -756,7 +756,7 @@ jobs: run: | echo "artifacts_path=$(realpath .)" >> $GITHUB_ENV - name: upload crash - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: steps.run.outcome != 'success' && steps.build.outcome == 'success' with: name: artifacts From a58a8fc1e814364dfbd8443102bdf758de2ae68c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 23 Feb 2026 09:28:46 -0800 Subject: [PATCH 142/202] .: permit running binary built with TS_GO_NEXT=1 The old check was too aggressive and required TS_GO_NEXT=1 at runtime as well, which is too strict and onerous. This is a sanity check only (and an outdated one, at that); it's okay for it to be slightly loose and permit two possible values. If either is working, we're already way past the old bug that this was introduced to catch. Updates tailscale/corp#36382 Change-Id: Ib9a62e10382cd889ba590c3539e6b8535c6b19fe Signed-off-by: Brad Fitzpatrick --- assert_ts_toolchain_match.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assert_ts_toolchain_match.go b/assert_ts_toolchain_match.go index 901dbb8ec83a1..4df0eeb1570d3 100644 --- a/assert_ts_toolchain_match.go +++ b/assert_ts_toolchain_match.go @@ -17,10 +17,10 @@ func init() { panic("binary built with tailscale_go build tag but failed to read build info or find tailscale.toolchain.rev in build info") } want := strings.TrimSpace(GoToolchainRev) - if os.Getenv("TS_GO_NEXT") == "1" { - want = strings.TrimSpace(GoToolchainNextRev) - } - if tsRev != want { + // Also permit the "next" toolchain rev, which is used in the main branch and will eventually become the new "current" rev. + // This allows building with TS_GO_NEXT=1 and then running the resulting binary without TS_GO_NEXT=1. + wantAlt := strings.TrimSpace(GoToolchainNextRev) + if tsRev != want && tsRev != wantAlt { if os.Getenv("TS_PERMIT_TOOLCHAIN_MISMATCH") == "1" { fmt.Fprintf(os.Stderr, "tailscale.toolchain.rev = %q, want %q; but ignoring due to TS_PERMIT_TOOLCHAIN_MISMATCH=1\n", tsRev, want) return From dc80fd6324eb1e2e183408451761ff38a5eeafd2 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 22 Feb 2026 06:29:55 +0100 Subject: [PATCH 143/202] flake: fix default devShell The devshell had the wrong name expected by the flake compat package causing weird behaviour if you loaded it initiating the wrong go compiler. Updates #16637 Signed-off-by: Kristoffer Dalby --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index e15eeca6a664f..4e315a5cab7e6 100644 --- a/flake.nix +++ b/flake.nix @@ -132,7 +132,7 @@ }); devShells = eachSystem (pkgs: { - devShell = pkgs.mkShell { + default = pkgs.mkShell { packages = with pkgs; [ curl git From 811fe7d18ed832a1b48880ab8d893c7909a900e1 Mon Sep 17 00:00:00 2001 From: Michael Ben-Ami Date: Fri, 20 Feb 2026 17:36:40 +0000 Subject: [PATCH 144/202] ipnext,ipnlocal,wgengine/filter: add extension hooks for custom filter matchers Add PacketMatch hooks to the packet filter, allowing extensions to customize filtering decisions: - IngressAllowHooks: checked in RunIn after pre() but before the standard runIn4/runIn6 match rules. Hooks can accept packets to destinations outside the local IP set. First match wins; the returned why string is used for logging. - LinkLocalAllowHooks: checked inside pre() for both ingress and egress, providing exceptions to the default policy of dropping link-local unicast packets. First match wins. The GCP DNS address (169.254.169.254) is always allowed regardless of hooks. PacketMatch returns (match bool, why string) to provide a log reason consistent with the existing filter functions. Hooks are registered via the new FilterHooks struct in ipnext.Hooks and wired through to filter.Filter in LocalBackend.updateFilterLocked. Fixes tailscale/corp#35989 Fixes tailscale/corp#37207 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Michael Ben-Ami --- ipn/ipnext/ipnext.go | 34 ++++++ ipn/ipnlocal/local.go | 6 +- wgengine/filter/filter.go | 76 +++++++++++-- wgengine/filter/filter_test.go | 198 +++++++++++++++++++++++++++++++-- 4 files changed, 296 insertions(+), 18 deletions(-) diff --git a/ipn/ipnext/ipnext.go b/ipn/ipnext/ipnext.go index 275e28c85bddc..6dea49939af91 100644 --- a/ipn/ipnext/ipnext.go +++ b/ipn/ipnext/ipnext.go @@ -21,6 +21,7 @@ import ( "tailscale.com/tstime" "tailscale.com/types/logger" "tailscale.com/types/mapx" + "tailscale.com/wgengine/filter" ) // Extension augments LocalBackend with additional functionality. @@ -377,6 +378,39 @@ type Hooks struct { // ShouldUploadServices reports whether this node should include services // in Hostinfo from the portlist extension. ShouldUploadServices feature.Hook[func() bool] + + // Filter contains hooks for the packet filter. + // See [filter.Filter] for details on how these hooks are invoked. + Filter FilterHooks +} + +// FilterHooks contains hooks that extensions can use to customize the packet +// filter. Field names match the corresponding fields in filter.Filter. +type FilterHooks struct { + // IngressAllowHooks are hooks that allow extensions to accept inbound + // packets beyond the standard filter rules. Packets that are not dropped + // by the direction-agnostic pre-check, but would be not accepted by the + // main filter rules, including the check for destinations in the node's + // local IP set, will be accepted if they match one of these hooks. + // As of 2026-02-24, the ingress filter does not implement explicit drop + // rules, but if it does, an explicitly dropped packet will be dropped, + // and these hooks will not be evaluated. + // + // Processing of hooks stop after the first one that returns true. + // The returned why string of the first match is used in logging. + // Returning false does not drop the packet. + // See also [filter.Filter.IngressAllowHooks]. + IngressAllowHooks feature.Hooks[filter.PacketMatch] + + // LinkLocalAllowHooks are hooks that provide exceptions to the default + // policy of dropping link-local unicast packets. They run inside the + // direction-agnostic pre-checks for both ingress and egress. + // + // A hook can allow a link-local packet to pass the link-local check, + // but the packet is still subject to all other filter rules, and could be + // dropped elsewhere. Matching link-local packets are not logged. + // See also [filter.Filter.LinkLocalAllowHooks]. + LinkLocalAllowHooks feature.Hooks[filter.PacketMatch] } // NodeBackend is an interface to query the current node and its peers. diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 4221b45e5615a..3fccb4399dd13 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -2884,7 +2884,11 @@ func (b *LocalBackend) updateFilterLocked(prefs ipn.PrefsView) { b.setFilter(filter.NewShieldsUpFilter(localNets, logNets, oldFilter, b.logf)) } else { b.logf("[v1] netmap packet filter: %v filters", len(packetFilter)) - b.setFilter(filter.New(packetFilter, b.srcIPHasCapForFilter, localNets, logNets, oldFilter, b.logf)) + filt := filter.New(packetFilter, b.srcIPHasCapForFilter, localNets, logNets, oldFilter, b.logf) + + filt.IngressAllowHooks = b.extHost.Hooks().Filter.IngressAllowHooks + filt.LinkLocalAllowHooks = b.extHost.Hooks().Filter.LinkLocalAllowHooks + b.setFilter(filt) } // The filter for a jailed node is the exact same as a ShieldsUp filter. oldJailedFilter := b.e.GetJailedFilter() diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index 63a7aee1e461f..b2be836c73395 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -32,8 +32,9 @@ import ( type Filter struct { logf logger.Logf // local4 and local6 report whether an IP is "local" to this node, for the - // respective address family. All packets coming in over tailscale must have - // a destination within local, regardless of the policy filter below. + // respective address family. Inbound packets that pass the direction-agnostic + // pre-checks and are not accepted by [Filter.IngressAllowHooks] must have a destination + // within local to be considered by the policy filter. local4 func(netip.Addr) bool local6 func(netip.Addr) bool @@ -66,8 +67,38 @@ type Filter struct { state *filterState shieldsUp bool + + // IngressAllowHooks are hooks that allow extensions to accept inbound + // packets beyond the standard filter rules. Packets that are not dropped + // by the direction-agnostic pre-check, but would be not accepted by the + // main filter rules, including the check for destinations in the node's + // local IP set, will be accepted if they match one of these hooks. + // As of 2026-02-24, the ingress filter does not implement explicit drop + // rules, but if it does, an explicitly dropped packet will be dropped, + // and these hooks will not be evaluated. + // + // Processing of hooks stop after the first one that returns true. + // The returned why string of the first match is used in logging. + // Returning false does not drop the packet. + // See also [filter.Filter.IngressAllowHooks]. + IngressAllowHooks []PacketMatch + + // LinkLocalAllowHooks are hooks that provide exceptions to the default + // policy of dropping link-local unicast packets. They run inside the + // direction-agnostic pre-checks for both ingress and egress. + // + // A hook can allow a link-local packet to pass the link-local check, + // but the packet is still subject to all other filter rules, and could be + // dropped elsewhere. Matching link-local packets are not logged. + // See also [filter.Filter.LinkLocalAllowHooks]. + LinkLocalAllowHooks []PacketMatch } +// PacketMatch is a function that inspects a packet and reports whether it +// matches a custom filter criterion. If match is true, why should be a short +// human-readable reason for the match, used in filter logging (e.g. "corp-dns ok"). +type PacketMatch func(packet.Parsed) (match bool, why string) + // filterState is a state cache of past seen packets. type filterState struct { mu sync.Mutex @@ -426,6 +457,16 @@ func (f *Filter) RunIn(q *packet.Parsed, rf RunFlags) Response { default: r, why = Drop, "not-ip" } + + if r == noVerdict { + for _, pm := range f.IngressAllowHooks { + if match, why := pm(*q); match { + f.logRateLimit(rf, q, dir, Accept, why) + return Accept + } + } + r = Drop + } f.logRateLimit(rf, q, dir, r, why) return r } @@ -439,6 +480,7 @@ func (f *Filter) RunOut(q *packet.Parsed, rf RunFlags) (Response, usermetric.Dro // already logged return r, reason } + r, why := f.runOut(q) f.logRateLimit(rf, q, dir, r, why) return r, "" @@ -455,12 +497,14 @@ func unknownProtoString(proto ipproto.Proto) string { return s } +// runIn4 returns noVerdict for unaccepted packets that may ultimately +// be accepted through [Filter.IngressAllowHooks]. func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { // A compromised peer could try to send us packets for // destinations we didn't explicitly advertise. This check is to // prevent that. if !f.local4(q.Dst.Addr()) { - return Drop, "destination not allowed" + return noVerdict, "destination not allowed" } switch q.IPProto { @@ -510,17 +554,19 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { if f.matches4.matchProtoAndIPsOnlyIfAllPorts(q) { return Accept, "other-portless ok" } - return Drop, unknownProtoString(q.IPProto) + return noVerdict, unknownProtoString(q.IPProto) } - return Drop, "no rules matched" + return noVerdict, "no rules matched" } +// runIn6 returns noVerdict for unaccepted packets that may ultimately +// be accepted through [Filter.IngressAllowHooks]. func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { // A compromised peer could try to send us packets for // destinations we didn't explicitly advertise. This check is to // prevent that. if !f.local6(q.Dst.Addr()) { - return Drop, "destination not allowed" + return noVerdict, "destination not allowed" } switch q.IPProto { @@ -570,9 +616,9 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { if f.matches6.matchProtoAndIPsOnlyIfAllPorts(q) { return Accept, "other-portless ok" } - return Drop, unknownProtoString(q.IPProto) + return noVerdict, unknownProtoString(q.IPProto) } - return Drop, "no rules matched" + return noVerdict, "no rules matched" } // runIn runs the output-specific part of the filter logic. @@ -609,6 +655,18 @@ func (d direction) String() string { var gcpDNSAddr = netaddr.IPv4(169, 254, 169, 254) +func (f *Filter) isAllowedLinkLocal(q *packet.Parsed) bool { + if q.Dst.Addr() == gcpDNSAddr { + return true + } + for _, pm := range f.LinkLocalAllowHooks { + if match, _ := pm(*q); match { + return true + } + } + return false +} + // pre runs the direction-agnostic filter logic. dir is only used for // logging. func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) (Response, usermetric.DropReason) { @@ -630,7 +688,7 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) (Response, us f.logRateLimit(rf, q, dir, Drop, "multicast") return Drop, usermetric.ReasonMulticast } - if q.Dst.Addr().IsLinkLocalUnicast() && q.Dst.Addr() != gcpDNSAddr { + if q.Dst.Addr().IsLinkLocalUnicast() && !f.isAllowedLinkLocal(q) { f.logRateLimit(rf, q, dir, Drop, "link-local-unicast") return Drop, usermetric.ReasonLinkLocalUnicast } diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index 4b364d30e85cb..c588a506e0dc9 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -171,12 +171,8 @@ func TestFilter(t *testing.T) { {Drop, parsed(ipproto.TCP, ipWithoutCap.String(), "1.2.3.4", 30000, 22)}, } for i, test := range tests { - aclFunc := filt.runIn4 - if test.p.IPVersion == 6 { - aclFunc = filt.runIn6 - } - if got, why := aclFunc(&test.p); test.want != got { - t.Errorf("#%d runIn got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p) + if got := filt.RunIn(&test.p, 0); test.want != got { + t.Errorf("#%d RunIn got=%v want=%v packet:%v", i, got, test.want, test.p) continue } if test.p.IPProto == ipproto.TCP { @@ -191,8 +187,8 @@ func TestFilter(t *testing.T) { } // TCP and UDP are treated equivalently in the filter - verify that. test.p.IPProto = ipproto.UDP - if got, why := aclFunc(&test.p); test.want != got { - t.Errorf("#%d runIn (UDP) got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p) + if got := filt.RunIn(&test.p, 0); test.want != got { + t.Errorf("#%d RunIn (UDP) got=%v want=%v packet:%v", i, got, test.want, test.p) } } // Update UDP state @@ -1071,6 +1067,192 @@ type benchOpt struct { udp, udpOpen bool } +func TestIngressAllowHooks(t *testing.T) { + matchSrc := func(ip string) PacketMatch { + return func(q packet.Parsed) (bool, string) { + return q.Src.Addr() == mustIP(ip), "match-src" + } + } + matchDst := func(ip string) PacketMatch { + return func(q packet.Parsed) (bool, string) { + return q.Dst.Addr() == mustIP(ip), "match-dst" + } + } + noMatch := func(q packet.Parsed) (bool, string) { return false, "" } + + tests := []struct { + name string + p packet.Parsed + hooks []PacketMatch + want Response + }{ + { + name: "no_hooks_denied_src", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + want: Drop, + }, + { + name: "non_matching_hook", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + hooks: []PacketMatch{noMatch}, + want: Drop, + }, + { + name: "matching_hook_denied_src", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + hooks: []PacketMatch{matchSrc("99.99.99.99")}, + want: Accept, + }, + { + name: "non_local_dst_no_hooks", + p: parsed(ipproto.TCP, "8.1.1.1", "16.32.48.64", 0, 443), + want: Drop, + }, + { + name: "non_local_dst_with_hook", + p: parsed(ipproto.TCP, "8.1.1.1", "16.32.48.64", 0, 443), + hooks: []PacketMatch{matchDst("16.32.48.64")}, + want: Accept, + }, + { + name: "first_match_wins", + p: parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22), + hooks: []PacketMatch{noMatch, matchSrc("99.99.99.99")}, + want: Accept, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filt := newFilter(t.Logf) + filt.IngressAllowHooks = tt.hooks + if got := filt.RunIn(&tt.p, 0); got != tt.want { + t.Errorf("RunIn = %v; want %v", got, tt.want) + } + }) + } + + // Verify first-match-wins stops calling subsequent hooks. + t.Run("first_match_stops_iteration", func(t *testing.T) { + filt := newFilter(t.Logf) + p := parsed(ipproto.TCP, "99.99.99.99", "1.2.3.4", 0, 22) + var called []int + filt.IngressAllowHooks = []PacketMatch{ + func(q packet.Parsed) (bool, string) { + called = append(called, 0) + return true, "first" + }, + func(q packet.Parsed) (bool, string) { + called = append(called, 1) + return true, "second" + }, + } + filt.RunIn(&p, 0) + if len(called) != 1 || called[0] != 0 { + t.Errorf("called = %v; want [0]", called) + } + }) +} + +func TestLinkLocalAllowHooks(t *testing.T) { + matchDst := func(ip string) PacketMatch { + return func(q packet.Parsed) (bool, string) { + return q.Dst.Addr() == mustIP(ip), "match-dst" + } + } + noMatch := func(q packet.Parsed) (bool, string) { return false, "" } + + llPkt := func() packet.Parsed { + p := parsed(ipproto.UDP, "8.1.1.1", "169.254.1.2", 0, 53) + p.StuffForTesting(1024) + return p + } + gcpPkt := func() packet.Parsed { + p := parsed(ipproto.UDP, "8.1.1.1", "169.254.169.254", 0, 53) + p.StuffForTesting(1024) + return p + } + + tests := []struct { + name string + p packet.Parsed + hooks []PacketMatch + dir direction + want Response + }{ + { + name: "dropped_by_default", + p: llPkt(), + dir: in, + want: Drop, + }, + { + name: "non_matching_hook", + p: llPkt(), + hooks: []PacketMatch{noMatch}, + dir: in, + want: Drop, + }, + { + name: "matching_hook_allows", + p: llPkt(), + hooks: []PacketMatch{matchDst("169.254.1.2")}, + dir: in, + want: noVerdict, + }, + { + name: "gcp_dns_always_allowed", + p: gcpPkt(), + dir: in, + want: noVerdict, + }, + { + name: "matching_hook_allows_egress", + p: llPkt(), + hooks: []PacketMatch{matchDst("169.254.1.2")}, + dir: out, + want: noVerdict, + }, + { + name: "first_match_wins", + p: llPkt(), + hooks: []PacketMatch{noMatch, matchDst("169.254.1.2")}, + dir: in, + want: noVerdict, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filt := newFilter(t.Logf) + filt.LinkLocalAllowHooks = tt.hooks + got, reason := filt.pre(&tt.p, 0, tt.dir) + if got != tt.want { + t.Errorf("pre = %v (%s); want %v", got, reason, tt.want) + } + }) + } + + // Verify first-match-wins stops calling subsequent hooks. + t.Run("first_match_stops_iteration", func(t *testing.T) { + filt := newFilter(t.Logf) + p := llPkt() + var called []int + filt.LinkLocalAllowHooks = []PacketMatch{ + func(q packet.Parsed) (bool, string) { + called = append(called, 0) + return true, "first" + }, + func(q packet.Parsed) (bool, string) { + called = append(called, 1) + return true, "second" + }, + } + filt.pre(&p, 0, in) + if len(called) != 1 || called[0] != 0 { + t.Errorf("called = %v; want [0]", called) + } + }) +} + func benchmarkFile(b *testing.B, file string, opt benchOpt) { var matches []Match bts, err := os.ReadFile(file) From eb819c580eb7a2b47047fd5f0a63bf29dbb423fe Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 25 Feb 2026 09:52:50 -0800 Subject: [PATCH 145/202] cmd/containerboot, net/dns/resolver: remove unused funcs in tests staticcheck was complaining about it on a PR I sent: https://github.com/tailscale/tailscale/actions/runs/22408882872/job/64876543467?pr=18804 And: https://github.com/tailscale/tailscale/actions/runs/22408882872/job/64876543475?pr=18804 Updates #cleanup Updates #18157 Change-Id: I6225481f3aab9e43ef1920aa1a12e86c5073a638 Signed-off-by: Brad Fitzpatrick --- cmd/containerboot/main_test.go | 6 ------ net/dns/resolver/forwarder_test.go | 6 ------ 2 files changed, 12 deletions(-) diff --git a/cmd/containerboot/main_test.go b/cmd/containerboot/main_test.go index 1970fb4bfa449..58ab757950612 100644 --- a/cmd/containerboot/main_test.go +++ b/cmd/containerboot/main_test.go @@ -1603,12 +1603,6 @@ func (k *kubeServer) serveSecret(w http.ResponseWriter, r *http.Request) { } } -func mustBase64(t *testing.T, v any) string { - b := mustJSON(t, v) - s := base64.StdEncoding.WithPadding('=').EncodeToString(b) - return s -} - func mustJSON(t *testing.T, v any) []byte { b, err := json.Marshal(v) if err != nil { diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 6c7459b1f619c..6fd186c25a61c 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -595,12 +595,6 @@ func beVerbose(f *forwarder) { f.verboseFwd = true } -// makeTestRequestWithEDNS returns a new TypeTXT request for the given domain with EDNS buffer size. -// Deprecated: Use makeTestRequest with queryType and ednsSize parameters instead. -func makeTestRequestWithEDNS(tb testing.TB, domain string, ednsSize uint16) []byte { - return makeTestRequest(tb, domain, dns.TypeTXT, ednsSize) -} - // makeEDNSResponse creates a DNS response of approximately the specified size // with TXT records and an OPT record. The response will NOT have the TC flag set // (simulating a non-compliant server that doesn't set TC when response exceeds EDNS buffer). From 329d2e2643b804c3666d93b3f7195c22b6ae2523 Mon Sep 17 00:00:00 2001 From: Mike O'Driscoll Date: Wed, 25 Feb 2026 13:52:01 -0500 Subject: [PATCH 146/202] prober: fix race condition in TestExcludeInRunAll (#18807) The test was making HTTP requests before waiting for probes to complete their initial run in "once" mode. This created a race where sometimes the probe's previous state was empty (0 results) and sometimes it had one result, causing inconsistent RecentResults and PreviousSuccessRatio values. Fixed by waiting for all probes to complete via their stopped channels before making HTTP requests, matching the pattern used in other tests like TestProberRunHandler and TestRunAllHandler. Fixes #18806 Signed-off-by: Mike O'Driscoll --- prober/prober_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/prober/prober_test.go b/prober/prober_test.go index 8da5127875859..14b75d5b5c2b1 100644 --- a/prober/prober_test.go +++ b/prober/prober_test.go @@ -793,9 +793,14 @@ func TestExcludeInRunAll(t *testing.T) { }, } - p.Run("includedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) - p.Run("excludedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) - p.Run("excludedOtherProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + includedProbe := p.Run("includedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + excludedProbe := p.Run("excludedProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + excludedOtherProbe := p.Run("excludedOtherProbe", probeInterval, nil, FuncProbe(func(context.Context) error { return nil })) + + // Wait for all probes to complete their initial run + <-includedProbe.stopped + <-excludedProbe.stopped + <-excludedOtherProbe.stopped mux := http.NewServeMux() server := httptest.NewServer(mux) From fd2ebcd5bdf5a166513e7b86114dcbcb5d8c67e3 Mon Sep 17 00:00:00 2001 From: Chris Mosetick Date: Wed, 25 Feb 2026 20:30:21 +0100 Subject: [PATCH 147/202] cmd/k8s-operator: add exit node example (#18087) * cmd/k8s-operator/deploy/examples Adds exitnode.yaml to k8s-operator Fixes #18086 Signed-off-by: Christopher Mosetick * cmd/k8s-operator/deploy/examples: update connector and add exitnode examples - Remove exitNode: true from connector.yaml to keep it focused as a subnet router example - Update connector.yaml header comment to remove exit node reference and add pointer hint to exitnode.yaml - Clarify exitnode.yaml comments to accurately describe separate Connector deployment pattern Fixes #18086 Signed-off-by: Christopher Mosetick * Update cmd/k8s-operator/deploy/examples/exitnode.yaml Co-authored-by: David Bond Signed-off-by: Chris Mosetick * Update cmd/k8s-operator/deploy/examples/exitnode.yaml Co-authored-by: David Bond Signed-off-by: Chris Mosetick * Update cmd/k8s-operator/deploy/examples/exitnode.yaml Co-authored-by: David Bond Signed-off-by: Chris Mosetick * Update cmd/k8s-operator/deploy/examples/exitnode.yaml Co-authored-by: David Bond Signed-off-by: Chris Mosetick --------- Signed-off-by: Christopher Mosetick Signed-off-by: Chris Mosetick Co-authored-by: David Bond --- .../deploy/examples/connector.yaml | 4 +-- .../deploy/examples/exitnode.yaml | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 cmd/k8s-operator/deploy/examples/exitnode.yaml diff --git a/cmd/k8s-operator/deploy/examples/connector.yaml b/cmd/k8s-operator/deploy/examples/connector.yaml index f5447400e8722..a025eef98cd26 100644 --- a/cmd/k8s-operator/deploy/examples/connector.yaml +++ b/cmd/k8s-operator/deploy/examples/connector.yaml @@ -1,9 +1,10 @@ # Before applying ensure that the operator owns tag:prod. # https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator. -# To set up autoapproval set tag:prod as approver for 10.40.0.0/14 route and exit node. +# To set up autoapproval set tag:prod as approver for 10.40.0.0/14 route. # Otherwise approve it manually in Machines panel once the # ts-prod Tailscale node has been created. # See https://tailscale.com/kb/1018/acls/#auto-approvers-for-routes-and-exit-nodes +# For an exit node example, see exitnode.yaml apiVersion: tailscale.com/v1alpha1 kind: Connector metadata: @@ -17,4 +18,3 @@ spec: advertiseRoutes: - "10.40.0.0/14" - "192.168.0.0/14" - exitNode: true diff --git a/cmd/k8s-operator/deploy/examples/exitnode.yaml b/cmd/k8s-operator/deploy/examples/exitnode.yaml new file mode 100644 index 0000000000000..b2ce516cd98bf --- /dev/null +++ b/cmd/k8s-operator/deploy/examples/exitnode.yaml @@ -0,0 +1,26 @@ +# Before applying ensure that the operator owns tag:k8s-operator +# To use both subnet routing and exit node on the same cluster, deploy a separate +# Connector resource for each. +# See connector.yaml for a subnet router example. +# See: https://tailscale.com/kb/1441/kubernetes-operator-connector +--- +apiVersion: tailscale.com/v1alpha1 +kind: Connector +metadata: + name: exit-node +spec: + # Exit node configuration - allows Tailscale clients to route all internet traffic through this Connector + exitNode: true + + # High availability: 2 replicas for redundancy + # Note: Must use hostnamePrefix (not hostname) when replicas > 1 + replicas: 2 + + # Hostname prefix for the exit node devices + # Devices will be named: exit-node-0, exit-node-1 + hostnamePrefix: exit-node + + # Tailscale tags for ACL policy management + tags: + - tag:k8s-operator + From 7370c24eb4989ca82f83009a0d36395bab4ea8c0 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 25 Feb 2026 17:45:51 +0000 Subject: [PATCH 148/202] tool/listpkgs: add --affected-by-tag For paring back build tag variant CI runs' set of packages to test. Updates tailscale/corp#28679 Change-Id: Iba46fd1f58c1eaee1f7888ef573bc8b14fa73208 Signed-off-by: Brad Fitzpatrick --- tool/listpkgs/listpkgs.go | 81 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/tool/listpkgs/listpkgs.go b/tool/listpkgs/listpkgs.go index e2c286efc0f7d..1c2dda257a7ca 100644 --- a/tool/listpkgs/listpkgs.go +++ b/tool/listpkgs/listpkgs.go @@ -26,6 +26,7 @@ var ( withTagsAllStr = flag.String("with-tags-all", "", "if non-empty, a comma-separated list of builds tags to require (a package will only be listed if it contains all of these build tags)") withoutTagsAnyStr = flag.String("without-tags-any", "", "if non-empty, a comma-separated list of build constraints to exclude (a package will be omitted if it contains any of these build tags)") shard = flag.String("shard", "", "if non-empty, a string of the form 'N/M' to only print packages in shard N of M (e.g. '1/3', '2/3', '3/3/' for different thirds of the list)") + affectedByTag = flag.String("affected-by-tag", "", "if non-empty, only list packages whose test binary would be affected by the presence or absence of this build tag") ) func main() { @@ -41,6 +42,10 @@ func main() { Mode: packages.LoadFiles, Env: os.Environ(), } + if *affectedByTag != "" { + cfg.Mode |= packages.NeedImports + cfg.Tests = true + } if *goos != "" { cfg.Env = append(cfg.Env, "GOOS="+*goos) } @@ -62,6 +67,11 @@ func main() { withAll = strings.Split(*withTagsAllStr, ",") } + var affected map[string]bool // PkgPath → true + if *affectedByTag != "" { + affected = computeAffected(pkgs, *affectedByTag) + } + seen := map[string]bool{} matches := 0 Pkg: @@ -69,6 +79,17 @@ Pkg: if pkg.PkgPath == "" { // malformed (shouldn’t happen) continue } + if affected != nil { + // Skip synthetic packages created by Tests: true: + // - for-test variants like "foo [foo.test]" (ID != PkgPath) + // - test binary packages like "foo.test" (PkgPath ends in ".test") + if pkg.ID != pkg.PkgPath || strings.HasSuffix(pkg.PkgPath, ".test") { + continue + } + if !affected[pkg.PkgPath] { + continue + } + } if seen[pkg.PkgPath] { continue // suppress duplicates when patterns overlap } @@ -96,7 +117,7 @@ Pkg: if *shard != "" { var n, m int if _, err := fmt.Sscanf(*shard, "%d/%d", &n, &m); err != nil || n < 1 || m < 1 { - log.Fatalf("invalid shard format %q; expected 'N/M'", *shard) + log.Fatalf("invalid shard format %q; expected ‘N/M’", *shard) } if m > 0 && (matches-1)%m != n-1 { continue // not in this shard @@ -112,6 +133,62 @@ Pkg: } } +// computeAffected returns the set of package paths whose test binaries would +// differ with vs without the given build tag. It finds packages that directly +// mention the tag, then propagates transitively via reverse dependencies. +func computeAffected(pkgs []*packages.Package, tag string) map[string]bool { + // Build a map from package ID to package for quick lookup. + byID := make(map[string]*packages.Package, len(pkgs)) + for _, pkg := range pkgs { + byID[pkg.ID] = pkg + } + + // First pass: find directly affected package IDs. + directlyAffected := make(map[string]bool) + for _, pkg := range pkgs { + if hasBuildTag(pkg, tag) { + directlyAffected[pkg.ID] = true + } + } + + // Build reverse dependency graph: importedID → []importingID. + reverseDeps := make(map[string][]string) + for _, pkg := range pkgs { + for _, imp := range pkg.Imports { + reverseDeps[imp.ID] = append(reverseDeps[imp.ID], pkg.ID) + } + } + + // BFS from directly affected packages through reverse deps. + affectedIDs := make(map[string]bool) + queue := make([]string, 0, len(directlyAffected)) + for id := range directlyAffected { + affectedIDs[id] = true + queue = append(queue, id) + } + for len(queue) > 0 { + id := queue[0] + queue = queue[1:] + for _, rdep := range reverseDeps[id] { + if !affectedIDs[rdep] { + affectedIDs[rdep] = true + queue = append(queue, rdep) + } + } + } + + // Map affected IDs back to PkgPaths. For-test variants like + // "foo [foo.test]" share the same PkgPath as "foo", so the + // result naturally deduplicates. + affected := make(map[string]bool) + for id := range affectedIDs { + if pkg, ok := byID[id]; ok { + affected[pkg.PkgPath] = true + } + } + return affected +} + func isThirdParty(pkg string) bool { return strings.HasPrefix(pkg, "tailscale.com/tempfork/") } @@ -194,7 +271,7 @@ func getFileTags(filename string) (tagSet, error) { mu.Lock() defer mu.Unlock() fileTags[filename] = ts - return tags, nil + return ts, nil } func fileMentionsTag(filename, tag string) (bool, error) { From 518d2417003657f955b98a546987e376ad9fe740 Mon Sep 17 00:00:00 2001 From: joshua stein Date: Sun, 22 Feb 2026 17:13:58 -0600 Subject: [PATCH 149/202] netns,wgengine: add OpenBSD support to netns via an rtable When an exit node has been set and a new default route is added, create a new rtable in the default rdomain and add the current default route via its physical interface. When control() is requesting a connection not go through the exit-node default route, we can use the SO_RTABLE socket option to force it through the new rtable we created. Updates #17321 Signed-off-by: joshua stein --- ipn/ipnlocal/local.go | 2 +- net/netmon/defaultroute_bsd.go | 5 +- net/netmon/interfaces_bsd.go | 2 +- ...aces_freebsd.go => interfaces_bsdroute.go} | 4 +- net/netmon/interfaces_defaultrouteif_todo.go | 2 +- net/netns/netns_default.go | 2 +- net/netns/netns_openbsd.go | 178 ++++++++++++++++++ net/routetable/routetable_bsd.go | 2 +- ...able_freebsd.go => routetable_bsdconst.go} | 3 +- net/routetable/routetable_other.go | 2 +- wgengine/router/osrouter/router_openbsd.go | 49 ++++- 11 files changed, 231 insertions(+), 20 deletions(-) rename net/netmon/{interfaces_freebsd.go => interfaces_bsdroute.go} (87%) create mode 100644 net/netns/netns_openbsd.go rename net/routetable/{routetable_freebsd.go => routetable_bsdconst.go} (90%) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 3fccb4399dd13..bae1e66393a4b 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -5601,7 +5601,7 @@ func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView b.logf("failed to discover interface ips: %v", err) } switch runtime.GOOS { - case "linux", "windows", "darwin", "ios", "android": + case "linux", "windows", "darwin", "ios", "android", "openbsd": rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks if prefs.ExitNodeAllowLANAccess() { rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...) diff --git a/net/netmon/defaultroute_bsd.go b/net/netmon/defaultroute_bsd.go index 88f2c8ea54be1..741948599758e 100644 --- a/net/netmon/defaultroute_bsd.go +++ b/net/netmon/defaultroute_bsd.go @@ -1,11 +1,10 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -// Common code for FreeBSD. This might also work on other -// BSD systems (e.g. OpenBSD) but has not been tested. +// Common code for FreeBSD and OpenBSD. // Not used on iOS or macOS. See defaultroute_darwin.go. -//go:build freebsd +//go:build freebsd || openbsd package netmon diff --git a/net/netmon/interfaces_bsd.go b/net/netmon/interfaces_bsd.go index d53e2cfc18f99..4c09aa55eeb31 100644 --- a/net/netmon/interfaces_bsd.go +++ b/net/netmon/interfaces_bsd.go @@ -4,7 +4,7 @@ // Common code for FreeBSD and Darwin. This might also work on other // BSD systems (e.g. OpenBSD) but has not been tested. -//go:build darwin || freebsd +//go:build darwin || freebsd || openbsd package netmon diff --git a/net/netmon/interfaces_freebsd.go b/net/netmon/interfaces_bsdroute.go similarity index 87% rename from net/netmon/interfaces_freebsd.go rename to net/netmon/interfaces_bsdroute.go index 5573643ca7370..7ac28c4b576fd 100644 --- a/net/netmon/interfaces_freebsd.go +++ b/net/netmon/interfaces_bsdroute.go @@ -1,9 +1,9 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -// This might work on other BSDs, but only tested on FreeBSD. +// FreeBSD and OpenBSD routing table functions. -//go:build freebsd +//go:build freebsd || openbsd package netmon diff --git a/net/netmon/interfaces_defaultrouteif_todo.go b/net/netmon/interfaces_defaultrouteif_todo.go index e428f16a1f946..55d284153815e 100644 --- a/net/netmon/interfaces_defaultrouteif_todo.go +++ b/net/netmon/interfaces_defaultrouteif_todo.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build !linux && !windows && !darwin && !freebsd && !android +//go:build !linux && !windows && !darwin && !freebsd && !android && !openbsd package netmon diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go index 4087e40488e60..33f4c1333e395 100644 --- a/net/netns/netns_default.go +++ b/net/netns/netns_default.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build !linux && !windows && !darwin +//go:build !linux && !windows && !darwin && !openbsd package netns diff --git a/net/netns/netns_openbsd.go b/net/netns/netns_openbsd.go new file mode 100644 index 0000000000000..47968bd42f35e --- /dev/null +++ b/net/netns/netns_openbsd.go @@ -0,0 +1,178 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +//go:build openbsd + +package netns + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + "sync" + "syscall" + + "golang.org/x/sys/unix" + "tailscale.com/net/netmon" + "tailscale.com/types/logger" +) + +var ( + bypassMu sync.Mutex + bypassRtable int +) + +// Called by the router when exit node routes are configured. +func SetBypassRtable(rtable int) { + bypassMu.Lock() + defer bypassMu.Unlock() + bypassRtable = rtable +} + +func GetBypassRtable() int { + bypassMu.Lock() + defer bypassMu.Unlock() + return bypassRtable +} + +func control(logf logger.Logf, _ *netmon.Monitor) func(network, address string, c syscall.RawConn) error { + return func(network, address string, c syscall.RawConn) error { + return controlC(logf, network, address, c) + } +} + +func controlC(logf logger.Logf, _, address string, c syscall.RawConn) error { + if isLocalhost(address) { + return nil + } + + rtable := GetBypassRtable() + if rtable == 0 { + return nil + } + + return bindToRtable(c, rtable, logf) +} + +func bindToRtable(c syscall.RawConn, rtable int, logf logger.Logf) error { + var sockErr error + err := c.Control(func(fd uintptr) { + sockErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RTABLE, rtable) + }) + if sockErr != nil { + logf("netns: SO_RTABLE(%d): %v", rtable, sockErr) + } + if err != nil { + return fmt.Errorf("RawConn.Control: %w", err) + } + return sockErr +} + +// SetupBypassRtable creates a bypass rtable with the existing default route +// in it routing through its existing physical interface. It should be called +// by the router when exit node routes are being added. +// Returns the rtable number. +func SetupBypassRtable(logf logger.Logf) (int, error) { + bypassMu.Lock() + defer bypassMu.Unlock() + + if bypassRtable != 0 { + return bypassRtable, nil + } + + gw, err := getPhysicalGateway() + if err != nil { + return 0, fmt.Errorf("getPhysicalGateway: %w", err) + } + + rtable, err := findAvailableRtable() + if err != nil { + return 0, fmt.Errorf("findAvailableRtable: %w", err) + } + + // Add the existing default route interface to the new bypass rtable + out, err := exec.Command("route", "-T", strconv.Itoa(rtable), "-qn", "add", "default", gw).CombinedOutput() + if err != nil { + return 0, fmt.Errorf("route -T%d add default %s: %w\n%s", rtable, gw, err, out) + } + + bypassRtable = rtable + logf("netns: created bypass rtable %d with default route via %s", rtable, gw) + return rtable, nil +} + +func CleanupBypassRtable(logf logger.Logf) { + bypassMu.Lock() + defer bypassMu.Unlock() + + if bypassRtable == 0 { + return + } + + // Delete the default route from the bypass rtable which should clear it + out, err := exec.Command("route", "-T", strconv.Itoa(bypassRtable), "-qn", "delete", "default").CombinedOutput() + if err != nil { + logf("netns: failed to clear bypass route: %v\n%s", err, out) + } else { + logf("netns: cleared bypass rtable %d", bypassRtable) + } + + bypassRtable = 0 +} + +// getPhysicalGateway returns the default gateway IP that goes through a +// physical interface (not tun). +func getPhysicalGateway() (string, error) { + out, err := exec.Command("route", "-n", "show", "-inet").CombinedOutput() + if err != nil { + return "", fmt.Errorf("route show: %w", err) + } + + // Parse the routing table looking for default routes not via tun + for _, line := range strings.Split(string(out), "\n") { + fields := strings.Fields(line) + if len(fields) < 8 { + continue + } + // Format: Destination Gateway Flags Refs Use Mtu Prio Iface + dest := fields[0] + gateway := fields[1] + iface := fields[7] + + if dest == "default" && !strings.HasPrefix(iface, "tun") { + return gateway, nil + } + } + + return "", fmt.Errorf("no physical default gateway found") +} + +func findAvailableRtable() (int, error) { + for i := 1; i <= 255; i++ { + out, err := exec.Command("route", "-T", strconv.Itoa(i), "-n", "show", "-inet").CombinedOutput() + if err != nil { + // rtable doesn't exist, consider it available + return i, nil + } + // Check if the output only contains the header (no actual routes) + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + hasRoutes := false + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "Routing") || strings.HasPrefix(line, "Destination") { + continue + } + hasRoutes = true + break + } + if !hasRoutes { + return i, nil + } + } + return 0, fmt.Errorf("no available rtable") +} + +func UseSocketMark() bool { + return false +} diff --git a/net/routetable/routetable_bsd.go b/net/routetable/routetable_bsd.go index 7a6bf48cc96e8..f5306d8942a02 100644 --- a/net/routetable/routetable_bsd.go +++ b/net/routetable/routetable_bsd.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build darwin || freebsd +//go:build darwin || freebsd || openbsd package routetable diff --git a/net/routetable/routetable_freebsd.go b/net/routetable/routetable_bsdconst.go similarity index 90% rename from net/routetable/routetable_freebsd.go rename to net/routetable/routetable_bsdconst.go index 313febf3ca94d..9de9aad73802f 100644 --- a/net/routetable/routetable_freebsd.go +++ b/net/routetable/routetable_bsdconst.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build freebsd +//go:build freebsd || openbsd package routetable @@ -21,6 +21,7 @@ var flags = map[int]string{ unix.RTF_BROADCAST: "broadcast", unix.RTF_GATEWAY: "gateway", unix.RTF_HOST: "host", + unix.RTF_LOCAL: "local", unix.RTF_MULTICAST: "multicast", unix.RTF_REJECT: "reject", unix.RTF_STATIC: "static", diff --git a/net/routetable/routetable_other.go b/net/routetable/routetable_other.go index da162c3f8e191..25d008ccc276f 100644 --- a/net/routetable/routetable_other.go +++ b/net/routetable/routetable_other.go @@ -1,7 +1,7 @@ // Copyright (c) Tailscale Inc & contributors // SPDX-License-Identifier: BSD-3-Clause -//go:build android || (!linux && !darwin && !freebsd) +//go:build android || (!linux && !darwin && !freebsd && !openbsd) package routetable diff --git a/wgengine/router/osrouter/router_openbsd.go b/wgengine/router/osrouter/router_openbsd.go index 8807a32d5b860..1c7eed52e2e76 100644 --- a/wgengine/router/osrouter/router_openbsd.go +++ b/wgengine/router/osrouter/router_openbsd.go @@ -14,6 +14,7 @@ import ( "go4.org/netipx" "tailscale.com/health" "tailscale.com/net/netmon" + "tailscale.com/net/netns" "tailscale.com/types/logger" "tailscale.com/util/eventbus" "tailscale.com/util/set" @@ -32,12 +33,13 @@ func init() { // https://git.zx2c4.com/wireguard-openbsd. type openbsdRouter struct { - logf logger.Logf - netMon *netmon.Monitor - tunname string - local4 netip.Prefix - local6 netip.Prefix - routes set.Set[netip.Prefix] + logf logger.Logf + netMon *netmon.Monitor + tunname string + local4 netip.Prefix + local6 netip.Prefix + routes set.Set[netip.Prefix] + areDefaultRoute bool } func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker, bus *eventbus.Bus) (router.Router, error) { @@ -76,6 +78,10 @@ func inet(p netip.Prefix) string { return "inet" } +func isDefaultRoute(p netip.Prefix) bool { + return p.Bits() == 0 +} + func (r *openbsdRouter) Set(cfg *router.Config) error { if cfg == nil { cfg = &shutdownConfig @@ -219,8 +225,12 @@ func (r *openbsdRouter) Set(cfg *router.Config) error { dst = localAddr6.Addr().String() } routeadd := []string{"route", "-q", "-n", - "add", "-" + inet(route), nstr, - "-iface", dst} + "add", "-" + inet(route), nstr} + if isDefaultRoute(route) { + // 1 is reserved for kernel + routeadd = append(routeadd, "-priority", "2") + } + routeadd = append(routeadd, "-iface", dst) out, err := cmd(routeadd...).CombinedOutput() if err != nil { r.logf("addr add failed: %v: %v\n%s", routeadd, err, out) @@ -235,10 +245,33 @@ func (r *openbsdRouter) Set(cfg *router.Config) error { r.local6 = localAddr6 r.routes = newRoutes + areDefault := false + for route := range newRoutes { + if isDefaultRoute(route) { + areDefault = true + break + } + } + + // Set up or tear down the bypass rtable as needed + if areDefault && !r.areDefaultRoute { + if _, err := netns.SetupBypassRtable(r.logf); err != nil { + r.logf("router: failed to set up bypass rtable: %v", err) + } + r.areDefaultRoute = true + } else if !areDefault && r.areDefaultRoute { + netns.CleanupBypassRtable(r.logf) + r.areDefaultRoute = false + } + return errq } func (r *openbsdRouter) Close() error { + if r.areDefaultRoute { + netns.CleanupBypassRtable(r.logf) + r.areDefaultRoute = false + } cleanUp(r.logf, r.tunname) return nil } From 54de5daae00cd491d7c7174d400be6f0c630a5f0 Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Wed, 25 Feb 2026 17:41:51 -0500 Subject: [PATCH 150/202] tstest/integration/nat: use per-call timeout in natlab ping (#18811) The test ping() passed the full 60s context to each PingWithOpts call, so if the first attempt hung (DERP not yet registered), the retry loop never reached attempt 2. Use a 2s per-call timeout instead. Updates: #18810 Signed-off-by: Fernando Serboncini --- tstest/integration/nat/nat_test.go | 33 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tstest/integration/nat/nat_test.go b/tstest/integration/nat/nat_test.go index 56d602222cbe9..2322e243a8ee9 100644 --- a/tstest/integration/nat/nat_test.go +++ b/tstest/integration/nat/nat_test.go @@ -415,7 +415,7 @@ func (nt *natTest) runTest(addNode ...addNodeFunc) pingRoute { return "" } - pingRes, err := ping(ctx, clients[0], sts[1].Self.TailscaleIPs[0]) + pingRes, err := ping(ctx, t, clients[0], sts[1].Self.TailscaleIPs[0]) if err != nil { t.Fatalf("ping failure: %v", err) } @@ -450,35 +450,38 @@ const ( routeNil pingRoute = "nil" // *ipnstate.PingResult is nil ) -func ping(ctx context.Context, c *vnet.NodeAgentClient, target netip.Addr) (*ipnstate.PingResult, error) { - n := 0 - var res *ipnstate.PingResult - anyPong := false - for n < 10 { - n++ - pr, err := c.PingWithOpts(ctx, target, tailcfg.PingDisco, tailscale.PingOpts{}) +func ping(ctx context.Context, t testing.TB, c *vnet.NodeAgentClient, target netip.Addr) (*ipnstate.PingResult, error) { + var lastRes *ipnstate.PingResult + for n := range 10 { + t.Logf("ping attempt %d to %v ...", n+1, target) + pingCtx, cancel := context.WithTimeout(ctx, 2*time.Second) + pr, err := c.PingWithOpts(pingCtx, target, tailcfg.PingDisco, tailscale.PingOpts{}) + cancel() if err != nil { - if anyPong { - return res, nil + t.Logf("ping attempt %d error: %v", n+1, err) + if ctx.Err() != nil { + break } - return nil, err + continue } if pr.Err != "" { return nil, errors.New(pr.Err) } + t.Logf("ping attempt %d: derp=%d endpoint=%v latency=%v", n+1, pr.DERPRegionID, pr.Endpoint, pr.LatencySeconds) if pr.DERPRegionID == 0 { return pr, nil } - res = pr + lastRes = pr select { case <-ctx.Done(): + return lastRes, nil case <-time.After(time.Second): } } - if res == nil { - return nil, errors.New("no ping response") + if lastRes != nil { + return lastRes, nil } - return res, nil + return nil, fmt.Errorf("no ping response (ctx: %v)", ctx.Err()) } func up(ctx context.Context, c *vnet.NodeAgentClient) error { From 6e2677b4ad8c79dd3112e5136aae7a352e2f4414 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Wed, 25 Feb 2026 16:00:32 -0800 Subject: [PATCH 151/202] client/systray: open BrowseToURL from WatchIPN in a browser (#18816) This works for Tailscale SSH, but not for account logins (due to another process potentially starting that login, or `--operator` limitations). RELNOTE=The systray app now opens login links for SSH check mode in a browser. Updates #8551 Signed-off-by: Andrew Lytvynov --- client/systray/systray.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/systray/systray.go b/client/systray/systray.go index 7018f0f3be2be..65c1bec20a184 100644 --- a/client/systray/systray.go +++ b/client/systray/systray.go @@ -531,6 +531,15 @@ func (menu *Menu) watchIPNBusInner() error { if err != nil { return fmt.Errorf("ipnbus error: %w", err) } + if url := n.BrowseToURL; url != nil { + // Avoid opening the browser when running as root, just in case. + runningAsRoot := os.Getuid() == 0 + if !runningAsRoot { + if err := webbrowser.Open(*url); err != nil { + log.Printf("failed to open BrowseToURL: %v", err) + } + } + } var rebuild bool if n.State != nil { log.Printf("new state: %v", n.State) From 15836e56245c590bfddf342a9ce77bcfbb364f00 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 26 Feb 2026 00:37:15 +0000 Subject: [PATCH 152/202] util/set: make Set.Slice return elements in sorted order for ordered types This makes Set.MarshalJSON produce deterministic output in many cases now. We still need to do make it deterministic for non-ordered types. Updates #18808 Change-Id: I7f341ec039c661a8e88d07d7f4dc0f15d5d4ab86 Signed-off-by: Brad Fitzpatrick --- util/set/set.go | 43 +++++++++++++++++++++++++++++++++++++++++-- util/set/set_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/util/set/set.go b/util/set/set.go index df4b1fa3a24ac..c3d2350a7e6ba 100644 --- a/util/set/set.go +++ b/util/set/set.go @@ -7,6 +7,8 @@ package set import ( "encoding/json" "maps" + "reflect" + "sort" ) // Set is a set of T. @@ -53,16 +55,53 @@ func (s *Set[T]) Make() { } } -// Slice returns the elements of the set as a slice. The elements will not be -// in any particular order. +// Slice returns the elements of the set as a slice. If the element type is +// ordered (integers, floats, or strings), the elements are returned in sorted +// order. Otherwise, the order is not defined. func (s Set[T]) Slice() []T { es := make([]T, 0, s.Len()) for k := range s { es = append(es, k) } + if f := genOrderedSwapper(reflect.TypeFor[T]()); f != nil { + sort.Slice(es, f(reflect.ValueOf(es))) + } return es } +// genOrderedSwapper returns a generator for a swap function that can be used to +// sort a slice of the given type. If rt is not an ordered type, +// genOrderedSwapper returns nil. +func genOrderedSwapper(rt reflect.Type) func(reflect.Value) func(i, j int) bool { + switch rt.Kind() { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).Uint() < rv.Index(j).Uint() + } + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).Int() < rv.Index(j).Int() + } + } + case reflect.Float32, reflect.Float64: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).Float() < rv.Index(j).Float() + } + } + case reflect.String: + return func(rv reflect.Value) func(i, j int) bool { + return func(i, j int) bool { + return rv.Index(i).String() < rv.Index(j).String() + } + } + } + return nil +} + // Delete removes e from the set. func (s Set[T]) Delete(e T) { delete(s, e) } diff --git a/util/set/set_test.go b/util/set/set_test.go index 4afaeea5747fc..2188cbb4ddff7 100644 --- a/util/set/set_test.go +++ b/util/set/set_test.go @@ -159,6 +159,39 @@ func TestSetJSONRoundTrip(t *testing.T) { } } +func checkSliceSorted[T comparable](t *testing.T, s Set[T], want []T) { + t.Helper() + got := s.Slice() + if !slices.Equal(got, want) { + t.Errorf("got %v; want %v", got, want) + } +} + +func TestSliceSorted(t *testing.T) { + t.Run("int", func(t *testing.T) { + checkSliceSorted(t, Of(3, 1, 4, 1, 5), []int{1, 3, 4, 5}) + }) + t.Run("int8", func(t *testing.T) { + checkSliceSorted(t, Of[int8](-1, 3, -100, 50), []int8{-100, -1, 3, 50}) + }) + t.Run("uint16", func(t *testing.T) { + checkSliceSorted(t, Of[uint16](300, 1, 65535, 0), []uint16{0, 1, 300, 65535}) + }) + t.Run("float64", func(t *testing.T) { + checkSliceSorted(t, Of(2.7, 1.0, 3.14), []float64{1.0, 2.7, 3.14}) + }) + t.Run("float32", func(t *testing.T) { + checkSliceSorted(t, Of[float32](2.5, 1.0, 3.0), []float32{1.0, 2.5, 3.0}) + }) + t.Run("string", func(t *testing.T) { + checkSliceSorted(t, Of("banana", "apple", "cherry"), []string{"apple", "banana", "cherry"}) + }) + t.Run("named-uint", func(t *testing.T) { + type Port uint16 + checkSliceSorted(t, Of[Port](443, 80, 8080), []Port{80, 443, 8080}) + }) +} + func TestMake(t *testing.T) { var s Set[int] s.Make() From da90ea664d7a601a73cd531bc5ae1db0993033c1 Mon Sep 17 00:00:00 2001 From: Fernando Serboncini Date: Thu, 26 Feb 2026 12:36:26 -0500 Subject: [PATCH 153/202] wgengine/magicsock: only run derpActiveFunc after connecting to DERP (#18814) derpActiveFunc was being called immediately as a bare goroutine, before startGate was resolved. For the firstDerp case, startGate is c.derpStarted which only closes after dc.Connect() completes, so derpActiveFunc was firing before the DERP connection existed. We now block it with the same logic used by runDerpReader and by runDerpWriter. Updates: #18810 Signed-off-by: Fernando Serboncini --- wgengine/magicsock/derp.go | 9 ++++- wgengine/magicsock/magicsock_test.go | 51 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go index b3cc5c2ce4927..f9e5050705b31 100644 --- a/wgengine/magicsock/derp.go +++ b/wgengine/magicsock/derp.go @@ -436,7 +436,14 @@ func (c *Conn) derpWriteChanForRegion(regionID int, peer key.NodePublic) chan de go c.runDerpReader(ctx, regionID, dc, wg, startGate) go c.runDerpWriter(ctx, dc, ch, wg, startGate) - go c.derpActiveFunc() + go func() { + select { + case <-ctx.Done(): + return + case <-startGate: + c.derpActiveFunc() + } + }() return ad.writeCh } diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 5fa177b3bce8b..9d6cae87bdcc6 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -530,6 +530,57 @@ func TestPickDERPFallback(t *testing.T) { // have fixed DERP fallback logic. } +// TestDERPActiveFuncCalledAfterConnect verifies that DERPActiveFunc is not +// called until the DERP connection is actually established (i.e. after +// startGate / derpStarted is closed). +func TestDERPActiveFuncCalledAfterConnect(t *testing.T) { + derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1)) + defer cleanup() + + bus := eventbustest.NewBus(t) + + netMon, err := netmon.New(bus, t.Logf) + if err != nil { + t.Fatal(err) + } + + resultCh := make(chan bool, 1) + var conn *Conn + + conn, err = NewConn(Options{ + Logf: t.Logf, + NetMon: netMon, + EventBus: bus, + HealthTracker: health.NewTracker(bus), + Metrics: new(usermetric.Registry), + DisablePortMapper: true, + TestOnlyPacketListener: localhostListener{}, + EndpointsFunc: func([]tailcfg.Endpoint) {}, + DERPActiveFunc: func() { + // derpStarted should already be closed when DERPActiveFunc is called. + select { + case <-conn.derpStarted: + resultCh <- true + default: + resultCh <- false + } + }, + }) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + conn.SetDERPMap(derpMap) + if err := conn.SetPrivateKey(key.NewNode()); err != nil { + t.Fatal(err) + } + + if ok := <-resultCh; !ok { + t.Error("DERPActiveFunc was called before DERP connection was established") + } +} + // TestDeviceStartStop exercises the startup and shutdown logic of // wireguard-go, which is intimately intertwined with magicsock's own // lifecycle. We seem to be good at generating deadlocks here, so if From 5ac35b665b5e2308faa7a48f85b45b7b29e7d551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20Lensb=C3=B8l?= Date: Thu, 26 Feb 2026 12:59:45 -0500 Subject: [PATCH 154/202] client/systray: add installer for a freedesktop autostart file (#18767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds freedesktop as an option for installing autostart desktop files for starting the systray application. Fixes #18766 Signed-off-by: Claus Lensbøl --- client/freedesktop/freedesktop.go | 43 +++++++ client/freedesktop/freedesktop_test.go | 145 +++++++++++++++++++++++ client/systray/startup-creator.go | 142 +++++++++++++++++++++- client/systray/tailscale-systray.desktop | 13 ++ client/systray/tailscale.png | Bin 0 -> 14069 bytes client/systray/tailscale.svg | 14 +++ cmd/tailscale/cli/configure_linux.go | 2 +- cmd/tailscale/depaware.txt | 1 + 8 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 client/freedesktop/freedesktop.go create mode 100644 client/freedesktop/freedesktop_test.go create mode 100644 client/systray/tailscale-systray.desktop create mode 100644 client/systray/tailscale.png create mode 100644 client/systray/tailscale.svg diff --git a/client/freedesktop/freedesktop.go b/client/freedesktop/freedesktop.go new file mode 100644 index 0000000000000..6ed1e8ccf88fc --- /dev/null +++ b/client/freedesktop/freedesktop.go @@ -0,0 +1,43 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package freedesktop provides helpers for freedesktop systems. +package freedesktop + +import "strings" + +const needsEscape = " \t\n\"'\\><~|&;$*?#()`" + +var escaper = strings.NewReplacer(`"`, `\"`, "`", "\\`", `$`, `\$`, `\`, `\\`) + +// Quote quotes according to the Desktop Entry Specification, as below: +// +// Arguments may be quoted in whole. If an argument contains a reserved +// character the argument must be quoted. The rules for quoting of arguments is +// also applicable to the executable name or path of the executable program as +// provided. +// +// Quoting must be done by enclosing the argument between double quotes and +// escaping the double quote character, backtick character ("`"), dollar sign +// ("$") and backslash character ("\") by preceding it with an additional +// backslash character. Implementations must undo quoting before expanding field +// codes and before passing the argument to the executable program. Reserved +// characters are space (" "), tab, newline, double quote, single quote ("'"), +// backslash character ("\"), greater-than sign (">"), less-than sign ("<"), +// tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon (";"), dollar +// sign ("$"), asterisk ("*"), question mark ("?"), hash mark ("#"), parenthesis +// ("(") and (")") and backtick character ("`"). +func Quote(s string) string { + if s == "" { + return `""` + } + if !strings.ContainsAny(s, needsEscape) { + return s + } + + var b strings.Builder + b.WriteString(`"`) + escaper.WriteString(&b, s) + b.WriteString(`"`) + return b.String() +} diff --git a/client/freedesktop/freedesktop_test.go b/client/freedesktop/freedesktop_test.go new file mode 100644 index 0000000000000..07a1104f36940 --- /dev/null +++ b/client/freedesktop/freedesktop_test.go @@ -0,0 +1,145 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package freedesktop + +import ( + "strings" + "testing" +) + +func TestEscape(t *testing.T) { + tests := []struct { + name, input, want string + }{ + { + name: "no illegal chars", + input: "/home/user", + want: "/home/user", + }, + { + name: "empty string", + input: "", + want: "\"\"", + }, + { + name: "space", + input: " ", + want: "\" \"", + }, + { + name: "tab", + input: "\t", + want: "\"\t\"", + }, + { + name: "newline", + input: "\n", + want: "\"\n\"", + }, + { + name: "double quote", + input: "\"", + want: "\"\\\"\"", + }, + { + name: "single quote", + input: "'", + want: "\"'\"", + }, + { + name: "backslash", + input: "\\", + want: "\"\\\\\"", + }, + { + name: "greater than", + input: ">", + want: "\">\"", + }, + { + name: "less than", + input: "<", + want: "\"<\"", + }, + { + name: "tilde", + input: "~", + want: "\"~\"", + }, + { + name: "pipe", + input: "|", + want: "\"|\"", + }, + { + name: "ampersand", + input: "&", + want: "\"&\"", + }, + { + name: "semicolon", + input: ";", + want: "\";\"", + }, + { + name: "dollar", + input: "$", + want: "\"\\$\"", + }, + { + name: "asterisk", + input: "*", + want: "\"*\"", + }, + { + name: "question mark", + input: "?", + want: "\"?\"", + }, + { + name: "hash", + input: "#", + want: "\"#\"", + }, + { + name: "open paren", + input: "(", + want: "\"(\"", + }, + { + name: "close paren", + input: ")", + want: "\")\"", + }, + { + name: "backtick", + input: "`", + want: "\"\\`\"", + }, + { + name: "char without escape", + input: "/home/user\t", + want: "\"/home/user\t\"", + }, + { + name: "char with escape", + input: "/home/user\\", + want: "\"/home/user\\\\\"", + }, + { + name: "all illegal chars", + input: "/home/user" + needsEscape, + want: "\"/home/user \t\n\\\"'\\\\><~|&;\\$*?#()\\`\"", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Quote(tt.input) + if strings.Compare(got, tt.want) != 0 { + t.Errorf("expected %s, got %s", tt.want, got) + } + }) + } +} diff --git a/client/systray/startup-creator.go b/client/systray/startup-creator.go index 34d85e6175fc6..369190012ce6c 100644 --- a/client/systray/startup-creator.go +++ b/client/systray/startup-creator.go @@ -10,20 +10,34 @@ import ( "bufio" "bytes" _ "embed" + "errors" "fmt" "os" "os/exec" "path/filepath" "strings" + + "tailscale.com/client/freedesktop" ) //go:embed tailscale-systray.service var embedSystemd string +//go:embed tailscale-systray.desktop +var embedFreedesktop string + +//go:embed tailscale.svg +var embedLogoSvg string + +//go:embed tailscale.png +var embedLogoPng string + func InstallStartupScript(initSystem string) error { switch initSystem { case "systemd": return installSystemd() + case "freedesktop": + return installFreedesktop() default: return fmt.Errorf("unsupported init system '%s'", initSystem) } @@ -58,7 +72,7 @@ func installSystemd() error { systemdDir := filepath.Join(configDir, "systemd", "user") if err := os.MkdirAll(systemdDir, 0o755); err != nil { - return fmt.Errorf("failed creating systemd uuser dir: %w", err) + return fmt.Errorf("failed creating systemd user dir: %w", err) } serviceFile := filepath.Join(systemdDir, "tailscale-systray.service") @@ -74,3 +88,129 @@ func installSystemd() error { return nil } + +func installFreedesktop() error { + tmpDir, err := os.MkdirTemp("", "tailscale-systray") + if err != nil { + return fmt.Errorf("unable to make tmpDir: %w", err) + } + defer os.RemoveAll(tmpDir) + + // Install icon, and use it if it works, and if not change to some generic + // network/vpn icon. + iconName := "tailscale" + if err := installIcon(tmpDir); err != nil { + iconName = "network-transmit" + fmt.Printf("unable to install icon, continuing without: %s\n", err.Error()) + } + + // Create desktop file in a tmp dir + desktopTmpPath := filepath.Join(tmpDir, "tailscale-systray.desktop") + if err := os.WriteFile(desktopTmpPath, []byte(embedFreedesktop), + 0o0755); err != nil { + return fmt.Errorf("unable to create desktop file: %w", err) + } + + // Ensure autostart dir exists and install the desktop file + configDir, err := os.UserConfigDir() + if err != nil { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("unable to locate user home: %w", err) + } + configDir = filepath.Join(homeDir, ".config") + } + + autostartDir := filepath.Join(configDir, "autostart") + if err := os.MkdirAll(autostartDir, 0o644); err != nil { + return fmt.Errorf("failed creating freedesktop autostart dir: %w", err) + } + + desktopCmd := exec.Command("desktop-file-install", "--dir", autostartDir, + desktopTmpPath) + if output, err := desktopCmd.Output(); err != nil { + return fmt.Errorf("unable to install desktop file: %w - %s", err, output) + } + + // Find the path to tailscale, just in case it's not where the example file + // has it placed, and replace that before writing the file. + tailscaleBin, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to find tailscale binary %w", err) + } + tailscaleBin = freedesktop.Quote(tailscaleBin) + + // Make possible changes to the desktop file + runEdit := func(args ...string) error { + cmd := exec.Command("desktop-file-edit", args...) + out, err := cmd.Output() + if err != nil { + return fmt.Errorf("cmd: %s: %w\n%s", cmd.String(), err, out) + } + return nil + } + + edits := [][]string{ + {"--set-key=Exec", "--set-value=" + tailscaleBin + " systray"}, + {"--set-key=TryExec", "--set-value=" + tailscaleBin}, + {"--set-icon=" + iconName}, + } + + var errs []error + desktopFile := filepath.Join(autostartDir, "tailscale-systray.desktop") + for _, args := range edits { + args = append(args, desktopFile) + if err := runEdit(args...); err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return fmt.Errorf( + "failed changing autostart file, try rebooting: %w", errors.Join(errs...)) + } + + fmt.Printf("Successfully installed freedesktop autostart service to: %s\n", desktopFile) + fmt.Println("The service will run upon logging in.") + + return nil +} + +// installIcon installs an icon using the freedesktop tools. SVG support +// is still on its way for some distros, notably missing on Ubuntu 25.10 as of +// 2026-02-19. Try to install both icons and let the DE decide from what is +// available. +// Reference: https://gitlab.freedesktop.org/xdg/xdg-utils/-/merge_requests/116 +func installIcon(tmpDir string) error { + svgPath := filepath.Join(tmpDir, "tailscale.svg") + if err := os.WriteFile(svgPath, []byte(embedLogoSvg), 0o0644); err != nil { + return fmt.Errorf("unable to create svg: %w", err) + } + + pngPath := filepath.Join(tmpDir, "tailscale.png") + if err := os.WriteFile(pngPath, []byte(embedLogoPng), 0o0644); err != nil { + return fmt.Errorf("unable to create png: %w", err) + } + + var errs []error + installed := false + svgCmd := exec.Command("xdg-icon-resource", "install", "--size", "scalable", + "--novendor", svgPath, "tailscale") + if output, err := svgCmd.Output(); err != nil { + errs = append(errs, fmt.Errorf("unable to install svg: %s - %s", err, output)) + } else { + installed = true + } + pngCmd := exec.Command("xdg-icon-resource", "install", "--size", "512", + "--novendor", pngPath, "tailscale") + if output, err := pngCmd.Output(); err != nil { + errs = append(errs, fmt.Errorf("unable to install png: %s - %s", err, output)) + } else { + installed = true + } + + if !installed { + return errors.Join(errs...) + } + return nil +} diff --git a/client/systray/tailscale-systray.desktop b/client/systray/tailscale-systray.desktop new file mode 100644 index 0000000000000..b79b72d181ddc --- /dev/null +++ b/client/systray/tailscale-systray.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=Tailscale System Tray +Comment=Tailscale system tray applet for managing Tailscale +Exec=/usr/bin/tailscale systray +TryExec=/usr/bin/tailscale +Terminal=false +NoDisplay=true +StartupNotify=false +Icon=tailscale +Categories=Network;System; +X-GNOME-Autostart-enabled=true diff --git a/client/systray/tailscale.png b/client/systray/tailscale.png new file mode 100644 index 0000000000000000000000000000000000000000..d476e88fc262f5409fc76b484e0550a398196df5 GIT binary patch literal 14069 zcmdUW^ zDM-i0GrT{q=a2aQvb*;^edf%Z>zs4#OI>X>8cJ450051Ky0RVsNca;8D9GT~j(7hN z0FpHt$_fVFi1i5zCyV*Sjeo>ZPHql+C$%4Kx2=7{*mUXR8Pjfbs+buM1oyqk=ulTM zV9iF;541OBDXe!?@&xB_I`B7kq=*^nOY5Z@Df2VwslAjwzg)xnh0-nJ(&w*=Wj1Pz z(XCHe`6-HqOzTVc!^_w4w!ZZ@MW$XqSmPy|sonD-)GrcmI_CYvO8oxVY&CiC$iZQL zm6xTBaHDL&D)H{Nqh6CGGH|5|qRgI7yHE5O%f0&_vjn80@7 z!uv~@#& z7363Lq5Ge!2zeM3bKmKZx0}-OreHucDDAQwnHUZ4Kf08}w0nhVx8JFNNremeweRn`AU|=D4 z&Y@UaTbtD{%W;(IcXDh;dSC^_6|xKm7oTOxjXiXKNF+YLS&p6{Q+~?t(Xk8 z5wUNNNwC5WWyu-Kr#1UnP~iIIRmvK5;}l;KEG-`nOec=y3T*m43?Qf|X_8>i{$^l! zov!iZ=Wr7psQVpDPJ!$=4F+57aBXccFc+408UMTq&PxlNLu)dH4{UVDpdi9^^0R2Zn3ih_=4cmoefu|#M>%MobS*_B| zB7ibq4x-O=f4|{iZ#`ll$JkEj+8s||zmEd(w_^G zB~jqs)iTo<_Dy^e!crJvUt3?-M#Lh(RY8rdCwSWDlj~HQ!rUl=A0};{2mM}0IR~#<0B~W=@j`M+&|Swc zp?_#&02q(Wur%kH_5iIB^&?_r0IPPUm}N)_Ta3WY(1Fd5t2_EQo+qLt*BJmG0brvm&muojfJ0A$`3gCp_@vlyaNwPOH;?c;)M=>; zX0xIK6(YZOb9j*8A$-j9NFL|0lekh@G9BbJ`juk}sAMMnFE zJ$^QgMxmcs&lHMlRE*OBEZL8Wn{M}f!SFxW!ffCu02@xzfJTA@0su?D8zf0-A23XB zZ50jT+u#K{dS%Gbc52t34m*e!(x`I=AmvN0guJYrbd`yP`As|j{-KLt5RU-E@9H+l zmsZaJuOqm1`prlF7;K#oqQ78Y{vH7L#D3$=DVN&Lplf$AJpeR(=#NGE5V}+91QDCw zRsi^1$jSCMrCd5Z1M=Wz_^jmj^z=M{%2~3js}l?C@hH%JPPU#uB~ugp@cDcifWXy5 zLxijuveos#=IJni>h`Xs+%-AJvUc<>O`xnPGOL_W`mX>=E5)jW@muw|F3P1}XBxJ) zwxl9YgjFB4yJCtk`wPB7L14ds^+NEMk-|snm24H*CgU6%vb-PBdWk4DzC#Wxv{eDC z)_9QV{I;wgu{1RC5)SEWdhVVk$AK0(_UIwzAqs$?*bK3ipJSJtr9qlT0Nt(TtVh3?>Y18kvZ1<2(*TDmgSvDy3PlX=^kdlmG z<&+IPkHE`mCLBvcik+XISEB*oy4@EGZ2ksGt1J-#+|2a&qr8sO8ve=vO--n`;M050 z6*{luS_^U@4Et_yHZ`kXjXJU*#U3?3FM{30MtN4Ve9Hk13vhn8=z%rVgrAi$f?Fdd zpI`&`s-oy2R;^gL!6b8Il^MX>j9gGlh7bk-mcgJ{X0kZb2AF8a6fgs@sdi3tp92Nf z;o@*H@t(O8&M0)<+vq*maow`F1p1g39V>GR0kCG$7Bt&T}ttM~+B!HV&OLD(}4z#tM z?Cp2^veRz<;2w$_w$zC4-}6LpKRgAc@-58)uCvmdv#rjd9cqPo^WfxZH)nF}2o%Hp zyN0u{68L$q;}TJ?TAl>HGO=_Q!44p>0`NVBUbY2L9|05q9j#Yv_||U^zN8O(GArV8 zlUhBNJgu$m9L-W=UioA^rf$7#Y!E+E>NvFaR!6RIb*O-C{PXP7?GC9$F305x|ARka zWaUdH6CQn9T3r%}Hqy4A9G8sr_}B9v?Cf$$-~vXlm;TmlYqN22l@}Hk;#RHEE6P06 zzuyZSwp4G_ISxFM5V^J_&vAjH*rNLByo#08xzs!c4iOF+j^h1EUlF1NsqIyd$nfpF zgQW#?29C=ifirQ-2MDcklNGCFaQim9`rEn7p-b7`S#C>8L3hegx>Dj-9k4#f>*d=8 zJu=cRUQ_;F0pouwW@%T`VN)@l{Bn<1%ihbwo@~8UcY-TILPBuEh?unH+4qNs=e7$X zrUZ4g<=)0~h$?HM-h_vdeYQM{Hxf5{2G_ie2xnPXSgx4B5b?4iM|pfCHM@gmhmU_O z?1<$`e7Tx3o4y?d&Xwmb$s1OBEXUQkm?NhtvFbEcSVr-&k4Z_LL0DV9b0U@x+(rRY zwC_f(lvO+nZ>`SG9g^#|u@m}#>KBuIw`L_@L|b$d9ryoE9ENp@JxABDZjictL0y%Q z*k#b{LxtMg>%XDgOrgREFuwasE%2@M^X%TyYQY!|SCcBij;wsD_$qNuV0swc&cXF^ZmupDzNMV28U?mG@IvGwuFK67OS2kpT1KyuDj zshvvomfKt}lw;aEJ39O?5sMCLvwvw=AWbAmI!aA3GSs&P43y>eE4yy6472t$nXZ2) z=V~`2xe9~@k+b?(fr?W;^+}+Zf^ctDvp;?iWUzVFSdJqcq>G=I(F*WBFFL4m)C-1 z{O85Q#P%*@wk|K^tdEsDx))m1`Q3=8|7O!dJ64aWcOEP2@JpY+)H9z%p&!FbEQr67 zMA~o>8`zs6IyKtT!AD|mG{2(y)so*fng^E~Pr7R7@RV(=JL_Q8Zr9;u9+M}P#8YmQ zhxjjN!G{kN0MVr9wgRW-=lfzh)N%YkJZtv8(CoQvnS4}$; zyqUe&Z+aO?M+d>4xaZh@$ADk-j2)`h_G1nWG|P+qS|1gMb9!C|G)D($*ODs<6G$go z`9l5K3r+I9@DV^)l7y$kv-N3-DvP3MPXnudf}y-4MU!&zO!VGx$rtUgzmC|e4?-{{ zgb_ilm^(rH;@_IDvNvDSJmnrL_#z!Rl>lnb&0=}^FFn#Zx>c-h z<+@YMbo9~x@8nPBGL@FdbU;jBLr`e8|1$3~5OJ}bd26C%rXXMan(fA}WkmXJ{aH=K zcasqaEgVLsg_vSY$kU#`4l!sEY&LzM(DbuK7`;ZSJHf_!JJ2?@3t@{ldUk@Yhc5w^yK?weMl|fh@h7X4B7`+$ z!HT`G%GK-Rl+4W0bn>|FZcg}kRo?^9^HQGjnTK{olv|ku&_M&xG0jD(VK-D@B{k$$ z=h^HycNvKdTDiJhAoVlUq_zd6+X{;@PjIuA+~SI&T@G$`J4 zb}<`u$N8xe&FAIQHLl$Pn~aw&9Qn`Z*U2%y9W~Cg-B0=sNBwWRo}oWm zjE*)oKCgtPsgzFcpkW$LHB($5HF5k?TgZcefXVJpZ7Ls3UEC>4B9#c|f%n>a`IKbt z;uD^pdm5sqUAu~nm4obwr|VCHo2opfIt4FR#m*|!FPO5t*@gD{d+`v0h!6AfvmGpTZ*IH=2$S{qPqfpac!PYt| z@8=U(na(8dDf8vhS2!}zxe!<7k!ZmD?hz`+HTFUe=~dtL(xLXtB!BPP+iY7g!)vZP zWoOljIBK1a_|;;~>U$2Y^0P(q$y%V}B!Vuh*uEXB%Xx_+hY?p8!q#pGu zWMZ(2bs7xM^Ho}vze$2^$fBax6z%C^{f%XW-~T8dp3P3a_#r>R<={ugz$QIFrdgly z(HpwQ*n2na)sRTQKPoB<^;E4LPU_mL&0CX^rxD!fCP9IImieZLb3yDLrNInak$NqH zKNDJ(7JGfKDYGhxf9O7}boa={Kp@;YGilNd01$fhq%{469?s+ z4e?9+LrTZ0Vji9LoSqwHu2XW=v#(^@4W*5wZAHn~O-_UJ>46$N==%*9sa4mreYY#S z{5vZ*Gj?Z&tS}QfYR9xOIjLrCOsNwIh=~h8SzGA$^73*j-EDrHPy1K7IN7HQw-k83 zd3L3D=g}tY4bl(Kr~Xet0F#Hh zL2t>GBDzS-hfzD4y`m-H*M)a|t(I=PgkFG5IA^%;A*RT|ijSWSb)g3Uyr8E;Apy4{g#6&VGW__z_Y?D=foyHGv~6!~#AdAAwX6QD zf33eq|JjAbZ7VB0eq+0!z_RwyVpnpPyiG+sN4W(9!{Opgg^-}&99PrhA8t%7{PHd% zho$(wn=^?-B9|Y@kvQ#%l^k(T`GtXJ|+g5+nL&lMecT8*VK1E zzPb}IzS5%I;w-@+6@^POz!| zvC?e0)uLsAcx5(D0Z!}xF+R;=`^PHcd9UT^VUKb1C%A)uHE(}Bc7&jxtA0B%^(>(@ z{1x^&m{A5Vq9cNO?y|-)l6+HyOLrDa_(2x@v^M#8zTz$TmulXW^LxkzHu^A!p zCo8i5q3_XhW`}Xr?fq39xnD785(*Hg|4;Yde(4MG@GB$`k5?Q@2AON-Jq*E`{1wbf zXC0EXcOh$Yeay_l#^K*gpB5cnKj)aOaR&DU&DUh*QaOM2cvS>5()sCz%|<=3E=2I% zjEK+1a(-~t6iUjU@*KSWr2{crTW#MNiHb`+XebY%;2EnIJS4#eII2UT^!@c>W6Nhv za)bjLC2w!mdnx91=1w{06$ov4MvjE&aEyZd+pc4l(Vq2KB>_T+H5X;0GC4OoJ_@%! zMaT=eod`N^D?ZX8tXUByhPyBEbnE%|J%tl);`o6M-_jEKPB{h|ErF>Rer-^$1L}3pxyknLC4iSJ*vScU!a)ax^Fie zt{bi)n3^{|>rd0oiQwjrKWq`_e!0<}RU zqaIIm-)=YNClrQAUg5M^%Q2B^cKCGv3O73m7JXn&QvQ2rByK~+Eys^W{%+Ar(Gn)> zHItBVK#v_J=j}JXXs#j~UIqI5{#j}R%7!%y9>R&8@xBi-aeG`Rb0F+XAq_G5k*ew_P7^?OIy zJX>u!#h3tv5&@!@_SolA>T=Y#Rz}@qcPVSnKFX`^46C2zc3Au4zb1vmw(t>Fvn7^~ zQhtsSee%7Ox1t*#n}zAlofI)g4+{L1+W3mwP;)bD*&X3+$-VKR2&W2RU`Sf&GI6)Q z-H^R_(45xgZrOJqLDWGMn7&e4)$^&X_zV*5C`?{1yFX!P|EjRn>VM#{jmv6fDfj2R z>ECaaN25!%%5NN_{=WDRc}GLkxvIqD&@nI+3ekG3>QMyjY;a<9tkgZTGE}j5B>&|p zcbb+w*$-+s6*z++JzwF@TNKU^bJy91jzN46`{A4kUTtq0dNDgVvZsR?HJ`OMZj`7| z4S*o?1?*z4=gvtNV$xCpPedzH#gO2+a-m8AJ&>N2*kV8cW_q$;o#sv#-N)(qk>Dyf z;~5~#1Gv?V3oIpu1O)||N6q4rr9hAZ*o=;7>VQ2jF+FF4SbmefDX%yuB4U9Rf^^|J z)laU8LgsZ&n{+U%!x+=a#~bkEUqzu{4@<$DWVoB>NlF%HR^17>XG*?n6Pt7!jxddK zvq@tpDWc#tkctc+k z&s3aQ$qoKqBw=jDRvSyw0bZawLtwY=`IV`=cRM8wWB$&A_b*|Tgym;-h{#tNG;Uk2Y6eq|CY zHY@JnCfg(*je3{daqgjTnf2#niC~EbQ%dcsO?%tTSU?|V68^HW)0peoPsP;l5}}^+ zx{3*m8{e6$T;dOl|A|1r4jiyF_^+U}Ln%IraUvnw&g5uBk zQy^I1`B~C-ORrLi)Z8o7rSP7AZ(KtQy@Kl*2@ApWNROHl^B?WbettWbY$lZ=)3PRj!F<}(`-DKx_M-FeljeaaqgbGQ<=A1!6D~2Oe=@`R{7bx z=ze6IV-^nt=BT&Nv1A$d$q-BdG^&O2gB51^jEu7Esx46sgNgX#{V{0nsdvrWWEe)S z(FD?vfH`q)Egc=YnhR)%($7;+KglyyQ7R9K+SLfHO>x(T zK0G`eUs-I&^1?^GMWacX1Hn-rz{hzbZ>H<(_~5U*bDXo(c!hpxwS!&i zq=?2o1Yl`;8u|}-_^*$_JmviWIpXfX6mc?NSa<=3f@MQ&4#GDFy~m$w-JVO1m^EV0 znW?y6;ZftlUcP4HOx(%bo*EjGt`FZ1pAGFe4`5K8=PyJj=wJ%?Tloe2_`mm)(25lv z_uKAxx|&x$3X_V2$9-B-BhKX$A25T-Omeu*AGmFp9oQ-;Ft25|h>XX2q9eiC{?E`D7wP3-C?*xohPIN$b^fFZPWCd(%iImvVuN1`kl z2xC9xI&z1+#9a5>>s>pkQz&VT|8ghs-5^ad^*N@6!)w^cKE-E$!pr&o{qK`7h<=kh zWk39R!D~F7Y{}Pd*HA^QoOa?(2k5k2px=2(c|74Ym6&WNy}_7UuDA#zrJYiVUT85A zkgLb&Porwa5j@%hcK60y7g3DG@ALG(%j5d>%j~+6Zj$V@y=1t&_ES@2{ys3%XY@~W zHKQ3`&k{@7Uhj~a;`O#+LV_p!IOz9btjIsxGJlfP2_Zn7Km0w2y!>Y06d!3hMC-6I z+m}X6m+by-qNxvn9 z*FE3+Mn$|E{P(2U+{(Rv^}Jghy3BEp#QTO=iK$9q?GIaCbNy!``uXeyN-DeWJBD;~ zh#=9*{m=VzF);72fO4kw!|x7#;fgD%BSIwZJ-v!cNOx&~_dD;Ar>*m5-{HcOZkIHd z2FHTh=8W+dk&#iaZ+Q7!DjIBUZH@Yz`3fC*a&zFfz3LrL?(;*%2}*n| zbi$)CKh-%TnJ~3s{;6&LnHd>tTk~zfSao`E{v;-!@bTj>SKgTm&ISh9m(Hr$ABcTA z^3LVsPa+OgF^lztt=M8tn==C!gI0?0O;$LSx{)rmqhkRN0XNJ}k6P%r(*T({Lu?WW z=z&2NTe-e9WmI3fW~`Hg!!O0mnF0$!UEh(HAP>4pe{nRKu$&y%DJ5kS`S{<8#Z|2vz9{#<8X$ zia}1TP4y`PtbUcB3fX+|vihgBiu z!V3=~x-=1mo};V|&qVuaUW=XwB-{%KjqsM-Bh^@kl@FDLdQ=lF7ugdX3qzg%-Egy! zK%&8pTXGyZ?;Wt|tlMkjZlrn2x2=7qtv$kPxE9y~*8t zsHGYdWt6KbeSwnlS1l)_RMPxJ%0+{UF~a+gZX> zsX_!hRKfJE3wjHEDRW2H(CsorvyYSIUFI1bm=fa9ti(Tv@odsI7R)WlblIMDCCP>k zY-zor^>XF&=ny@1QmP;SCNktvqoo@;{FavKB=BhmkLi#P5L$X@UEfM&ffOPKT_=aUK=lxvh#byw&lQ zY{RX$ba5tEIku{hFcshUCL$IUy4AWIa9?oC_=-lKN|4v}Wf&w!q5o~PH_@K*bsnAF z{iknqEFg&RRu^))8?#=?4E!oaI=)ph#|+O6CN*Dhp<4Sd0#7)Jz{6fyG(mv%ZRkWa zC{e4QSMP?MaT`zqCRDeVAi`r|YXSr|wD%wY+1$!NPUv2~ z@RK`duJVxplI4@Q{}6Oa-2X{D`BzJ_gM^(z3?o+Jg+KB{`2@n00`zJ4O|yR{g468h z$kReXNR@n{+d=ZT6WSf6Ag`|%&F(@luX>ry`PZH~d$b91sl5p_R1UIm0J?OC_ZNZA zYPOH<%{kUW3KhiCaHV!1uuubrr&CwzNp=>dlL6|=V${tS6Yw0Nj)F=`>2L9 zpzW5t6mpB>@*m1PwWBA$2LEmNcj>yX4U29ER%zxwE7;_5`ciuy7*JbU>)!pBFokRO zdyG4HTm_7h@xC4oyeb8%iE4jePnU)h6v(*&^{vtl%r@<$NGycq>nQQIC!k(^^WPva`(u=&mYwbrm zCa-31)bS3POQK^t0Uw*T{8_G;oSR$vhK(5|#eMYApj17}fF3r$2)aFezJG`638hUx z_rFCMZuy$QidRL2NV9gi8vW_}wO=`0LBkBFkcWlmlYcWR5rNI`l8Ly*(0g+h7$o}M zyWW8$Y72Y$%nwNG3VyWwmw-XtC0S-A&6^*R#Uz_JvEe+-@l0XkU;EH$BkJ~vuk4cj z2O-FDP3`T0|ZIc;~j`TB%j&xxTry@ov=P~$xH zB&;$Zqd@*JEku1orS`ItxliBq#wrX}Rdp*LlTC;S=eho9ETlx$G*>AU=k(M$no5Xd z$?lZIFyl@Vb)G{m1@e6Q36lqk>ZJP4#O??TPa1v7-#@0WU3wyE z%$UG13G>ydF>jb_ShJIl_27d7eI^@pRhR z+PcxD?$Fjfk?b=O*F}unJKI9qk!Gm7H|~|}JXJSgo*_&9I$+@Stltuyx5V1i@} z=NRHLbj7~@XeH<75+CjRrlzLF-KJ6C7RzJL8W$eCvC@#1vSmfwwUQ#!?H}wdWlsSq z+|%dr`2MYWw?EHQRY?tFyDM3%BsiC9)1z5@yI-lswA&)4$&a>F0W%ke;p@i*Jd{lh zX$lT74`ZH!uz&hgk}I-exz#7$BB%9H1EC#7cfF6M`(JOKzm@l&5Iz{WWK1>Na7c8_ zCkeD3-I# zF%n(rciqDB)&59Y#c7iM{F>#Agxo1{dWXH#527KM?Y)gD*RUk-#*EBNXL|A3Aw15; zFZ_PN60Y}I#b6|Qk*xW18lHG_fV^dX|6LY zJ!IBpGti{tCr~Tbrq8C+yTORKFDIG7S}87NB;dA=FguA5)!JmOO#P#KPPtyk-wby= zUtM|7)z!sN^ak=b>az{_MHa2j)qnH^d$t@{DQXiX9&}JYhu8T1@`Z&-?HPS(`Hq58T^e3-$gDv)-zC3V zvw8`;YToC53Q~T|`<|&asEV0_1I4pI`@aE6u}{u|8Sk=-Ww(Q38qvixTF?NskzcT7 z|9dAkWh4o~JO)mo&6PnWg&tRx6Np5!S8Um>@QLffI^8gT`D zD=`p-?qZT&b_XZ+&FTLLSs8{Ba;C?b`uh6DMIlH|Dr$hf{?$EJIL~KmY_tu$r&xmA zGy0fI#)o-Spv*Rw8&cKGM$^iqsDKA_H7V4ye?<`n$$=yi3Ftpa7=yX`fw?h$NH}|b z=O*IYo$Oyy%SM47kex@N5cA1pZ!A4y%JFG40NDNBvkAwVLUjqWupb29`&*d_gn+&c zP8Nrw4oIYw(Ym~=k+YE$|EVUiCOZ8z#&jw(=Iz=3E7lQ=g}sTtcm=C5>2i=B_9{|1GNRVy0tZ=nrn z0nxkkmg2D4YrX8*xmtJ7EkyD*?GbE6K5#3&XqOhtN%u(zg=>IqwyPY7no=AfAIhcS zT)Lj|+r3xD2*wIlIL(&QX9rz(Cp>{*J?r!{$j9rv6*FgFJ~ieETJKUqf;p&I^C+Jh zhL2LHOTQdWYWy19v2ya-AeL5bfH~ssUo$EI?qRFOamGtN4EIuRO6)$0&p6>sjz{wr zySuw5kF?eo71E;DF^cElkYNurci1Mg>i^JUZ)<5;l!yyK`7-Czm>&(<&vJmV;fJtk z3g(2zIe?(Af-R^=F4+-_vSAOzk2hW5VD@FbZ>BuR(G@TPf#n%#T(gp#Kx7fSM&L{u z=$%R#>eGR4wt=?07aFoNasr0x`ydN-70h9%_Mk>x)lKjHAhUU)Ol6?`U}?#FZ@vVo zblt6vq^>~%=7CwsE-jj1bw&2AC-N(t%h6>G{&Y^+#PZ9C6hc}{(F8noBw8G zsmG7U8*%ORX;`7K4_KzzyWrnDAVJ<2j~P44t=9xn1d+pH@V@7<-}_J+wUGg5OYsAgq#d=+dn;;O9`*v@ux$7c~mMbBPabe z|3@TTk~!Xom~?ZY+ze%gv^PkKKhh{;cWZ!2Tyx27OyxDHeJRO%u*f9+DFzF#6?(CO zMxF^d3?G8qY$j`B8Vhfi7luYCLn=cCXNu4w5?#oj^DR!t8dqL}7sGqR#(}Htbxq(o z(j}Wv3s6@3P&wiZjzdjx4^xa0;`(_;0<*KT?#&r!fF_C2TmF~3w?BUh!zQ`|xXn2! zz>Du!h4&qsUIOlcrl!7N7`@6ez=>E??L^*a{0O*DUc4|DAj}v5dPM-4tHNh7kA(pR z4;&n698rIr1q8z>ZhmZQvid{=6yZTXXx*I=(IyAKl)ygRL~q5R!|Es5Rx(PpOq;6j z+U7rsYhfClgAhRb>%^q7*MW>P=jJ7#eir$A*N*%hq|+MrbFO`vk#|hm38ol zj||%{DETvY+b)p~GQ2B#ojRq)0_dd%KHrxga4r5cMKXN5^e#RN+FF}@I3H@w04t(q zEWuLZ*Uh4DKI3qaz@_ck1|CUp>dh9uWUg2XzVUT{Srs>E#RJD+@&$ z*5ECWLRa*GxLudOFU6oto^of9+FNjy>}Crds+w%-2TVL?o_W=rXB#GO8j1bJ3-vt2 zsTm`E+%lWme#Az~0w|%mw=4S4aT=1=gNl%c2KL1SRRwa83QziQH19{PN5uiIe^<&` z;D1bz6Hv%zrKN;LfmiZP1;(FGfzK}J!BYnTgQw%*d0A1kTy?$p73gMJU2zM3h=97Y zekwrcp(jx#B67$JIiCBU#>o--atFQuac6*CTB;)GWv5*}NGzsM89~^;6QL3mal|xy zAp;bak?RE8&vYO}ki7jt8*<9d6o5tp5u93a6=oC-Od=rZo<43Af6SG+S)dB+1Gxc6 z%3&gW5p~&IB}1g3NCO2){4P+Td}+Y+&Aa#s9^j$jOXBqGP*jo%iA~_cu_=M!6x>%t zG0O)?vqtPz@0(_O2nyf~m4+yQncEkEXkM#7|9-%vROx48Q0`KFyAx)c0?5XM1foot zucMg(RxXPP9}E966SQVr+`d=-3Urh2b~XgA(HuXMq(e4j7#5x1)weyuD}be9>n`+J z3frQ;4T~dE00?DICwDgg5|h04M0!tVcb5%uuVE$dUI9{JkmtZX={5zokw#4WLX(`9 z-IaqNHQd!S22I)9UK$#L&Apa>bVWizDEJ=&j%_sgd*xNP1cL`dI#FuBG|VIT~W?>txjqz(t^*8L;b)D*CK9hr6f?P+pje-&}~ z){?y{M?}Kk{BUPoqZa}~1ofs0(?5x3yE%H<@m)WuN*incZv3F;aKvMp;~qR& TPdE!t8~_?B+RFKg_n!Yh!VnPt literal 0 HcmV?d00001 diff --git a/client/systray/tailscale.svg b/client/systray/tailscale.svg new file mode 100644 index 0000000000000..9e6990271e472 --- /dev/null +++ b/client/systray/tailscale.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/cmd/tailscale/cli/configure_linux.go b/cmd/tailscale/cli/configure_linux.go index ccde06c72ddbc..9ba3b8e878d52 100644 --- a/cmd/tailscale/cli/configure_linux.go +++ b/cmd/tailscale/cli/configure_linux.go @@ -33,7 +33,7 @@ func systrayConfigCmd() *ffcli.Command { FlagSet: (func() *flag.FlagSet { fs := newFlagSet("systray") fs.StringVar(&systrayArgs.initSystem, "enable-startup", "", - "Install startup script for init system. Currently supported systems are [systemd].") + "Install startup script for init system. Currently supported systems are [systemd, freedesktop].") return fs })(), } diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 8cef9725847a3..d83ac2710a897 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -157,6 +157,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep software.sslmate.com/src/go-pkcs12/internal/rc2 from software.sslmate.com/src/go-pkcs12 tailscale.com from tailscale.com/version 💣 tailscale.com/atomicfile from tailscale.com/cmd/tailscale/cli+ + L tailscale.com/client/freedesktop from tailscale.com/client/systray tailscale.com/client/local from tailscale.com/client/tailscale+ L tailscale.com/client/systray from tailscale.com/cmd/tailscale/cli tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale From a98036b41d5f78d403f8328ccd88df9ba3aa441a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 27 Feb 2026 02:09:12 +0000 Subject: [PATCH 155/202] go.mod: bump gvisor Updates #8043 Change-Id: Ia229ad4f28f2ff20e0bdecb99ca9e1bd0356ad8e Signed-off-by: Brad Fitzpatrick --- cmd/k8s-operator/depaware.txt | 4 ++-- cmd/tailscaled/depaware.txt | 4 ++-- cmd/tsidp/depaware.txt | 4 ++-- flake.nix | 2 +- go.mod | 4 ++-- go.mod.sri | 2 +- go.sum | 12 ++++++------ shell.nix | 2 +- tsnet/depaware.txt | 4 ++-- wgengine/netstack/netstack.go | 15 ++++++++++++++- 10 files changed, 33 insertions(+), 20 deletions(-) diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index cd87d49872028..d801c0285ca62 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -123,7 +123,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ 💣 github.com/gogo/protobuf/proto from k8s.io/api/admission/v1+ github.com/gogo/protobuf/sortkeys from k8s.io/api/admission/v1+ github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp github.com/google/gnostic-models/compiler from github.com/google/gnostic-models/openapiv2+ github.com/google/gnostic-models/extensions from github.com/google/gnostic-models/compiler github.com/google/gnostic-models/jsonschema from github.com/google/gnostic-models/compiler @@ -271,7 +271,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack 💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 4128ecc4ce972..48a7d09495c5f 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -115,7 +115,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+ github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp github.com/google/go-tpm/legacy/tpm2 from github.com/google/go-tpm/tpm2/transport+ github.com/google/go-tpm/tpm2 from tailscale.com/feature/tpm github.com/google/go-tpm/tpm2/transport from github.com/google/go-tpm/tpm2/transport/linuxtpm+ @@ -220,7 +220,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack 💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index ae13b20449b41..03f7e1f09b21d 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -103,7 +103,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+ L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp D github.com/google/uuid from github.com/prometheus-community/pro-bing github.com/hdevalence/ed25519consensus from tailscale.com/tka github.com/huin/goupnp from github.com/huin/goupnp/dcps/internetgateway2+ @@ -170,7 +170,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack 💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ diff --git a/flake.nix b/flake.nix index 4e315a5cab7e6..0dbf74e7884aa 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-4orp8iQekVbhCFpt7DXLvj6dediKxo1qkWr1oe7+RaE= +# nix-direnv cache busting line: sha256-Lr+5B0LEFk66WahPczRcfzH8rSL5Cc2qvNJuW6B0Llc= diff --git a/go.mod b/go.mod index 80b453cd5a9f7..caa58b60833bc 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/creachadair/mds v0.25.9 github.com/creachadair/msync v0.7.1 github.com/creachadair/taskgroup v0.13.2 - github.com/creack/pty v1.1.23 + github.com/creack/pty v1.1.24 github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e github.com/distribution/reference v0.6.0 @@ -123,7 +123,7 @@ require ( golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wireguard/windows v0.5.3 gopkg.in/square/go-jose.v2 v2.6.0 - gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 + gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 helm.sh/helm/v3 v3.19.0 honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 k8s.io/api v0.34.0 diff --git a/go.mod.sri b/go.mod.sri index feea9b11b1ab0..91887e63b3c8c 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-4orp8iQekVbhCFpt7DXLvj6dediKxo1qkWr1oe7+RaE= +sha256-Lr+5B0LEFk66WahPczRcfzH8rSL5Cc2qvNJuW6B0Llc= diff --git a/go.sum b/go.sum index ab4f3303623c8..1f8195e47fff6 100644 --- a/go.sum +++ b/go.sum @@ -276,8 +276,8 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6 github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creachadair/mds v0.25.9 h1:080Hr8laN2h+l3NeVCGMBpXtIPnl9mz8e4HLraGPqtA= github.com/creachadair/mds v0.25.9/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs= @@ -287,8 +287,8 @@ github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoi github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= -github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= @@ -1726,8 +1726,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= +gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA= +gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q= helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/shell.nix b/shell.nix index 07d3c1ad53bad..a822b705a3062 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-4orp8iQekVbhCFpt7DXLvj6dediKxo1qkWr1oe7+RaE= +# nix-direnv cache busting line: sha256-Lr+5B0LEFk66WahPczRcfzH8rSL5Cc2qvNJuW6B0Llc= diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index bcc00590a4d2c..8c81aa4d70d5d 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -103,7 +103,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+ L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns github.com/golang/groupcache/lru from tailscale.com/net/dnscache - github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ + github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/transport/tcp DI github.com/google/uuid from github.com/prometheus-community/pro-bing github.com/hdevalence/ed25519consensus from tailscale.com/tka github.com/huin/goupnp from github.com/huin/goupnp/dcps/internetgateway2+ @@ -166,7 +166,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+ gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+ - 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack + 💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack+ gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack 💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+ diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 42ac0ab1e4dba..bab94e2bed01b 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -613,7 +613,7 @@ func (ns *Impl) Start(b LocalBackend) error { } ns.lb = lb tcpFwd := tcp.NewForwarder(ns.ipstack, tcpRXBufDefSize, maxInFlightConnectionAttempts(), ns.acceptTCP) - udpFwd := udp.NewForwarder(ns.ipstack, ns.acceptUDP) + udpFwd := udp.NewForwarder(ns.ipstack, ns.acceptUDPNoICMP) ns.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, ns.wrapTCPProtocolHandler(tcpFwd.HandlePacket)) ns.ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, ns.wrapUDPProtocolHandler(udpFwd.HandlePacket)) go ns.inject() @@ -1769,6 +1769,19 @@ func (ns *Impl) ListenTCP(network, address string) (*gonet.TCPListener, error) { return gonet.ListenTCP(ns.ipstack, localAddress, networkProto) } +// acceptUDPNoICMP wraps acceptUDP to satisfy udp.ForwarderHandler. +// A gvisor bump from 9414b50a to 573d5e71 on 2026-02-27 changed +// udp.ForwarderHandler from func(*ForwarderRequest) to +// func(*ForwarderRequest) bool, where returning false means unhandled +// and causes gvisor to send an ICMP port unreachable. Previously there +// was no such distinction and all packets were implicitly treated as +// handled. Always returning true preserves the old behavior of silently +// dropping packets we don't service rather than sending ICMP errors. +func (ns *Impl) acceptUDPNoICMP(r *udp.ForwarderRequest) bool { + ns.acceptUDP(r) + return true +} + func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) { sess := r.ID() if debugNetstack() { From 30e12310f19fa85a9e35fe5800b067d7b033bd33 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 30 Jan 2026 17:30:39 -0800 Subject: [PATCH 156/202] cmd/tailscaled/*.{target,unit}: add systemd online target Using the new wait command from #18574 provide a tailscale-online.target that has a similar usage model to the conventional `network-online.target`. Updates #3340 Updates #11504 Signed-off-by: James Tucker --- cmd/tailscaled/tailscale-online.target | 4 ++++ cmd/tailscaled/tailscale-wait-online.service | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 cmd/tailscaled/tailscale-online.target create mode 100644 cmd/tailscaled/tailscale-wait-online.service diff --git a/cmd/tailscaled/tailscale-online.target b/cmd/tailscaled/tailscale-online.target new file mode 100644 index 0000000000000..a8ee7db475378 --- /dev/null +++ b/cmd/tailscaled/tailscale-online.target @@ -0,0 +1,4 @@ +[Unit] +Description=Tailscale is online +Requires=tailscale-wait-online.service +After=tailscale-wait-online.service diff --git a/cmd/tailscaled/tailscale-wait-online.service b/cmd/tailscaled/tailscale-wait-online.service new file mode 100644 index 0000000000000..eb46a18bf92d2 --- /dev/null +++ b/cmd/tailscaled/tailscale-wait-online.service @@ -0,0 +1,12 @@ +[Unit] +Description=Wait for Tailscale to be online +After=tailscaled.service +Requires=tailscaled.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/tailscale wait +RemainAfterExit=yes + +[Install] +WantedBy=tailscale-online.target From 0fb207c3d045b888523914dce0b6c9e9a1abdd69 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 27 Feb 2026 13:49:05 -0800 Subject: [PATCH 157/202] wgengine/netstack: deliver self-addressed packets via loopback When a tsnet.Server dials its own Tailscale IP, TCP SYN packets are silently dropped. In inject(), outbound packets with dst=self fail the shouldSendToHost check and fall through to WireGuard, which has no peer for the node's own address. Fix this by detecting self-addressed packets in inject() using isLocalIP and delivering them back into gVisor's network stack as inbound packets via a new DeliverLoopback method on linkEndpoint. The outbound packet must be re-serialized into a new PacketBuffer because outbound packets have their headers parsed into separate views, but DeliverNetworkPacket expects raw unparsed data. Updates #18829 Signed-off-by: James Tucker --- tsnet/tsnet_test.go | 73 +++++++ wgengine/netstack/link_endpoint.go | 38 ++++ wgengine/netstack/netstack.go | 24 +++ wgengine/netstack/netstack_test.go | 293 +++++++++++++++++++++++++++++ 4 files changed, 428 insertions(+) diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 0b6b61bd10061..e2b37a365d04d 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -2792,3 +2792,76 @@ func TestResolveAuthKey(t *testing.T) { }) } } + +// TestSelfDial verifies that a single tsnet.Server can Dial its own Listen +// address. This is a regression test for a bug where self-addressed TCP SYN +// packets were sent to WireGuard (which has no peer for the node's own IP) +// and silently dropped, causing Dial to hang indefinitely. +func TestSelfDial(t *testing.T) { + tstest.Shard(t) + tstest.ResourceCheck(t) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + controlURL, _ := startControl(t) + s1, s1ip, _ := startServer(t, ctx, controlURL, "s1") + + ln, err := s1.Listen("tcp", ":8081") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + errc := make(chan error, 1) + connc := make(chan net.Conn, 1) + go func() { + c, err := ln.Accept() + if err != nil { + errc <- err + return + } + connc <- c + }() + + // Self-dial: the same server dials its own Tailscale IP. + w, err := s1.Dial(ctx, "tcp", fmt.Sprintf("%s:8081", s1ip)) + if err != nil { + t.Fatalf("self-dial failed: %v", err) + } + defer w.Close() + + var accepted net.Conn + select { + case accepted = <-connc: + case err := <-errc: + t.Fatalf("accept failed: %v", err) + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for accept") + } + defer accepted.Close() + + // Verify bidirectional data exchange. + want := "hello self" + if _, err := io.WriteString(w, want); err != nil { + t.Fatal(err) + } + got := make([]byte, len(want)) + if _, err := io.ReadFull(accepted, got); err != nil { + t.Fatal(err) + } + if string(got) != want { + t.Errorf("client->server: got %q, want %q", got, want) + } + + reply := "hello back" + if _, err := io.WriteString(accepted, reply); err != nil { + t.Fatal(err) + } + gotReply := make([]byte, len(reply)) + if _, err := io.ReadFull(w, gotReply); err != nil { + t.Fatal(err) + } + if string(gotReply) != reply { + t.Errorf("server->client: got %q, want %q", gotReply, reply) + } +} diff --git a/wgengine/netstack/link_endpoint.go b/wgengine/netstack/link_endpoint.go index 4800ed1673d20..82b5446ac8789 100644 --- a/wgengine/netstack/link_endpoint.go +++ b/wgengine/netstack/link_endpoint.go @@ -7,6 +7,7 @@ import ( "context" "sync" + "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -198,6 +199,43 @@ func (ep *linkEndpoint) injectInbound(p *packet.Parsed) { pkt.DecRef() } +// DeliverLoopback delivers pkt back into gVisor's network stack as if it +// arrived from the network, for self-addressed (loopback) packets. It takes +// ownership of one reference count on pkt. The caller must not use pkt after +// calling this method. It returns false if the dispatcher is not attached. +// +// Outbound packets from gVisor have their headers already parsed into separate +// views (NetworkHeader, TransportHeader, Data). DeliverNetworkPacket expects +// a raw unparsed packet, so we must re-serialize the packet into a new +// PacketBuffer with all bytes in the payload for gVisor to parse on inbound. +func (ep *linkEndpoint) DeliverLoopback(pkt *stack.PacketBuffer) bool { + ep.mu.RLock() + d := ep.dispatcher + ep.mu.RUnlock() + if d == nil { + pkt.DecRef() + return false + } + + // Serialize the outbound packet back to raw bytes. + raw := stack.PayloadSince(pkt.NetworkHeader()).AsSlice() + proto := pkt.NetworkProtocolNumber + + // We're done with the original outbound packet. + pkt.DecRef() + + // Create a new PacketBuffer from the raw bytes for inbound delivery. + newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Payload: buffer.MakeWithData(raw), + }) + newPkt.NetworkProtocolNumber = proto + newPkt.RXChecksumValidated = true + + d.DeliverNetworkPacket(proto, newPkt) + newPkt.DecRef() + return true +} + // Attach saves the stack network-layer dispatcher for use later when packets // are injected. func (ep *linkEndpoint) Attach(dispatcher stack.NetworkDispatcher) { diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index bab94e2bed01b..59c2613451fa5 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -1037,6 +1037,16 @@ func (ns *Impl) inject() { return } } else { + // Self-addressed packet: deliver back into gVisor directly + // via the link endpoint's dispatcher, but only if the packet is not + // earmarked for the host. Neither the inbound path (fakeTUN Write is a + // no-op) nor the outbound path (WireGuard has no peer for our own IP) + // can handle these. + if ns.isSelfDst(pkt) { + ns.linkEP.DeliverLoopback(pkt) + continue + } + if err := ns.tundev.InjectOutboundPacketBuffer(pkt); err != nil { ns.logf("netstack inject outbound: %v", err) return @@ -1116,6 +1126,20 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool { return false } +// isSelfDst reports whether pkt's destination IP is a local Tailscale IP +// assigned to this node. This is used by inject() to detect self-addressed +// packets that need loopback delivery. +func (ns *Impl) isSelfDst(pkt *stack.PacketBuffer) bool { + hdr := pkt.Network() + switch v := hdr.(type) { + case header.IPv4: + return ns.isLocalIP(netip.AddrFrom4(v.DestinationAddress().As4())) + case header.IPv6: + return ns.isLocalIP(netip.AddrFrom16(v.DestinationAddress().As16())) + } + return false +} + // isLocalIP reports whether ip is a Tailscale IP assigned to this // node directly (but not a subnet-routed IP). func (ns *Impl) isLocalIP(ip netip.Addr) bool { diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index eea598937e4cf..da262fc13acbd 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -15,6 +15,7 @@ import ( "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/stack" "tailscale.com/envknob" @@ -1073,3 +1074,295 @@ func makeUDP6PacketBuffer(src, dst netip.AddrPort) *stack.PacketBuffer { return pkt } + +// TestIsSelfDst verifies that isSelfDst correctly identifies packets whose +// destination IP is a local Tailscale IP assigned to this node. +func TestIsSelfDst(t *testing.T) { + var ( + selfIP4 = netip.MustParseAddr("100.64.1.2") + selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123") + remoteIP4 = netip.MustParseAddr("100.64.99.88") + remoteIP6 = netip.MustParseAddr("fd7a:115c:a1e0::99") + ) + + ns := makeNetstack(t, func(impl *Impl) { + impl.ProcessLocalIPs = true + impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool { + return addr == selfIP4 || addr == selfIP6 + }) + }) + + testCases := []struct { + name string + src, dst netip.AddrPort + want bool + }{ + { + name: "self_to_self_v4", + src: netip.AddrPortFrom(selfIP4, 12345), + dst: netip.AddrPortFrom(selfIP4, 8081), + want: true, + }, + { + name: "self_to_self_v6", + src: netip.AddrPortFrom(selfIP6, 12345), + dst: netip.AddrPortFrom(selfIP6, 8081), + want: true, + }, + { + name: "remote_to_self_v4", + src: netip.AddrPortFrom(remoteIP4, 12345), + dst: netip.AddrPortFrom(selfIP4, 8081), + want: true, + }, + { + name: "remote_to_self_v6", + src: netip.AddrPortFrom(remoteIP6, 12345), + dst: netip.AddrPortFrom(selfIP6, 8081), + want: true, + }, + { + name: "self_to_remote_v4", + src: netip.AddrPortFrom(selfIP4, 12345), + dst: netip.AddrPortFrom(remoteIP4, 8081), + want: false, + }, + { + name: "self_to_remote_v6", + src: netip.AddrPortFrom(selfIP6, 12345), + dst: netip.AddrPortFrom(remoteIP6, 8081), + want: false, + }, + { + name: "remote_to_remote_v4", + src: netip.AddrPortFrom(remoteIP4, 12345), + dst: netip.MustParseAddrPort("100.64.77.66:7777"), + want: false, + }, + { + name: "service_ip_to_self_v4", + src: netip.AddrPortFrom(serviceIP, 53), + dst: netip.AddrPortFrom(selfIP4, 9999), + want: true, + }, + { + name: "service_ip_to_self_v6", + src: netip.AddrPortFrom(serviceIPv6, 53), + dst: netip.AddrPortFrom(selfIP6, 9999), + want: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + var pkt *stack.PacketBuffer + if tt.src.Addr().Is4() { + pkt = makeUDP4PacketBuffer(tt.src, tt.dst) + } else { + pkt = makeUDP6PacketBuffer(tt.src, tt.dst) + } + defer pkt.DecRef() + + if got := ns.isSelfDst(pkt); got != tt.want { + t.Errorf("isSelfDst(%v -> %v) = %v, want %v", tt.src, tt.dst, got, tt.want) + } + }) + } +} + +// TestDeliverLoopback verifies that DeliverLoopback correctly re-serializes an +// outbound packet and delivers it back into gVisor's inbound path. +func TestDeliverLoopback(t *testing.T) { + ep := newLinkEndpoint(64, 1280, "", groNotSupported) + + // Track delivered packets via a mock dispatcher. + type delivered struct { + proto tcpip.NetworkProtocolNumber + data []byte + } + deliveredCh := make(chan delivered, 4) + ep.Attach(&mockDispatcher{ + onDeliverNetworkPacket: func(proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { + // Capture the raw bytes from the delivered packet. At this + // point the packet is unparsed — everything is in the + // payload, no headers have been consumed yet. + buf := pkt.ToBuffer() + raw := buf.Flatten() + deliveredCh <- delivered{proto: proto, data: raw} + }, + }) + + t.Run("ipv4", func(t *testing.T) { + selfAddr := netip.MustParseAddrPort("100.64.1.2:8081") + pkt := makeUDP4PacketBuffer(selfAddr, selfAddr) + // Capture what the outbound bytes look like before loopback. + wantLen := pkt.Size() + wantProto := pkt.NetworkProtocolNumber + + if !ep.DeliverLoopback(pkt) { + t.Fatal("DeliverLoopback returned false") + } + + select { + case got := <-deliveredCh: + if got.proto != wantProto { + t.Errorf("proto = %d, want %d", got.proto, wantProto) + } + if len(got.data) != wantLen { + t.Errorf("data length = %d, want %d", len(got.data), wantLen) + } + case <-time.After(time.Second): + t.Fatal("timeout waiting for loopback delivery") + } + }) + + t.Run("ipv6", func(t *testing.T) { + selfAddr := netip.MustParseAddrPort("[fd7a:115c:a1e0::123]:8081") + pkt := makeUDP6PacketBuffer(selfAddr, selfAddr) + wantLen := pkt.Size() + wantProto := pkt.NetworkProtocolNumber + + if !ep.DeliverLoopback(pkt) { + t.Fatal("DeliverLoopback returned false") + } + + select { + case got := <-deliveredCh: + if got.proto != wantProto { + t.Errorf("proto = %d, want %d", got.proto, wantProto) + } + if len(got.data) != wantLen { + t.Errorf("data length = %d, want %d", len(got.data), wantLen) + } + case <-time.After(time.Second): + t.Fatal("timeout waiting for loopback delivery") + } + }) + + t.Run("nil_dispatcher", func(t *testing.T) { + ep2 := newLinkEndpoint(64, 1280, "", groNotSupported) + // Don't attach a dispatcher. + selfAddr := netip.MustParseAddrPort("100.64.1.2:8081") + pkt := makeUDP4PacketBuffer(selfAddr, selfAddr) + if ep2.DeliverLoopback(pkt) { + t.Error("DeliverLoopback should return false with nil dispatcher") + } + // pkt refcount was consumed by DeliverLoopback, so we don't DecRef. + }) +} + +// mockDispatcher implements stack.NetworkDispatcher for testing. +type mockDispatcher struct { + onDeliverNetworkPacket func(tcpip.NetworkProtocolNumber, *stack.PacketBuffer) +} + +func (d *mockDispatcher) DeliverNetworkPacket(proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { + if d.onDeliverNetworkPacket != nil { + d.onDeliverNetworkPacket(proto, pkt) + } +} + +func (d *mockDispatcher) DeliverLinkPacket(tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {} + +// udp4raw constructs a valid raw IPv4+UDP packet with proper checksums. +func udp4raw(t testing.TB, src, dst netip.Addr, sport, dport uint16, payload []byte) []byte { + t.Helper() + totalLen := header.IPv4MinimumSize + header.UDPMinimumSize + len(payload) + buf := make([]byte, totalLen) + + ip := header.IPv4(buf) + ip.Encode(&header.IPv4Fields{ + TotalLength: uint16(totalLen), + Protocol: uint8(header.UDPProtocolNumber), + TTL: 64, + SrcAddr: tcpip.AddrFrom4Slice(src.AsSlice()), + DstAddr: tcpip.AddrFrom4Slice(dst.AsSlice()), + }) + ip.SetChecksum(^ip.CalculateChecksum()) + + // Build UDP header + payload. + u := header.UDP(buf[header.IPv4MinimumSize:]) + u.Encode(&header.UDPFields{ + SrcPort: sport, + DstPort: dport, + Length: uint16(header.UDPMinimumSize + len(payload)), + }) + copy(buf[header.IPv4MinimumSize+header.UDPMinimumSize:], payload) + + xsum := header.PseudoHeaderChecksum( + header.UDPProtocolNumber, + tcpip.AddrFrom4Slice(src.AsSlice()), + tcpip.AddrFrom4Slice(dst.AsSlice()), + uint16(header.UDPMinimumSize+len(payload)), + ) + u.SetChecksum(^header.UDP(buf[header.IPv4MinimumSize:]).CalculateChecksum(xsum)) + return buf +} + +// TestInjectLoopback verifies that the inject goroutine delivers self-addressed +// packets back into gVisor (via DeliverLoopback) instead of sending them to +// WireGuard outbound. This is a regression test for a bug where self-dial +// packets were sent to WireGuard and silently dropped. +func TestInjectLoopback(t *testing.T) { + selfIP4 := netip.MustParseAddr("100.64.1.2") + + ns := makeNetstack(t, func(impl *Impl) { + impl.ProcessLocalIPs = true + impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool { + return addr == selfIP4 + }) + }) + + // Register gVisor's NIC address so the stack accepts and routes + // packets for this IP. + protocolAddr := tcpip.ProtocolAddress{ + Protocol: header.IPv4ProtocolNumber, + AddressWithPrefix: tcpip.AddrFrom4(selfIP4.As4()).WithPrefix(), + } + if err := ns.ipstack.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress: %v", err) + } + + // Bind a UDP socket on the gVisor stack to receive the loopback packet. + pc, err := gonet.DialUDP(ns.ipstack, &tcpip.FullAddress{ + NIC: nicID, + Addr: tcpip.AddrFrom4(selfIP4.As4()), + Port: 8081, + }, nil, header.IPv4ProtocolNumber) + if err != nil { + t.Fatalf("DialUDP: %v", err) + } + defer pc.Close() + + // Build a valid self-addressed UDP packet from raw bytes and wrap it + // in a gVisor PacketBuffer with headers already pushed, as gVisor's + // outbound path produces. + payload := []byte("loopback test") + raw := udp4raw(t, selfIP4, selfIP4, 12345, 8081, payload) + + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + ReserveHeaderBytes: header.IPv4MinimumSize + header.UDPMinimumSize, + Payload: buffer.MakeWithData(payload), + }) + copy(pkt.TransportHeader().Push(header.UDPMinimumSize), + raw[header.IPv4MinimumSize:header.IPv4MinimumSize+header.UDPMinimumSize]) + pkt.TransportProtocolNumber = header.UDPProtocolNumber + copy(pkt.NetworkHeader().Push(header.IPv4MinimumSize), raw[:header.IPv4MinimumSize]) + pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber + + if err := ns.linkEP.q.Write(pkt); err != nil { + t.Fatalf("queue.Write: %v", err) + } + + // The inject goroutine should detect the self-addressed packet via + // isSelfDst and deliver it back into gVisor via DeliverLoopback. + pc.SetReadDeadline(time.Now().Add(5 * time.Second)) + buf := make([]byte, 256) + n, _, err := pc.ReadFrom(buf) + if err != nil { + t.Fatalf("ReadFrom: %v (self-addressed packet was not looped back)", err) + } + if got := string(buf[:n]); got != "loopback test" { + t.Errorf("got %q, want %q", got, "loopback test") + } +} From 45305800a626929a4d5335102c6ad613cc21b8b6 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 27 Feb 2026 14:41:47 -0800 Subject: [PATCH 158/202] net/netmon: ignore NetBird interface on Linux Windows and macOS are not covered by this change, as neither have safely distinct names to make it easy to do so. This covers the requested case on Linux. Updates #18824 Signed-off-by: James Tucker --- net/netmon/state.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/netmon/state.go b/net/netmon/state.go index 10d68ab785edc..cdfa1d0fbe552 100644 --- a/net/netmon/state.go +++ b/net/netmon/state.go @@ -41,7 +41,12 @@ func isProblematicInterface(nif *net.Interface) bool { // DoS each other by doing traffic amplification, both of them // preferring/trying to use each other for transport. See: // https://github.com/tailscale/tailscale/issues/1208 - if strings.HasPrefix(name, "zt") || (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) { + // TODO(https://github.com/tailscale/tailscale/issues/18824): maybe exclude + // "WireGuard tunnel 0" as well on Windows (NetBird), but the name seems too + // generic where there is not a platform standard (on Linux wt0 is at least + // explicitly different from the WireGuard conventional default of wg0). + if strings.HasPrefix(name, "zt") || name == "wt0" /* NetBird */ || + (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) { return true } return false From 439d84134d169db13b731a0cf515e0f5e342b984 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 27 Feb 2026 19:09:24 -0800 Subject: [PATCH 159/202] tsnet: fix slow test shutdown leading to flakes TestDial in particular sometimes gets stuck in CI for minutes, letting chantun drop packets during shutdown avoids blocking shutdown. Updates #18423 Signed-off-by: James Tucker --- tsnet/tsnet_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index e2b37a365d04d..9481defae94e8 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -1881,8 +1881,8 @@ type chanTUN struct { func newChanTUN() *chanTUN { t := &chanTUN{ - Inbound: make(chan []byte, 10), - Outbound: make(chan []byte, 10), + Inbound: make(chan []byte, 1024), + Outbound: make(chan []byte, 1024), closed: make(chan struct{}), events: make(chan tun.Event, 1), } @@ -1922,6 +1922,10 @@ func (t *chanTUN) Write(bufs [][]byte, offset int) (int, error) { case <-t.closed: return 0, errors.New("closed") case t.Inbound <- slices.Clone(pkt): + default: + // Drop the packet if the channel is full, like a real + // TUN under congestion. This avoids blocking the + // WireGuard send path when no goroutine is draining. } } return len(bufs), nil From fa13f83375680d223d411604b7360b0325936b19 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Sat, 28 Feb 2026 15:53:09 -0800 Subject: [PATCH 160/202] tsnet: fix deadlock in Server.Close during shutdown Server.Close held s.mu for the entire shutdown duration, including netstack.Close (which waits for gVisor goroutines to exit) and lb.Shutdown. gVisor callbacks like getTCPHandlerForFlow acquire s.mu via listenerForDstAddr, so any in-flight gVisor goroutine attempting that callback during stack shutdown would deadlock with Close. Replace the mu-guarded closed bool with a sync.Once, and release s.mu after closing listeners but before the heavy shutdown operations. Also cancel shutdownCtx before netstack.Close so pending handlers observe cancellation rather than contending on the lock. Updates #18423 Signed-off-by: James Tucker --- tsnet/tsnet.go | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index ccea22d1619f1..416c907502147 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -198,7 +198,7 @@ type Server struct { listeners map[listenKey]*listener fallbackTCPHandlers set.HandleSet[FallbackTCPHandler] dialer *tsdial.Dialer - closed bool + closeOnce sync.Once } // FallbackTCPHandler describes the callback which @@ -439,11 +439,29 @@ func (s *Server) Up(ctx context.Context) (*ipnstate.Status, error) { // // It must not be called before or concurrently with Start. func (s *Server) Close() error { - s.mu.Lock() - defer s.mu.Unlock() - if s.closed { + didClose := false + s.closeOnce.Do(func() { + didClose = true + s.close() + }) + if !didClose { return fmt.Errorf("tsnet: %w", net.ErrClosed) } + return nil +} + +func (s *Server) close() { + // Close listeners under s.mu, then release before the heavy shutdown + // operations. We must not hold s.mu during netstack.Close, lb.Shutdown, + // etc. because callbacks from gVisor (e.g. getTCPHandlerForFlow) + // acquire s.mu, and waiting for those goroutines while holding the lock + // would deadlock. + s.mu.Lock() + for _, ln := range s.listeners { + ln.closeLocked() + } + s.mu.Unlock() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var wg sync.WaitGroup @@ -466,13 +484,12 @@ func (s *Server) Close() error { } }() - if s.netstack != nil { - s.netstack.Close() - s.netstack = nil - } if s.shutdownCancel != nil { s.shutdownCancel() } + if s.netstack != nil { + s.netstack.Close() + } if s.lb != nil { s.lb.Shutdown() } @@ -489,13 +506,8 @@ func (s *Server) Close() error { s.loopbackListener.Close() } - for _, ln := range s.listeners { - ln.closeLocked() - } wg.Wait() s.sys.Bus.Get().Close() - s.closed = true - return nil } func (s *Server) doInit() { From 142ce997cbf53f0bb0c86e96682bce75b34c10f8 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Sat, 28 Feb 2026 16:11:28 -0800 Subject: [PATCH 161/202] .github/workflows: rename tidy workflow to match what it is I was confused when everything I was reading in the CI failure was saying `go mod tidy`, but the thing that was actually failing was related to nix flakes. Rename the pipeline and step name to the `make tidy` that it actually runs. Updates #16637 Signed-off-by: James Tucker --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 862420f70f98d..e6c693188783f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -806,7 +806,7 @@ jobs: echo git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1) - go_mod_tidy: + make_tidy: runs-on: ubuntu-24.04 needs: gomod-cache steps: @@ -820,7 +820,7 @@ jobs: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - - name: check that 'go mod tidy' is clean + - name: check that 'make tidy' is clean working-directory: src run: | make tidy @@ -921,7 +921,7 @@ jobs: - fuzz - depaware - go_generate - - go_mod_tidy + - make_tidy - licenses - staticcheck runs-on: ubuntu-24.04 @@ -967,7 +967,7 @@ jobs: - fuzz - depaware - go_generate - - go_mod_tidy + - make_tidy - licenses - staticcheck steps: @@ -991,7 +991,7 @@ jobs: - tailscale_go - depaware - go_generate - - go_mod_tidy + - make_tidy - licenses - staticcheck steps: From 48e0334aaca92682f1ec59962de93afd21c49ac8 Mon Sep 17 00:00:00 2001 From: James Tucker Date: Fri, 27 Feb 2026 15:44:59 -0800 Subject: [PATCH 162/202] tsnet: fix Listen for unspecified addresses and ephemeral ports Normalize 0.0.0.0 and :: to wildcard in resolveListenAddr so listeners match incoming connections. Fix ephemeral port allocation across all three modes: extract assigned ports from gVisor listeners (TUN TCP and UDP), and add an ephemeral port allocator for netstack TCP. Updates #6815 Updates #12182 Fixes #14042 Signed-off-by: James Tucker --- tsnet/tsnet.go | 217 ++++++++++++++++++++++++++++++++-------- tsnet/tsnet_test.go | 236 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+), 42 deletions(-) diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 416c907502147..776854e227926 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -196,6 +196,7 @@ type Server struct { mu sync.Mutex listeners map[listenKey]*listener + nextEphemeralPort uint16 // next port to try in ephemeral range; 0 means use ephemeralPortFirst fallbackTCPHandlers set.HandleSet[FallbackTCPHandler] dialer *tsdial.Dialer closeOnce sync.Once @@ -1099,16 +1100,27 @@ func (s *Server) ListenPacket(network, addr string) (net.PacketConn, error) { network = "udp6" } } + if err := s.Start(); err != nil { + return nil, err + } - netLn, err := s.listen(network, addr, listenOnTailnet) + // Create the gVisor PacketConn first so it can handle port 0 allocation. + pc, err := s.netstack.ListenPacket(network, ap.String()) if err != nil { return nil, err } - ln := netLn.(*listener) - pc, err := s.netstack.ListenPacket(network, ap.String()) + // If port 0 was requested, use the port gVisor assigned. + if ap.Port() == 0 { + if p := portFromAddr(pc.LocalAddr()); p != 0 { + ap = netip.AddrPortFrom(ap.Addr(), p) + addr = ap.String() + } + } + + ln, err := s.registerListener(network, addr, ap, listenOnTailnet, nil) if err != nil { - ln.Close() + pc.Close() return nil, err } @@ -1621,6 +1633,11 @@ func resolveListenAddr(network, addr string) (netip.AddrPort, error) { if err != nil { return zero, fmt.Errorf("invalid Listen addr %q; host part must be empty or IP literal", host) } + // Normalize unspecified addresses (0.0.0.0, ::) to the zero value, + // equivalent to an empty host, so they match the node's own IPs. + if bindHostOrZero.IsUnspecified() { + return netip.AddrPortFrom(netip.Addr{}, uint16(port)), nil + } if strings.HasSuffix(network, "4") && !bindHostOrZero.Is4() { return zero, fmt.Errorf("invalid non-IPv4 addr %v for network %q", host, network) } @@ -1630,6 +1647,17 @@ func resolveListenAddr(network, addr string) (netip.AddrPort, error) { return netip.AddrPortFrom(bindHostOrZero, uint16(port)), nil } +// ephemeral port range for non-TUN listeners requesting port 0. This range is +// chosen to reduce the probability of collision with host listeners, avoiding +// both the typical ephemeral range, and privilege listener ranges. Collisions +// may still occur and could for example shadow host sockets in a netstack+TUN +// situation, the range here is a UX improvement, not a guarantee that +// application authors will never have to consider these cases. +const ( + ephemeralPortFirst = 10002 + ephemeralPortLast = 19999 +) + func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, error) { switch network { case "", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": @@ -1643,6 +1671,76 @@ func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, erro if err := s.Start(); err != nil { return nil, err } + + isTCP := network == "" || network == "tcp" || network == "tcp4" || network == "tcp6" + + // When using a TUN with TCP, create a gVisor TCP listener. + // gVisor handles port 0 allocation natively. + var gonetLn net.Listener + if s.Tun != nil && isTCP { + gonetLn, err = s.listenTCP(network, host) + if err != nil { + return nil, err + } + // If port 0 was requested, update host to the port gVisor assigned + // so that the listenKey uses the real port. + if host.Port() == 0 { + if p := portFromAddr(gonetLn.Addr()); p != 0 { + host = netip.AddrPortFrom(host.Addr(), p) + addr = listenAddr(host) + } + } + } + + ln, err := s.registerListener(network, addr, host, lnOn, gonetLn) + if err != nil { + if gonetLn != nil { + gonetLn.Close() + } + return nil, err + } + return ln, nil +} + +// listenTCP creates a gVisor TCP listener for TUN mode. +func (s *Server) listenTCP(network string, host netip.AddrPort) (net.Listener, error) { + var nsNetwork string + nsAddr := host + switch { + case network == "tcp4" || network == "tcp6": + nsNetwork = network + case host.Addr().Is4(): + nsNetwork = "tcp4" + case host.Addr().Is6(): + nsNetwork = "tcp6" + default: + // Wildcard address: use tcp6 for dual-stack (accepts both v4 and v6). + nsNetwork = "tcp6" + nsAddr = netip.AddrPortFrom(netip.IPv6Unspecified(), host.Port()) + } + ln, err := s.netstack.ListenTCP(nsNetwork, nsAddr.String()) + if err != nil { + return nil, fmt.Errorf("tsnet: %w", err) + } + return ln, nil +} + +// registerListener allocates a port (if 0) and registers the listener in +// s.listeners under s.mu. +func (s *Server) registerListener(network, addr string, host netip.AddrPort, lnOn listenOn, gonetLn net.Listener) (*listener, error) { + s.mu.Lock() + defer s.mu.Unlock() + + // Allocate an ephemeral port for non-TUN listeners requesting port 0. + if host.Port() == 0 && gonetLn == nil { + p, ok := s.allocEphemeralLocked(network, host.Addr(), lnOn) + if !ok { + return nil, errors.New("tsnet: no available port in ephemeral range") + } + host = netip.AddrPortFrom(host.Addr(), p) + addr = listenAddr(host) + } + var keys []listenKey switch lnOn { case listenOnTailnet: @@ -1654,58 +1752,93 @@ func (s *Server) listen(network, addr string, lnOn listenOn) (net.Listener, erro keys = append(keys, listenKey{network, host.Addr(), host.Port(), true}) } - ln := &listener{ - s: s, - keys: keys, - addr: addr, - - closedc: make(chan struct{}), - conn: make(chan net.Conn), - } - - // When using a TUN with TCP, create a gVisor TCP listener. - if s.Tun != nil && (network == "" || network == "tcp" || network == "tcp4" || network == "tcp6") { - var nsNetwork string - nsAddr := host - switch { - case network == "tcp4" || network == "tcp6": - nsNetwork = network - case host.Addr().Is4(): - nsNetwork = "tcp4" - case host.Addr().Is6(): - nsNetwork = "tcp6" - default: - // Wildcard address: use tcp6 for dual-stack (accepts both v4 and v6). - nsNetwork = "tcp6" - nsAddr = netip.AddrPortFrom(netip.IPv6Unspecified(), host.Port()) - } - gonetLn, err := s.netstack.ListenTCP(nsNetwork, nsAddr.String()) - if err != nil { - return nil, fmt.Errorf("tsnet: %w", err) - } - ln.gonetLn = gonetLn - } - - s.mu.Lock() for _, key := range keys { if _, ok := s.listeners[key]; ok { - s.mu.Unlock() - if ln.gonetLn != nil { - ln.gonetLn.Close() - } return nil, fmt.Errorf("tsnet: listener already open for %s, %s", network, addr) } } + + ln := &listener{ + s: s, + keys: keys, + addr: addr, + closedc: make(chan struct{}), + conn: make(chan net.Conn), + gonetLn: gonetLn, + } if s.listeners == nil { s.listeners = make(map[listenKey]*listener) } for _, key := range keys { s.listeners[key] = ln } - s.mu.Unlock() return ln, nil } +// allocEphemeralLocked finds an unused port in [ephemeralPortFirst, +// ephemeralPortLast] that does not collide with any existing listener for the +// given network, host, and listenOn. s.mu must be held. +func (s *Server) allocEphemeralLocked(network string, host netip.Addr, lnOn listenOn) (uint16, bool) { + if s.nextEphemeralPort < ephemeralPortFirst || s.nextEphemeralPort > ephemeralPortLast { + s.nextEphemeralPort = ephemeralPortFirst + } + start := s.nextEphemeralPort + for { + p := s.nextEphemeralPort + s.nextEphemeralPort++ + if s.nextEphemeralPort > ephemeralPortLast { + s.nextEphemeralPort = ephemeralPortFirst + } + if !s.portInUseLocked(network, host, p, lnOn) { + return p, true + } + if s.nextEphemeralPort == start { + return 0, false + } + } +} + +// portInUseLocked reports whether any listenKey for the given network, host, +// port, and listenOn already exists in s.listeners. +func (s *Server) portInUseLocked(network string, host netip.Addr, port uint16, lnOn listenOn) bool { + switch lnOn { + case listenOnTailnet: + _, ok := s.listeners[listenKey{network, host, port, false}] + return ok + case listenOnFunnel: + _, ok := s.listeners[listenKey{network, host, port, true}] + return ok + case listenOnBoth: + _, ok1 := s.listeners[listenKey{network, host, port, false}] + _, ok2 := s.listeners[listenKey{network, host, port, true}] + return ok1 || ok2 + } + return false +} + +// listenAddr formats host as a listen address string. +// If host has no IP, it returns ":port". +func listenAddr(host netip.AddrPort) string { + if !host.Addr().IsValid() { + return ":" + strconv.Itoa(int(host.Port())) + } + return host.String() +} + +// portFromAddr extracts the port from a net.Addr, or returns 0. +func portFromAddr(a net.Addr) uint16 { + switch v := a.(type) { + case *net.TCPAddr: + return uint16(v.Port) + case *net.UDPAddr: + return uint16(v.Port) + } + if ap, err := netip.ParseAddrPort(a.String()); err == nil { + return ap.Port() + } + return 0 +} + // GetRootPath returns the root path of the tsnet server. // This is where the state file and other data is stored. func (s *Server) GetRootPath() string { diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 9481defae94e8..266a60f78c5ec 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -112,6 +112,86 @@ func TestListenerPort(t *testing.T) { } } +func TestResolveListenAddrUnspecified(t *testing.T) { + tests := []struct { + name string + network string + addr string + wantIP netip.Addr + }{ + {"empty_host", "tcp", ":80", netip.Addr{}}, + {"ipv4_unspecified", "tcp", "0.0.0.0:80", netip.Addr{}}, + {"ipv6_unspecified", "tcp", "[::]:80", netip.Addr{}}, + {"specific_ipv4", "tcp", "100.64.0.1:80", netip.MustParseAddr("100.64.0.1")}, + {"specific_ipv6", "tcp6", "[fd7a:115c:a1e0::1]:80", netip.MustParseAddr("fd7a:115c:a1e0::1")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := resolveListenAddr(tt.network, tt.addr) + if err != nil { + t.Fatal(err) + } + if got.Addr() != tt.wantIP { + t.Errorf("Addr() = %v, want %v", got.Addr(), tt.wantIP) + } + }) + } +} + +func TestAllocEphemeral(t *testing.T) { + s := &Server{listeners: make(map[listenKey]*listener)} + + // Sequential allocations should return unique ports in range. + var ports []uint16 + for range 5 { + s.mu.Lock() + p, ok := s.allocEphemeralLocked("tcp", netip.Addr{}, listenOnTailnet) + s.mu.Unlock() + if !ok { + t.Fatal("allocEphemeralLocked failed unexpectedly") + } + if p < ephemeralPortFirst || p > ephemeralPortLast { + t.Errorf("port %d outside [%d, %d]", p, ephemeralPortFirst, ephemeralPortLast) + } + for _, prev := range ports { + if p == prev { + t.Errorf("duplicate port %d", p) + } + } + ports = append(ports, p) + // Occupy the port so the next call skips it. + s.listeners[listenKey{"tcp", netip.Addr{}, p, false}] = &listener{} + } + + // Verify skip over occupied port. + s.mu.Lock() + next := s.nextEphemeralPort + if next < ephemeralPortFirst || next > ephemeralPortLast { + next = ephemeralPortFirst + } + s.listeners[listenKey{"tcp", netip.Addr{}, next, false}] = &listener{} + p, ok := s.allocEphemeralLocked("tcp", netip.Addr{}, listenOnTailnet) + s.mu.Unlock() + if !ok { + t.Fatal("allocEphemeralLocked failed after skip") + } + if p == next { + t.Errorf("should have skipped occupied port %d", next) + } + + // Wrap-around. + s.mu.Lock() + s.nextEphemeralPort = ephemeralPortLast + p, ok = s.allocEphemeralLocked("tcp", netip.Addr{}, listenOnTailnet) + s.mu.Unlock() + if !ok { + t.Fatal("allocEphemeralLocked failed at wrap") + } + if p < ephemeralPortFirst || p > ephemeralPortLast { + t.Errorf("port %d outside range after wrap", p) + } +} + var verboseDERP = flag.Bool("verbose-derp", false, "if set, print DERP and STUN logs") var verboseNodes = flag.Bool("verbose-nodes", false, "if set, print tsnet.Server logs") @@ -2869,3 +2949,159 @@ func TestSelfDial(t *testing.T) { t.Errorf("server->client: got %q, want %q", gotReply, reply) } } + +// TestListenUnspecifiedAddr verifies that listening on 0.0.0.0 or [::] works +// the same as listening on an empty host (":port"), accepting connections +// destined to the node's Tailscale IPs. +func TestListenUnspecifiedAddr(t *testing.T) { + testUnspec := func(t *testing.T, lt *listenTest, addr, dialPort string) { + ln, err := lt.s2.Listen("tcp", addr) + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + echoErr := make(chan error, 1) + go func() { + conn, err := ln.Accept() + if err != nil { + echoErr <- err + return + } + defer conn.Close() + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + echoErr <- err + return + } + _, err = conn.Write(buf[:n]) + echoErr <- err + }() + + dialAddr := net.JoinHostPort(lt.s2ip4.String(), dialPort) + conn, err := lt.s1.Dial(t.Context(), "tcp", dialAddr) + if err != nil { + t.Fatalf("Dial(%q) failed: %v", dialAddr, err) + } + defer conn.Close() + want := "hello unspec" + if _, err := conn.Write([]byte(want)); err != nil { + t.Fatalf("Write failed: %v", err) + } + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + n, err := conn.Read(got) + if err != nil { + t.Fatalf("Read failed: %v", err) + } + if string(got[:n]) != want { + t.Errorf("got %q, want %q", got[:n], want) + } + if err := <-echoErr; err != nil { + t.Fatalf("echo error: %v", err) + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupTwoClientTest(t, false) + t.Run("0.0.0.0", func(t *testing.T) { testUnspec(t, lt, "0.0.0.0:8080", "8080") }) + t.Run("::", func(t *testing.T) { testUnspec(t, lt, "[::]:8081", "8081") }) + }) + t.Run("TUN", func(t *testing.T) { + lt := setupTwoClientTest(t, true) + t.Run("0.0.0.0", func(t *testing.T) { testUnspec(t, lt, "0.0.0.0:8080", "8080") }) + t.Run("::", func(t *testing.T) { testUnspec(t, lt, "[::]:8081", "8081") }) + }) +} + +// TestListenMultipleEphemeralPorts verifies that calling Listen with port 0 +// multiple times allocates distinct ports, each of which can receive +// connections independently. +func TestListenMultipleEphemeralPorts(t *testing.T) { + testMultipleEphemeral := func(t *testing.T, lt *listenTest) { + const n = 3 + listeners := make([]net.Listener, n) + ports := make([]string, n) + for i := range n { + ln, err := lt.s2.Listen("tcp", ":0") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { ln.Close() }) + _, portStr, err := net.SplitHostPort(ln.Addr().String()) + if err != nil { + t.Fatalf("parsing Addr %q: %v", ln.Addr(), err) + } + if portStr == "0" { + t.Fatal("Addr() returned port 0; expected allocated port") + } + for j := range i { + if ports[j] == portStr { + t.Fatalf("listeners %d and %d both got port %s", j, i, portStr) + } + } + listeners[i] = ln + ports[i] = portStr + } + + // Verify each listener independently accepts connections. + for i := range n { + echoErr := make(chan error, 1) + go func() { + conn, err := listeners[i].Accept() + if err != nil { + echoErr <- err + return + } + defer conn.Close() + buf := make([]byte, 1024) + rn, err := conn.Read(buf) + if err != nil { + echoErr <- err + return + } + _, err = conn.Write(buf[:rn]) + echoErr <- err + }() + + dialAddr := net.JoinHostPort(lt.s2ip4.String(), ports[i]) + conn, err := lt.s1.Dial(t.Context(), "tcp", dialAddr) + if err != nil { + t.Fatalf("listener %d: Dial(%q) failed: %v", i, dialAddr, err) + } + want := fmt.Sprintf("hello port %d", i) + if _, err := conn.Write([]byte(want)); err != nil { + conn.Close() + t.Fatalf("listener %d: Write failed: %v", i, err) + } + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + got := make([]byte, 1024) + rn, err := conn.Read(got) + conn.Close() + if err != nil { + select { + case e := <-echoErr: + t.Fatalf("listener %d: echo error: %v; read error: %v", i, e, err) + default: + t.Fatalf("listener %d: Read failed: %v", i, err) + } + } + if string(got[:rn]) != want { + t.Errorf("listener %d: got %q, want %q", i, got[:rn], want) + } + if err := <-echoErr; err != nil { + t.Fatalf("listener %d: echo error: %v", i, err) + } + } + } + + t.Run("Netstack", func(t *testing.T) { + lt := setupTwoClientTest(t, false) + testMultipleEphemeral(t, lt) + }) + t.Run("TUN", func(t *testing.T) { + lt := setupTwoClientTest(t, true) + testMultipleEphemeral(t, lt) + }) +} From 2743e0b681128380f87ab0c88b43dc35ab630917 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Mon, 2 Mar 2026 16:01:48 +0000 Subject: [PATCH 163/202] .github/actions/go-cache: check for pre-built cigocacher (#18833) Some CI runner images now have cigocacher baked in. Skip building if it's already present. Updates tailscale/corp#35667 Change-Id: I5ea0d606d44b1373bc1c8f7bca4ab780e763e2a9 Signed-off-by: Tom Proctor --- .github/actions/go-cache/action.sh | 33 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/actions/go-cache/action.sh b/.github/actions/go-cache/action.sh index f49d5bb779f4d..5cfafe4767fb2 100755 --- a/.github/actions/go-cache/action.sh +++ b/.github/actions/go-cache/action.sh @@ -23,22 +23,27 @@ if [ -z "${URL:-}" ]; then exit 0 fi -GOPATH=$(command -v go || true) -if [ -z "${GOPATH}" ]; then - if [ ! -f "tool/go" ]; then - echo "Go not available, unable to proceed" - exit 1 +BIN_PATH="$(PATH="$PATH:$HOME/bin" command -v cigocacher || true)" +if [ -z "${BIN_PATH}" ]; then + echo "cigocacher not found in PATH, attempting to build or fetch it" + + GOPATH=$(command -v go || true) + if [ -z "${GOPATH}" ]; then + if [ ! -f "tool/go" ]; then + echo "Go not available, unable to proceed" + exit 1 + fi + GOPATH="./tool/go" fi - GOPATH="./tool/go" -fi -BIN_PATH="${RUNNER_TEMP:-/tmp}/cigocacher$(${GOPATH} env GOEXE)" -if [ -d "cmd/cigocacher" ]; then - echo "cmd/cigocacher found locally, building from local source" - "${GOPATH}" build -o "${BIN_PATH}" ./cmd/cigocacher -else - echo "cmd/cigocacher not found locally, fetching from tailscale.com/cmd/cigocacher" - "${GOPATH}" build -o "${BIN_PATH}" tailscale.com/cmd/cigocacher + BIN_PATH="${RUNNER_TEMP:-/tmp}/cigocacher$(${GOPATH} env GOEXE)" + if [ -d "cmd/cigocacher" ]; then + echo "cmd/cigocacher found locally, building from local source" + "${GOPATH}" build -o "${BIN_PATH}" ./cmd/cigocacher + else + echo "cmd/cigocacher not found locally, fetching from tailscale.com/cmd/cigocacher" + "${GOPATH}" build -o "${BIN_PATH}" tailscale.com/cmd/cigocacher + fi fi CIGOCACHER_TOKEN="$("${BIN_PATH}" --auth --cigocached-url "${URL}" --cigocached-host "${HOST}" )" From 3e8913f959c3313103a1b51c2ceb474663c9399b Mon Sep 17 00:00:00 2001 From: License Updater Date: Mon, 2 Mar 2026 15:12:31 +0000 Subject: [PATCH 164/202] licenses: update license notices Signed-off-by: License Updater --- licenses/android.md | 6 +++--- licenses/apple.md | 4 ++-- licenses/tailscale.md | 4 ++-- licenses/windows.md | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/licenses/android.md b/licenses/android.md index 5c46b3cb13340..15098f0752e79 100644 --- a/licenses/android.md +++ b/licenses/android.md @@ -8,11 +8,11 @@ Client][]. See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.0/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) - [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.9.0/LICENSE)) - - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.18.0/LICENSE)) + - [github.com/gaissmai/bart](https://pkg.go.dev/github.com/gaissmai/bart) ([MIT](https://github.com/gaissmai/bart/blob/v0.26.1/LICENSE)) - [github.com/go-json-experiment/json](https://pkg.go.dev/github.com/go-json-experiment/json) ([BSD-3-Clause](https://github.com/go-json-experiment/json/blob/ebf49471dced/LICENSE)) - [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/2c02b8208cf8/LICENSE)) - [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.3/LICENSE)) @@ -26,7 +26,7 @@ Client][]. See also the dependencies in the [Tailscale CLI][]. - [github.com/klauspost/compress/zstd/internal/xxhash](https://pkg.go.dev/github.com/klauspost/compress/zstd/internal/xxhash) ([MIT](https://github.com/klauspost/compress/blob/v1.18.2/zstd/internal/xxhash/LICENSE.txt)) - [github.com/kortschak/wol](https://pkg.go.dev/github.com/kortschak/wol) ([BSD-3-Clause](https://github.com/kortschak/wol/blob/da482cc4850a/LICENSE)) - [github.com/mdlayher/socket](https://pkg.go.dev/github.com/mdlayher/socket) ([MIT](https://github.com/mdlayher/socket/blob/v0.5.0/LICENSE.md)) - - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.21/LICENSE)) + - [github.com/pierrec/lz4/v4](https://pkg.go.dev/github.com/pierrec/lz4/v4) ([BSD-3-Clause](https://github.com/pierrec/lz4/blob/v4.1.25/LICENSE)) - [github.com/pires/go-proxyproto](https://pkg.go.dev/github.com/pires/go-proxyproto) ([Apache-2.0](https://github.com/pires/go-proxyproto/blob/v0.8.1/LICENSE)) - [github.com/tailscale/peercred](https://pkg.go.dev/github.com/tailscale/peercred) ([BSD-3-Clause](https://github.com/tailscale/peercred/blob/35a0c7bd7edc/LICENSE)) - [github.com/tailscale/tailscale-android/libtailscale](https://pkg.go.dev/github.com/tailscale/tailscale-android/libtailscale) ([BSD-3-Clause](https://github.com/tailscale/tailscale-android/blob/HEAD/LICENSE)) diff --git a/licenses/apple.md b/licenses/apple.md index 93afd9385cdbe..f7989fe250a63 100644 --- a/licenses/apple.md +++ b/licenses/apple.md @@ -11,7 +11,7 @@ See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.1/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [github.com/aws/aws-sdk-go-v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/v1.41.0/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/config/v1.32.5/config/LICENSE.txt)) - [github.com/aws/aws-sdk-go-v2/credentials](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/credentials) ([Apache-2.0](https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.19.5/credentials/LICENSE.txt)) @@ -78,7 +78,7 @@ See also the dependencies in the [Tailscale CLI][]. - [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE)) - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) + - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/573d5e7127a8/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) ## Additional Dependencies diff --git a/licenses/tailscale.md b/licenses/tailscale.md index 521b6ff9ce887..5050b38db2178 100644 --- a/licenses/tailscale.md +++ b/licenses/tailscale.md @@ -38,7 +38,7 @@ Some packages may only be included on certain architectures or operating systems - [github.com/aws/smithy-go/internal/sync/singleflight](https://pkg.go.dev/github.com/aws/smithy-go/internal/sync/singleflight) ([BSD-3-Clause](https://github.com/aws/smithy-go/blob/v1.24.0/internal/sync/singleflight/LICENSE)) - [github.com/coder/websocket](https://pkg.go.dev/github.com/coder/websocket) ([ISC](https://github.com/coder/websocket/blob/v1.8.12/LICENSE.txt)) - [github.com/creachadair/msync/trigger](https://pkg.go.dev/github.com/creachadair/msync/trigger) ([BSD-3-Clause](https://github.com/creachadair/msync/blob/v0.7.1/LICENSE)) - - [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.23/LICENSE)) + - [github.com/creack/pty](https://pkg.go.dev/github.com/creack/pty) ([MIT](https://github.com/creack/pty/blob/v1.1.24/LICENSE)) - [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/a09d6be7affa/LICENSE)) - [github.com/digitalocean/go-smbios/smbios](https://pkg.go.dev/github.com/digitalocean/go-smbios/smbios) ([Apache-2.0](https://github.com/digitalocean/go-smbios/blob/390a4f403a8e/LICENSE.md)) - [github.com/djherbis/times](https://pkg.go.dev/github.com/djherbis/times) ([MIT](https://github.com/djherbis/times/blob/v1.6.0/LICENSE)) @@ -98,7 +98,7 @@ Some packages may only be included on certain architectures or operating systems - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE)) - [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2)) - [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3)) - - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/9414b50a5633/LICENSE)) + - [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/573d5e7127a8/LICENSE)) - [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.34.0/LICENSE)) - [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([Apache-2.0](https://github.com/kubernetes-sigs/yaml/blob/v1.6.0/LICENSE)) - [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE)) diff --git a/licenses/windows.md b/licenses/windows.md index 29581566c68ba..e8bcc932f332f 100644 --- a/licenses/windows.md +++ b/licenses/windows.md @@ -9,7 +9,7 @@ Windows][]. See also the dependencies in the [Tailscale CLI][]. ## Go Packages - - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.1.1/LICENSE)) + - [filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519) ([BSD-3-Clause](https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE)) - [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE)) - [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE)) - [github.com/beorn7/perks/quantile](https://pkg.go.dev/github.com/beorn7/perks/quantile) ([MIT](https://github.com/beorn7/perks/blob/v1.0.1/LICENSE)) From e0ca836c9928c2d2a687f7538cd3406b92393273 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:01:37 +0000 Subject: [PATCH 165/202] .github: Bump github/codeql-action from 4.32.3 to 4.32.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.3 to 4.32.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/9e907b5e64f6b83e7804b09294d44122997950d6...c793b717bc78562f491db7b0e93a3a178b099162) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.32.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 49657de707f17..e88003c769b6f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 + uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 + uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 + uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 From eeb1fa047bef2896d81eb01ab1a653419c129dc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:01:08 +0000 Subject: [PATCH 166/202] .github: Bump actions/setup-go from 6.2.0 to 6.3.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.2.0 to 6.3.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e88003c769b6f..51bae5a068df5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -49,7 +49,7 @@ jobs: # Install a more recent Go that understands modern go.mod content. - name: Install Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index dbabb361e14fa..6431a31d698c0 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6c693188783f..064765ca2a2af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -245,7 +245,7 @@ jobs: path: ${{ github.workspace }}/src - name: Install Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version-file: ${{ github.workspace }}/src/go.mod cache: false From 5a2168da9ef6d7daff8fd0419dbfb6026b02a0ed Mon Sep 17 00:00:00 2001 From: Erisa A Date: Mon, 2 Mar 2026 18:29:49 +0000 Subject: [PATCH 167/202] scripts/installer.sh: handle KDE Linux (#18861) Display a message pointing to KDE Linux documentation on installing Tailscale Fixes #18306 Signed-off-by: Erisa A --- scripts/installer.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/installer.sh b/scripts/installer.sh index 8ffd3f5720a2d..2c15ea6571678 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -341,6 +341,11 @@ main() { echo "https://github.com/tailscale-dev/deck-tailscale" exit 1 ;; + kde-linux) + echo "The maintainers of KDE Linux provide documentation on multiple ways to install Tailscale. These instructions are not officially supported by Tailscale:" + echo "https://kde.org/linux/docs/more-software/#tailscale" + exit 1 + ;; # TODO: wsl? # TODO: synology? qnap? From 8fd02bb6260c61c217589809372959c523be3a84 Mon Sep 17 00:00:00 2001 From: Amal Bansode Date: Mon, 2 Mar 2026 17:33:57 -0800 Subject: [PATCH 168/202] types/geo: fix floating point bug causing NaN returns in SphericalAngleTo (#18777) Subtle floating point imprecision can propagate and lead to trigonometric functions receiving inputs outside their domain, thus returning NaN. Clamp the input to the valid domain to prevent this. Also adds a fuzz test for SphericalAngleTo. Updates tailscale/corp#37518 Signed-off-by: Amal Bansode --- types/geo/point.go | 3 + types/geo/point_test.go | 147 ++++++++++++++++++++++++---------------- 2 files changed, 92 insertions(+), 58 deletions(-) diff --git a/types/geo/point.go b/types/geo/point.go index 820582b0ff6b3..d039ea1fad7e2 100644 --- a/types/geo/point.go +++ b/types/geo/point.go @@ -125,6 +125,9 @@ func (p Point) SphericalAngleTo(q Point) (Radians, error) { sLat, sLng := float64(qLat.Radians()), float64(qLng.Radians()) cosA := math.Sin(rLat)*math.Sin(sLat) + math.Cos(rLat)*math.Cos(sLat)*math.Cos(rLng-sLng) + // Subtle floating point imprecision can lead to cosA being outside + // the domain of arccosine [-1, 1]. Clamp the input to avoid NaN return. + cosA = min(max(-1.0, cosA), 1.0) return Radians(math.Acos(cosA)), nil } diff --git a/types/geo/point_test.go b/types/geo/point_test.go index f0d0cb3abba3e..32a73180add34 100644 --- a/types/geo/point_test.go +++ b/types/geo/point_test.go @@ -448,65 +448,79 @@ func TestPointMarshalUint64(t *testing.T) { }) } +const earthRadius = 6371.000 // volumetric mean radius (km) +const kmToRad = 1 / earthRadius + +// Test corpus for exercising PointSphericalAngleTo. +var corpusPointSphericalAngleTo = []struct { + name string + x geo.Point + y geo.Point + want geo.Radians + wantErr string +}{ + { + name: "same-point-null-island", + x: geo.MakePoint(0, 0), + y: geo.MakePoint(0, 0), + want: 0.0 * geo.Radian, + }, + { + name: "same-point-north-pole", + x: geo.MakePoint(+90, 0), + y: geo.MakePoint(+90, +90), + want: 0.0 * geo.Radian, + }, + { + name: "same-point-south-pole", + x: geo.MakePoint(-90, 0), + y: geo.MakePoint(-90, -90), + want: 0.0 * geo.Radian, + }, + { + name: "north-pole-to-south-pole", + x: geo.MakePoint(+90, 0), + y: geo.MakePoint(-90, -90), + want: math.Pi * geo.Radian, + }, + { + name: "toronto-to-montreal", + x: geo.MakePoint(+43.6532, -79.3832), + y: geo.MakePoint(+45.5019, -73.5674), + want: 504.26 * kmToRad * geo.Radian, + }, + { + name: "sydney-to-san-francisco", + x: geo.MakePoint(-33.8727, +151.2057), + y: geo.MakePoint(+37.7749, -122.4194), + want: 11948.18 * kmToRad * geo.Radian, + }, + { + name: "new-york-to-paris", + x: geo.MakePoint(+40.7128, -74.0060), + y: geo.MakePoint(+48.8575, +2.3514), + want: 5837.15 * kmToRad * geo.Radian, + }, + { + name: "seattle-to-tokyo", + x: geo.MakePoint(+47.6061, -122.3328), + y: geo.MakePoint(+35.6764, +139.6500), + want: 7700.00 * kmToRad * geo.Radian, + }, + { + // Subtle floating point imprecision can propagate and lead to + // trigonometric functions receiving inputs outside their + // domain, thus returning NaN. + // Test one such case. + name: "floating-point-precision-test", + x: geo.MakePoint(-6.0, 0.0), + y: geo.MakePoint(-6.0, 0.0), + want: 0.0 * geo.Radian, + }, +} + func TestPointSphericalAngleTo(t *testing.T) { - const earthRadius = 6371.000 // volumetric mean radius (km) - const kmToRad = 1 / earthRadius - for _, tt := range []struct { - name string - x geo.Point - y geo.Point - want geo.Radians - wantErr string - }{ - { - name: "same-point-null-island", - x: geo.MakePoint(0, 0), - y: geo.MakePoint(0, 0), - want: 0.0 * geo.Radian, - }, - { - name: "same-point-north-pole", - x: geo.MakePoint(+90, 0), - y: geo.MakePoint(+90, +90), - want: 0.0 * geo.Radian, - }, - { - name: "same-point-south-pole", - x: geo.MakePoint(-90, 0), - y: geo.MakePoint(-90, -90), - want: 0.0 * geo.Radian, - }, - { - name: "north-pole-to-south-pole", - x: geo.MakePoint(+90, 0), - y: geo.MakePoint(-90, -90), - want: math.Pi * geo.Radian, - }, - { - name: "toronto-to-montreal", - x: geo.MakePoint(+43.6532, -79.3832), - y: geo.MakePoint(+45.5019, -73.5674), - want: 504.26 * kmToRad * geo.Radian, - }, - { - name: "sydney-to-san-francisco", - x: geo.MakePoint(-33.8727, +151.2057), - y: geo.MakePoint(+37.7749, -122.4194), - want: 11948.18 * kmToRad * geo.Radian, - }, - { - name: "new-york-to-paris", - x: geo.MakePoint(+40.7128, -74.0060), - y: geo.MakePoint(+48.8575, +2.3514), - want: 5837.15 * kmToRad * geo.Radian, - }, - { - name: "seattle-to-tokyo", - x: geo.MakePoint(+47.6061, -122.3328), - y: geo.MakePoint(+35.6764, +139.6500), - want: 7700.00 * kmToRad * geo.Radian, - }, - } { + for _, tt := range corpusPointSphericalAngleTo { t.Run(tt.name, func(t *testing.T) { got, err := tt.x.SphericalAngleTo(tt.y) if tt.wantErr == "" && err != nil { @@ -536,6 +550,23 @@ func TestPointSphericalAngleTo(t *testing.T) { } } +func FuzzPointSphericalAngleTo(f *testing.F) { + for _, tt := range corpusPointSphericalAngleTo { + xLat, xLng, _ := tt.x.LatLngFloat64() + yLat, yLng, _ := tt.y.LatLngFloat64() + f.Add(xLat, xLng, yLat, yLng) + } + + f.Fuzz(func(t *testing.T, xLat float64, xLng float64, yLat float64, yLng float64) { + x := geo.MakePoint(geo.Degrees(xLat), geo.Degrees(xLng)) + y := geo.MakePoint(geo.Degrees(yLat), geo.Degrees(yLng)) + got, _ := x.SphericalAngleTo(y) + if math.IsNaN(float64(got)) { + t.Errorf("got NaN result with xLat=%.15f xLng=%.15f yLat=%.15f yLng=%.15f", xLat, xLng, yLat, yLng) + } + }) +} + func approx[T ~float64](x, y T) bool { return math.Abs(float64(x)-float64(y)) <= 1e-5 } From 0cca3bd4170f3fd126f1ac632f095fafd00099a1 Mon Sep 17 00:00:00 2001 From: Alex Chan Date: Fri, 27 Feb 2026 16:20:11 +0000 Subject: [PATCH 169/202] wgengine/magicsock: improve error message for moving Mullvad node keys The "public key moved" panic has caused confusion on multiple occasions, and is a known issue for Mullvad. Add a loose heuristic to detect Mullvad nodes, and trigger distinct panics for Mullvad and non-Mullvad instances, with a link to the associated bug. When this occurs again with Mullvad, it'll be easier for somebody to find the existing bug. If it occurs again with something other than Mullvad, it'll be more obvious that it's a distinct issue. Updates tailscale/corp#27300 Change-Id: Ie47271f45f2ff28f767578fcca5e6b21731d08a1 Signed-off-by: Alex Chan --- wgengine/magicsock/magicsock.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index b2852d2e2fdbc..dd8f27b23010f 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -3080,8 +3080,18 @@ func (c *Conn) updateNodes(self tailcfg.NodeView, peers []tailcfg.NodeView) (pee // we don't get this far. If ok was false above, that means it's a key // that differs from the one the NodeID had. But double check. if ep.nodeID != n.ID() { - // Server error. - devPanicf("public key moved between nodeIDs (old=%v new=%v, key=%s)", ep.nodeID, n.ID(), n.Key().String()) + // Server error. This is known to be a particular issue for Mullvad + // nodes (http://go/corp/27300), so log a distinct error for the + // Mullvad and non-Mullvad cases. The error will be logged either way, + // so an approximate heuristic is fine. + // + // When #27300 is fixed, we can delete this branch and log the same + // panic for any public key moving. + if strings.HasSuffix(n.Name(), ".mullvad.ts.net.") { + devPanicf("public key moved between Mullvad nodeIDs (old=%v new=%v, key=%s); see http://go/corp/27300", ep.nodeID, n.ID(), n.Key().String()) + } else { + devPanicf("public key moved between nodeIDs (old=%v new=%v, key=%s)", ep.nodeID, n.ID(), n.Key().String()) + } } else { // Internal data structures out of sync. devPanicf("public key found in peerMap but not by nodeID") From 2d21dd46cd9fbb2fcf020d6b5e764f3d4aaf2d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20Lensb=C3=B8l?= Date: Tue, 3 Mar 2026 09:04:37 -0500 Subject: [PATCH 170/202] wgengine/magicsoc,net/tstun: put disco key advertisement behind a nob (#18857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To be less spammy in stable, add a nob that disables the creation and processing of TSMPDiscoKeyAdvertisements until we have a proper rollout mechanism. Updates #12639 Signed-off-by: Claus Lensbøl --- net/tstun/wrap.go | 11 +++++++---- wgengine/magicsock/magicsock.go | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 3c1315437f510..2f5d8c1d13254 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -23,6 +23,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "go4.org/mem" "tailscale.com/disco" + "tailscale.com/envknob" "tailscale.com/feature/buildfeatures" "tailscale.com/net/packet" "tailscale.com/net/packet/checksum" @@ -1157,10 +1158,12 @@ func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook pa t.injectOutboundPong(p, pingReq) return filter.DropSilently, gro } else if discoKeyAdvert, ok := p.AsTSMPDiscoAdvertisement(); ok { - t.discoKeyAdvertisementPub.Publish(DiscoKeyAdvertisement{ - Src: discoKeyAdvert.Src, - Key: discoKeyAdvert.Key, - }) + if buildfeatures.HasCacheNetMap && envknob.Bool("TS_USE_CACHED_NETMAP") { + t.discoKeyAdvertisementPub.Publish(DiscoKeyAdvertisement{ + Src: discoKeyAdvert.Src, + Key: discoKeyAdvert.Key, + }) + } return filter.DropSilently, gro } else if data, ok := p.AsTSMPPong(); ok { if f := t.OnTSMPPongReceived; f != nil { diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index dd8f27b23010f..169369f4bb472 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -4309,6 +4309,10 @@ type NewDiscoKeyAvailable struct { // // We do not need the Conn to be locked, but the endpoint should be. func (c *Conn) maybeSendTSMPDiscoAdvert(de *endpoint) { + if !buildfeatures.HasCacheNetMap || !envknob.Bool("TS_USE_CACHED_NETMAP") { + return + } + de.mu.Lock() defer de.mu.Unlock() if !de.sentDiscoKeyAdvertisement { From 120f27f383d5501d1483c5238b591e66db500fe4 Mon Sep 17 00:00:00 2001 From: Fran Bull Date: Fri, 20 Feb 2026 08:00:17 -0800 Subject: [PATCH 171/202] feature/conn25: stop adding multiple entries for same domain+dst We should only add one entry to our magic ips for each domain+dst and look up any existing entry instead of always creating a new one. Fixes tailscale/corp#34252 Signed-off-by: Fran Bull --- feature/conn25/conn25.go | 84 ++++++++++++++++------- feature/conn25/conn25_test.go | 124 ++++++++++++++++++++++------------ 2 files changed, 139 insertions(+), 69 deletions(-) diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index 02bec132dc10c..05f087e21df46 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -12,7 +12,6 @@ import ( "errors" "net/http" "net/netip" - "strings" "sync" "go4.org/netipx" @@ -310,8 +309,8 @@ const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experime type config struct { isConfigured bool apps []appctype.Conn25Attr - appsByDomain map[string][]string - selfRoutedDomains set.Set[string] + appsByDomain map[dnsname.FQDN][]string + selfRoutedDomains set.Set[dnsname.FQDN] } func configFromNodeView(n tailcfg.NodeView) (config, error) { @@ -326,8 +325,8 @@ func configFromNodeView(n tailcfg.NodeView) (config, error) { cfg := config{ isConfigured: true, apps: apps, - appsByDomain: map[string][]string{}, - selfRoutedDomains: set.Set[string]{}, + appsByDomain: map[dnsname.FQDN][]string{}, + selfRoutedDomains: set.Set[dnsname.FQDN]{}, } for _, app := range apps { selfMatchesTags := false @@ -342,10 +341,9 @@ func configFromNodeView(n tailcfg.NodeView) (config, error) { if err != nil { return config{}, err } - key := fqdn.WithTrailingDot() - mak.Set(&cfg.appsByDomain, key, append(cfg.appsByDomain[key], app.Name)) + mak.Set(&cfg.appsByDomain, fqdn, append(cfg.appsByDomain[fqdn], app.Name)) if selfMatchesTags { - cfg.selfRoutedDomains.Add(key) + cfg.selfRoutedDomains.Add(fqdn) } } } @@ -362,9 +360,8 @@ type client struct { mu sync.Mutex // protects the fields below magicIPPool *ippool transitIPPool *ippool - // map of magic IP -> (transit IP, app) - magicIPs map[netip.Addr]appAddr - config config + assignments addrAssignments + config config } func (c *client) isConfigured() bool { @@ -407,13 +404,7 @@ func (c *client) reconfig(newCfg config) error { return nil } -func (c *client) setMagicIP(magicAddr, transitAddr netip.Addr, app string) { - c.mu.Lock() - defer c.mu.Unlock() - mak.Set(&c.magicIPs, magicAddr, appAddr{addr: transitAddr, app: app}) -} - -func (c *client) isConnectorDomain(domain string) bool { +func (c *client) isConnectorDomain(domain dnsname.FQDN) bool { c.mu.Lock() defer c.mu.Unlock() appNames, ok := c.config.appsByDomain[domain] @@ -424,9 +415,12 @@ func (c *client) isConnectorDomain(domain string) bool { // for this domain+dst address, so that this client can use conn25 connectors. // It checks that this domain should be routed and that this client is not itself a connector for the domain // and generally if it is valid to make the assignment. -func (c *client) reserveAddresses(domain string, dst netip.Addr) (addrs, error) { +func (c *client) reserveAddresses(domain dnsname.FQDN, dst netip.Addr) (addrs, error) { c.mu.Lock() defer c.mu.Unlock() + if existing, ok := c.assignments.lookupByDomainDst(domain, dst); ok { + return existing, nil + } appNames, _ := c.config.appsByDomain[domain] // only reserve for first app app := appNames[0] @@ -438,17 +432,20 @@ func (c *client) reserveAddresses(domain string, dst netip.Addr) (addrs, error) if err != nil { return addrs{}, err } - addrs := addrs{ + as := addrs{ dst: dst, magic: mip, transit: tip, app: app, + domain: domain, } - return addrs, nil + if err := c.assignments.insert(as); err != nil { + return addrs{}, err + } + return as, nil } func (c *client) enqueueAddressAssignment(addrs addrs) { - c.setMagicIP(addrs.magic, addrs.transit, addrs.app) // TODO(fran) 2026-02-03 asynchronously send peerapi req to connector to // allocate these addresses for us. } @@ -483,8 +480,12 @@ func (c *client) mapDNSResponse(buf []byte) []byte { switch h.Type { case dnsmessage.TypeA: - domain := strings.ToLower(h.Name.String()) - if len(domain) == 0 || !c.isConnectorDomain(domain) { + domain, err := dnsname.ToFQDN(h.Name.String()) + if err != nil { + c.logf("bad dnsname: %v", err) + return buf + } + if !c.isConnectorDomain(domain) { if err := p.SkipAnswer(); err != nil { c.logf("error parsing dns response: %v", err) return buf @@ -540,9 +541,44 @@ type addrs struct { dst netip.Addr magic netip.Addr transit netip.Addr + domain dnsname.FQDN app string } func (c addrs) isValid() bool { return c.dst.IsValid() } + +// domainDst is a key for looking up an existing address assignment by the +// DNS response domain and destination IP pair. +type domainDst struct { + domain dnsname.FQDN + dst netip.Addr +} + +// addrAssignments is the collection of addrs assigned by this client +// supporting lookup by magicip or domain+dst +type addrAssignments struct { + byMagicIP map[netip.Addr]addrs + byDomainDst map[domainDst]addrs +} + +func (a *addrAssignments) insert(as addrs) error { + // we likely will want to allow overwriting in the future when we + // have address expiry, but for now this should not happen + if _, ok := a.byMagicIP[as.magic]; ok { + return errors.New("byMagicIP key exists") + } + ddst := domainDst{domain: as.domain, dst: as.dst} + if _, ok := a.byDomainDst[ddst]; ok { + return errors.New("byDomainDst key exists") + } + mak.Set(&a.byMagicIP, as.magic, as) + mak.Set(&a.byDomainDst, ddst, as) + return nil +} + +func (a *addrAssignments) lookupByDomainDst(domain dnsname.FQDN, dst netip.Addr) (addrs, bool) { + v, ok := a.byDomainDst[domainDst{domain: domain, dst: dst}] + return v, ok +} diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go index 0489b22a14e4d..d63e84e024738 100644 --- a/feature/conn25/conn25_test.go +++ b/feature/conn25/conn25_test.go @@ -16,6 +16,8 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/appctype" "tailscale.com/types/logger" + "tailscale.com/util/dnsname" + "tailscale.com/util/must" "tailscale.com/util/set" ) @@ -206,34 +208,16 @@ func TestTransitIPTargetUnknownTIP(t *testing.T) { } } -func TestSetMagicIP(t *testing.T) { - c := newConn25(logger.Discard) - mip := netip.MustParseAddr("0.0.0.1") - tip := netip.MustParseAddr("0.0.0.2") - app := "a" - c.client.setMagicIP(mip, tip, app) - val, ok := c.client.magicIPs[mip] - if !ok { - t.Fatal("expected there to be a value stored for the magic IP") - } - if val.addr != tip { - t.Fatalf("want %v, got %v", tip, val.addr) - } - if val.app != app { - t.Fatalf("want %s, got %s", app, val.app) - } -} - func TestReserveIPs(t *testing.T) { c := newConn25(logger.Discard) c.client.magicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24")) c.client.transitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24")) - mbd := map[string][]string{} + mbd := map[dnsname.FQDN][]string{} mbd["example.com."] = []string{"a"} c.client.config.appsByDomain = mbd dst := netip.MustParseAddr("0.0.0.1") - con, err := c.client.reserveAddresses("example.com.", dst) + addrs, err := c.client.reserveAddresses("example.com.", dst) if err != nil { t.Fatal(err) } @@ -242,18 +226,22 @@ func TestReserveIPs(t *testing.T) { wantMagic := netip.MustParseAddr("100.64.0.0") // first from magic pool wantTransit := netip.MustParseAddr("169.254.0.0") // first from transit pool wantApp := "a" // the app name related to example.com. + wantDomain := must.Get(dnsname.ToFQDN("example.com.")) - if wantDst != con.dst { - t.Errorf("want %v, got %v", wantDst, con.dst) + if wantDst != addrs.dst { + t.Errorf("want %v, got %v", wantDst, addrs.dst) + } + if wantMagic != addrs.magic { + t.Errorf("want %v, got %v", wantMagic, addrs.magic) } - if wantMagic != con.magic { - t.Errorf("want %v, got %v", wantMagic, con.magic) + if wantTransit != addrs.transit { + t.Errorf("want %v, got %v", wantTransit, addrs.transit) } - if wantTransit != con.transit { - t.Errorf("want %v, got %v", wantTransit, con.transit) + if wantApp != addrs.app { + t.Errorf("want %s, got %s", wantApp, addrs.app) } - if wantApp != con.app { - t.Errorf("want %s, got %s", wantApp, con.app) + if wantDomain != addrs.domain { + t.Errorf("want %s, got %s", wantDomain, addrs.domain) } } @@ -287,8 +275,8 @@ func TestConfigReconfig(t *testing.T) { cfg []appctype.Conn25Attr tags []string wantErr bool - wantAppsByDomain map[string][]string - wantSelfRoutedDomains set.Set[string] + wantAppsByDomain map[dnsname.FQDN][]string + wantSelfRoutedDomains set.Set[dnsname.FQDN] }{ { name: "bad-config", @@ -302,11 +290,11 @@ func TestConfigReconfig(t *testing.T) { {Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}}, }, tags: []string{"tag:one"}, - wantAppsByDomain: map[string][]string{ + wantAppsByDomain: map[dnsname.FQDN][]string{ "a.example.com.": {"one"}, "b.example.com.": {"two"}, }, - wantSelfRoutedDomains: set.SetOf([]string{"a.example.com."}), + wantSelfRoutedDomains: set.SetOf([]dnsname.FQDN{"a.example.com."}), }, { name: "more-complex", @@ -317,7 +305,7 @@ func TestConfigReconfig(t *testing.T) { {Name: "four", Domains: []string{"4.b.example.com", "4.d.example.com"}, Connectors: []string{"tag:four"}}, }, tags: []string{"tag:onea", "tag:four", "tag:unrelated"}, - wantAppsByDomain: map[string][]string{ + wantAppsByDomain: map[dnsname.FQDN][]string{ "1.a.example.com.": {"one"}, "1.b.example.com.": {"one", "three"}, "1.c.example.com.": {"three"}, @@ -326,7 +314,7 @@ func TestConfigReconfig(t *testing.T) { "4.b.example.com.": {"four"}, "4.d.example.com.": {"four"}, }, - wantSelfRoutedDomains: set.SetOf([]string{"1.a.example.com.", "1.b.example.com.", "4.b.example.com.", "4.d.example.com."}), + wantSelfRoutedDomains: set.SetOf([]dnsname.FQDN{"1.a.example.com.", "1.b.example.com.", "4.b.example.com.", "4.d.example.com."}), }, } { t.Run(tt.name, func(t *testing.T) { @@ -431,18 +419,24 @@ func TestMapDNSResponse(t *testing.T) { } for _, tt := range []struct { - name string - domain string - addrs []dnsmessage.AResource - wantMagicIPs map[netip.Addr]appAddr + name string + domain string + addrs []dnsmessage.AResource + wantByMagicIP map[netip.Addr]addrs }{ { name: "one-ip-matches", domain: "example.com.", addrs: []dnsmessage.AResource{{A: [4]byte{1, 0, 0, 0}}}, // these are 'expected' because they are the beginning of the provided pools - wantMagicIPs: map[netip.Addr]appAddr{ - netip.MustParseAddr("100.64.0.0"): {app: "app1", addr: netip.MustParseAddr("100.64.0.40")}, + wantByMagicIP: map[netip.Addr]addrs{ + netip.MustParseAddr("100.64.0.0"): { + domain: "example.com.", + dst: netip.MustParseAddr("1.0.0.0"), + magic: netip.MustParseAddr("100.64.0.0"), + transit: netip.MustParseAddr("100.64.0.40"), + app: "app1", + }, }, }, { @@ -452,9 +446,21 @@ func TestMapDNSResponse(t *testing.T) { {A: [4]byte{1, 0, 0, 0}}, {A: [4]byte{2, 0, 0, 0}}, }, - wantMagicIPs: map[netip.Addr]appAddr{ - netip.MustParseAddr("100.64.0.0"): {app: "app1", addr: netip.MustParseAddr("100.64.0.40")}, - netip.MustParseAddr("100.64.0.1"): {app: "app1", addr: netip.MustParseAddr("100.64.0.41")}, + wantByMagicIP: map[netip.Addr]addrs{ + netip.MustParseAddr("100.64.0.0"): { + domain: "example.com.", + dst: netip.MustParseAddr("1.0.0.0"), + magic: netip.MustParseAddr("100.64.0.0"), + transit: netip.MustParseAddr("100.64.0.40"), + app: "app1", + }, + netip.MustParseAddr("100.64.0.1"): { + domain: "example.com.", + dst: netip.MustParseAddr("2.0.0.0"), + magic: netip.MustParseAddr("100.64.0.1"), + transit: netip.MustParseAddr("100.64.0.41"), + app: "app1", + }, }, }, { @@ -482,9 +488,37 @@ func TestMapDNSResponse(t *testing.T) { if !reflect.DeepEqual(dnsResp, bs) { t.Fatal("shouldn't be changing the bytes (yet)") } - if diff := cmp.Diff(tt.wantMagicIPs, c.client.magicIPs, cmpopts.EquateComparable(appAddr{}, netip.Addr{})); diff != "" { - t.Errorf("magicIPs diff (-want, +got):\n%s", diff) + if diff := cmp.Diff(tt.wantByMagicIP, c.client.assignments.byMagicIP, cmpopts.EquateComparable(addrs{}, netip.Addr{})); diff != "" { + t.Errorf("byMagicIP diff (-want, +got):\n%s", diff) } }) } } + +func TestReserveAddressesDeduplicated(t *testing.T) { + c := newConn25(logger.Discard) + c.client.magicIPPool = newIPPool(mustIPSetFromPrefix("100.64.0.0/24")) + c.client.transitIPPool = newIPPool(mustIPSetFromPrefix("169.254.0.0/24")) + c.client.config.appsByDomain = map[dnsname.FQDN][]string{"example.com.": {"a"}} + + dst := netip.MustParseAddr("0.0.0.1") + first, err := c.client.reserveAddresses("example.com.", dst) + if err != nil { + t.Fatal(err) + } + + second, err := c.client.reserveAddresses("example.com.", dst) + if err != nil { + t.Fatal(err) + } + + if first != second { + t.Errorf("expected same addrs on repeated call, got first=%v second=%v", first, second) + } + if got := len(c.client.assignments.byMagicIP); got != 1 { + t.Errorf("want 1 entry in byMagicIP, got %d", got) + } + if got := len(c.client.assignments.byDomainDst); got != 1 { + t.Errorf("want 1 entry in byDomainDst, got %d", got) + } +} From d42b3743b72c8fd7df77945f99bf6aaec617f33d Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Mar 2026 03:31:13 +0000 Subject: [PATCH 172/202] net/porttrack: add net.Listen wrapper to help tests allocate ports race-free Updates tailscale/corp#27805 Updates tailscale/corp#27806 Updates tailscale/corp#37964 Change-Id: I7bb5ed7f258e840a8208e5d725c7b2f126d7ef96 Signed-off-by: Brad Fitzpatrick --- net/porttrack/porttrack.go | 176 ++++++++++++++++++++++++++++++++ net/porttrack/porttrack_test.go | 95 +++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 net/porttrack/porttrack.go create mode 100644 net/porttrack/porttrack_test.go diff --git a/net/porttrack/porttrack.go b/net/porttrack/porttrack.go new file mode 100644 index 0000000000000..822e7200e19e7 --- /dev/null +++ b/net/porttrack/porttrack.go @@ -0,0 +1,176 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +// Package porttrack provides race-free ephemeral port assignment for +// subprocess tests. The parent test process creates a [Collector] that +// listens on a TCP port; the child process uses [Listen] which, when +// given a magic address, binds to localhost:0 and reports the actual +// port back to the collector. +// +// The magic address format is: +// +// testport-report:HOST:PORT/LABEL +// +// where HOST:PORT is the collector's TCP address and LABEL identifies +// which listener this is (e.g. "main", "plaintext"). +// +// When [Listen] is called with a non-magic address, it falls through to +// [net.Listen] with zero overhead beyond a single [strings.HasPrefix] +// check. +package porttrack + +import ( + "bufio" + "context" + "fmt" + "net" + "strconv" + "strings" + "sync" + + "tailscale.com/util/testenv" +) + +const magicPrefix = "testport-report:" + +// Collector is the parent/test side of the porttrack protocol. It +// listens for port reports from child processes that used [Listen] +// with a magic address obtained from [Collector.Addr]. +type Collector struct { + ln net.Listener + mu sync.Mutex + cond *sync.Cond + ports map[string]int + err error // non-nil if a context passed to Port was cancelled +} + +// NewCollector creates a new Collector. The collector's TCP listener is +// closed when t finishes. +func NewCollector(t testenv.TB) *Collector { + t.Helper() + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("porttrack.NewCollector: %v", err) + } + c := &Collector{ + ln: ln, + ports: make(map[string]int), + } + c.cond = sync.NewCond(&c.mu) + go c.accept(t) + t.Cleanup(func() { ln.Close() }) + return c +} + +// accept runs in a goroutine, accepting connections and parsing port +// reports until the listener is closed. +func (c *Collector) accept(t testenv.TB) { + for { + conn, err := c.ln.Accept() + if err != nil { + return // listener closed + } + go c.handleConn(t, conn) + } +} + +func (c *Collector) handleConn(t testenv.TB, conn net.Conn) { + defer conn.Close() + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + line := scanner.Text() + label, portStr, ok := strings.Cut(line, "\t") + if !ok { + t.Errorf("porttrack: malformed report line: %q", line) + return + } + port, err := strconv.Atoi(portStr) + if err != nil { + t.Errorf("porttrack: bad port in report %q: %v", line, err) + return + } + c.mu.Lock() + c.ports[label] = port + c.cond.Broadcast() + c.mu.Unlock() + } +} + +// Addr returns a magic address string that, when passed to [Listen], +// causes the child to bind to localhost:0 and report its actual port +// back to this collector under the given label. +func (c *Collector) Addr(label string) string { + return magicPrefix + c.ln.Addr().String() + "/" + label +} + +// Port blocks until the child process has reported the port for the +// given label, then returns it. If ctx is cancelled before a port is +// reported, Port returns the context's cause as an error. +func (c *Collector) Port(ctx context.Context, label string) (int, error) { + stop := context.AfterFunc(ctx, func() { + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.err = context.Cause(ctx) + } + c.cond.Broadcast() + }) + defer stop() + + c.mu.Lock() + defer c.mu.Unlock() + for { + if p, ok := c.ports[label]; ok { + return p, nil + } + if c.err != nil { + return 0, c.err + } + c.cond.Wait() + } +} + +// Listen is the child/production side of the porttrack protocol. +// +// If address has the magic prefix (as returned by [Collector.Addr]), +// Listen binds to localhost:0 on the given network, then TCP-connects +// to the collector and writes "LABEL\tPORT\n" to report the actual +// port. The collector connection is closed before returning. +// +// If address does not have the magic prefix, Listen is simply +// [net.Listen](network, address). +func Listen(network, address string) (net.Listener, error) { + rest, ok := strings.CutPrefix(address, magicPrefix) + if !ok { + return net.Listen(network, address) + } + + // rest is "HOST:PORT/LABEL" + slashIdx := strings.LastIndex(rest, "/") + if slashIdx < 0 { + return nil, fmt.Errorf("porttrack: malformed magic address %q: missing /LABEL", address) + } + collectorAddr := rest[:slashIdx] + label := rest[slashIdx+1:] + + ln, err := net.Listen(network, "localhost:0") + if err != nil { + return nil, err + } + + port := ln.Addr().(*net.TCPAddr).Port + + conn, err := net.Dial("tcp", collectorAddr) + if err != nil { + ln.Close() + return nil, fmt.Errorf("porttrack: failed to connect to collector at %s: %v", collectorAddr, err) + } + _, err = fmt.Fprintf(conn, "%s\t%d\n", label, port) + conn.Close() + if err != nil { + ln.Close() + return nil, fmt.Errorf("porttrack: failed to report port to collector: %v", err) + } + + return ln, nil +} diff --git a/net/porttrack/porttrack_test.go b/net/porttrack/porttrack_test.go new file mode 100644 index 0000000000000..06412d87554fc --- /dev/null +++ b/net/porttrack/porttrack_test.go @@ -0,0 +1,95 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package porttrack + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "testing" +) + +func TestCollectorAndListen(t *testing.T) { + c := NewCollector(t) + + labels := []string{"main", "plaintext", "debug"} + ports := make([]int, len(labels)) + + for i, label := range labels { + ln, err := Listen("tcp", c.Addr(label)) + if err != nil { + t.Fatalf("Listen(%q): %v", label, err) + } + defer ln.Close() + p, err := c.Port(t.Context(), label) + if err != nil { + t.Fatalf("Port(%q): %v", label, err) + } + ports[i] = p + } + + // All ports should be distinct non-zero values. + seen := map[int]string{} + for i, label := range labels { + if ports[i] == 0 { + t.Errorf("Port(%q) = 0", label) + } + if prev, ok := seen[ports[i]]; ok { + t.Errorf("Port(%q) = Port(%q) = %d", label, prev, ports[i]) + } + seen[ports[i]] = label + } +} + +func TestListenPassthrough(t *testing.T) { + ln, err := Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("Listen passthrough: %v", err) + } + defer ln.Close() + if ln.Addr().(*net.TCPAddr).Port == 0 { + t.Fatal("expected non-zero port") + } +} + +func TestRoundTrip(t *testing.T) { + c := NewCollector(t) + + ln, err := Listen("tcp", c.Addr("http")) + if err != nil { + t.Fatalf("Listen: %v", err) + } + defer ln.Close() + + // Start a server on the listener. + go http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + + port, err := c.Port(t.Context(), "http") + if err != nil { + t.Fatalf("Port: %v", err) + } + resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port)) + if err != nil { + t.Fatalf("http.Get: %v", err) + } + resp.Body.Close() + if resp.StatusCode != http.StatusNoContent { + t.Fatalf("status = %d, want %d", resp.StatusCode, http.StatusNoContent) + } +} + +func TestPortContextCancelled(t *testing.T) { + c := NewCollector(t) + // Nobody will ever report "never", so Port should block until ctx is done. + ctx, cancel := context.WithCancel(t.Context()) + cancel() + _, err := c.Port(ctx, "never") + if !errors.Is(err, context.Canceled) { + t.Fatalf("Port with cancelled context: got %v, want %v", err, context.Canceled) + } +} From dab8922fcfeec7f5d944ea10ad3816c3ae1e51dd Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Wed, 4 Mar 2026 10:59:43 -0800 Subject: [PATCH 173/202] go.mod: bump github.com/cloudflare/circl version (#18878) Pick up a fix in https://pkg.go.dev/vuln/GO-2026-4550 Updates #cleanup Signed-off-by: Andrew Lytvynov --- flake.nix | 2 +- go.mod | 2 +- go.mod.sri | 2 +- go.sum | 4 ++-- shell.nix | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 0dbf74e7884aa..64956a97fef55 100644 --- a/flake.nix +++ b/flake.nix @@ -151,4 +151,4 @@ }); }; } -# nix-direnv cache busting line: sha256-Lr+5B0LEFk66WahPczRcfzH8rSL5Cc2qvNJuW6B0Llc= +# nix-direnv cache busting line: sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4= diff --git a/go.mod b/go.mod index caa58b60833bc..202ad894bdaff 100644 --- a/go.mod +++ b/go.mod @@ -313,7 +313,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.12.3 // indirect diff --git a/go.mod.sri b/go.mod.sri index 91887e63b3c8c..a307075942f64 100644 --- a/go.mod.sri +++ b/go.mod.sri @@ -1 +1 @@ -sha256-Lr+5B0LEFk66WahPczRcfzH8rSL5Cc2qvNJuW6B0Llc= +sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4= diff --git a/go.sum b/go.sum index 1f8195e47fff6..b61f1d24a1db1 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/ckaznocha/intrange v0.1.0 h1:ZiGBhvrdsKpoEfzh9CjBfDSZof6QB0ORY5tXasUtiew= github.com/ckaznocha/intrange v0.1.0/go.mod h1:Vwa9Ekex2BrEQMg6zlrWwbs/FtYw7eS5838Q7UjK7TQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= diff --git a/shell.nix b/shell.nix index a822b705a3062..7ddf62c52df5c 100644 --- a/shell.nix +++ b/shell.nix @@ -16,4 +16,4 @@ ) { src = ./.; }).shellNix -# nix-direnv cache busting line: sha256-Lr+5B0LEFk66WahPczRcfzH8rSL5Cc2qvNJuW6B0Llc= +# nix-direnv cache busting line: sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4= From 26ef46bf8196f5ab36e94aeeda458dcf65868fcf Mon Sep 17 00:00:00 2001 From: Mike O'Driscoll Date: Wed, 4 Mar 2026 14:09:11 -0500 Subject: [PATCH 174/202] util/linuxfw,wgengine/router: add connmark rules for rp_filter workaround (#18860) When a Linux system acts as an exit node or subnet router with strict reverse path filtering (rp_filter=1), reply packets may be dropped because they fail the RPF check. Reply packets arrive on the WAN interface but the routing table indicates they should have arrived on the Tailscale interface, causing the kernel to drop them. This adds firewall rules in the mangle table to save outbound packet marks to conntrack and restore them on reply packets before the routing decision. When reply packets have their marks restored, the kernel uses the correct routing table (based on the mark) and the packets pass the rp_filter check. Implementation adds two rules per address family (IPv4/IPv6): - mangle/OUTPUT: Save packet marks to conntrack for NEW connections with non-zero marks in the Tailscale fwmark range (0xff0000) - mangle/PREROUTING: Restore marks from conntrack to packets for ESTABLISHED,RELATED connections before routing decision and rp_filter check The workaround is automatically enabled when UseConnmarkForRPFilter is set in the router configuration, which happens when subnet routes are advertised on Linux systems. Both iptables and nftables implementations are provided, with automatic backend detection. Fixes #3310 Fixes #14409 Fixes #12022 Fixes #15815 Fixes #9612 Signed-off-by: Mike O'Driscoll --- util/linuxfw/fake_netfilter.go | 2 + util/linuxfw/iptables_runner.go | 98 +++++++ util/linuxfw/nftables_runner.go | 245 ++++++++++++++++++ util/linuxfw/nftables_runner_test.go | 243 +++++++++++++++++ wgengine/router/osrouter/router_linux.go | 36 +++ wgengine/router/osrouter/router_linux_test.go | 202 ++++++++++++++- 6 files changed, 814 insertions(+), 12 deletions(-) diff --git a/util/linuxfw/fake_netfilter.go b/util/linuxfw/fake_netfilter.go index d760edfcf757e..eac5d904cff3a 100644 --- a/util/linuxfw/fake_netfilter.go +++ b/util/linuxfw/fake_netfilter.go @@ -71,6 +71,8 @@ func (f *FakeNetfilterRunner) AddHooks() error { retur func (f *FakeNetfilterRunner) DelHooks(logf logger.Logf) error { return nil } func (f *FakeNetfilterRunner) AddSNATRule() error { return nil } func (f *FakeNetfilterRunner) DelSNATRule() error { return nil } +func (f *FakeNetfilterRunner) AddConnmarkSaveRule() error { return nil } +func (f *FakeNetfilterRunner) DelConnmarkSaveRule() error { return nil } func (f *FakeNetfilterRunner) AddStatefulRule(tunname string) error { return nil } func (f *FakeNetfilterRunner) DelStatefulRule(tunname string) error { return nil } func (f *FakeNetfilterRunner) AddLoopbackRule(addr netip.Addr) error { return nil } diff --git a/util/linuxfw/iptables_runner.go b/util/linuxfw/iptables_runner.go index ed55960b36d7c..b8eb39f219be9 100644 --- a/util/linuxfw/iptables_runner.go +++ b/util/linuxfw/iptables_runner.go @@ -527,6 +527,104 @@ func (i *iptablesRunner) DelStatefulRule(tunname string) error { return nil } +// AddConnmarkSaveRule adds conntrack marking rules to save and restore marks. +// These rules run in mangle/PREROUTING (to restore marks from conntrack) and +// mangle/OUTPUT (to save marks to conntrack) before rp_filter checks, enabling +// proper routing table lookups for exit nodes and subnet routers. +func (i *iptablesRunner) AddConnmarkSaveRule() error { + // Check if rules already exist (idempotency) + for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + rules, err := ipt.List("mangle", "PREROUTING") + if err != nil { + continue + } + // Look for existing connmark restore rule + for _, rule := range rules { + if strings.Contains(rule, "CONNMARK") && + strings.Contains(rule, "restore-mark") && + strings.Contains(rule, "ctmask 0xff0000") { + // Rules already exist, skip adding + return nil + } + } + } + + // mangle/PREROUTING: Restore mark from conntrack for ESTABLISHED/RELATED connections + // This runs BEFORE routing decision and rp_filter check + for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + args := []string{ + "-m", "conntrack", + "--ctstate", "ESTABLISHED,RELATED", + "-j", "CONNMARK", + "--restore-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Insert("mangle", "PREROUTING", 1, args...); err != nil { + return fmt.Errorf("adding %v in mangle/PREROUTING: %w", args, err) + } + } + + // mangle/OUTPUT: Save mark to conntrack for NEW connections with non-zero marks + for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + args := []string{ + "-m", "conntrack", + "--ctstate", "NEW", + "-m", "mark", + "!", "--mark", "0x0/" + fwmarkMask, + "-j", "CONNMARK", + "--save-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Insert("mangle", "OUTPUT", 1, args...); err != nil { + return fmt.Errorf("adding %v in mangle/OUTPUT: %w", args, err) + } + } + + return nil +} + +// DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. +func (i *iptablesRunner) DelConnmarkSaveRule() error { + for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + // Delete PREROUTING rule + args := []string{ + "-m", "conntrack", + "--ctstate", "ESTABLISHED,RELATED", + "-j", "CONNMARK", + "--restore-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Delete("mangle", "PREROUTING", args...); err != nil { + if !isNotExistError(err) { + return fmt.Errorf("deleting connmark rule in mangle/PREROUTING: %w", err) + } + // Rule doesn't exist - this is fine for idempotency + } + + // Delete OUTPUT rule + args = []string{ + "-m", "conntrack", + "--ctstate", "NEW", + "-m", "mark", + "!", "--mark", "0x0/" + fwmarkMask, + "-j", "CONNMARK", + "--save-mark", + "--nfmask", fwmarkMask, + "--ctmask", fwmarkMask, + } + if err := ipt.Delete("mangle", "OUTPUT", args...); err != nil { + if !isNotExistError(err) { + return fmt.Errorf("deleting connmark rule in mangle/OUTPUT: %w", err) + } + // Rule doesn't exist - this is fine for idempotency + } + } + return nil +} + // buildMagicsockPortRule generates the string slice containing the arguments // to describe a rule accepting traffic on a particular port to iptables. It is // separated out here to avoid repetition in AddMagicsockPortRule and diff --git a/util/linuxfw/nftables_runner.go b/util/linuxfw/nftables_runner.go index 2c44a6218e76e..7496e7034a98a 100644 --- a/util/linuxfw/nftables_runner.go +++ b/util/linuxfw/nftables_runner.go @@ -521,6 +521,15 @@ type NetfilterRunner interface { // using conntrack. DelStatefulRule(tunname string) error + // AddConnmarkSaveRule adds conntrack marking rules to save marks from packets. + // These rules run in mangle/PREROUTING and mangle/OUTPUT to mark connections + // and restore marks on reply packets before rp_filter checks, enabling proper + // routing table lookups for exit nodes and subnet routers. + AddConnmarkSaveRule() error + + // DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. + DelConnmarkSaveRule() error + // HasIPV6 reports true if the system supports IPv6. HasIPV6() bool @@ -1950,6 +1959,242 @@ func (n *nftablesRunner) DelStatefulRule(tunname string) error { return nil } +// makeConnmarkRestoreExprs creates nftables expressions to restore mark from conntrack. +// Implements: ct state established,related ct mark & 0xff0000 != 0 meta mark set ct mark & 0xff0000 +func makeConnmarkRestoreExprs() []expr.Any { + return []expr.Any{ + // Load conntrack state into register 1 + &expr.Ct{ + Register: 1, + Key: expr.CtKeySTATE, + }, + // Check if state is ESTABLISHED or RELATED + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: nativeUint32( + expr.CtStateBitESTABLISHED | + expr.CtStateBitRELATED), + Xor: nativeUint32(0), + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0, 0, 0, 0}, + }, + // Load conntrack mark into register 1 + &expr.Ct{ + Register: 1, + Key: expr.CtKeyMARK, + }, + // Mask to Tailscale mark bits (0xff0000) + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: getTailscaleFwmarkMask(), + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // Set packet mark from register 1 + &expr.Meta{ + Key: expr.MetaKeyMARK, + SourceRegister: true, + Register: 1, + }, + } +} + +// makeConnmarkSaveExprs creates nftables expressions to save mark to conntrack. +// Implements: ct state new meta mark & 0xff0000 != 0 ct mark set meta mark & 0xff0000 +func makeConnmarkSaveExprs() []expr.Any { + return []expr.Any{ + // Load conntrack state into register 1 + &expr.Ct{ + Register: 1, + Key: expr.CtKeySTATE, + }, + // Check if state is NEW + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: nativeUint32(expr.CtStateBitNEW), + Xor: nativeUint32(0), + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0, 0, 0, 0}, + }, + // Load packet mark into register 1 + &expr.Meta{ + Key: expr.MetaKeyMARK, + Register: 1, + }, + // Mask to Tailscale mark bits (0xff0000) + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: getTailscaleFwmarkMask(), + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // Check if mark is non-zero + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0, 0, 0, 0}, + }, + // Load packet mark again for saving + &expr.Meta{ + Key: expr.MetaKeyMARK, + Register: 1, + }, + // Mask again + &expr.Bitwise{ + SourceRegister: 1, + DestRegister: 1, + Len: 4, + Mask: getTailscaleFwmarkMask(), + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // Set conntrack mark from register 1 + &expr.Ct{ + Key: expr.CtKeyMARK, + SourceRegister: true, + Register: 1, + }, + } +} + +// AddConnmarkSaveRule adds conntrack marking rules to save and restore marks. +// These rules run in mangle/PREROUTING (to restore marks from conntrack) and +// mangle/OUTPUT (to save marks to conntrack) before rp_filter checks, enabling +// proper routing table lookups for exit nodes and subnet routers. +func (n *nftablesRunner) AddConnmarkSaveRule() error { + conn := n.conn + + // Check if rules already exist (idempotency) + for _, table := range n.getTables() { + mangleTable := &nftables.Table{ + Family: table.Proto, + Name: "mangle", + } + + // Check PREROUTING chain for restore rule + preroutingChain, err := getChainFromTable(conn, mangleTable, "PREROUTING") + if err == nil { + rules, _ := conn.GetRules(preroutingChain.Table, preroutingChain) + for _, rule := range rules { + if string(rule.UserData) == "ts-connmark-restore" { + // Rules already exist, skip adding + return nil + } + } + } + } + + // Add rules for both IPv4 and IPv6 + for _, table := range n.getTables() { + // Get or create mangle table + mangleTable := &nftables.Table{ + Family: table.Proto, + Name: "mangle", + } + conn.AddTable(mangleTable) + + // Get or create PREROUTING chain + preroutingChain, err := getChainFromTable(conn, mangleTable, "PREROUTING") + if err != nil { + // Chain doesn't exist, create it + preroutingChain = conn.AddChain(&nftables.Chain{ + Name: "PREROUTING", + Table: mangleTable, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityMangle, + }) + } + + // Add PREROUTING rule to restore mark from conntrack + conn.InsertRule(&nftables.Rule{ + Table: mangleTable, + Chain: preroutingChain, + Exprs: makeConnmarkRestoreExprs(), + UserData: []byte("ts-connmark-restore"), + }) + + // Get or create OUTPUT chain + outputChain, err := getChainFromTable(conn, mangleTable, "OUTPUT") + if err != nil { + // Chain doesn't exist, create it + outputChain = conn.AddChain(&nftables.Chain{ + Name: "OUTPUT", + Table: mangleTable, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookOutput, + Priority: nftables.ChainPriorityMangle, + }) + } + + // Add OUTPUT rule to save mark to conntrack + conn.InsertRule(&nftables.Rule{ + Table: mangleTable, + Chain: outputChain, + Exprs: makeConnmarkSaveExprs(), + UserData: []byte("ts-connmark-save"), + }) + } + + if err := conn.Flush(); err != nil { + return fmt.Errorf("flush add connmark rules: %w", err) + } + + return nil +} + +// DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. +func (n *nftablesRunner) DelConnmarkSaveRule() error { + conn := n.conn + + for _, table := range n.getTables() { + mangleTable := &nftables.Table{ + Family: table.Proto, + Name: "mangle", + } + + // Remove PREROUTING rule - look for restore-mark rule by UserData + preroutingChain, err := getChainFromTable(conn, mangleTable, "PREROUTING") + if err == nil { + rules, _ := conn.GetRules(preroutingChain.Table, preroutingChain) + for _, rule := range rules { + if string(rule.UserData) == "ts-connmark-restore" { + conn.DelRule(rule) + break + } + } + } + + // Remove OUTPUT rule - look for save-mark rule by UserData + outputChain, err := getChainFromTable(conn, mangleTable, "OUTPUT") + if err == nil { + rules, _ := conn.GetRules(outputChain.Table, outputChain) + for _, rule := range rules { + if string(rule.UserData) == "ts-connmark-save" { + conn.DelRule(rule) + break + } + } + } + } + + // Ignore errors during deletion - rules might not exist + conn.Flush() + + return nil +} + // cleanupChain removes a jump rule from hookChainName to tsChainName, and then // the entire chain tsChainName. Errors are logged, but attempts to remove both // the jump rule and chain continue even if one errors. diff --git a/util/linuxfw/nftables_runner_test.go b/util/linuxfw/nftables_runner_test.go index dc4d3194a23ba..8299a9cbd72da 100644 --- a/util/linuxfw/nftables_runner_test.go +++ b/util/linuxfw/nftables_runner_test.go @@ -1070,3 +1070,246 @@ func checkSNATRule_nft(t *testing.T, runner *nftablesRunner, fam nftables.TableF wantsRule := snatRule(chain.Table, chain, src, dst, meta) checkRule(t, wantsRule, runner.conn) } + +// TestNFTAddAndDelConnmarkRules tests adding and removing connmark rules +// in a real network namespace. This verifies the rules are correctly created +// and cleaned up. +func TestNFTAddAndDelConnmarkRules(t *testing.T) { + conn := newSysConn(t) + runner := newFakeNftablesRunnerWithConn(t, conn, true) + + // Helper to get mangle chains + getMangleChains := func(fam nftables.TableFamily) (prerouting, output *nftables.Chain, err error) { + chains, err := conn.ListChainsOfTableFamily(fam) + if err != nil { + return nil, nil, err + } + for _, ch := range chains { + if ch.Table.Name != "mangle" { + continue + } + if ch.Name == "PREROUTING" { + prerouting = ch + } else if ch.Name == "OUTPUT" { + output = ch + } + } + return prerouting, output, nil + } + + // Check initial state - mangle chains might not exist yet + prerouting4Before, output4Before, _ := getMangleChains(nftables.TableFamilyIPv4) + prerouting6Before, output6Before, _ := getMangleChains(nftables.TableFamilyIPv6) + + var prerouting4RulesBefore, output4RulesBefore, prerouting6RulesBefore, output6RulesBefore int + if prerouting4Before != nil { + rules, _ := conn.GetRules(prerouting4Before.Table, prerouting4Before) + prerouting4RulesBefore = len(rules) + } + if output4Before != nil { + rules, _ := conn.GetRules(output4Before.Table, output4Before) + output4RulesBefore = len(rules) + } + if prerouting6Before != nil { + rules, _ := conn.GetRules(prerouting6Before.Table, prerouting6Before) + prerouting6RulesBefore = len(rules) + } + if output6Before != nil { + rules, _ := conn.GetRules(output6Before.Table, output6Before) + output6RulesBefore = len(rules) + } + + // Add connmark rules + if err := runner.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule() failed: %v", err) + } + + // Verify rules were added + prerouting4After, output4After, err := getMangleChains(nftables.TableFamilyIPv4) + if err != nil { + t.Fatalf("Failed to get IPv4 mangle chains: %v", err) + } + if prerouting4After == nil || output4After == nil { + t.Fatal("IPv4 mangle chains not created") + } + + prerouting4Rules, err := conn.GetRules(prerouting4After.Table, prerouting4After) + if err != nil { + t.Fatalf("GetRules(PREROUTING) failed: %v", err) + } + output4Rules, err := conn.GetRules(output4After.Table, output4After) + if err != nil { + t.Fatalf("GetRules(OUTPUT) failed: %v", err) + } + + // Should have added 1 rule to each chain + if len(prerouting4Rules) != prerouting4RulesBefore+1 { + t.Fatalf("PREROUTING rules: got %d, want %d", len(prerouting4Rules), prerouting4RulesBefore+1) + } + if len(output4Rules) != output4RulesBefore+1 { + t.Fatalf("OUTPUT rules: got %d, want %d", len(output4Rules), output4RulesBefore+1) + } + + // Verify IPv6 rules + prerouting6After, output6After, err := getMangleChains(nftables.TableFamilyIPv6) + if err != nil { + t.Fatalf("Failed to get IPv6 mangle chains: %v", err) + } + if prerouting6After == nil || output6After == nil { + t.Fatal("IPv6 mangle chains not created") + } + + prerouting6Rules, err := conn.GetRules(prerouting6After.Table, prerouting6After) + if err != nil { + t.Fatalf("GetRules(IPv6 PREROUTING) failed: %v", err) + } + output6Rules, err := conn.GetRules(output6After.Table, output6After) + if err != nil { + t.Fatalf("GetRules(IPv6 OUTPUT) failed: %v", err) + } + + if len(prerouting6Rules) != prerouting6RulesBefore+1 { + t.Fatalf("IPv6 PREROUTING rules: got %d, want %d", len(prerouting6Rules), prerouting6RulesBefore+1) + } + if len(output6Rules) != output6RulesBefore+1 { + t.Fatalf("IPv6 OUTPUT rules: got %d, want %d", len(output6Rules), output6RulesBefore+1) + } + + // Verify the rules contain conntrack expressions + foundCtInPrerouting := false + foundCtInOutput := false + for _, e := range prerouting4Rules[0].Exprs { + if _, ok := e.(*expr.Ct); ok { + foundCtInPrerouting = true + break + } + } + for _, e := range output4Rules[0].Exprs { + if _, ok := e.(*expr.Ct); ok { + foundCtInOutput = true + break + } + } + if !foundCtInPrerouting { + t.Error("PREROUTING rule doesn't contain conntrack expression") + } + if !foundCtInOutput { + t.Error("OUTPUT rule doesn't contain conntrack expression") + } + + // Delete connmark rules + if err := runner.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule() failed: %v", err) + } + + // Verify rules were deleted + prerouting4After, output4After, _ = getMangleChains(nftables.TableFamilyIPv4) + if prerouting4After != nil { + rules, _ := conn.GetRules(prerouting4After.Table, prerouting4After) + if len(rules) != prerouting4RulesBefore { + t.Fatalf("IPv4 PREROUTING rules after delete: got %d, want %d", len(rules), prerouting4RulesBefore) + } + } + if output4After != nil { + rules, _ := conn.GetRules(output4After.Table, output4After) + if len(rules) != output4RulesBefore { + t.Fatalf("IPv4 OUTPUT rules after delete: got %d, want %d", len(rules), output4RulesBefore) + } + } + + prerouting6After, output6After, _ = getMangleChains(nftables.TableFamilyIPv6) + if prerouting6After != nil { + rules, _ := conn.GetRules(prerouting6After.Table, prerouting6After) + if len(rules) != prerouting6RulesBefore { + t.Fatalf("IPv6 PREROUTING rules after delete: got %d, want %d", len(rules), prerouting6RulesBefore) + } + } + if output6After != nil { + rules, _ := conn.GetRules(output6After.Table, output6After) + if len(rules) != output6RulesBefore { + t.Fatalf("IPv6 OUTPUT rules after delete: got %d, want %d", len(rules), output6RulesBefore) + } + } +} + +// TestMakeConnmarkRestoreExprs tests the nftables expressions for restoring +// marks from conntrack. This is a regression test that ensures the byte encoding +// doesn't change unexpectedly. +func TestMakeConnmarkRestoreExprs(t *testing.T) { + // Expected netlink bytes for the restore rule + // Generated by running makeConnmarkRestoreExprs() and capturing the output + want := [][]byte{ + // batch begin + []byte("\x00\x00\x00\x0a"), + // nft add table ip mangle + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), + // nft add chain ip mangle PREROUTING { type filter hook prerouting priority mangle; } + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x03\x00\x50\x52\x45\x52\x4f\x55\x54\x49\x4e\x47\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x00\x08\x00\x02\x00\xff\xff\xff\x6a\x0b\x00\x07\x00\x66\x69\x6c\x74\x65\x72\x00\x00"), + // nft add rule ip mangle PREROUTING ct state established,related ct mark & 0xff0000 != 0 meta mark set ct mark & 0xff0000 + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0f\x00\x02\x00\x50\x52\x45\x52\x4f\x55\x54\x49\x4e\x47\x00\x00\x1c\x01\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x06\x00\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x03\x00\x00\x00\x00\x01"), + // batch end + []byte("\x00\x00\x00\x0a"), + } + + testConn := newTestConn(t, want, nil) + table := testConn.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "mangle", + }) + chain := testConn.AddChain(&nftables.Chain{ + Name: "PREROUTING", + Table: table, + Type: nftables.ChainTypeFilter, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityMangle, + }) + testConn.InsertRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: makeConnmarkRestoreExprs(), + }) + if err := testConn.Flush(); err != nil { + t.Fatalf("Flush() failed: %v", err) + } +} + +// TestMakeConnmarkSaveExprs tests the nftables expressions for saving marks +// to conntrack. This is a regression test that ensures the byte encoding +// doesn't change unexpectedly. +func TestMakeConnmarkSaveExprs(t *testing.T) { + // Expected netlink bytes for the save rule + // Generated by running makeConnmarkSaveExprs() and capturing the output + want := [][]byte{ + // batch begin + []byte("\x00\x00\x00\x0a"), + // nft add table ip mangle + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x08\x00\x02\x00\x00\x00\x00\x00"), + // nft add chain ip mangle OUTPUT { type route hook output priority mangle; } + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0b\x00\x03\x00\x4f\x55\x54\x50\x55\x54\x00\x00\x14\x00\x04\x80\x08\x00\x01\x00\x00\x00\x00\x03\x08\x00\x02\x00\xff\xff\xff\x6a\x0a\x00\x07\x00\x72\x6f\x75\x74\x65\x00\x00\x00"), + // nft add rule ip mangle OUTPUT ct state new meta mark & 0xff0000 != 0 ct mark set meta mark & 0xff0000 + []byte("\x02\x00\x00\x00\x0b\x00\x01\x00\x6d\x61\x6e\x67\x6c\x65\x00\x00\x0b\x00\x02\x00\x4f\x55\x54\x50\x55\x54\x00\x00\xb0\x01\x04\x80\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x08\x00\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x2c\x00\x01\x80\x08\x00\x01\x00\x63\x6d\x70\x00\x20\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x0c\x00\x03\x80\x08\x00\x01\x00\x00\x00\x00\x00\x24\x00\x01\x80\x09\x00\x01\x00\x6d\x65\x74\x61\x00\x00\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x01\x00\x00\x00\x00\x01\x44\x00\x01\x80\x0c\x00\x01\x00\x62\x69\x74\x77\x69\x73\x65\x00\x34\x00\x02\x80\x08\x00\x01\x00\x00\x00\x00\x01\x08\x00\x02\x00\x00\x00\x00\x01\x08\x00\x03\x00\x00\x00\x00\x04\x0c\x00\x04\x80\x08\x00\x01\x00\x00\xff\x00\x00\x0c\x00\x05\x80\x08\x00\x01\x00\x00\x00\x00\x00\x20\x00\x01\x80\x07\x00\x01\x00\x63\x74\x00\x00\x14\x00\x02\x80\x08\x00\x02\x00\x00\x00\x00\x03\x08\x00\x04\x00\x00\x00\x00\x01"), + // batch end + []byte("\x00\x00\x00\x0a"), + } + + testConn := newTestConn(t, want, nil) + table := testConn.AddTable(&nftables.Table{ + Family: nftables.TableFamilyIPv4, + Name: "mangle", + }) + chain := testConn.AddChain(&nftables.Chain{ + Name: "OUTPUT", + Table: table, + Type: nftables.ChainTypeRoute, + Hooknum: nftables.ChainHookOutput, + Priority: nftables.ChainPriorityMangle, + }) + testConn.InsertRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: makeConnmarkSaveExprs(), + }) + if err := testConn.Flush(); err != nil { + t.Fatalf("Flush() failed: %v", err) + } +} diff --git a/wgengine/router/osrouter/router_linux.go b/wgengine/router/osrouter/router_linux.go index 8ca38f9ecd15d..3c261c9120785 100644 --- a/wgengine/router/osrouter/router_linux.go +++ b/wgengine/router/osrouter/router_linux.go @@ -86,6 +86,7 @@ type linuxRouter struct { localRoutes map[netip.Prefix]bool snatSubnetRoutes bool statefulFiltering bool + connmarkEnabled bool // whether connmark rules are currently enabled netfilterMode preftype.NetfilterMode netfilterKind string magicsockPortV4 uint16 @@ -370,6 +371,12 @@ func (r *linuxRouter) Close() error { r.unregNetMon() } r.eventClient.Close() + + // Clean up connmark rules + if err := r.nfr.DelConnmarkSaveRule(); err != nil { + r.logf("warning: failed to delete connmark rules: %v", err) + } + if err := r.downInterface(); err != nil { return err } @@ -479,6 +486,35 @@ func (r *linuxRouter) Set(cfg *router.Config) error { r.statefulFiltering = cfg.StatefulFiltering r.updateStatefulFilteringWithDockerWarning(cfg) + // Connmark rules for rp_filter compatibility. + // Always enabled when netfilter is ON to handle all rp_filter=1 scenarios + // (normal operation, exit nodes, subnet routers, and clients using exit nodes). + netfilterOn := cfg.NetfilterMode == netfilterOn + switch { + case netfilterOn == r.connmarkEnabled: + // state already correct, nothing to do. + case netfilterOn: + r.logf("enabling connmark-based rp_filter workaround") + if err := r.nfr.AddConnmarkSaveRule(); err != nil { + r.logf("warning: failed to add connmark rules (rp_filter workaround may not work): %v", err) + errs = append(errs, fmt.Errorf("enabling connmark rules: %w", err)) + } else { + // Only update state on success to keep it in sync with actual rules + r.connmarkEnabled = true + } + default: + r.logf("disabling connmark-based rp_filter workaround") + if err := r.nfr.DelConnmarkSaveRule(); err != nil { + // Deletion errors are only logged, not returned, because: + // 1. Rules may not exist (e.g., first run or after manual deletion) + // 2. Failure to delete is less critical than failure to add + // 3. We still want to update state to attempt re-add on next enable + r.logf("warning: failed to delete connmark rules: %v", err) + } + // Always clear state when disabling, even if delete failed + r.connmarkEnabled = false + } + // Issue 11405: enable IP forwarding on gokrazy. advertisingRoutes := len(cfg.SubnetRoutes) > 0 if getDistroFunc() == distro.Gokrazy && advertisingRoutes { diff --git a/wgengine/router/osrouter/router_linux_test.go b/wgengine/router/osrouter/router_linux_test.go index bce0ea09275e3..bae997e331d55 100644 --- a/wgengine/router/osrouter/router_linux_test.go +++ b/wgengine/router/osrouter/router_linux_test.go @@ -124,6 +124,8 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE v6/filter/FORWARD -j ts-forward @@ -132,6 +134,8 @@ v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE `, @@ -160,6 +164,8 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE v6/filter/FORWARD -j ts-forward @@ -167,6 +173,8 @@ v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE `, @@ -192,12 +200,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -225,12 +237,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -255,12 +271,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -310,12 +330,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -342,12 +366,16 @@ v4/filter/ts-forward -o tailscale0 -j ACCEPT v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v4/nat/POSTROUTING -j ts-postrouting v6/filter/FORWARD -j ts-forward v6/filter/INPUT -j ts-input v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 v6/nat/POSTROUTING -j ts-postrouting `, }, @@ -367,6 +395,120 @@ ip route add 100.100.100.100/32 dev tailscale0 table 52 ip route add throw 10.0.0.0/8 table 52 ip route add throw 192.168.0.0/24 table 52` + basic, }, + { + name: "subnet routes with connmark for rp_filter", + in: &Config{ + LocalAddrs: mustCIDRs("100.101.102.104/10"), + Routes: mustCIDRs("100.100.100.100/32"), + SubnetRoutes: mustCIDRs("10.0.0.0/16"), + SNATSubnetRoutes: true, + NetfilterMode: netfilterOn, + }, + want: ` +up +ip addr add 100.101.102.104/10 dev tailscale0 +ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic + + `v4/filter/FORWARD -j ts-forward +v4/filter/INPUT -j ts-input +v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v4/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP +v4/filter/ts-forward -o tailscale0 -j ACCEPT +v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT +v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN +v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/nat/POSTROUTING -j ts-postrouting +v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +v6/filter/FORWARD -j ts-forward +v6/filter/INPUT -j ts-input +v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/nat/POSTROUTING -j ts-postrouting +v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +`, + }, + { + name: "subnet routes (connmark always enabled)", + in: &Config{ + LocalAddrs: mustCIDRs("100.101.102.104/10"), + Routes: mustCIDRs("100.100.100.100/32"), + SubnetRoutes: mustCIDRs("10.0.0.0/16"), + SNATSubnetRoutes: true, + NetfilterMode: netfilterOn, + }, + want: ` +up +ip addr add 100.101.102.104/10 dev tailscale0 +ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic + + `v4/filter/FORWARD -j ts-forward +v4/filter/INPUT -j ts-input +v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v4/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP +v4/filter/ts-forward -o tailscale0 -j ACCEPT +v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT +v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN +v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/nat/POSTROUTING -j ts-postrouting +v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +v6/filter/FORWARD -j ts-forward +v6/filter/INPUT -j ts-input +v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/nat/POSTROUTING -j ts-postrouting +v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +`, + }, + { + name: "connmark with stateful filtering", + in: &Config{ + LocalAddrs: mustCIDRs("100.101.102.104/10"), + Routes: mustCIDRs("100.100.100.100/32"), + SubnetRoutes: mustCIDRs("10.0.0.0/16"), + SNATSubnetRoutes: true, + StatefulFiltering: true, + NetfilterMode: netfilterOn, + }, + want: ` +up +ip addr add 100.101.102.104/10 dev tailscale0 +ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic + + `v4/filter/FORWARD -j ts-forward +v4/filter/INPUT -j ts-input +v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v4/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP +v4/filter/ts-forward -o tailscale0 -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP +v4/filter/ts-forward -o tailscale0 -j ACCEPT +v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT +v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN +v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP +v4/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v4/nat/POSTROUTING -j ts-postrouting +v4/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +v6/filter/FORWARD -j ts-forward +v6/filter/INPUT -j ts-input +v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000/0xff0000 +v6/filter/ts-forward -m mark --mark 0x40000/0xff0000 -j ACCEPT +v6/filter/ts-forward -o tailscale0 -m conntrack ! --ctstate ESTABLISHED,RELATED -j DROP +v6/filter/ts-forward -o tailscale0 -j ACCEPT +v6/mangle/OUTPUT -m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/mangle/PREROUTING -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000 +v6/nat/POSTROUTING -j ts-postrouting +v6/nat/ts-postrouting -m mark --mark 0x40000/0xff0000 -j MASQUERADE +`, + }, } bus := eventbus.New() @@ -426,20 +568,24 @@ func newIPTablesRunner(t *testing.T) linuxfw.NetfilterRunner { return &fakeIPTablesRunner{ t: t, ipt4: map[string][]string{ - "filter/INPUT": nil, - "filter/OUTPUT": nil, - "filter/FORWARD": nil, - "nat/PREROUTING": nil, - "nat/OUTPUT": nil, - "nat/POSTROUTING": nil, + "filter/INPUT": nil, + "filter/OUTPUT": nil, + "filter/FORWARD": nil, + "nat/PREROUTING": nil, + "nat/OUTPUT": nil, + "nat/POSTROUTING": nil, + "mangle/PREROUTING": nil, + "mangle/OUTPUT": nil, }, ipt6: map[string][]string{ - "filter/INPUT": nil, - "filter/OUTPUT": nil, - "filter/FORWARD": nil, - "nat/PREROUTING": nil, - "nat/OUTPUT": nil, - "nat/POSTROUTING": nil, + "filter/INPUT": nil, + "filter/OUTPUT": nil, + "filter/FORWARD": nil, + "nat/PREROUTING": nil, + "nat/OUTPUT": nil, + "nat/POSTROUTING": nil, + "mangle/PREROUTING": nil, + "mangle/OUTPUT": nil, }, } } @@ -775,6 +921,38 @@ func (n *fakeIPTablesRunner) DelMagicsockPortRule(port uint16, network string) e return nil } +func (n *fakeIPTablesRunner) AddConnmarkSaveRule() error { + // PREROUTING rule: restore mark from conntrack + prerouteRule := "-m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + if err := insertRule(n, ipt, "mangle/PREROUTING", prerouteRule); err != nil { + return err + } + } + + // OUTPUT rule: save mark to conntrack for NEW connections + outputRule := "-m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + if err := insertRule(n, ipt, "mangle/OUTPUT", outputRule); err != nil { + return err + } + } + return nil +} + +func (n *fakeIPTablesRunner) DelConnmarkSaveRule() error { + prerouteRule := "-m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + deleteRule(n, ipt, "mangle/PREROUTING", prerouteRule) // ignore errors + } + + outputRule := "-m conntrack --ctstate NEW -m mark ! --mark 0x0/0xff0000 -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000" + for _, ipt := range []map[string][]string{n.ipt4, n.ipt6} { + deleteRule(n, ipt, "mangle/OUTPUT", outputRule) // ignore errors + } + return nil +} + func (n *fakeIPTablesRunner) HasIPV6() bool { return true } func (n *fakeIPTablesRunner) HasIPV6NAT() bool { return true } func (n *fakeIPTablesRunner) HasIPV6Filter() bool { return true } From 2c9ffdd188bd53ce43c8389f42594b2a8be6c390 Mon Sep 17 00:00:00 2001 From: Mike O'Driscoll Date: Wed, 4 Mar 2026 14:09:19 -0500 Subject: [PATCH 175/202] cmd/tailscale,ipn,net/netutil: remove rp_filter strict mode warnings (#18863) PR #18860 adds firewall rules in the mangle table to save outbound packet marks to conntrack and restore them on reply packets before the routing decision. When reply packets have their marks restored, the kernel uses the correct routing table (based on the mark) and the packets pass the rp_filter check. This makes the risk check and reverse path filtering warnings unnecessary. Updates #3310 Fixes tailscale/corp#37846 Signed-off-by: Mike O'Driscoll --- client/local/local.go | 19 ------ cmd/k8s-operator/depaware.txt | 2 +- cmd/tailscale/cli/risks.go | 19 ------ cmd/tailscale/cli/set.go | 4 +- cmd/tailscale/cli/up.go | 4 -- cmd/tailscaled/depaware-min.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- cmd/tsidp/depaware.txt | 2 +- health/healthmsg/healthmsg.go | 2 - ipn/ipnlocal/local.go | 30 --------- ipn/localapi/localapi.go | 30 --------- net/netutil/ip_forward.go | 104 -------------------------------- net/netutil/netutil_test.go | 21 ------- tsnet/depaware.txt | 2 +- 14 files changed, 6 insertions(+), 237 deletions(-) diff --git a/client/local/local.go b/client/local/local.go index 5794734f27133..a7b8b83b10a77 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -818,25 +818,6 @@ func (lc *Client) CheckUDPGROForwarding(ctx context.Context) error { return nil } -// CheckReversePathFiltering asks the local Tailscale daemon whether strict -// reverse path filtering is enabled, which would break exit node usage on Linux. -func (lc *Client) CheckReversePathFiltering(ctx context.Context) error { - body, err := lc.get200(ctx, "/localapi/v0/check-reverse-path-filtering") - if err != nil { - return err - } - var jres struct { - Warning string - } - if err := json.Unmarshal(body, &jres); err != nil { - return fmt.Errorf("invalid JSON from check-reverse-path-filtering: %w", err) - } - if jres.Warning != "" { - return errors.New(jres.Warning) - } - return nil -} - // SetUDPGROForwarding enables UDP GRO forwarding for the main interface of this // node. This can be done to improve performance of tailnet nodes acting as exit // nodes or subnet routers. diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index d801c0285ca62..c0cf0fd7cd35e 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -813,7 +813,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ tailscale.com/feature/syspolicy from tailscale.com/logpolicy tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/internal/client/tailscale from tailscale.com/cmd/k8s-operator+ tailscale.com/ipn from tailscale.com/client/local+ diff --git a/cmd/tailscale/cli/risks.go b/cmd/tailscale/cli/risks.go index 058eff1f8501a..1bd128d566125 100644 --- a/cmd/tailscale/cli/risks.go +++ b/cmd/tailscale/cli/risks.go @@ -4,13 +4,10 @@ package cli import ( - "context" "errors" "flag" - "runtime" "strings" - "tailscale.com/ipn" "tailscale.com/util/prompt" "tailscale.com/util/testenv" ) @@ -19,7 +16,6 @@ var ( riskTypes []string riskLoseSSH = registerRiskType("lose-ssh") riskMacAppConnector = registerRiskType("mac-app-connector") - riskStrictRPFilter = registerRiskType("linux-strict-rp-filter") riskAll = registerRiskType("all") ) @@ -72,18 +68,3 @@ func presentRiskToUser(riskType, riskMessage, acceptedRisks string) error { return errAborted } - -// checkExitNodeRisk checks if the user is using an exit node on Linux and -// whether reverse path filtering is enabled. If so, it presents a risk message. -func checkExitNodeRisk(ctx context.Context, prefs *ipn.Prefs, acceptedRisks string) error { - if runtime.GOOS != "linux" { - return nil - } - if !prefs.ExitNodeIP.IsValid() && prefs.ExitNodeID == "" { - return nil - } - if err := localClient.CheckReversePathFiltering(ctx); err != nil { - return presentRiskToUser(riskStrictRPFilter, err.Error(), acceptedRisks) - } - return nil -} diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go index 615900833596c..22d78641f38a9 100644 --- a/cmd/tailscale/cli/set.go +++ b/cmd/tailscale/cli/set.go @@ -193,9 +193,7 @@ func runSet(ctx context.Context, args []string) (retErr error) { } warnOnAdvertiseRoutes(ctx, &maskedPrefs.Prefs) - if err := checkExitNodeRisk(ctx, &maskedPrefs.Prefs, setArgs.acceptedRisks); err != nil { - return err - } + var advertiseExitNodeSet, advertiseRoutesSet bool setFlagSet.Visit(func(f *flag.Flag) { updateMaskedPrefsFromUpOrSetFlag(maskedPrefs, f.Name) diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index d78cb2d44bfb2..79cc60ca2347f 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -543,9 +543,6 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE } warnOnAdvertiseRoutes(ctx, prefs) - if err := checkExitNodeRisk(ctx, prefs, upArgs.acceptedRisks); err != nil { - return err - } curPrefs, err := localClient.GetPrefs(ctx) if err != nil { @@ -834,7 +831,6 @@ func upWorthyWarning(s string) bool { return strings.Contains(s, healthmsg.TailscaleSSHOnBut) || strings.Contains(s, healthmsg.WarnAcceptRoutesOff) || strings.Contains(s, healthmsg.LockedOut) || - strings.Contains(s, healthmsg.WarnExitNodeUsage) || strings.Contains(s, healthmsg.InMemoryTailnetLockState) || strings.Contains(strings.ToLower(s), "update available: ") } diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index f97e0368c0d12..fc39a980b7741 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -64,7 +64,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/condregister/portmapper from tailscale.com/feature/condregister tailscale.com/feature/condregister/useproxy from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/cmd/tailscaled+ tailscale.com/ipn from tailscale.com/cmd/tailscaled+ tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 48a7d09495c5f..efd1ea1090ed8 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -309,7 +309,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/ipn from tailscale.com/client/local+ W tailscale.com/ipn/auditlog from tailscale.com/cmd/tailscaled diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index 03f7e1f09b21d..5016e568aa334 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -232,7 +232,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar tailscale.com/feature/syspolicy from tailscale.com/logpolicy tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/internal/client/tailscale from tailscale.com/tsnet+ tailscale.com/ipn from tailscale.com/client/local+ diff --git a/health/healthmsg/healthmsg.go b/health/healthmsg/healthmsg.go index 3de885d53a61a..c6efb0d574db4 100644 --- a/health/healthmsg/healthmsg.go +++ b/health/healthmsg/healthmsg.go @@ -11,7 +11,5 @@ const ( WarnAcceptRoutesOff = "Some peers are advertising routes but --accept-routes is false" TailscaleSSHOnBut = "Tailscale SSH enabled, but " // + ... something from caller LockedOut = "this node is locked out; it will not have connectivity until it is signed. For more info, see https://tailscale.com/s/locked-out" - WarnExitNodeUsage = "The following issues on your machine will likely make usage of exit nodes impossible" - DisableRPFilter = "Please set rp_filter=2 instead of rp_filter=1; see https://github.com/tailscale/tailscale/issues/3310" InMemoryTailnetLockState = "Tailnet Lock state is only being stored in-memory. Set --statedir to store state on disk, which is more secure. See https://tailscale.com/kb/1226/tailnet-lock#tailnet-lock-state" ) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index bae1e66393a4b..9cb86642fa1f0 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1031,7 +1031,6 @@ func (b *LocalBackend) linkChange(delta *netmon.ChangeDelta) { // If the local network configuration has changed, our filter may // need updating to tweak default routes. b.updateFilterLocked(prefs) - updateExitNodeUsageWarning(prefs, delta.CurrentState(), b.health) if buildfeatures.HasPeerAPIServer { cn := b.currentNode() @@ -4213,35 +4212,6 @@ func (b *LocalBackend) isDefaultServerLocked() bool { return prefs.ControlURLOrDefault(b.polc) == ipn.DefaultControlURL } -var exitNodeMisconfigurationWarnable = health.Register(&health.Warnable{ - Code: "exit-node-misconfiguration", - Title: "Exit node misconfiguration", - Severity: health.SeverityMedium, - Text: func(args health.Args) string { - return "Exit node misconfiguration: " + args[health.ArgError] - }, -}) - -// updateExitNodeUsageWarning updates a warnable meant to notify users of -// configuration issues that could break exit node usage. -func updateExitNodeUsageWarning(p ipn.PrefsView, state *netmon.State, healthTracker *health.Tracker) { - if !buildfeatures.HasUseExitNode { - return - } - var msg string - if p.ExitNodeIP().IsValid() || p.ExitNodeID() != "" { - warn, _ := netutil.CheckReversePathFiltering(state) - if len(warn) > 0 { - msg = fmt.Sprintf("%s: %v, %s", healthmsg.WarnExitNodeUsage, warn, healthmsg.DisableRPFilter) - } - } - if len(msg) > 0 { - healthTracker.SetUnhealthy(exitNodeMisconfigurationWarnable, health.Args{health.ArgError: msg}) - } else { - healthTracker.SetHealthy(exitNodeMisconfigurationWarnable) - } -} - func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error { tryingToUseExitNode := p.ExitNodeIP.IsValid() || p.ExitNodeID != "" if !tryingToUseExitNode { diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index dc558b36e61d9..ed25e875da409 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -28,7 +28,6 @@ import ( "tailscale.com/envknob" "tailscale.com/feature" "tailscale.com/feature/buildfeatures" - "tailscale.com/health/healthmsg" "tailscale.com/hostinfo" "tailscale.com/ipn" "tailscale.com/ipn/ipnauth" @@ -100,9 +99,6 @@ func init() { Register("check-udp-gro-forwarding", (*Handler).serveCheckUDPGROForwarding) Register("set-udp-gro-forwarding", (*Handler).serveSetUDPGROForwarding) } - if buildfeatures.HasUseExitNode && runtime.GOOS == "linux" { - Register("check-reverse-path-filtering", (*Handler).serveCheckReversePathFiltering) - } if buildfeatures.HasClientMetrics { Register("upload-client-metrics", (*Handler).serveUploadClientMetrics) } @@ -780,32 +776,6 @@ func (h *Handler) serveCheckSOMarkInUse(w http.ResponseWriter, r *http.Request) }) } -func (h *Handler) serveCheckReversePathFiltering(w http.ResponseWriter, r *http.Request) { - if !h.PermitRead { - http.Error(w, "reverse path filtering check access denied", http.StatusForbidden) - return - } - var warning string - - state := h.b.Sys().NetMon.Get().InterfaceState() - warn, err := netutil.CheckReversePathFiltering(state) - if err == nil && len(warn) > 0 { - var msg strings.Builder - msg.WriteString(healthmsg.WarnExitNodeUsage + ":\n") - for _, w := range warn { - msg.WriteString("- " + w + "\n") - } - msg.WriteString(healthmsg.DisableRPFilter) - warning = msg.String() - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(struct { - Warning string - }{ - Warning: warning, - }) -} - func (h *Handler) serveCheckUDPGROForwarding(w http.ResponseWriter, r *http.Request) { if !h.PermitRead { http.Error(w, "UDP GRO forwarding check access denied", http.StatusForbidden) diff --git a/net/netutil/ip_forward.go b/net/netutil/ip_forward.go index 0711953f52e68..bc0f1961dfcbc 100644 --- a/net/netutil/ip_forward.go +++ b/net/netutil/ip_forward.go @@ -5,7 +5,6 @@ package netutil import ( "bytes" - "errors" "fmt" "net/netip" "os" @@ -146,64 +145,6 @@ func CheckIPForwarding(routes []netip.Prefix, state *netmon.State) (warn, err er return nil, nil } -// CheckReversePathFiltering reports whether reverse path filtering is either -// disabled or set to 'loose' mode for exit node functionality on any -// interface. -// -// The routes should only be advertised routes, and should not contain the -// node's Tailscale IPs. -// -// This function returns an error if it is unable to determine whether reverse -// path filtering is enabled, or a warning describing configuration issues if -// reverse path fitering is non-functional or partly functional. -func CheckReversePathFiltering(state *netmon.State) (warn []string, err error) { - if runtime.GOOS != "linux" { - return nil, nil - } - - if state == nil { - return nil, errors.New("no link state") - } - - // The kernel uses the maximum value for rp_filter between the 'all' - // setting and each per-interface config, so we need to fetch both. - allSetting, err := reversePathFilterValueLinux("all") - if err != nil { - return nil, fmt.Errorf("reading global rp_filter value: %w", err) - } - - const ( - filtOff = 0 - filtStrict = 1 - filtLoose = 2 - ) - - // Because the kernel use the max rp_filter value, each interface will use 'loose', so we - // can abort early. - if allSetting == filtLoose { - return nil, nil - } - - for _, iface := range state.Interface { - if iface.IsLoopback() { - continue - } - - iSetting, err := reversePathFilterValueLinux(iface.Name) - if err != nil { - return nil, fmt.Errorf("reading interface rp_filter value for %q: %w", iface.Name, err) - } - // Perform the same max() that the kernel does - if allSetting > iSetting { - iSetting = allSetting - } - if iSetting == filtStrict { - warn = append(warn, fmt.Sprintf("interface %q has strict reverse-path filtering enabled", iface.Name)) - } - } - return warn, nil -} - // ipForwardSysctlKey returns the sysctl key for the given protocol and iface. // When the dotFormat parameter is true the output is formatted as `net.ipv4.ip_forward`, // else it is `net/ipv4/ip_forward` @@ -235,25 +176,6 @@ func ipForwardSysctlKey(format sysctlFormat, p protocol, iface string) string { return fmt.Sprintf(k, iface) } -// rpFilterSysctlKey returns the sysctl key for the given iface. -// -// Format controls whether the output is formatted as -// `net.ipv4.conf.iface.rp_filter` or `net/ipv4/conf/iface/rp_filter`. -func rpFilterSysctlKey(format sysctlFormat, iface string) string { - // No iface means all interfaces - if iface == "" { - iface = "all" - } - - k := "net/ipv4/conf/%s/rp_filter" - if format == dotFormat { - // Swap the delimiters. - iface = strings.ReplaceAll(iface, ".", "/") - k = strings.ReplaceAll(k, "/", ".") - } - return fmt.Sprintf(k, iface) -} - type sysctlFormat int const ( @@ -305,32 +227,6 @@ func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) { return on, nil } -// reversePathFilterValueLinux reports the reverse path filter setting on Linux -// for the given interface. -// -// The iface param determines which interface to check against; the empty -// string means to check the global config. -// -// This function tries to look up the value directly from `/proc/sys`, and -// falls back to using the `sysctl` command on failure. -func reversePathFilterValueLinux(iface string) (int, error) { - k := rpFilterSysctlKey(slashFormat, iface) - bs, err := os.ReadFile(filepath.Join("/proc/sys", k)) - if err != nil { - // Fall back to the sysctl command - k := rpFilterSysctlKey(dotFormat, iface) - bs, err = exec.Command("sysctl", "-n", k).Output() - if err != nil { - return -1, fmt.Errorf("couldn't check %s (%v)", k, err) - } - } - v, err := strconv.Atoi(string(bytes.TrimSpace(bs))) - if err != nil { - return -1, fmt.Errorf("couldn't parse %s (%v)", k, err) - } - return v, nil -} - func ipForwardingEnabledSunOS(p protocol, iface string) (bool, error) { var proto string if p == ipv4 { diff --git a/net/netutil/netutil_test.go b/net/netutil/netutil_test.go index a512238d5f5ee..2c40d8d9ee68e 100644 --- a/net/netutil/netutil_test.go +++ b/net/netutil/netutil_test.go @@ -8,9 +8,6 @@ import ( "net" "runtime" "testing" - - "tailscale.com/net/netmon" - "tailscale.com/util/eventbus" ) type conn struct { @@ -68,21 +65,3 @@ func TestIPForwardingEnabledLinux(t *testing.T) { t.Errorf("got true; want false") } } - -func TestCheckReversePathFiltering(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skipf("skipping on %s", runtime.GOOS) - } - bus := eventbus.New() - defer bus.Close() - - netMon, err := netmon.New(bus, t.Logf) - if err != nil { - t.Fatal(err) - } - defer netMon.Close() - - warn, err := CheckReversePathFiltering(netMon.InterfaceState()) - t.Logf("err: %v", err) - t.Logf("warnings: %v", warn) -} diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index 8c81aa4d70d5d..b61545d2487ac 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -228,7 +228,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) tailscale.com/feature/syspolicy from tailscale.com/logpolicy tailscale.com/feature/useproxy from tailscale.com/feature/condregister/useproxy tailscale.com/health from tailscale.com/control/controlclient+ - tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ + tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/internal/client/tailscale from tailscale.com/tsnet+ tailscale.com/ipn from tailscale.com/client/local+ From 30adf4527b9e55175a3391d04d6d4fbe751791d8 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Mar 2026 09:32:14 -0800 Subject: [PATCH 176/202] feature/portlist: address case where poller misses CollectServices updates This is a minimal hacky fix for a case where the portlist poller extension could miss updates to NetMap's CollectServices bool. Updates tailscale/corp#36813 Change-Id: I9b50de8ba8b09e4a44f9fbfe90c9df4d8ab4d586 Signed-off-by: Brad Fitzpatrick --- feature/portlist/portlist.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/feature/portlist/portlist.go b/feature/portlist/portlist.go index b651c64cb6afa..4d2908962bca8 100644 --- a/feature/portlist/portlist.go +++ b/feature/portlist/portlist.go @@ -122,6 +122,19 @@ func (e *Extension) runPollLoop() { return } + // Before we do potentially expensive work below (polling what might be + // a ton of ports), double check that we actually need to do it. + // TODO(bradfitz): the onSelfChange, and onChangeProfile hooks above are + // not enough, because CollectServices is a NetMap-level thing and not a + // change to the local self node. We should add an eventbus topic for + // when CollectServices changes probably, or move the CollectServices + // thing into a local node self cap (at least logically, if not on the + // wire) so then the onSelfChange hook would cover it. In the meantime, + // we'll just end up doing some extra checks every PollInterval, which + // is not the end of the world, and fixes the problem of picking up + // changes to CollectServices that come down in the netmap. + e.updateShouldUploadServices() + if !e.shouldUploadServicesAtomic.Load() { continue } From ea1f1616b9099f8ffae43ae2a461f59b90ae8be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20Lensb=C3=B8l?= Date: Thu, 26 Feb 2026 10:15:58 -0800 Subject: [PATCH 177/202] .github/workflows: enable natlab in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After fixing the flakey tests in #18811 and #18814 we can enable running the natlab testsuite running on CI generally. Fixes #18810 Signed-off-by: Claus Lensbøl --- .github/workflows/natlab-integrationtest.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/natlab-integrationtest.yml b/.github/workflows/natlab-integrationtest.yml index c3821db17f22f..162153cb23293 100644 --- a/.github/workflows/natlab-integrationtest.yml +++ b/.github/workflows/natlab-integrationtest.yml @@ -7,9 +7,15 @@ concurrency: cancel-in-progress: true on: + push: + branches: + - "main" + - "release-branch/*" pull_request: - paths: - - "tstest/integration/nat/nat_test.go" + # all PRs on all branches + merge_group: + branches: + - "main" jobs: natlab-integrationtest: runs-on: ubuntu-latest From 26951a1cbbabdcc09782007b3dce38ed82dfe585 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Wed, 4 Mar 2026 15:13:30 -0800 Subject: [PATCH 178/202] ipn/ipnlocal: skip writing netmaps to disk when disabled (#18883) We use the TS_USE_CACHED_NETMAP knob to condition loading a cached netmap, but were hitherto writing the map out to disk even when it was disabled. Let's not do that; the two should travel together. Updates #12639 Change-Id: Iee5aa828e2c59937d5b95093ea1ac26c9536721e Signed-off-by: M. J. Fromberger --- ipn/ipnlocal/local.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 9cb86642fa1f0..ec16f6a80aff6 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -6241,8 +6241,10 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { var login string if nm != nil { login = cmp.Or(profileFromView(nm.UserProfiles[nm.User()]).LoginName, "") - if err := b.writeNetmapToDiskLocked(nm); err != nil { - b.logf("write netmap to cache: %v", err) + if envknob.Bool("TS_USE_CACHED_NETMAP") { + if err := b.writeNetmapToDiskLocked(nm); err != nil { + b.logf("write netmap to cache: %v", err) + } } } b.currentNode().SetNetMap(nm) From d58bfb8a1b519afffa6796d16f49b9de7c4fef8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pa=C3=B1eda?= Date: Wed, 4 Mar 2026 17:51:01 +0100 Subject: [PATCH 179/202] net/udprelay: use GOMAXPROCS instead of NumCPU for socket count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runtime.NumCPU() returns the number of CPUs on the host, which in containerized environments is the node's CPU count rather than the container's CPU limit. This causes excessive memory allocation in pods with low CPU requests running on large nodes, as each socket's packetReadLoop allocates significant buffer memory. Use runtime.GOMAXPROCS(0) instead, which is container-aware since Go 1.25 and respects CPU limits set via cgroups. Fixes #18774 Signed-off-by: Daniel Pañeda --- net/udprelay/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/udprelay/server.go b/net/udprelay/server.go index 3d870904493ec..03d8e3dc3050d 100644 --- a/net/udprelay/server.go +++ b/net/udprelay/server.go @@ -651,8 +651,9 @@ func trySetSOMark(logf logger.Logf, netMon *netmon.Monitor, network, address str // single packet syscall operations. func (s *Server) bindSockets(desiredPort uint16) error { // maxSocketsPerAF is a conservative starting point, but is somewhat - // arbitrary. - maxSocketsPerAF := min(16, runtime.NumCPU()) + // arbitrary. Use GOMAXPROCS rather than NumCPU as it is container-aware + // and respects CPU limits/quotas set via cgroups. + maxSocketsPerAF := min(16, runtime.GOMAXPROCS(0)) listenConfig := &net.ListenConfig{ Control: func(network, address string, c syscall.RawConn) error { trySetReusePort(network, address, c) From 87bf76de89a81c540103c05009d4be07158ea2a4 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Mar 2026 21:30:02 -0800 Subject: [PATCH 180/202] net/porttrack: change magic listen address format for Go 1.26 Go 1.26's url.Parser is stricter and made our tests elsewhere fail with this scheme because when these listen addresses get shoved into a URL, it can't parse back out. I verified this makes tests elsewhere pass with Go 1.26. Updates #18682 Change-Id: I04dd3cee591aa85a9417a0bbae2b6f699d8302fa Signed-off-by: Brad Fitzpatrick --- net/porttrack/porttrack.go | 42 +++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/net/porttrack/porttrack.go b/net/porttrack/porttrack.go index 822e7200e19e7..f71154f78e631 100644 --- a/net/porttrack/porttrack.go +++ b/net/porttrack/porttrack.go @@ -9,9 +9,9 @@ // // The magic address format is: // -// testport-report:HOST:PORT/LABEL +// testport-report-LABEL:PORT // -// where HOST:PORT is the collector's TCP address and LABEL identifies +// where localhost:PORT is the collector's TCP address and LABEL identifies // which listener this is (e.g. "main", "plaintext"). // // When [Listen] is called with a non-magic address, it falls through to @@ -31,17 +31,18 @@ import ( "tailscale.com/util/testenv" ) -const magicPrefix = "testport-report:" +const magicPrefix = "testport-report-" // Collector is the parent/test side of the porttrack protocol. It // listens for port reports from child processes that used [Listen] // with a magic address obtained from [Collector.Addr]. type Collector struct { - ln net.Listener - mu sync.Mutex - cond *sync.Cond - ports map[string]int - err error // non-nil if a context passed to Port was cancelled + ln net.Listener + lnPort int + mu sync.Mutex + cond *sync.Cond + ports map[string]int + err error // non-nil if a context passed to Port was cancelled } // NewCollector creates a new Collector. The collector's TCP listener is @@ -53,8 +54,9 @@ func NewCollector(t testenv.TB) *Collector { t.Fatalf("porttrack.NewCollector: %v", err) } c := &Collector{ - ln: ln, - ports: make(map[string]int), + ln: ln, + lnPort: ln.Addr().(*net.TCPAddr).Port, + ports: make(map[string]int), } c.cond = sync.NewCond(&c.mu) go c.accept(t) @@ -100,7 +102,14 @@ func (c *Collector) handleConn(t testenv.TB, conn net.Conn) { // causes the child to bind to localhost:0 and report its actual port // back to this collector under the given label. func (c *Collector) Addr(label string) string { - return magicPrefix + c.ln.Addr().String() + "/" + label + for _, c := range label { + switch { + case 'a' <= c && c <= 'z', 'A' <= c && c <= 'Z', '0' <= c && c <= '9', c == '-': + default: + panic(fmt.Sprintf("invalid label %q: only letters, digits, and hyphens are allowed", label)) + } + } + return fmt.Sprintf("%s%s:%d", magicPrefix, label, c.lnPort) } // Port blocks until the child process has reported the port for the @@ -145,13 +154,11 @@ func Listen(network, address string) (net.Listener, error) { return net.Listen(network, address) } - // rest is "HOST:PORT/LABEL" - slashIdx := strings.LastIndex(rest, "/") - if slashIdx < 0 { - return nil, fmt.Errorf("porttrack: malformed magic address %q: missing /LABEL", address) + // rest is LABEL:PORT. + label, collectorPort, ok := strings.Cut(rest, ":") + if !ok { + return nil, fmt.Errorf("porttrack: malformed magic address %q: missing :PORT", address) } - collectorAddr := rest[:slashIdx] - label := rest[slashIdx+1:] ln, err := net.Listen(network, "localhost:0") if err != nil { @@ -160,6 +167,7 @@ func Listen(network, address string) (net.Listener, error) { port := ln.Addr().(*net.TCPAddr).Port + collectorAddr := net.JoinHostPort("localhost", collectorPort) conn, err := net.Dial("tcp", collectorAddr) if err != nil { ln.Close() From d784dcc61bf4e43a610a58feebb2ac693af81f01 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 4 Mar 2026 11:36:08 -0800 Subject: [PATCH 181/202] go.toolchain.branch: switch to Go 1.26 Updates #18682 Change-Id: I1eadfab950e55d004484af880a5d8df6893e85e8 Signed-off-by: Brad Fitzpatrick --- .github/workflows/golangci-lint.yml | 4 +-- Dockerfile | 2 +- cmd/derper/depaware.txt | 38 +++++++++++++++------------ cmd/k8s-operator/depaware.txt | 38 +++++++++++++++------------ cmd/stund/depaware.txt | 40 +++++++++++++++++------------ cmd/tailscale/cli/network-lock.go | 4 +-- cmd/tailscale/cli/serve_v2.go | 2 +- cmd/tailscale/cli/status.go | 10 ++++---- cmd/tailscale/depaware.txt | 36 +++++++++++++++----------- cmd/tailscaled/depaware-min.txt | 39 ++++++++++++++++------------ cmd/tailscaled/depaware-minbox.txt | 39 ++++++++++++++++------------ cmd/tailscaled/depaware.txt | 35 ++++++++++++++----------- cmd/tailscaled/deps_test.go | 3 ++- cmd/tsconnect/common.go | 2 +- cmd/tsidp/depaware.txt | 37 ++++++++++++++------------ flake.nix | 4 +-- go.mod | 2 +- go.toolchain.branch | 2 +- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- go.toolchain.version | 2 +- tsnet/depaware.txt | 37 ++++++++++++++------------ tsnet/tsnet_test.go | 2 +- tstest/nettest/nettest.go | 4 +-- version/mkversion/mkversion.go | 2 +- 25 files changed, 219 insertions(+), 169 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 6431a31d698c0..66b8497e65441 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -35,9 +35,9 @@ jobs: cache: true - name: golangci-lint - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 + uses: golangci/golangci-lint-action@b7bcab6379029e905e3f389a6bf301f1bc220662 # head as of 2026-03-04 with: - version: v2.4.0 + version: v2.10.1 # Show only new issues if it's a pull request. only-new-issues: true diff --git a/Dockerfile b/Dockerfile index 413a4b8211465..ee12922f2d719 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ # $ docker exec tailscaled tailscale status -FROM golang:1.25-alpine AS build-env +FROM golang:1.26-alpine AS build-env WORKDIR /go/src/tailscale diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index d04c66eba118e..a0eb4a29e259c 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 github.com/axiomhq/hyperloglog from tailscale.com/derp/derpserver @@ -203,7 +204,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/cmd/derper+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -226,7 +227,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -234,13 +235,14 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -255,7 +257,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -269,19 +271,21 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from golang.org/x/crypto/acme+ @@ -322,9 +326,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -337,14 +340,17 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -387,7 +393,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa W os/user from tailscale.com/util/winutil path from github.com/prometheus/client_golang/prometheus/internal+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ regexp from github.com/prometheus/client_golang/prometheus/internal+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index c0cf0fd7cd35e..77739350b199c 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -1050,7 +1051,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -1075,7 +1076,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -1084,12 +1085,13 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -1104,7 +1106,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -1118,20 +1120,21 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/prometheus-community/pro-bing+ @@ -1162,6 +1165,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ go/build/constraint from go/parser go/doc from k8s.io/apimachinery/pkg/runtime go/doc/comment from go/doc + go/internal/scannerhooks from go/parser+ go/parser from k8s.io/apimachinery/pkg/runtime go/scanner from go/ast+ go/token from go/ast+ @@ -1185,9 +1189,8 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/lazyregexp from go/doc internal/msan from internal/runtime/maps+ internal/nettrace from net+ @@ -1201,14 +1204,17 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -1255,7 +1261,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from database/sql+ regexp from github.com/davecgh/go-spew/spew+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/stund/depaware.txt b/cmd/stund/depaware.txt index 7b945dd77ea79..d25974b2df424 100644 --- a/cmd/stund/depaware.txt +++ b/cmd/stund/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus 💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus github.com/go-json-experiment/json from tailscale.com/types/opt+ @@ -100,7 +101,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar LD golang.org/x/sys/unix from github.com/prometheus/procfs+ W golang.org/x/sys/windows from github.com/prometheus/client_golang/prometheus vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -118,12 +119,12 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar bufio from compress/flate+ bytes from bufio+ cmp from slices+ - compress/flate from compress/gzip + compress/flate from compress/gzip+ compress/gzip from google.golang.org/protobuf/internal/impl+ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -131,13 +132,14 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -152,7 +154,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -166,19 +168,21 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -218,9 +222,8 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -233,14 +236,17 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -280,7 +286,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar os/signal from tailscale.com/cmd/stund path from github.com/prometheus/client_golang/prometheus/internal+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ regexp from github.com/prometheus/client_golang/prometheus/internal+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/cmd/tailscale/cli/network-lock.go b/cmd/tailscale/cli/network-lock.go index d8cff4aca402d..9ec0e1d7fe819 100644 --- a/cmd/tailscale/cli/network-lock.go +++ b/cmd/tailscale/cli/network-lock.go @@ -224,7 +224,7 @@ func runNetworkLockStatus(ctx context.Context, args []string) error { if nlStatusArgs.json.Value == 1 { return jsonoutput.PrintNetworkLockStatusJSONV1(os.Stdout, st) } else { - return fmt.Errorf("unrecognised version: %q", nlStatusArgs.json.Value) + return fmt.Errorf("unrecognised version: %d", nlStatusArgs.json.Value) } } @@ -717,7 +717,7 @@ func printNetworkLockLog(updates []ipnstate.NetworkLockUpdate, out io.Writer, js if jsonSchema.Value == 1 { return jsonoutput.PrintNetworkLockLogJSONV1(out, updates) } else { - return fmt.Errorf("unrecognised version: %q", jsonSchema.Value) + return fmt.Errorf("unrecognised version: %d", jsonSchema.Value) } } diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 06a4ce1bbde3e..840c47ac66dd1 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -219,7 +219,7 @@ var errHelpFunc = func(m serveMode) error { // newServeV2Command returns a new "serve" subcommand using e as its environment. func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command { if subcmd != serve && subcmd != funnel { - log.Fatalf("newServeDevCommand called with unknown subcmd %q", subcmd) + log.Fatalf("newServeDevCommand called with unknown subcmd %v", subcmd) } info := infoMap[subcmd] diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 49c565febb9cc..9ce4debda8dea 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -176,13 +176,13 @@ func runStatus(ctx context.Context, args []string) error { } if !ps.Active { if ps.ExitNode { - f("idle; exit node" + offline) + f("idle; exit node%s", offline) } else if ps.ExitNodeOption { - f("idle; offers exit node" + offline) + f("idle; offers exit node%s", offline) } else if anyTraffic { - f("idle" + offline) + f("idle%s", offline) } else if !ps.Online { - f("offline" + lastSeenFmt(ps.LastSeen)) + f("offline%s", lastSeenFmt(ps.LastSeen)) } else { f("-") } @@ -201,7 +201,7 @@ func runStatus(ctx context.Context, args []string) error { f("peer-relay %s", ps.PeerRelay) } if !ps.Online { - f(offline) + f("%s", offline) } } if anyTraffic { diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index d83ac2710a897..b4605f9f2e926 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 L fyne.io/systray from tailscale.com/client/systray @@ -352,7 +353,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/cmd/tailscale/cli+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -377,7 +378,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -385,13 +386,14 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -406,7 +408,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -420,19 +422,21 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -482,9 +486,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from archive/tar+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -497,14 +500,17 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index fc39a980b7741..2ad5cbca7b3af 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg github.com/gaissmai/bart from tailscale.com/net/ipset+ github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ @@ -230,7 +231,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/derp vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -248,12 +249,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de bufio from compress/flate+ bytes from bufio+ cmp from encoding/json+ - compress/flate from compress/gzip + compress/flate from compress/gzip+ compress/gzip from net/http container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -261,13 +262,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/fips140+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -282,7 +284,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/hkdf+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/ecdsa+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -296,19 +298,21 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -344,9 +348,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from runtime internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -357,14 +360,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/runtime/atomic from internal/runtime/exithook+ internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime internal/runtime/sys from crypto/subtle+ - internal/runtime/syscall from internal/runtime/cgroup+ + internal/runtime/syscall/linux from internal/runtime/cgroup+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -402,7 +407,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de os/user from tailscale.com/ipn/ipnauth+ path from io/fs+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ slices from crypto/tls+ diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 8dfa00af75a68..9b09604875446 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg github.com/gaissmai/bart from tailscale.com/net/ipset+ github.com/gaissmai/bart/internal/allot from github.com/gaissmai/bart/internal/nodes github.com/gaissmai/bart/internal/art from github.com/gaissmai/bart+ @@ -250,7 +251,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/derp vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -268,12 +269,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de bufio from compress/flate+ bytes from bufio+ cmp from encoding/json+ - compress/flate from compress/gzip + compress/flate from compress/gzip+ compress/gzip from net/http+ container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509 @@ -281,13 +282,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/ecdsa from crypto/tls+ crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ - crypto/fips140 from crypto/tls/internal/fips140tls - crypto/hkdf from crypto/internal/hpke+ + crypto/fips140 from crypto/tls/internal/fips140tls+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/fips140+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -302,7 +304,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/hkdf+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/ecdsa+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -316,19 +318,21 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ + crypto/mlkem from crypto/hpke+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from net/http+ @@ -364,9 +368,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from runtime internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -377,14 +380,16 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/runtime/atomic from internal/runtime/exithook+ internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime internal/runtime/sys from crypto/subtle+ - internal/runtime/syscall from internal/runtime/cgroup+ + internal/runtime/syscall/linux from internal/runtime/cgroup+ internal/saferio from encoding/asn1 internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -423,7 +428,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de os/user from tailscale.com/ipn/ipnauth+ path from io/fs+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from encoding/asn1+ runtime from crypto/internal/fips140+ runtime/debug from github.com/klauspost/compress/zstd+ slices from crypto/tls+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index efd1ea1090ed8..207d86243b607 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -546,7 +547,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -572,7 +573,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -581,12 +582,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -601,7 +603,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -615,20 +617,21 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/aws/aws-sdk-go-v2/aws/transport/http+ @@ -672,9 +675,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from archive/tar+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -687,14 +689,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync diff --git a/cmd/tailscaled/deps_test.go b/cmd/tailscaled/deps_test.go index c7ab01298f223..be4f65a7dd576 100644 --- a/cmd/tailscaled/deps_test.go +++ b/cmd/tailscaled/deps_test.go @@ -265,7 +265,6 @@ func TestMinTailscaledWithCLI(t *testing.T) { badSubstrs := []string{ "cbor", "hujson", - "pprof", "multierr", // https://github.com/tailscale/tailscale/pull/17379 "tailscale.com/metrics", "tailscale.com/tsweb/varz", @@ -287,6 +286,8 @@ func TestMinTailscaledWithCLI(t *testing.T) { BadDeps: map[string]string{ "golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305", "expvar": "unexpected expvar dep", + "runtime/pprof": "unexpected runtime/pprof dep", + "net/http/pprof": "unexpected net/http/pprof dep", "github.com/mdlayher/genetlink": "unexpected genetlink dep", "tailscale.com/clientupdate": "unexpected clientupdate dep", "filippo.io/edwards25519": "unexpected edwards25519 dep", diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go index 9daa402692c04..bc9e1ed4ff532 100644 --- a/cmd/tsconnect/common.go +++ b/cmd/tsconnect/common.go @@ -269,7 +269,7 @@ func runWasmOpt(path string) error { return fmt.Errorf("Cannot stat %v: %w", path, err) } startSize := stat.Size() - cmd := exec.Command("../../tool/wasm-opt", "--enable-bulk-memory", "-Oz", path, "-o", path) + cmd := exec.Command("../../tool/wasm-opt", "--enable-bulk-memory", "--enable-nontrapping-float-to-int", "-Oz", path, "-o", path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index 5016e568aa334..bb991383c8a06 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -448,7 +449,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -473,7 +474,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -482,12 +483,13 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -502,7 +504,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -516,20 +518,21 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/prometheus-community/pro-bing+ @@ -573,9 +576,8 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -588,14 +590,17 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar internal/runtime/atomic from internal/runtime/exithook+ L internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - L internal/runtime/syscall from runtime+ + L internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -639,7 +644,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from database/sql/driver+ regexp from github.com/huin/goupnp/httpu+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/flake.nix b/flake.nix index 64956a97fef55..c9e3b50a1ad73 100644 --- a/flake.nix +++ b/flake.nix @@ -55,7 +55,7 @@ system = system; overlays = [ (final: prev: { - go_1_25 = prev.go_1_25.overrideAttrs { + go_1_26 = prev.go_1_26.overrideAttrs { version = goVersion; src = prev.fetchFromGitHub { owner = "tailscale"; @@ -140,7 +140,7 @@ gotools graphviz perl - go_1_25 + go_1_26 yarn # qemu and e2fsprogs are needed for natlab diff --git a/go.mod b/go.mod index 202ad894bdaff..24c39a4cf3d91 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module tailscale.com -go 1.25.7 +go 1.26.0 require ( filippo.io/mkcert v1.4.4 diff --git a/go.toolchain.branch b/go.toolchain.branch index a2bebbeb7858e..6022b95593bbe 100644 --- a/go.toolchain.branch +++ b/go.toolchain.branch @@ -1 +1 @@ -tailscale.go1.25 +tailscale.go1.26 diff --git a/go.toolchain.rev b/go.toolchain.rev index 05e37f312da5c..ea3d3c773f779 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -692441891e061f8ae2cb2f8f2c898f86bb1c5dca +5b5cb0db47535a0a8d2f450cb1bf83af8e70f164 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index b7a7163f79f68..34a9b157d33d6 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-gWKrpBTXfsQmgOWoMrbvCaWGsBXCt5X12BAcwfAPMQY= +sha256-f12BE5+H8wHZNKaD6pv9nJJym+1QwxkFNpBtnNcltdc= diff --git a/go.toolchain.version b/go.toolchain.version index f1968aa8818d5..5ff8c4f5d2ad2 100644 --- a/go.toolchain.version +++ b/go.toolchain.version @@ -1 +1 @@ -1.25.7 +1.26.0 diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index b61545d2487ac..cb6b6996b7a87 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) + 💣 crypto/internal/entropy/v1.0.0 from crypto/internal/fips140/drbg filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ @@ -441,7 +442,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+ vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305 - vendor/golang.org/x/crypto/chacha20poly1305 from crypto/internal/hpke+ + vendor/golang.org/x/crypto/chacha20poly1305 from crypto/hpke+ vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+ vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ vendor/golang.org/x/crypto/internal/alias from vendor/golang.org/x/crypto/chacha20+ @@ -466,7 +467,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdh+ - crypto/aes from crypto/internal/hpke+ + crypto/aes from crypto/tls+ crypto/cipher from crypto/aes+ crypto/des from crypto/tls+ crypto/dsa from crypto/x509+ @@ -475,12 +476,13 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) crypto/ed25519 from crypto/tls+ crypto/elliptic from crypto/ecdsa+ crypto/fips140 from crypto/tls/internal/fips140tls+ - crypto/hkdf from crypto/internal/hpke+ + crypto/hkdf from crypto/hpke+ crypto/hmac from crypto/tls+ + crypto/hpke from crypto/tls crypto/internal/boring from crypto/aes+ crypto/internal/boring/bbig from crypto/ecdsa+ crypto/internal/boring/sig from crypto/internal/boring - crypto/internal/entropy from crypto/internal/fips140/drbg + crypto/internal/constanttime from crypto/internal/fips140/edwards25519+ crypto/internal/fips140 from crypto/internal/fips140/aes+ crypto/internal/fips140/aes from crypto/aes+ crypto/internal/fips140/aes/gcm from crypto/cipher+ @@ -495,7 +497,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) crypto/internal/fips140/edwards25519/field from crypto/ecdh+ crypto/internal/fips140/hkdf from crypto/internal/fips140/tls13+ crypto/internal/fips140/hmac from crypto/hmac+ - crypto/internal/fips140/mlkem from crypto/tls+ + crypto/internal/fips140/mlkem from crypto/mlkem crypto/internal/fips140/nistec from crypto/elliptic+ crypto/internal/fips140/nistec/fiat from crypto/internal/fips140/nistec crypto/internal/fips140/rsa from crypto/rsa @@ -509,20 +511,21 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) crypto/internal/fips140deps/byteorder from crypto/internal/fips140/aes+ crypto/internal/fips140deps/cpu from crypto/internal/fips140/aes+ crypto/internal/fips140deps/godebug from crypto/internal/fips140+ + crypto/internal/fips140deps/time from crypto/internal/entropy/v1.0.0 crypto/internal/fips140hash from crypto/ecdsa+ crypto/internal/fips140only from crypto/cipher+ - crypto/internal/hpke from crypto/tls crypto/internal/impl from crypto/internal/fips140/aes+ - crypto/internal/randutil from crypto/dsa+ - crypto/internal/sysrand from crypto/internal/entropy+ + crypto/internal/rand from crypto/dsa+ + crypto/internal/randutil from crypto/internal/rand + crypto/internal/sysrand from crypto/internal/fips140/drbg crypto/md5 from crypto/tls+ - LD crypto/mlkem from golang.org/x/crypto/ssh + crypto/mlkem from golang.org/x/crypto/ssh+ crypto/rand from crypto/ed25519+ crypto/rc4 from crypto/tls+ crypto/rsa from crypto/tls+ crypto/sha1 from crypto/tls+ crypto/sha256 from crypto/tls+ - crypto/sha3 from crypto/internal/fips140hash + crypto/sha3 from crypto/internal/fips140hash+ crypto/sha512 from crypto/ecdsa+ crypto/subtle from crypto/cipher+ crypto/tls from github.com/prometheus-community/pro-bing+ @@ -566,9 +569,8 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) internal/goarch from crypto/internal/fips140deps/cpu+ internal/godebug from crypto/internal/fips140deps/godebug+ internal/godebugs from internal/godebug+ - internal/goexperiment from hash/maphash+ + internal/goexperiment from net/http/pprof+ internal/goos from crypto/x509+ - internal/itoa from internal/poll+ internal/msan from internal/runtime/maps+ internal/nettrace from net+ internal/oserror from io/fs+ @@ -581,14 +583,17 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) internal/runtime/atomic from internal/runtime/exithook+ LA internal/runtime/cgroup from runtime internal/runtime/exithook from runtime - internal/runtime/gc from runtime + internal/runtime/gc from runtime+ + internal/runtime/gc/scan from runtime internal/runtime/maps from reflect+ internal/runtime/math from internal/runtime/maps+ - internal/runtime/strconv from internal/runtime/cgroup+ + internal/runtime/pprof/label from runtime+ internal/runtime/sys from crypto/subtle+ - LA internal/runtime/syscall from runtime+ + LA internal/runtime/syscall/linux from internal/runtime/cgroup+ + W internal/runtime/syscall/windows from internal/syscall/windows+ internal/saferio from debug/pe+ internal/singleflight from net + internal/strconv from internal/poll+ internal/stringslite from embed+ internal/sync from sync+ internal/synctest from sync @@ -631,7 +636,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ - reflect from crypto/x509+ + reflect from database/sql/driver+ regexp from github.com/huin/goupnp/httpu+ regexp/syntax from regexp runtime from crypto/internal/fips140+ diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index 266a60f78c5ec..1cf4bf48fe5bd 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -445,7 +445,7 @@ func TestConn(t *testing.T) { for { c, err := ln.Accept() if err != nil { - if ctx.Err() != nil { + if ctx.Err() != nil || errors.Is(err, net.ErrClosed) { return } t.Errorf("s1.Accept: %v", err) diff --git a/tstest/nettest/nettest.go b/tstest/nettest/nettest.go index 0ceef463d8160..cfb0a921904eb 100644 --- a/tstest/nettest/nettest.go +++ b/tstest/nettest/nettest.go @@ -91,8 +91,8 @@ func NewUnstartedHTTPServer(nw netx.Network, handler http.Handler) *httptest.Ser c.Transport = &http.Transport{} } tr := c.Transport.(*http.Transport) - if tr.Dial != nil || tr.DialContext != nil { - panic("unexpected non-nil Dial or DialContext in httptest.Server.Client.Transport") + if tr.Dial != nil { + panic("unexpected non-nil Dial in httptest.Server.Client.Transport") } tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return nw.Dial(ctx, network, addr) diff --git a/version/mkversion/mkversion.go b/version/mkversion/mkversion.go index f42b3ad036de3..45576e4c1161b 100644 --- a/version/mkversion/mkversion.go +++ b/version/mkversion/mkversion.go @@ -384,7 +384,7 @@ func infoFromCache(ref string, runner dirRunner) (verInfo, error) { } changeCount, err := strconv.Atoi(s) if err != nil { - return verInfo{}, fmt.Errorf("infoFromCache: parsing changeCount %q: %w", changeCount, err) + return verInfo{}, fmt.Errorf("infoFromCache: parsing changeCount %q: %w", s, err) } return verInfo{ From faf7f2bc45880cf4abf4bd94d35a3e4a72136ecc Mon Sep 17 00:00:00 2001 From: BeckyPauley <64131207+BeckyPauley@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:09:11 +0000 Subject: [PATCH 182/202] cmd/k8s-operator: remove deprecated TS_EXPERIMENTAL_KUBE_API_EVENTS (#18893) Remove the TS_EXPERIMENTAL_KUBE_API_EVENTS env var from the operator and its helm chart. This has already been marked as deprecated, and has been scheduled to be removed in release 1.96. Add a check in helm chart to fail if the removed variable is set to true, prompting users to move to ACLs instead. Fixes: #18875 Signed-off-by: Becky Pauley --- .../deploy/chart/templates/NOTES.txt | 10 ++++++++ k8s-operator/api-proxy/proxy.go | 25 +++---------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/cmd/k8s-operator/deploy/chart/templates/NOTES.txt b/cmd/k8s-operator/deploy/chart/templates/NOTES.txt index 1bee6704616e6..a1a351c5e526a 100644 --- a/cmd/k8s-operator/deploy/chart/templates/NOTES.txt +++ b/cmd/k8s-operator/deploy/chart/templates/NOTES.txt @@ -1,3 +1,13 @@ +{{/* +Fail on presence of removed TS_EXPERIMENTAL_KUBE_API_EVENTS extraEnv var. +*/}} +{{- $removed := "TS_EXPERIMENTAL_KUBE_API_EVENTS" -}} +{{- range .Values.operatorConfig.extraEnv }} + {{- if and .name (eq .name $removed) (eq .value "true") -}} + {{- fail (printf "ERROR: operatorConfig.extraEnv.%s has been removed. Use ACLs instead." $removed) -}} + {{- end -}} +{{- end -}} + You have successfully installed the Tailscale Kubernetes Operator! Once connected, the operator should appear as a device within the Tailscale admin console: diff --git a/k8s-operator/api-proxy/proxy.go b/k8s-operator/api-proxy/proxy.go index c4c651b1fb029..cbcad1582e673 100644 --- a/k8s-operator/api-proxy/proxy.go +++ b/k8s-operator/api-proxy/proxy.go @@ -28,7 +28,6 @@ import ( "k8s.io/client-go/transport" "tailscale.com/client/local" "tailscale.com/client/tailscale/apitype" - "tailscale.com/envknob" ksr "tailscale.com/k8s-operator/sessionrecording" "tailscale.com/kube/kubetypes" "tailscale.com/net/netx" @@ -43,13 +42,7 @@ import ( var ( // counterNumRequestsproxies counts the number of API server requests proxied via this proxy. counterNumRequestsProxied = clientmetric.NewCounter("k8s_auth_proxy_requests_proxied") - // NOTE: adding this metric so we can keep track of users during deprecation - counterExperimentalEventsVarUsed = clientmetric.NewCounter("ts_experimental_kube_api_events_var_used") - whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil)) -) - -const ( - eventsEnabledVar = "TS_EXPERIMENTAL_KUBE_API_EVENTS" + whoIsKey = ctxkey.New("", (*apitype.WhoIsResponse)(nil)) ) // NewAPIServerProxy creates a new APIServerProxy that's ready to start once Run @@ -103,7 +96,6 @@ func NewAPIServerProxy(zlog *zap.SugaredLogger, restConfig *rest.Config, ts *tsn upstreamURL: u, ts: ts, sendEventFunc: sessionrecording.SendEvent, - eventsEnabled: envknob.Bool(eventsEnabledVar), } ap.rp = &httputil.ReverseProxy{ Rewrite: func(pr *httputil.ProxyRequest) { @@ -134,11 +126,6 @@ func (ap *APIServerProxy) Run(ctx context.Context) error { TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } - if ap.eventsEnabled { - counterExperimentalEventsVarUsed.Add(1) - ap.log.Warnf("DEPRECATED: %q environment variable is deprecated, and will be removed in v1.96. See documentation for more detail.", eventsEnabledVar) - } - mode := "noauth" if ap.authMode { mode = "auth" @@ -205,10 +192,6 @@ type APIServerProxy struct { upstreamURL *url.URL sendEventFunc func(ap netip.AddrPort, event io.Reader, dial netx.DialFunc) error - - // Flag used to enable sending API requests as events to tsrecorder. - // Deprecated: events are now set via ACLs (see https://tailscale.com/kb/1246/tailscale-ssh-session-recording#turn-on-session-recording-in-your-tailnet-policy-file) - eventsEnabled bool } // serveDefault is the default handler for Kubernetes API server requests. @@ -237,8 +220,7 @@ func (ap *APIServerProxy) serveDefault(w http.ResponseWriter, r *http.Request) { return } - // NOTE: (ChaosInTheCRD) ap.eventsEnabled deprecated, remove in v1.96 - if c.enableEvents || ap.eventsEnabled { + if c.enableEvents { if err = ap.recordRequestAsEvent(r, who, c.recorderAddresses, c.failOpen); err != nil { msg := fmt.Sprintf("error recording Kubernetes API request: %v", err) ap.log.Errorf(msg) @@ -308,8 +290,7 @@ func (ap *APIServerProxy) sessionForProto(w http.ResponseWriter, r *http.Request return } - // NOTE: (ChaosInTheCRD) ap.eventsEnabled deprecated, remove in v1.96 - if c.enableEvents || ap.eventsEnabled { + if c.enableEvents { if err = ap.recordRequestAsEvent(r, who, c.recorderAddresses, c.failOpen); err != nil { msg := fmt.Sprintf("error recording Kubernetes API request: %v", err) ap.log.Errorf(msg) From d82e478dbcae0f01ab1cfa15f900544729ad75a6 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 18 Feb 2026 15:36:13 +0100 Subject: [PATCH 183/202] cli: `--json` for `tailscale dns status|query` This commit adds `--json` output mode to dns debug commands. It defines structs for the data that is returned from: `tailscale dns status` and `tailscale dns query ` and populates that as it runs the diagnostics. When all the information is collected, it is serialised to JSON or string built into an output and returned to the user. The structs are defined and exported to golang consumers of this command can use them for unmarshalling. Updates #13326 Signed-off-by: Kristoffer Dalby --- cmd/tailscale/cli/dns-query.go | 158 +++++++++---- cmd/tailscale/cli/dns-status.go | 337 ++++++++++++++++++---------- cmd/tailscale/cli/dns_test.go | 65 ++++++ cmd/tailscale/cli/jsonoutput/dns.go | 116 ++++++++++ cmd/tailscaled/depaware-minbox.txt | 1 + 5 files changed, 514 insertions(+), 163 deletions(-) create mode 100644 cmd/tailscale/cli/dns_test.go create mode 100644 cmd/tailscale/cli/jsonoutput/dns.go diff --git a/cmd/tailscale/cli/dns-query.go b/cmd/tailscale/cli/dns-query.go index 88a897f21ed8d..2993441b3d2fc 100644 --- a/cmd/tailscale/cli/dns-query.go +++ b/cmd/tailscale/cli/dns-query.go @@ -5,93 +5,165 @@ package cli import ( "context" + "encoding/json" + "errors" "flag" "fmt" "net/netip" - "os" "strings" "text/tabwriter" "github.com/peterbourgon/ff/v3/ffcli" "golang.org/x/net/dns/dnsmessage" - "tailscale.com/types/dnstype" + "tailscale.com/cmd/tailscale/cli/jsonoutput" ) +var dnsQueryArgs struct { + json bool +} + var dnsQueryCmd = &ffcli.Command{ Name: "query", - ShortUsage: "tailscale dns query [a|aaaa|cname|mx|ns|opt|ptr|srv|txt]", + ShortUsage: "tailscale dns query [--json] [type]", Exec: runDNSQuery, ShortHelp: "Perform a DNS query", LongHelp: strings.TrimSpace(` The 'tailscale dns query' subcommand performs a DNS query for the specified name using the internal DNS forwarder (100.100.100.100). -By default, the DNS query will request an A record. Another DNS record type can -be specified as the second parameter. +By default, the DNS query will request an A record. Specify the record type as +a second argument after the name (e.g. AAAA, CNAME, MX, NS, PTR, SRV, TXT). The output also provides information about the resolver(s) used to resolve the query. `), + FlagSet: (func() *flag.FlagSet { + fs := newFlagSet("query") + fs.BoolVar(&dnsQueryArgs.json, "json", false, "output in JSON format") + return fs + })(), } func runDNSQuery(ctx context.Context, args []string) error { - if len(args) < 1 { - return flag.ErrHelp + if len(args) == 0 { + return errors.New("missing required argument: name") + } + if len(args) > 1 { + var flags []string + for _, a := range args[1:] { + if strings.HasPrefix(a, "-") { + flags = append(flags, a) + } + } + if len(flags) > 0 { + return fmt.Errorf("unexpected flags after query name: %s; see 'tailscale dns query --help'", strings.Join(flags, ", ")) + } + if len(args) > 2 { + return fmt.Errorf("unexpected extra arguments: %s", strings.Join(args[2:], " ")) + } } name := args[0] queryType := "A" - if len(args) >= 2 { - queryType = args[1] + if len(args) > 1 { + queryType = strings.ToUpper(args[1]) } - fmt.Printf("DNS query for %q (%s) using internal resolver:\n", name, queryType) - fmt.Println() - bytes, resolvers, err := localClient.QueryDNS(ctx, name, queryType) + + rawBytes, resolvers, err := localClient.QueryDNS(ctx, name, queryType) if err != nil { - fmt.Printf("failed to query DNS: %v\n", err) - return nil + return fmt.Errorf("failed to query DNS: %w", err) } - if len(resolvers) == 1 { - fmt.Printf("Forwarding to resolver: %v\n", makeResolverString(*resolvers[0])) - } else { - fmt.Println("Multiple resolvers available:") - for _, r := range resolvers { - fmt.Printf(" - %v\n", makeResolverString(*r)) - } + data := &jsonoutput.DNSQueryResult{ + Name: name, + QueryType: queryType, + } + + for _, r := range resolvers { + data.Resolvers = append(data.Resolvers, makeDNSResolverInfo(r)) } - fmt.Println() + var p dnsmessage.Parser - header, err := p.Start(bytes) + header, err := p.Start(rawBytes) if err != nil { - fmt.Printf("failed to parse DNS response: %v\n", err) - return err + return fmt.Errorf("failed to parse DNS response: %w", err) } - fmt.Printf("Response code: %v\n", header.RCode.String()) - fmt.Println() + data.ResponseCode = header.RCode.String() + p.SkipAllQuestions() - if header.RCode != dnsmessage.RCodeSuccess { - fmt.Println("No answers were returned.") + + if header.RCode == dnsmessage.RCodeSuccess { + answers, err := p.AllAnswers() + if err != nil { + return fmt.Errorf("failed to parse DNS answers: %w", err) + } + data.Answers = make([]jsonoutput.DNSAnswer, 0, len(answers)) + for _, a := range answers { + data.Answers = append(data.Answers, jsonoutput.DNSAnswer{ + Name: a.Header.Name.String(), + TTL: a.Header.TTL, + Class: a.Header.Class.String(), + Type: a.Header.Type.String(), + Body: makeAnswerBody(a), + }) + } + } + + if dnsQueryArgs.json { + j, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + printf("%s\n", j) return nil } - answers, err := p.AllAnswers() - if err != nil { - fmt.Printf("failed to parse DNS answers: %v\n", err) - return err + printf("%s", formatDNSQueryText(data)) + return nil +} + +func formatDNSQueryText(data *jsonoutput.DNSQueryResult) string { + var sb strings.Builder + + fmt.Fprintf(&sb, "DNS query for %q (%s) using internal resolver:\n", data.Name, data.QueryType) + fmt.Fprintf(&sb, "\n") + if len(data.Resolvers) == 1 { + fmt.Fprintf(&sb, "Forwarding to resolver: %v\n", formatResolverString(data.Resolvers[0])) + } else { + fmt.Fprintf(&sb, "Multiple resolvers available:\n") + for _, r := range data.Resolvers { + fmt.Fprintf(&sb, " - %v\n", formatResolverString(r)) + } } - if len(answers) == 0 { - fmt.Println(" (no answers found)") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "Response code: %v\n", data.ResponseCode) + fmt.Fprintf(&sb, "\n") + + if data.Answers == nil { + fmt.Fprintf(&sb, "No answers were returned.\n") + return sb.String() } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + if len(data.Answers) == 0 { + fmt.Fprintf(&sb, " (no answers found)\n") + } + + w := tabwriter.NewWriter(&sb, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "Name\tTTL\tClass\tType\tBody") fmt.Fprintln(w, "----\t---\t-----\t----\t----") - for _, a := range answers { - fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", a.Header.Name.String(), a.Header.TTL, a.Header.Class.String(), a.Header.Type.String(), makeAnswerBody(a)) + for _, a := range data.Answers { + fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", a.Name, a.TTL, a.Class, a.Type, a.Body) } w.Flush() - fmt.Println() - return nil + fmt.Fprintf(&sb, "\n") + return sb.String() +} + +// formatResolverString formats a jsonoutput.DNSResolverInfo for human-readable text output. +func formatResolverString(r jsonoutput.DNSResolverInfo) string { + if len(r.BootstrapResolution) > 0 { + return fmt.Sprintf("%s (bootstrap: %v)", r.Addr, r.BootstrapResolution) + } + return r.Addr } // makeAnswerBody returns a string with the DNS answer body in a human-readable format. @@ -174,9 +246,3 @@ func makeTXTBody(txt dnsmessage.ResourceBody) string { } return "" } -func makeResolverString(r dnstype.Resolver) string { - if len(r.BootstrapResolution) > 0 { - return fmt.Sprintf("%s (bootstrap: %v)", r.Addr, r.BootstrapResolution) - } - return fmt.Sprintf("%s", r.Addr) -} diff --git a/cmd/tailscale/cli/dns-status.go b/cmd/tailscale/cli/dns-status.go index f63f418281987..66a5e21d89700 100644 --- a/cmd/tailscale/cli/dns-status.go +++ b/cmd/tailscale/cli/dns-status.go @@ -5,6 +5,7 @@ package cli import ( "context" + "encoding/json" "flag" "fmt" "maps" @@ -12,13 +13,15 @@ import ( "strings" "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/cmd/tailscale/cli/jsonoutput" "tailscale.com/ipn" + "tailscale.com/types/dnstype" "tailscale.com/types/netmap" ) var dnsStatusCmd = &ffcli.Command{ Name: "status", - ShortUsage: "tailscale dns status [--all]", + ShortUsage: "tailscale dns status [--all] [--json]", Exec: runDNSStatus, ShortHelp: "Print the current DNS status and configuration", LongHelp: strings.TrimSpace(` @@ -72,17 +75,30 @@ https://tailscale.com/kb/1054/dns. FlagSet: (func() *flag.FlagSet { fs := newFlagSet("status") fs.BoolVar(&dnsStatusArgs.all, "all", false, "outputs advanced debugging information") + fs.BoolVar(&dnsStatusArgs.json, "json", false, "output in JSON format") return fs })(), } // dnsStatusArgs are the arguments for the "dns status" subcommand. var dnsStatusArgs struct { - all bool + all bool + json bool +} + +// makeDNSResolverInfo converts a dnstype.Resolver to a jsonoutput.DNSResolverInfo. +func makeDNSResolverInfo(r *dnstype.Resolver) jsonoutput.DNSResolverInfo { + info := jsonoutput.DNSResolverInfo{Addr: r.Addr} + if r.BootstrapResolution != nil { + info.BootstrapResolution = make([]string, 0, len(r.BootstrapResolution)) + for _, a := range r.BootstrapResolution { + info.BootstrapResolution = append(info.BootstrapResolution, a.String()) + } + } + return info } func runDNSStatus(ctx context.Context, args []string) error { - all := dnsStatusArgs.all s, err := localClient.Status(ctx) if err != nil { return err @@ -92,167 +108,254 @@ func runDNSStatus(ctx context.Context, args []string) error { if err != nil { return err } - enabledStr := "disabled.\n\n(Run 'tailscale set --accept-dns=true' to start sending DNS queries to the Tailscale DNS resolver)" - if prefs.CorpDNS { - enabledStr = "enabled.\n\nTailscale is configured to handle DNS queries on this device.\nRun 'tailscale set --accept-dns=false' to revert to your system default DNS resolver." + + data := &jsonoutput.DNSStatusResult{ + TailscaleDNS: prefs.CorpDNS, } - fmt.Print("\n") - fmt.Println("=== 'Use Tailscale DNS' status ===") - fmt.Print("\n") - fmt.Printf("Tailscale DNS: %s\n", enabledStr) - fmt.Print("\n") - fmt.Println("=== MagicDNS configuration ===") - fmt.Print("\n") - fmt.Println("This is the DNS configuration provided by the coordination server to this device.") - fmt.Print("\n") - if s.CurrentTailnet == nil { - fmt.Println("No tailnet information available; make sure you're logged in to a tailnet.") + + if s.CurrentTailnet != nil { + data.CurrentTailnet = &jsonoutput.DNSTailnetInfo{ + MagicDNSEnabled: s.CurrentTailnet.MagicDNSEnabled, + MagicDNSSuffix: s.CurrentTailnet.MagicDNSSuffix, + SelfDNSName: s.Self.DNSName, + } + + netMap, err := fetchNetMap() + if err != nil { + return fmt.Errorf("failed to fetch network map: %w", err) + } + dnsConfig := netMap.DNS + + for _, r := range dnsConfig.Resolvers { + data.Resolvers = append(data.Resolvers, makeDNSResolverInfo(r)) + } + + data.SplitDNSRoutes = make(map[string][]jsonoutput.DNSResolverInfo) + for k, v := range dnsConfig.Routes { + for _, r := range v { + data.SplitDNSRoutes[k] = append(data.SplitDNSRoutes[k], makeDNSResolverInfo(r)) + } + } + + for _, r := range dnsConfig.FallbackResolvers { + data.FallbackResolvers = append(data.FallbackResolvers, makeDNSResolverInfo(r)) + } + + domains := slices.Clone(dnsConfig.Domains) + slices.Sort(domains) + data.SearchDomains = domains + + for _, a := range dnsConfig.Nameservers { + data.Nameservers = append(data.Nameservers, a.String()) + } + + data.CertDomains = dnsConfig.CertDomains + + for _, er := range dnsConfig.ExtraRecords { + data.ExtraRecords = append(data.ExtraRecords, jsonoutput.DNSExtraRecord{ + Name: er.Name, + Type: er.Type, + Value: er.Value, + }) + } + + data.ExitNodeFilteredSet = dnsConfig.ExitNodeFilteredSet + + osCfg, err := localClient.GetDNSOSConfig(ctx) + if err != nil { + if strings.Contains(err.Error(), "not supported") { + data.SystemDNSError = "not supported on this platform" + } else { + data.SystemDNSError = err.Error() + } + } else if osCfg != nil { + data.SystemDNS = &jsonoutput.DNSSystemConfig{ + Nameservers: osCfg.Nameservers, + SearchDomains: osCfg.SearchDomains, + MatchDomains: osCfg.MatchDomains, + } + } + } + + if dnsStatusArgs.json { + j, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + printf("%s\n", j) return nil - } else if s.CurrentTailnet.MagicDNSEnabled { - fmt.Printf("MagicDNS: enabled tailnet-wide (suffix = %s)", s.CurrentTailnet.MagicDNSSuffix) - fmt.Print("\n\n") - fmt.Printf("Other devices in your tailnet can reach this device at %s\n", s.Self.DNSName) + } + printf("%s", formatDNSStatusText(data, dnsStatusArgs.all)) + return nil +} + +func formatDNSStatusText(data *jsonoutput.DNSStatusResult, all bool) string { + var sb strings.Builder + + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "=== 'Use Tailscale DNS' status ===\n") + fmt.Fprintf(&sb, "\n") + if data.TailscaleDNS { + fmt.Fprintf(&sb, "Tailscale DNS: enabled.\n\nTailscale is configured to handle DNS queries on this device.\nRun 'tailscale set --accept-dns=false' to revert to your system default DNS resolver.\n") } else { - fmt.Printf("MagicDNS: disabled tailnet-wide.\n") + fmt.Fprintf(&sb, "Tailscale DNS: disabled.\n\n(Run 'tailscale set --accept-dns=true' to start sending DNS queries to the Tailscale DNS resolver)\n") + } + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "=== MagicDNS configuration ===\n") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "This is the DNS configuration provided by the coordination server to this device.\n") + fmt.Fprintf(&sb, "\n") + if data.CurrentTailnet == nil { + fmt.Fprintf(&sb, "No tailnet information available; make sure you're logged in to a tailnet.\n") + return sb.String() } - fmt.Print("\n") - netMap, err := fetchNetMap() - if err != nil { - fmt.Printf("Failed to fetch network map: %v\n", err) - return err + if data.CurrentTailnet.MagicDNSEnabled { + fmt.Fprintf(&sb, "MagicDNS: enabled tailnet-wide (suffix = %s)", data.CurrentTailnet.MagicDNSSuffix) + fmt.Fprintf(&sb, "\n\n") + fmt.Fprintf(&sb, "Other devices in your tailnet can reach this device at %s\n", data.CurrentTailnet.SelfDNSName) + } else { + fmt.Fprintf(&sb, "MagicDNS: disabled tailnet-wide.\n") } - dnsConfig := netMap.DNS - fmt.Println("Resolvers (in preference order):") - if len(dnsConfig.Resolvers) == 0 { - fmt.Println(" (no resolvers configured, system default will be used: see 'System DNS configuration' below)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Resolvers (in preference order):\n") + if len(data.Resolvers) == 0 { + fmt.Fprintf(&sb, " (no resolvers configured, system default will be used: see 'System DNS configuration' below)\n") } - for _, r := range dnsConfig.Resolvers { - fmt.Printf(" - %v", r.Addr) + for _, r := range data.Resolvers { + fmt.Fprintf(&sb, " - %v", r.Addr) if r.BootstrapResolution != nil { - fmt.Printf(" (bootstrap: %v)", r.BootstrapResolution) + fmt.Fprintf(&sb, " (bootstrap: %v)", r.BootstrapResolution) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } - fmt.Print("\n") - fmt.Println("Split DNS Routes:") - if len(dnsConfig.Routes) == 0 { - fmt.Println(" (no routes configured: split DNS disabled)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Split DNS Routes:\n") + if len(data.SplitDNSRoutes) == 0 { + fmt.Fprintf(&sb, " (no routes configured: split DNS disabled)\n") } - for _, k := range slices.Sorted(maps.Keys(dnsConfig.Routes)) { - v := dnsConfig.Routes[k] - for _, r := range v { - fmt.Printf(" - %-30s -> %v", k, r.Addr) + for _, k := range slices.Sorted(maps.Keys(data.SplitDNSRoutes)) { + for _, r := range data.SplitDNSRoutes[k] { + fmt.Fprintf(&sb, " - %-30s -> %v", k, r.Addr) if r.BootstrapResolution != nil { - fmt.Printf(" (bootstrap: %v)", r.BootstrapResolution) + fmt.Fprintf(&sb, " (bootstrap: %v)", r.BootstrapResolution) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") + if all { - fmt.Println("Fallback Resolvers:") - if len(dnsConfig.FallbackResolvers) == 0 { - fmt.Println(" (no fallback resolvers configured)") + fmt.Fprintf(&sb, "Fallback Resolvers:\n") + if len(data.FallbackResolvers) == 0 { + fmt.Fprintf(&sb, " (no fallback resolvers configured)\n") } - for i, r := range dnsConfig.FallbackResolvers { - fmt.Printf(" %d: %v\n", i, r) + for i, r := range data.FallbackResolvers { + fmt.Fprintf(&sb, " %d: %v", i, r.Addr) + if r.BootstrapResolution != nil { + fmt.Fprintf(&sb, " (bootstrap: %v)", r.BootstrapResolution) + } + fmt.Fprintf(&sb, "\n") } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } - fmt.Println("Search Domains:") - if len(dnsConfig.Domains) == 0 { - fmt.Println(" (no search domains configured)") + + fmt.Fprintf(&sb, "Search Domains:\n") + if len(data.SearchDomains) == 0 { + fmt.Fprintf(&sb, " (no search domains configured)\n") } - domains := dnsConfig.Domains - slices.Sort(domains) - for _, r := range domains { - fmt.Printf(" - %v\n", r) + for _, r := range data.SearchDomains { + fmt.Fprintf(&sb, " - %v\n", r) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") + if all { - fmt.Println("Nameservers IP Addresses:") - if len(dnsConfig.Nameservers) == 0 { - fmt.Println(" (none were provided)") + fmt.Fprintf(&sb, "Nameservers IP Addresses:\n") + if len(data.Nameservers) == 0 { + fmt.Fprintf(&sb, " (none were provided)\n") } - for _, r := range dnsConfig.Nameservers { - fmt.Printf(" - %v\n", r) + for _, r := range data.Nameservers { + fmt.Fprintf(&sb, " - %v\n", r) } - fmt.Print("\n") - fmt.Println("Certificate Domains:") - if len(dnsConfig.CertDomains) == 0 { - fmt.Println(" (no certificate domains are configured)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Certificate Domains:\n") + if len(data.CertDomains) == 0 { + fmt.Fprintf(&sb, " (no certificate domains are configured)\n") } - for _, r := range dnsConfig.CertDomains { - fmt.Printf(" - %v\n", r) + for _, r := range data.CertDomains { + fmt.Fprintf(&sb, " - %v\n", r) } - fmt.Print("\n") - fmt.Println("Additional DNS Records:") - if len(dnsConfig.ExtraRecords) == 0 { - fmt.Println(" (no extra records are configured)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Additional DNS Records:\n") + if len(data.ExtraRecords) == 0 { + fmt.Fprintf(&sb, " (no extra records are configured)\n") } - for _, er := range dnsConfig.ExtraRecords { + for _, er := range data.ExtraRecords { if er.Type == "" { - fmt.Printf(" - %-50s -> %v\n", er.Name, er.Value) + fmt.Fprintf(&sb, " - %-50s -> %v\n", er.Name, er.Value) } else { - fmt.Printf(" - [%s] %-50s -> %v\n", er.Type, er.Name, er.Value) + fmt.Fprintf(&sb, " - [%s] %-50s -> %v\n", er.Type, er.Name, er.Value) } } - fmt.Print("\n") - fmt.Println("Filtered suffixes when forwarding DNS queries as an exit node:") - if len(dnsConfig.ExitNodeFilteredSet) == 0 { - fmt.Println(" (no suffixes are filtered)") + fmt.Fprintf(&sb, "\n") + + fmt.Fprintf(&sb, "Filtered suffixes when forwarding DNS queries as an exit node:\n") + if len(data.ExitNodeFilteredSet) == 0 { + fmt.Fprintf(&sb, " (no suffixes are filtered)\n") } - for _, s := range dnsConfig.ExitNodeFilteredSet { - fmt.Printf(" - %s\n", s) + for _, s := range data.ExitNodeFilteredSet { + fmt.Fprintf(&sb, " - %s\n", s) } - fmt.Print("\n") + fmt.Fprintf(&sb, "\n") } - fmt.Println("=== System DNS configuration ===") - fmt.Print("\n") - fmt.Println("This is the DNS configuration that Tailscale believes your operating system is using.\nTailscale may use this configuration if 'Override Local DNS' is disabled in the admin console,\nor if no resolvers are provided by the coordination server.") - fmt.Print("\n") - osCfg, err := localClient.GetDNSOSConfig(ctx) - if err != nil { - if strings.Contains(err.Error(), "not supported") { - // avoids showing the HTTP error code which would be odd here - fmt.Println(" (reading the system DNS configuration is not supported on this platform)") + fmt.Fprintf(&sb, "=== System DNS configuration ===\n") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "This is the DNS configuration that Tailscale believes your operating system is using.\nTailscale may use this configuration if 'Override Local DNS' is disabled in the admin console,\nor if no resolvers are provided by the coordination server.\n") + fmt.Fprintf(&sb, "\n") + + if data.SystemDNSError != "" { + if strings.Contains(data.SystemDNSError, "not supported") { + fmt.Fprintf(&sb, " (reading the system DNS configuration is not supported on this platform)\n") } else { - fmt.Printf(" (failed to read system DNS configuration: %v)\n", err) + fmt.Fprintf(&sb, " (failed to read system DNS configuration: %s)\n", data.SystemDNSError) } - } else if osCfg == nil { - fmt.Println(" (no OS DNS configuration available)") + } else if data.SystemDNS == nil { + fmt.Fprintf(&sb, " (no OS DNS configuration available)\n") } else { - fmt.Println("Nameservers:") - if len(osCfg.Nameservers) == 0 { - fmt.Println(" (no nameservers found, DNS queries might fail\nunless the coordination server is providing a nameserver)") + fmt.Fprintf(&sb, "Nameservers:\n") + if len(data.SystemDNS.Nameservers) == 0 { + fmt.Fprintf(&sb, " (no nameservers found, DNS queries might fail\nunless the coordination server is providing a nameserver)\n") } - for _, ns := range osCfg.Nameservers { - fmt.Printf(" - %v\n", ns) + for _, ns := range data.SystemDNS.Nameservers { + fmt.Fprintf(&sb, " - %v\n", ns) } - fmt.Print("\n") - fmt.Println("Search domains:") - if len(osCfg.SearchDomains) == 0 { - fmt.Println(" (no search domains found)") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "Search domains:\n") + if len(data.SystemDNS.SearchDomains) == 0 { + fmt.Fprintf(&sb, " (no search domains found)\n") } - for _, sd := range osCfg.SearchDomains { - fmt.Printf(" - %v\n", sd) + for _, sd := range data.SystemDNS.SearchDomains { + fmt.Fprintf(&sb, " - %v\n", sd) } if all { - fmt.Print("\n") - fmt.Println("Match domains:") - if len(osCfg.MatchDomains) == 0 { - fmt.Println(" (no match domains found)") + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "Match domains:\n") + if len(data.SystemDNS.MatchDomains) == 0 { + fmt.Fprintf(&sb, " (no match domains found)\n") } - for _, md := range osCfg.MatchDomains { - fmt.Printf(" - %v\n", md) + for _, md := range data.SystemDNS.MatchDomains { + fmt.Fprintf(&sb, " - %v\n", md) } } } - fmt.Print("\n") - fmt.Println("[this is a preliminary version of this command; the output format may change in the future]") - return nil + fmt.Fprintf(&sb, "\n") + fmt.Fprintf(&sb, "[this is a preliminary version of this command; the output format may change in the future]\n") + return sb.String() } func fetchNetMap() (netMap *netmap.NetworkMap, err error) { diff --git a/cmd/tailscale/cli/dns_test.go b/cmd/tailscale/cli/dns_test.go new file mode 100644 index 0000000000000..cc01a52702fac --- /dev/null +++ b/cmd/tailscale/cli/dns_test.go @@ -0,0 +1,65 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "context" + "strings" + "testing" +) + +func TestRunDNSQueryArgs(t *testing.T) { + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "no_args", + args: []string{}, + wantErr: "missing required argument: name", + }, + { + name: "flag_after_name", + args: []string{"example.com", "--json"}, + wantErr: "unexpected flags after query name: --json", + }, + { + name: "flag_after_name_and_type", + args: []string{"example.com", "AAAA", "--json"}, + wantErr: "unexpected flags after query name: --json", + }, + { + name: "extra_args_after_type", + args: []string{"example.com", "AAAA", "extra"}, + wantErr: "unexpected extra arguments: extra", + }, + { + name: "multiple_extra_args", + args: []string{"example.com", "AAAA", "extra1", "extra2"}, + wantErr: "unexpected extra arguments: extra1 extra2", + }, + { + name: "non_flag_then_flag", + args: []string{"example.com", "AAAA", "foo", "--json"}, + wantErr: "unexpected flags after query name: --json", + }, + { + name: "multiple_misplaced_flags", + args: []string{"example.com", "--json", "--verbose"}, + wantErr: "unexpected flags after query name: --json, --verbose", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := runDNSQuery(context.Background(), tt.args) + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("error = %q, want it to contain %q", err.Error(), tt.wantErr) + } + }) + } +} diff --git a/cmd/tailscale/cli/jsonoutput/dns.go b/cmd/tailscale/cli/jsonoutput/dns.go new file mode 100644 index 0000000000000..d9d3cc0bbb3b6 --- /dev/null +++ b/cmd/tailscale/cli/jsonoutput/dns.go @@ -0,0 +1,116 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package jsonoutput + +// DNSResolverInfo is the JSON form of [dnstype.Resolver]. +type DNSResolverInfo struct { + // Addr is a plain IP, IP:port, DoH URL, or HTTP-over-WireGuard URL. + Addr string + + // BootstrapResolution is optional pre-resolved IPs for DoT/DoH + // resolvers whose address is not already an IP. + BootstrapResolution []string `json:",omitempty"` +} + +// DNSExtraRecord is the JSON form of [tailcfg.DNSRecord]. +type DNSExtraRecord struct { + Name string + Type string `json:",omitempty"` // empty means A or AAAA, depending on Value + Value string // typically an IP address +} + +// DNSSystemConfig is the OS DNS configuration as observed by Tailscale, +// mirroring [net/dns.OSConfig]. +type DNSSystemConfig struct { + Nameservers []string `json:",omitzero"` + SearchDomains []string `json:",omitzero"` + + // MatchDomains are DNS suffixes restricting which queries use + // these Nameservers. Empty means Nameservers is the primary + // resolver. + MatchDomains []string `json:",omitzero"` +} + +// DNSTailnetInfo describes MagicDNS configuration for the tailnet, +// combining [ipnstate.TailnetStatus] and [ipnstate.PeerStatus]. +type DNSTailnetInfo struct { + // MagicDNSEnabled is whether MagicDNS is enabled for the + // tailnet. The device may still not use it if + // --accept-dns=false. + MagicDNSEnabled bool + + // MagicDNSSuffix is the tailnet's MagicDNS suffix + // (e.g. "tail1234.ts.net"), without surrounding dots. + MagicDNSSuffix string `json:",omitempty"` + + // SelfDNSName is this device's FQDN + // (e.g. "host.tail1234.ts.net."), with trailing dot. + SelfDNSName string `json:",omitempty"` +} + +// DNSStatusResult is the full DNS status collected from the local +// Tailscale daemon. +type DNSStatusResult struct { + // TailscaleDNS is whether the Tailscale DNS configuration is + // installed on this device (the --accept-dns setting). + TailscaleDNS bool + + // CurrentTailnet describes MagicDNS configuration for the tailnet. + CurrentTailnet *DNSTailnetInfo `json:",omitzero"` // nil if not connected + + // Resolvers are the DNS resolvers, in preference order. If + // empty, the system defaults are used. + Resolvers []DNSResolverInfo `json:",omitzero"` + + // SplitDNSRoutes maps domain suffixes to dedicated resolvers. + // An empty resolver slice means the suffix is handled by + // Tailscale's built-in resolver (100.100.100.100). + SplitDNSRoutes map[string][]DNSResolverInfo `json:",omitzero"` + + // FallbackResolvers are like Resolvers but only used when + // split DNS needs explicit default resolvers. + FallbackResolvers []DNSResolverInfo `json:",omitzero"` + + SearchDomains []string `json:",omitzero"` + + // Nameservers are nameserver IPs. + // + // Deprecated: old protocol versions only. Use Resolvers. + Nameservers []string `json:",omitzero"` + + // CertDomains are FQDNs for which the coordination server + // provisions TLS certificates via dns-01 ACME challenges. + CertDomains []string `json:",omitzero"` + + // ExtraRecords contains extra DNS records in the MagicDNS config. + ExtraRecords []DNSExtraRecord `json:",omitzero"` + + // ExitNodeFilteredSet are DNS suffixes this node won't resolve + // when acting as an exit node DNS proxy. Period-prefixed + // entries are suffix matches; others are exact. Always + // lowercase, no trailing dots. + ExitNodeFilteredSet []string `json:",omitzero"` + + SystemDNS *DNSSystemConfig `json:",omitzero"` // nil if unavailable + SystemDNSError string `json:",omitempty"` +} + +// DNSAnswer is a single DNS resource record from a query response. +type DNSAnswer struct { + Name string + TTL uint32 + Class string // e.g. "ClassINET" + Type string // e.g. "TypeA", "TypeAAAA" + Body string // human-readable record data +} + +// DNSQueryResult is the result of a DNS query via the Tailscale +// internal forwarder (100.100.100.100). +type DNSQueryResult struct { + Name string + QueryType string // e.g. "A", "AAAA" + Resolvers []DNSResolverInfo `json:",omitzero"` + ResponseCode string // e.g. "RCodeSuccess", "RCodeNameError" + Answers []DNSAnswer `json:",omitzero"` +} diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 9b09604875446..64911d9318f03 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -54,6 +54,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscaled tailscale.com/cmd/tailscale/cli/ffcomplete from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete + tailscale.com/cmd/tailscale/cli/jsonoutput from tailscale.com/cmd/tailscale/cli tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled tailscale.com/control/controlbase from tailscale.com/control/controlhttp+ tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+ From 1b53c00f2bd10ce99e9d7148a292e344bfd72768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20Lensb=C3=B8l?= Date: Thu, 5 Mar 2026 13:39:07 -0500 Subject: [PATCH 184/202] clientupdate,net/tstun: add support for OpenWrt 25.12.0 using apk (#18545) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenWrt is changing to using alpine like `apk` for package installation over its previous opkg. Additionally, they are not using the same repo files as alpine making installation fail. Add support for the new repository files and ensure that the required package detection system uses apk. Updates #18535 Signed-off-by: Claus Lensbøl --- clientupdate/clientupdate.go | 59 +++++++++----- clientupdate/clientupdate_test.go | 124 ++++++++++++++++++++++++++++++ net/tstun/tun_linux.go | 34 ++++++-- 3 files changed, 188 insertions(+), 29 deletions(-) diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 1ed7894bf3d43..d52241483812a 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -661,7 +661,7 @@ func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) { func (up *Updater) updateAlpineLike() (err error) { if up.Version != "" { - return errors.New("installing a specific version on Alpine-based distros is not supported") + return errors.New("installing a specific version on apk-based distros is not supported") } if err := requireRoot(); err != nil { return err @@ -691,7 +691,7 @@ func (up *Updater) updateAlpineLike() (err error) { return fmt.Errorf(`failed to parse latest version from "apk info tailscale": %w`, err) } if !up.confirm(ver) { - if err := checkOutdatedAlpineRepo(up.Logf, ver, up.Track); err != nil { + if err := checkOutdatedAlpineRepo(up.Logf, apkDirPaths, ver, up.Track); err != nil { up.Logf("failed to check whether Alpine release is outdated: %v", err) } return nil @@ -731,9 +731,12 @@ func parseAlpinePackageVersion(out []byte) (string, error) { return "", errors.New("tailscale version not found in output") } -var apkRepoVersionRE = regexp.MustCompile(`v[0-9]+\.[0-9]+`) +var ( + apkRepoVersionRE = regexp.MustCompile(`v[0-9]+\.[0-9]+`) + apkDirPaths = []string{"/etc/apk/repositories", "/etc/apk/repositories.d/distfeeds.list"} +) -func checkOutdatedAlpineRepo(logf logger.Logf, apkVer, track string) error { +func checkOutdatedAlpineRepo(logf logger.Logf, filePaths []string, apkVer, track string) error { latest, err := LatestTailscaleVersion(track) if err != nil { return err @@ -742,22 +745,34 @@ func checkOutdatedAlpineRepo(logf logger.Logf, apkVer, track string) error { // Actually on latest release. return nil } - f, err := os.Open("/etc/apk/repositories") - if err != nil { - return err - } - defer f.Close() - // Read the first repo line. Typically, there are multiple repos that all - // contain the same version in the path, like: - // https://dl-cdn.alpinelinux.org/alpine/v3.20/main - // https://dl-cdn.alpinelinux.org/alpine/v3.20/community - s := bufio.NewScanner(f) - if !s.Scan() { - return s.Err() - } - alpineVer := apkRepoVersionRE.FindString(s.Text()) - if alpineVer != "" { - logf("The latest Tailscale release for Linux is %q, but your apk repository only provides %q.\nYour Alpine version is %q, you may need to upgrade the system to get the latest Tailscale version: https://wiki.alpinelinux.org/wiki/Upgrading_Alpine", latest, apkVer, alpineVer) + + // OpenWrt uses a different repo file in repositories.d, check for that as well. + for _, repoFile := range filePaths { + f, err := os.Open(repoFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } else { + return err + } + } + defer f.Close() + // Read the first repo line. Typically, there are multiple repos that all + // contain the same version in the path, like: + // https://dl-cdn.alpinelinux.org/alpine/v3.20/main + // https://dl-cdn.alpinelinux.org/alpine/v3.20/community + s := bufio.NewScanner(f) + if !s.Scan() { + if s.Err() != nil { + return s.Err() + } + logf("The latest Tailscale release for Linux is %q, but your apk repository only provides %q.\nYou may need to upgrade your Alpine system to get the latest Tailscale version: https://wiki.alpinelinux.org/wiki/Upgrading_Alpine", latest, apkVer) + } + alpineVer := apkRepoVersionRE.FindString(s.Text()) + if alpineVer != "" { + logf("The latest Tailscale release for Linux is %q, but your apk repository only provides %q.\nYour Alpine version is %q, you may need to upgrade the system to get the latest Tailscale version: https://wiki.alpinelinux.org/wiki/Upgrading_Alpine", latest, apkVer, alpineVer) + } + return nil } return nil } @@ -1246,8 +1261,10 @@ type trackPackages struct { SPKsVersion string } +var tailscaleHTTPEndpoint = "https://pkgs.tailscale.com" + func latestPackages(track string) (*trackPackages, error) { - url := fmt.Sprintf("https://pkgs.tailscale.com/%s/?mode=json&os=%s", track, runtime.GOOS) + url := fmt.Sprintf("%s/%s/?mode=json&os=%s", tailscaleHTTPEndpoint, track, runtime.GOOS) res, err := http.Get(url) if err != nil { return nil, fmt.Errorf("fetching latest tailscale version: %w", err) diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index 7487026355326..13fc8f08a6a2e 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -6,9 +6,12 @@ package clientupdate import ( "archive/tar" "compress/gzip" + "encoding/json" "fmt" "io/fs" "maps" + "net/http" + "net/http/httptest" "os" "path/filepath" "slices" @@ -299,6 +302,127 @@ tailscale-1.58.2-r0 installed size: } } +func TestCheckOutdatedAlpineRepo(t *testing.T) { + anyToString := func(a any) string { + str, ok := a.(string) + if !ok { + panic("failed to parse param as string") + } + return str + } + + tests := []struct { + name string + fileContent string + latestHTTPVersion string + latestApkVersion string + wantHTTPVersion string + wantApkVersion string + wantAlpineVersion string + track string + }{ + { + name: "Up to date", + fileContent: "https://dl-cdn.alpinelinux.org/alpine/v3.20/main", + latestHTTPVersion: "1.95.3", + latestApkVersion: "1.95.3", + track: "unstable", + }, + { + name: "Behind unstable", + fileContent: "https://dl-cdn.alpinelinux.org/alpine/v3.20/main", + latestHTTPVersion: "1.95.4", + latestApkVersion: "1.95.3", + wantHTTPVersion: "1.95.4", + wantApkVersion: "1.95.3", + wantAlpineVersion: "v3.20", + track: "unstable", + }, + { + name: "Behind stable", + fileContent: "https://dl-cdn.alpinelinux.org/alpine/v2.40/main", + latestHTTPVersion: "1.94.3", + latestApkVersion: "1.92.1", + wantHTTPVersion: "1.94.3", + wantApkVersion: "1.92.1", + wantAlpineVersion: "v2.40", + track: "stable", + }, + { + name: "Nothing in dist file", + fileContent: "", + latestHTTPVersion: "1.94.3", + latestApkVersion: "1.92.1", + wantHTTPVersion: "1.94.3", + wantApkVersion: "1.92.1", + track: "stable", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := os.MkdirTemp("", "example") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + t.Cleanup(func() { os.RemoveAll(dir) }) // clean up + + file := filepath.Join(dir, "distfile") + if err := os.WriteFile(file, []byte(tt.fileContent), 0o666); err != nil { + t.Fatalf("error creating dist file: %v", err) + } + + testServ := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, _ *http.Request) { + version := trackPackages{ + MSIsVersion: tt.latestHTTPVersion, + MacZipsVersion: tt.latestHTTPVersion, + TarballsVersion: tt.latestHTTPVersion, + SPKsVersion: tt.latestHTTPVersion, + } + jsonData, err := json.Marshal(version) + if err != nil { + t.Errorf("failed to marshal version string: %v", err) + } + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(jsonData); err != nil { + t.Errorf("failed to write json blob: %v", err) + } + }, + )) + defer testServ.Close() + + oldEndpoint := tailscaleHTTPEndpoint + tailscaleHTTPEndpoint = testServ.URL + defer func() { tailscaleHTTPEndpoint = oldEndpoint }() + + var paramLatest string + var paramApkVer string + var paramAlpineVer string + logf := func(_ string, params ...any) { + paramLatest = anyToString(params[0]) + paramApkVer = anyToString(params[1]) + if len(params) > 2 { + paramAlpineVer = anyToString(params[2]) + } + } + + err = checkOutdatedAlpineRepo(logf, []string{file}, tt.latestApkVersion, tt.track) + if err != nil { + t.Errorf("did not expect error, got: %v", err) + } + if paramLatest != tt.wantHTTPVersion { + t.Errorf("expected HTTP version '%s', got '%s'", tt.wantHTTPVersion, paramLatest) + } + if paramApkVer != tt.wantApkVersion { + t.Errorf("expected APK version '%s', got '%s'", tt.wantApkVersion, paramApkVer) + } + if paramAlpineVer != tt.wantAlpineVersion { + t.Errorf("expected alpine version '%s', got '%s'", tt.wantAlpineVersion, paramAlpineVer) + } + }) + } +} + func TestSynoArch(t *testing.T) { tests := []struct { goarch string diff --git a/net/tstun/tun_linux.go b/net/tstun/tun_linux.go index 028e0a14b5bd8..fb4a8a415dac7 100644 --- a/net/tstun/tun_linux.go +++ b/net/tstun/tun_linux.go @@ -86,14 +86,32 @@ func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf, createErr error) logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut) } case distro.OpenWrt: - out, err := exec.Command("opkg", "list-installed").CombinedOutput() - if err != nil { - logf("error querying OpenWrt installed packages: %s", out) - return - } - for _, pkg := range []string{"kmod-tun", "ca-bundle"} { - if !bytes.Contains(out, []byte(pkg+" - ")) { - logf("Missing required package %s; run: opkg install %s", pkg, pkg) + // OpenWRT switched to using apk as a package manager as of OpenWrt 25.12.0. + // Find out what is used on this system and use that, Maybe we can get rid + // of opkg in the future but for now keep checking. + + if path, err := exec.LookPath("apk"); err == nil && path != "" { + // Test with apk + out, err := exec.Command("apk", "info").CombinedOutput() + if err != nil { + logf("error querying OpenWrt installed packages with apk: %s", out) + return + } + for _, pkg := range []string{"kmod-tun", "ca-bundle"} { + if !bytes.Contains(out, []byte(pkg)) { + logf("Missing required package %s; run: apk add %s", pkg, pkg) + } + } + } else { // Check for package with opkg (legacy) + out, err := exec.Command("opkg", "list-installed").CombinedOutput() + if err != nil { + logf("error querying OpenWrt installed packages with opkg: %s", out) + return + } + for _, pkg := range []string{"kmod-tun", "ca-bundle"} { + if !bytes.Contains(out, []byte(pkg+" - ")) { + logf("Missing required package %s; run: opkg install %s", pkg, pkg) + } } } } From 19e2c8c49f64c2cf7c8ac52fe1d8a01eedf23c0d Mon Sep 17 00:00:00 2001 From: Raj Singh Date: Thu, 5 Mar 2026 13:47:54 -0500 Subject: [PATCH 185/202] cmd/k8s-proxy: use L4 TCPForward instead of L7 HTTP proxy (#18179) considerable latency was seen when using k8s-proxy with ProxyGroup in the kubernetes operator. Switching to L4 TCPForward solves this. Fixes tailscale#18171 Signed-off-by: chaosinthecrd Co-authored-by: chaosinthecrd --- cmd/k8s-operator/depaware.txt | 2 +- cmd/k8s-proxy/k8s-proxy.go | 22 ++++++++++------------ k8s-operator/api-proxy/proxy.go | 11 ++++++++++- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index 77739350b199c..8718127b6e75f 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -161,7 +161,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ 💣 github.com/modern-go/reflect2 from github.com/json-iterator/go github.com/munnerz/goautoneg from k8s.io/kube-openapi/pkg/handler3+ github.com/opencontainers/go-digest from github.com/distribution/reference - github.com/pires/go-proxyproto from tailscale.com/ipn/ipnlocal + github.com/pires/go-proxyproto from tailscale.com/ipn/ipnlocal+ github.com/pkg/errors from github.com/evanphx/json-patch/v5+ github.com/pmezard/go-difflib/difflib from k8s.io/apimachinery/pkg/util/diff D github.com/prometheus-community/pro-bing from tailscale.com/wgengine/netstack diff --git a/cmd/k8s-proxy/k8s-proxy.go b/cmd/k8s-proxy/k8s-proxy.go index e00d43a948dba..38a86a5e0ade5 100644 --- a/cmd/k8s-proxy/k8s-proxy.go +++ b/cmd/k8s-proxy/k8s-proxy.go @@ -50,6 +50,12 @@ import ( "tailscale.com/tsnet" ) +const ( + // proxyProtocolV2 enables PROXY protocol v2 to preserve original client + // connection info after TLS termination. + proxyProtocolV2 = 2 +) + func main() { encoderCfg := zap.NewProductionEncoderConfig() encoderCfg.EncodeTime = zapcore.RFC3339TimeEncoder @@ -441,24 +447,16 @@ func setServeConfig(ctx context.Context, lc *local.Client, cm *certs.CertManager if err != nil { return fmt.Errorf("error getting local client status: %w", err) } - serviceHostPort := ipn.HostPort(fmt.Sprintf("%s.%s:443", name.WithoutPrefix(), status.CurrentTailnet.MagicDNSSuffix)) + serviceSNI := fmt.Sprintf("%s.%s", name.WithoutPrefix(), status.CurrentTailnet.MagicDNSSuffix) serveConfig := ipn.ServeConfig{ - // Configure for the Service hostname. Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ name: { TCP: map[uint16]*ipn.TCPPortHandler{ 443: { - HTTPS: true, - }, - }, - Web: map[ipn.HostPort]*ipn.WebServerConfig{ - serviceHostPort: { - Handlers: map[string]*ipn.HTTPHandler{ - "/": { - Proxy: "http://localhost:80", - }, - }, + TCPForward: "localhost:80", + TerminateTLS: serviceSNI, + ProxyProtocol: proxyProtocolV2, }, }, }, diff --git a/k8s-operator/api-proxy/proxy.go b/k8s-operator/api-proxy/proxy.go index cbcad1582e673..acc7b62341b83 100644 --- a/k8s-operator/api-proxy/proxy.go +++ b/k8s-operator/api-proxy/proxy.go @@ -21,6 +21,7 @@ import ( "strings" "time" + "github.com/pires/go-proxyproto" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/endpoints/request" @@ -150,10 +151,18 @@ func (ap *APIServerProxy) Run(ctx context.Context) error { } } else { var err error - proxyLn, err = net.Listen("tcp", "localhost:80") + baseLn, err := net.Listen("tcp", "localhost:80") if err != nil { return fmt.Errorf("could not listen on :80: %w", err) } + proxyLn = &proxyproto.Listener{ + Listener: baseLn, + ReadHeaderTimeout: 10 * time.Second, + ConnPolicy: proxyproto.ConnPolicyFunc(func(opts proxyproto.ConnPolicyOptions) (proxyproto.Policy, + error) { + return proxyproto.REQUIRE, nil + }), + } serve = ap.hs.Serve } From 7d43dcad275f1dec405fb7d3d789c4d6494efc51 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Thu, 5 Mar 2026 15:08:34 -0500 Subject: [PATCH 186/202] VERSION.txt: this is v1.96.0 Signed-off-by: Jonathan Nobels --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 55f6ae93382d1..9141007a55821 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.95.0 +1.96.0 From 3b6719d2164c9de9c7dfb822beb8cdbd537eb6c7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 9 Mar 2026 17:51:25 -0500 Subject: [PATCH 187/202] .github/workflows: use tailscale/go for Windows CI too We did so for Linux and macOS already, so also do so for Windows. We only didn't already because originally we never produced binaries for it (due to our corp repo not needing them), and later because we had no ./tool/go wrapper. But we have both of those things now. Updates #18884 Signed-off-by: Brad Fitzpatrick (cherry picked from commit 0023f1a969dfa91ddbe573432a2b790e4b9bdf17) --- .github/workflows/test.yml | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 064765ca2a2af..4f6068e6e33cd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -244,12 +244,6 @@ jobs: with: path: ${{ github.workspace }}/src - - name: Install Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 - with: - go-version-file: ${{ github.workspace }}/src/go.mod - cache: false - - name: Restore Go module cache uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: @@ -269,7 +263,9 @@ jobs: - name: test if: matrix.key != 'win-bench' # skip on bench builder working-directory: src - run: go run ./cmd/testwrapper sharded:${{ matrix.shard }} + run: ./tool/go run ./cmd/testwrapper sharded:${{ matrix.shard }} + env: + NOPWSHDEBUG: "true" # to quiet tool/gocross/gocross-wrapper.ps1 in CI - name: bench all if: matrix.key == 'win-bench' @@ -277,7 +273,9 @@ jobs: # Don't use -bench=. -benchtime=1x. # Somewhere in the layers (powershell?) # the equals signs cause great confusion. - run: go test ./... -bench . -benchtime 1x -run "^$" + run: ./tool/go test ./... -bench . -benchtime 1x -run "^$" + env: + NOPWSHDEBUG: "true" # to quiet tool/gocross/gocross-wrapper.ps1 in CI - name: Print stats shell: pwsh @@ -287,19 +285,6 @@ jobs: run: | Invoke-Expression "$env:GOCACHEPROG --stats" | jq . - win-tool-go: - runs-on: windows-latest - needs: gomod-cache - name: Windows (win-tool-go) - steps: - - name: checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - path: src - - name: test-tool-go - working-directory: src - run: ./tool/go version - macos: runs-on: macos-latest needs: gomod-cache From 013368acf48cd080fe8409dce73c45496bb916eb Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 6 Mar 2026 09:55:57 -0800 Subject: [PATCH 188/202] go.mod: bump to Go 1.26.1 Updates #18682 Change-Id: I855c0dfa4c61eb33123bbb7b00c1ab5506e80b09 Signed-off-by: Brad Fitzpatrick (cherry picked from commit 4453cc5f531f1570904e7e0633647fe5418a67d4) --- go.mod | 2 +- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- go.toolchain.version | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 24c39a4cf3d91..352023fefe006 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module tailscale.com -go 1.26.0 +go 1.26.1 require ( filippo.io/mkcert v1.4.4 diff --git a/go.toolchain.rev b/go.toolchain.rev index ea3d3c773f779..753deba47a297 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -5b5cb0db47535a0a8d2f450cb1bf83af8e70f164 +0f1a3326f30508521e7b8322f4e0f084560c1404 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 34a9b157d33d6..d6105252b02d0 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-f12BE5+H8wHZNKaD6pv9nJJym+1QwxkFNpBtnNcltdc= +sha256-zyo1dIQnrwq8TVxwKCjJ3PfiShjAXO4wMQb/F7ze/mU= diff --git a/go.toolchain.version b/go.toolchain.version index 5ff8c4f5d2ad2..dd43a143f0217 100644 --- a/go.toolchain.version +++ b/go.toolchain.version @@ -1 +1 @@ -1.26.0 +1.26.1 From cf3f31fd0c5eb5486a86bac38ad2516ebf16b1d2 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Mon, 9 Mar 2026 16:23:43 -0500 Subject: [PATCH 189/202] go.mod: bump for internal/poll: move rsan to heap on windows This picks up the change in tailscale/go@5cce30e20c1fc6d8463b0a99acdd9777c4ad124b Updates #18884 Updates tailscale/go#158 Updates golang/go#77975 Signed-off-by: Nick Khyl (cherry picked from commit 8d3efd488dd512afa95af0927e45b3c608e7ae31) --- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.toolchain.rev b/go.toolchain.rev index 753deba47a297..0b07150d516a0 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -0f1a3326f30508521e7b8322f4e0f084560c1404 +5cce30e20c1fc6d8463b0a99acdd9777c4ad124b diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index d6105252b02d0..39306739de25f 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-zyo1dIQnrwq8TVxwKCjJ3PfiShjAXO4wMQb/F7ze/mU= +sha256-nYXUQfKPoHgKCvK5BCh0BKOgPh6n90XX+iUNETLETBA= From 6af139fa027b44475bd3c32e581f115165ab2ebb Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Tue, 10 Mar 2026 10:09:37 -0400 Subject: [PATCH 190/202] VERSION.txt: this is v1.96.1 (#18942) Signed-off-by: Jonathan Nobels --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 9141007a55821..024f4b4caab94 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.96.0 +1.96.1 From f2c3a67044b6f807b0fe2853978c042c936e98c6 Mon Sep 17 00:00:00 2001 From: David Bond Date: Tue, 10 Mar 2026 16:33:13 +0000 Subject: [PATCH 191/202] cmd/k8s-operator: use correct tailnet client for L7 & L3 ingresses (#18749) (#18938) * cmd/k8s-operator: use correct tailnet client for L7 & L3 ingresses This commit fixes a bug when using multi-tailnet within the operator to spin up L7 & L3 ingresses where the client used to create the tailscale services was not switching depending on the tailnet used by the proxygroup backing the service/ingress. Updates: https://github.com/tailscale/corp/issues/34561 * cmd/k8s-operator: adding server url to proxygroups when a custom tailnet has been specified (cherry picked from commit 3b21ac5504e713e32dfcd43d9ee21e7e712ac200) --------- (cherry picked from commit 9522619031287c6dcda68cda11dbcf2baf326ed3) Signed-off-by: David Bond Signed-off-by: chaosinthecrd Co-authored-by: chaosinthecrd --- cmd/k8s-operator/api-server-proxy-pg.go | 57 ++++--- cmd/k8s-operator/api-server-proxy-pg_test.go | 47 +++--- cmd/k8s-operator/ingress-for-pg.go | 154 ++++++++----------- cmd/k8s-operator/ingress-for-pg_test.go | 105 ++++++++----- cmd/k8s-operator/operator.go | 3 - cmd/k8s-operator/proxygroup.go | 73 +++++---- cmd/k8s-operator/sts.go | 48 ++++-- cmd/k8s-operator/svc-for-pg.go | 144 +++++++++-------- cmd/k8s-operator/svc-for-pg_test.go | 14 +- cmd/k8s-operator/tailnet.go | 23 ++- cmd/k8s-operator/testutils_test.go | 17 +- cmd/k8s-operator/tsrecorder.go | 40 +++-- 12 files changed, 403 insertions(+), 322 deletions(-) diff --git a/cmd/k8s-operator/api-server-proxy-pg.go b/cmd/k8s-operator/api-server-proxy-pg.go index ff04d553a7da3..0900fd0aaa264 100644 --- a/cmd/k8s-operator/api-server-proxy-pg.go +++ b/cmd/k8s-operator/api-server-proxy-pg.go @@ -23,6 +23,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "tailscale.com/internal/client/tailscale" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" @@ -52,7 +53,6 @@ type KubeAPIServerTSServiceReconciler struct { logger *zap.SugaredLogger tsClient tsClient tsNamespace string - lc localClient defaultTags []string operatorID string // stableID of the operator's Tailscale device @@ -78,9 +78,14 @@ func (r *KubeAPIServerTSServiceReconciler) Reconcile(ctx context.Context, req re serviceName := serviceNameForAPIServerProxy(pg) logger = logger.With("Tailscale Service", serviceName) + tailscaleClient, err := r.getClient(ctx, pg.Spec.Tailnet) + if err != nil { + return res, fmt.Errorf("failed to get tailscale client: %w", err) + } + if markedForDeletion(pg) { logger.Debugf("ProxyGroup is being deleted, ensuring any created resources are cleaned up") - if err = r.maybeCleanup(ctx, serviceName, pg, logger); err != nil && strings.Contains(err.Error(), optimisticLockErrorMsg) { + if err = r.maybeCleanup(ctx, serviceName, pg, logger, tailscaleClient); err != nil && strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) return res, nil } @@ -88,7 +93,7 @@ func (r *KubeAPIServerTSServiceReconciler) Reconcile(ctx context.Context, req re return res, err } - err = r.maybeProvision(ctx, serviceName, pg, logger) + err = r.maybeProvision(ctx, serviceName, pg, logger, tailscaleClient) if err != nil { if strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) @@ -100,11 +105,27 @@ func (r *KubeAPIServerTSServiceReconciler) Reconcile(ctx context.Context, req re return reconcile.Result{}, nil } +// getClient returns the appropriate Tailscale client for the given tailnet. +// If no tailnet is specified, returns the default client. +func (r *KubeAPIServerTSServiceReconciler) getClient(ctx context.Context, tailnetName string) (tsClient, + error) { + if tailnetName == "" { + return r.tsClient, nil + } + + tc, _, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tailnetName) + if err != nil { + return nil, err + } + + return tc, nil +} + // maybeProvision ensures that a Tailscale Service for this ProxyGroup exists // and is up to date. // // Returns true if the operation resulted in a Tailscale Service update. -func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (err error) { +func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) (err error) { var dnsName string oldPGStatus := pg.Status.DeepCopy() defer func() { @@ -156,7 +177,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s // 1. Check there isn't a Tailscale Service with the same hostname // already created and not owned by this ProxyGroup. - existingTSSvc, err := r.tsClient.GetVIPService(ctx, serviceName) + existingTSSvc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return fmt.Errorf("error getting Tailscale Service %q: %w", serviceName, err) } @@ -198,17 +219,17 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s !ownersAreSetAndEqual(tsSvc, existingTSSvc) || !slices.Equal(tsSvc.Ports, existingTSSvc.Ports) { logger.Infof("Ensuring Tailscale Service exists and is up to date") - if err := r.tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { + if err = tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { return fmt.Errorf("error creating Tailscale Service: %w", err) } } // 3. Ensure that TLS Secret and RBAC exists. - tcd, err := tailnetCertDomain(ctx, r.lc) + dnsName, err = dnsNameForService(ctx, r.Client, serviceName, pg, r.tsNamespace) if err != nil { - return fmt.Errorf("error determining DNS name base: %w", err) + return fmt.Errorf("error determining service DNS name: %w", err) } - dnsName = serviceName.WithoutPrefix() + "." + tcd + if err = r.ensureCertResources(ctx, pg, dnsName); err != nil { return fmt.Errorf("error ensuring cert resources: %w", err) } @@ -219,7 +240,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s } // 5. Clean up any stale Tailscale Services from previous resource versions. - if err = r.maybeDeleteStaleServices(ctx, pg, logger); err != nil { + if err = r.maybeDeleteStaleServices(ctx, pg, logger, tsClient); err != nil { return fmt.Errorf("failed to delete stale Tailscale Services: %w", err) } @@ -230,7 +251,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeProvision(ctx context.Context, s // Service is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only // deleted if it does not contain any other owner references. If it does, the cleanup only removes the owner reference // corresponding to this Service. -func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (err error) { +func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) (err error) { ix := slices.Index(pg.Finalizers, proxyPGFinalizerName) if ix < 0 { logger.Debugf("no finalizer, nothing to do") @@ -244,11 +265,11 @@ func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, ser } }() - if _, err = cleanupTailscaleService(ctx, r.tsClient, serviceName, r.operatorID, logger); err != nil { + if _, err = cleanupTailscaleService(ctx, tsClient, serviceName, r.operatorID, logger); err != nil { return fmt.Errorf("error deleting Tailscale Service: %w", err) } - if err = cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, pg.Name, serviceName); err != nil { + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, serviceName, pg); err != nil { return fmt.Errorf("failed to clean up cert resources: %w", err) } @@ -257,10 +278,10 @@ func (r *KubeAPIServerTSServiceReconciler) maybeCleanup(ctx context.Context, ser // maybeDeleteStaleServices deletes Services that have previously been created for // this ProxyGroup but are no longer needed. -func (r *KubeAPIServerTSServiceReconciler) maybeDeleteStaleServices(ctx context.Context, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) error { +func (r *KubeAPIServerTSServiceReconciler) maybeDeleteStaleServices(ctx context.Context, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) error { serviceName := serviceNameForAPIServerProxy(pg) - svcs, err := r.tsClient.ListVIPServices(ctx) + svcs, err := tsClient.ListVIPServices(ctx) if err != nil { return fmt.Errorf("error listing Tailscale Services: %w", err) } @@ -285,11 +306,11 @@ func (r *KubeAPIServerTSServiceReconciler) maybeDeleteStaleServices(ctx context. } logger.Infof("Deleting Tailscale Service %s", svc.Name) - if err := r.tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { + if err = tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { return fmt.Errorf("error deleting Tailscale Service %s: %w", svc.Name, err) } - if err = cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, pg.Name, svc.Name); err != nil { + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, svc.Name, pg); err != nil { return fmt.Errorf("failed to clean up cert resources: %w", err) } } @@ -343,7 +364,7 @@ func (r *KubeAPIServerTSServiceReconciler) maybeAdvertiseServices(ctx context.Co // Only advertise a Tailscale Service once the TLS certs required for // serving it are available. - shouldBeAdvertised, err := hasCerts(ctx, r.Client, r.lc, r.tsNamespace, serviceName) + shouldBeAdvertised, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg) if err != nil { return fmt.Errorf("error checking TLS credentials provisioned for Tailscale Service %q: %w", serviceName, err) } diff --git a/cmd/k8s-operator/api-server-proxy-pg_test.go b/cmd/k8s-operator/api-server-proxy-pg_test.go index d7e88123fb28b..f7277c70d5717 100644 --- a/cmd/k8s-operator/api-server-proxy-pg_test.go +++ b/cmd/k8s-operator/api-server-proxy-pg_test.go @@ -16,8 +16,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "tailscale.com/internal/client/tailscale" - "tailscale.com/ipn/ipnstate" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/k8s-proxy/conf" @@ -108,14 +108,6 @@ func TestAPIServerProxyReconciler(t *testing.T) { } ft.CreateOrUpdateVIPService(t.Context(), ingressTSSvc) - lc := &fakeLocalClient{ - status: &ipnstate.Status{ - CurrentTailnet: &ipnstate.TailnetStatus{ - MagicDNSSuffix: "ts.net", - }, - }, - } - r := &KubeAPIServerTSServiceReconciler{ Client: fc, tsClient: ft, @@ -123,7 +115,6 @@ func TestAPIServerProxyReconciler(t *testing.T) { tsNamespace: ns, logger: zap.Must(zap.NewDevelopment()).Sugar(), recorder: record.NewFakeRecorder(10), - lc: lc, clock: tstest.NewClock(tstest.ClockOpts{}), operatorID: "self-id", } @@ -148,6 +139,20 @@ func TestAPIServerProxyReconciler(t *testing.T) { if err := ft.DeleteVIPService(t.Context(), "svc:"+pgName); err != nil { t.Fatalf("deleting initial Tailscale Service: %v", err) } + + // Create the state secret for the ProxyGroup without services being advertised. + mustCreate(t, fc, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pg-0", + Namespace: ns, + Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeState), + }, + Data: map[string][]byte{ + "_current-profile": []byte("test"), + "test": []byte(`{"Config":{"NodeID":"node-foo", "UserProfile": {"LoginName": "test-pg.ts.net" }}}`), + }, + }) + expectReconciled(t, r, "", pgName) tsSvc, err := ft.GetVIPService(t.Context(), "svc:"+pgName) @@ -191,17 +196,19 @@ func TestAPIServerProxyReconciler(t *testing.T) { expectEqual(t, fc, pg, omitPGStatusConditionMessages) // Unchanged status. // Simulate Pod prefs updated with advertised services; should see Configured condition updated to true. - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: ns, - Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:test-pg"],"Config":{"NodeID":"node-foo"}}`), - }, + mustUpdate(t, fc, ns, "test-pg-0", func(o *corev1.Secret) { + var p prefs + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:test-pg"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) + expectReconciled(t, r, "", pgName) tsoperator.SetProxyGroupCondition(pg, tsapi.KubeAPIServerProxyConfigured, metav1.ConditionTrue, reasonKubeAPIServerProxyConfigured, "", 1, r.clock, r.logger) pg.Status.URL = "https://" + defaultDomain diff --git a/cmd/k8s-operator/ingress-for-pg.go b/cmd/k8s-operator/ingress-for-pg.go index 5966ace3c388e..60196ce1505ff 100644 --- a/cmd/k8s-operator/ingress-for-pg.go +++ b/cmd/k8s-operator/ingress-for-pg.go @@ -32,7 +32,6 @@ import ( "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -44,22 +43,15 @@ import ( ) const ( - serveConfigKey = "serve-config.json" - TailscaleSvcOwnerRef = "tailscale.com/k8s-operator:owned-by:%s" + serveConfigKey = "serve-config.json" // FinalizerNamePG is the finalizer used by the IngressPGReconciler - FinalizerNamePG = "tailscale.com/ingress-pg-finalizer" - + FinalizerNamePG = "tailscale.com/ingress-pg-finalizer" indexIngressProxyGroup = ".metadata.annotations.ingress-proxy-group" // annotationHTTPEndpoint can be used to configure the Ingress to expose an HTTP endpoint to tailnet (as // well as the default HTTPS endpoint). - annotationHTTPEndpoint = "tailscale.com/http-endpoint" - - labelDomain = "tailscale.com/domain" - msgFeatureFlagNotEnabled = "Tailscale Service feature flag is not enabled for this tailnet, skipping provisioning. " + - "Please contact Tailscale support through https://tailscale.com/contact/support to enable the feature flag, then recreate the operator's Pod." - - warningTailscaleServiceFeatureFlagNotEnabled = "TailscaleServiceFeatureFlagNotEnabled" - managedTSServiceComment = "This Tailscale Service is managed by the Tailscale Kubernetes Operator, do not modify" + annotationHTTPEndpoint = "tailscale.com/http-endpoint" + labelDomain = "tailscale.com/domain" + managedTSServiceComment = "This Tailscale Service is managed by the Tailscale Kubernetes Operator, do not modify" ) var gaugePGIngressResources = clientmetric.NewGauge(kubetypes.MetricIngressPGResourceCount) @@ -74,7 +66,6 @@ type HAIngressReconciler struct { tsClient tsClient tsnetServer tsnetServer tsNamespace string - lc localClient defaultTags []string operatorID string // stableID of the operator's Tailscale device ingressClassName string @@ -108,11 +99,12 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque ing := new(networkingv1.Ingress) err = r.Get(ctx, req.NamespacedName, ing) - if apierrors.IsNotFound(err) { + switch { + case apierrors.IsNotFound(err): // Request object not found, could have been deleted after reconcile request. logger.Debugf("Ingress not found, assuming it was deleted") return res, nil - } else if err != nil { + case err != nil: return res, fmt.Errorf("failed to get Ingress: %w", err) } @@ -122,6 +114,23 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque hostname := hostnameForIngress(ing) logger = logger.With("hostname", hostname) + pgName := ing.Annotations[AnnotationProxyGroup] + pg := &tsapi.ProxyGroup{} + + err = r.Get(ctx, client.ObjectKey{Name: pgName}, pg) + switch { + case apierrors.IsNotFound(err): + logger.Infof("ProxyGroup %q does not exist, it may have been deleted. Reconciliation for ingress %q will be skipped until the ProxyGroup is found", pgName, ing.Name) + return res, nil + case err != nil: + return res, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) + } + + tailscaleClient, err := clientFromProxyGroup(ctx, r.Client, pg, r.tsNamespace, r.tsClient) + if err != nil { + return res, fmt.Errorf("failed to get tailscale client: %w", err) + } + // needsRequeue is set to true if the underlying Tailscale Service has // changed as a result of this reconcile. If that is the case, we // reconcile the Ingress one more time to ensure that concurrent updates @@ -129,9 +138,9 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque // resulted in another actor overwriting our Tailscale Service update. needsRequeue := false if !ing.DeletionTimestamp.IsZero() || !r.shouldExpose(ing) { - needsRequeue, err = r.maybeCleanup(ctx, hostname, ing, logger) + needsRequeue, err = r.maybeCleanup(ctx, hostname, ing, logger, tailscaleClient, pg) } else { - needsRequeue, err = r.maybeProvision(ctx, hostname, ing, logger) + needsRequeue, err = r.maybeProvision(ctx, hostname, ing, logger, tailscaleClient, pg) } if err != nil { return res, err @@ -150,16 +159,16 @@ func (r *HAIngressReconciler) Reconcile(ctx context.Context, req reconcile.Reque // If a Tailscale Service exists, but does not have an owner reference from any operator, we error // out assuming that this is an owner reference created by an unknown actor. // Returns true if the operation resulted in a Tailscale Service update. -func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger, tsClient tsClient, pg *tsapi.ProxyGroup) (svcsChanged bool, err error) { // Currently (2025-05) Tailscale Services are behind an alpha feature flag that // needs to be explicitly enabled for a tailnet to be able to use them. serviceName := tailcfg.ServiceName("svc:" + hostname) - existingTSSvc, err := r.tsClient.GetVIPService(ctx, serviceName) + existingTSSvc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return false, fmt.Errorf("error getting Tailscale Service %q: %w", hostname, err) } - if err := validateIngressClass(ctx, r.Client, r.ingressClassName); err != nil { + if err = validateIngressClass(ctx, r.Client, r.ingressClassName); err != nil { logger.Infof("error validating tailscale IngressClass: %v.", err) return false, nil } @@ -171,14 +180,6 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin } logger = logger.With("ProxyGroup", pgName) - pg := &tsapi.ProxyGroup{} - if err := r.Get(ctx, client.ObjectKey{Name: pgName}, pg); err != nil { - if apierrors.IsNotFound(err) { - logger.Infof("ProxyGroup does not exist") - return false, nil - } - return false, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) - } if !tsoperator.ProxyGroupAvailable(pg) { logger.Infof("ProxyGroup is not (yet) ready") return false, nil @@ -219,7 +220,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin // that in edge cases (a single update changed both hostname and removed // ProxyGroup annotation) the Tailscale Service is more likely to be // (eventually) removed. - svcsChanged, err = r.maybeCleanupProxyGroup(ctx, pgName, logger) + svcsChanged, err = r.maybeCleanupProxyGroup(ctx, logger, tsClient, pg) if err != nil { return false, fmt.Errorf("failed to cleanup Tailscale Service resources for ProxyGroup: %w", err) } @@ -244,12 +245,12 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin return false, nil } // 3. Ensure that TLS Secret and RBAC exists - tcd, err := tailnetCertDomain(ctx, r.lc) + dnsName, err := dnsNameForService(ctx, r.Client, serviceName, pg, r.tsNamespace) if err != nil { - return false, fmt.Errorf("error determining DNS name base: %w", err) + return false, fmt.Errorf("error determining DNS name for service: %w", err) } - dnsName := hostname + "." + tcd - if err := r.ensureCertResources(ctx, pg, dnsName, ing); err != nil { + + if err = r.ensureCertResources(ctx, pg, dnsName, ing); err != nil { return false, fmt.Errorf("error ensuring cert resources: %w", err) } @@ -357,7 +358,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin !reflect.DeepEqual(tsSvc.Ports, existingTSSvc.Ports) || !ownersAreSetAndEqual(tsSvc, existingTSSvc) { logger.Infof("Ensuring Tailscale Service exists and is up to date") - if err := r.tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { + if err := tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { return false, fmt.Errorf("error creating Tailscale Service: %w", err) } } @@ -368,7 +369,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin if isHTTPEndpointEnabled(ing) || isHTTPRedirectEnabled(ing) { mode = serviceAdvertisementHTTPAndHTTPS } - if err = r.maybeUpdateAdvertiseServicesConfig(ctx, pg.Name, serviceName, mode, logger); err != nil { + if err = r.maybeUpdateAdvertiseServicesConfig(ctx, serviceName, mode, pg); err != nil { return false, fmt.Errorf("failed to update tailscaled config: %w", err) } @@ -385,7 +386,7 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin ing.Status.LoadBalancer.Ingress = nil default: var ports []networkingv1.IngressPortStatus - hasCerts, err := hasCerts(ctx, r.Client, r.lc, r.tsNamespace, serviceName) + hasCerts, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg) if err != nil { return false, fmt.Errorf("error checking TLS credentials provisioned for Ingress: %w", err) } @@ -425,9 +426,10 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin logger.Infof("%s. %d Pod(s) advertising Tailscale Service", prefix, count) } - if err := r.Status().Update(ctx, ing); err != nil { + if err = r.Status().Update(ctx, ing); err != nil { return false, fmt.Errorf("failed to update Ingress status: %w", err) } + return svcsChanged, nil } @@ -437,9 +439,9 @@ func (r *HAIngressReconciler) maybeProvision(ctx context.Context, hostname strin // operator instances, else the owner reference is cleaned up. Returns true if // the operation resulted in an existing Tailscale Service updates (owner // reference removal). -func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyGroupName string, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, logger *zap.SugaredLogger, tsClient tsClient, pg *tsapi.ProxyGroup) (svcsChanged bool, err error) { // Get serve config for the ProxyGroup - cm, cfg, err := r.proxyGroupServeConfig(ctx, proxyGroupName) + cm, cfg, err := r.proxyGroupServeConfig(ctx, pg.Name) if err != nil { return false, fmt.Errorf("getting serve config: %w", err) } @@ -467,7 +469,7 @@ func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG if !found { logger.Infof("Tailscale Service %q is not owned by any Ingress, cleaning up", tsSvcName) - tsService, err := r.tsClient.GetVIPService(ctx, tsSvcName) + tsService, err := tsClient.GetVIPService(ctx, tsSvcName) if isErrorTailscaleServiceNotFound(err) { return false, nil } @@ -476,22 +478,24 @@ func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG } // Delete the Tailscale Service from control if necessary. - svcsChanged, err = r.cleanupTailscaleService(ctx, tsService, logger) + svcsChanged, err = r.cleanupTailscaleService(ctx, tsService, logger, tsClient) if err != nil { return false, fmt.Errorf("deleting Tailscale Service %q: %w", tsSvcName, err) } // Make sure the Tailscale Service is not advertised in tailscaled or serve config. - if err = r.maybeUpdateAdvertiseServicesConfig(ctx, proxyGroupName, tsSvcName, serviceAdvertisementOff, logger); err != nil { + if err = r.maybeUpdateAdvertiseServicesConfig(ctx, tsSvcName, serviceAdvertisementOff, pg); err != nil { return false, fmt.Errorf("failed to update tailscaled config services: %w", err) } + _, ok := cfg.Services[tsSvcName] if ok { logger.Infof("Removing Tailscale Service %q from serve config", tsSvcName) delete(cfg.Services, tsSvcName) serveConfigChanged = true } - if err := cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, proxyGroupName, tsSvcName); err != nil { + + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, tsSvcName, pg); err != nil { return false, fmt.Errorf("failed to clean up cert resources: %w", err) } } @@ -514,7 +518,7 @@ func (r *HAIngressReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG // Ingress is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only // deleted if it does not contain any other owner references. If it does the cleanup only removes the owner reference // corresponding to this Ingress. -func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger) (svcChanged bool, err error) { +func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, ing *networkingv1.Ingress, logger *zap.SugaredLogger, tsClient tsClient, pg *tsapi.ProxyGroup) (svcChanged bool, err error) { logger.Debugf("Ensuring any resources for Ingress are cleaned up") ix := slices.Index(ing.Finalizers, FinalizerNamePG) if ix < 0 { @@ -523,7 +527,7 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, } logger.Infof("Ensuring that Tailscale Service %q configuration is cleaned up", hostname) serviceName := tailcfg.ServiceName("svc:" + hostname) - svc, err := r.tsClient.GetVIPService(ctx, serviceName) + svc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return false, fmt.Errorf("error getting Tailscale Service: %w", err) } @@ -537,8 +541,7 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, }() // 1. Check if there is a Tailscale Service associated with this Ingress. - pg := ing.Annotations[AnnotationProxyGroup] - cm, cfg, err := r.proxyGroupServeConfig(ctx, pg) + cm, cfg, err := r.proxyGroupServeConfig(ctx, pg.Name) if err != nil { return false, fmt.Errorf("error getting ProxyGroup serve config: %w", err) } @@ -552,13 +555,13 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, } // 2. Clean up the Tailscale Service resources. - svcChanged, err = r.cleanupTailscaleService(ctx, svc, logger) + svcChanged, err = r.cleanupTailscaleService(ctx, svc, logger, tsClient) if err != nil { return false, fmt.Errorf("error deleting Tailscale Service: %w", err) } // 3. Clean up any cluster resources - if err := cleanupCertResources(ctx, r.Client, r.lc, r.tsNamespace, pg, serviceName); err != nil { + if err = cleanupCertResources(ctx, r.Client, r.tsNamespace, serviceName, pg); err != nil { return false, fmt.Errorf("failed to clean up cert resources: %w", err) } @@ -567,12 +570,12 @@ func (r *HAIngressReconciler) maybeCleanup(ctx context.Context, hostname string, } // 4. Unadvertise the Tailscale Service in tailscaled config. - if err = r.maybeUpdateAdvertiseServicesConfig(ctx, pg, serviceName, serviceAdvertisementOff, logger); err != nil { + if err = r.maybeUpdateAdvertiseServicesConfig(ctx, serviceName, serviceAdvertisementOff, pg); err != nil { return false, fmt.Errorf("failed to update tailscaled config services: %w", err) } // 5. Remove the Tailscale Service from the serve config for the ProxyGroup. - logger.Infof("Removing TailscaleService %q from serve config for ProxyGroup %q", hostname, pg) + logger.Infof("Removing TailscaleService %q from serve config for ProxyGroup %q", hostname, pg.Name) delete(cfg.Services, serviceName) cfgBytes, err := json.Marshal(cfg) if err != nil { @@ -630,19 +633,6 @@ func (r *HAIngressReconciler) proxyGroupServeConfig(ctx context.Context, pg stri return cm, cfg, nil } -type localClient interface { - StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) -} - -// tailnetCertDomain returns the base domain (TCD) of the current tailnet. -func tailnetCertDomain(ctx context.Context, lc localClient) (string, error) { - st, err := lc.StatusWithoutPeers(ctx) - if err != nil { - return "", fmt.Errorf("error getting tailscale status: %w", err) - } - return st.CurrentTailnet.MagicDNSSuffix, nil -} - // shouldExpose returns true if the Ingress should be exposed over Tailscale in HA mode (on a ProxyGroup). func (r *HAIngressReconciler) shouldExpose(ing *networkingv1.Ingress) bool { isTSIngress := ing != nil && @@ -707,7 +697,7 @@ func (r *HAIngressReconciler) validateIngress(ctx context.Context, ing *networki // If a Tailscale Service is found, but contains other owner references, only removes this operator's owner reference. // If a Tailscale Service by the given name is not found or does not contain this operator's owner reference, do nothing. // It returns true if an existing Tailscale Service was updated to remove owner reference, as well as any error that occurred. -func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc *tailscale.VIPService, logger *zap.SugaredLogger) (updated bool, _ error) { +func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc *tailscale.VIPService, logger *zap.SugaredLogger, tsClient tsClient) (updated bool, _ error) { if svc == nil { return false, nil } @@ -730,7 +720,7 @@ func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc * } if len(o.OwnerRefs) == 1 { logger.Infof("Deleting Tailscale Service %q", svc.Name) - if err = r.tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { + if err = tsClient.DeleteVIPService(ctx, svc.Name); err != nil && !isErrorTailscaleServiceNotFound(err) { return false, err } @@ -744,7 +734,7 @@ func (r *HAIngressReconciler) cleanupTailscaleService(ctx context.Context, svc * return false, fmt.Errorf("error marshalling updated Tailscale Service owner reference: %w", err) } svc.Annotations[ownerAnnotation] = string(json) - return true, r.tsClient.CreateOrUpdateVIPService(ctx, svc) + return true, tsClient.CreateOrUpdateVIPService(ctx, svc) } // isHTTPEndpointEnabled returns true if the Ingress has been configured to expose an HTTP endpoint to tailnet. @@ -764,10 +754,10 @@ const ( serviceAdvertisementHTTPAndHTTPS // Both ports 80 and 443 should be advertised ) -func (a *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, pgName string, serviceName tailcfg.ServiceName, mode serviceAdvertisementMode, logger *zap.SugaredLogger) (err error) { +func (r *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, serviceName tailcfg.ServiceName, mode serviceAdvertisementMode, pg *tsapi.ProxyGroup) (err error) { // Get all config Secrets for this ProxyGroup. secrets := &corev1.SecretList{} - if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil { + if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeConfig))); err != nil { return fmt.Errorf("failed to list config Secrets: %w", err) } @@ -779,7 +769,7 @@ func (a *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con // The only exception is Ingresses with an HTTP endpoint enabled - if an // Ingress has an HTTP endpoint enabled, it will be advertised even if the // TLS cert is not yet provisioned. - hasCert, err := hasCerts(ctx, a.Client, a.lc, a.tsNamespace, serviceName) + hasCert, err := hasCerts(ctx, r.Client, r.tsNamespace, serviceName, pg) if err != nil { return fmt.Errorf("error checking TLS credentials provisioned for service %q: %w", serviceName, err) } @@ -819,7 +809,7 @@ func (a *HAIngressReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con } if updated { - if err := a.Update(ctx, &secret); err != nil { + if err := r.Update(ctx, &secret); err != nil { return fmt.Errorf("error updating ProxyGroup config Secret: %w", err) } } @@ -979,12 +969,12 @@ func (r *HAIngressReconciler) ensureCertResources(ctx context.Context, pg *tsapi // cleanupCertResources ensures that the TLS Secret and associated RBAC // resources that allow proxies to read/write to the Secret are deleted. -func cleanupCertResources(ctx context.Context, cl client.Client, lc localClient, tsNamespace, pgName string, serviceName tailcfg.ServiceName) error { - domainName, err := dnsNameForService(ctx, lc, serviceName) +func cleanupCertResources(ctx context.Context, cl client.Client, tsNamespace string, serviceName tailcfg.ServiceName, pg *tsapi.ProxyGroup) error { + domainName, err := dnsNameForService(ctx, cl, serviceName, pg, tsNamespace) if err != nil { return fmt.Errorf("error getting DNS name for Tailscale Service %s: %w", serviceName, err) } - labels := certResourceLabels(pgName, domainName) + labels := certResourceLabels(pg.Name, domainName) if err := cl.DeleteAllOf(ctx, &rbacv1.RoleBinding{}, client.InNamespace(tsNamespace), client.MatchingLabels(labels)); err != nil { return fmt.Errorf("error deleting RoleBinding for domain name %s: %w", domainName, err) } @@ -1094,19 +1084,9 @@ func certResourceLabels(pgName, domain string) map[string]string { } } -// dnsNameForService returns the DNS name for the given Tailscale Service's name. -func dnsNameForService(ctx context.Context, lc localClient, svc tailcfg.ServiceName) (string, error) { - s := svc.WithoutPrefix() - tcd, err := tailnetCertDomain(ctx, lc) - if err != nil { - return "", fmt.Errorf("error determining DNS name base: %w", err) - } - return s + "." + tcd, nil -} - // hasCerts checks if the TLS Secret for the given service has non-zero cert and key data. -func hasCerts(ctx context.Context, cl client.Client, lc localClient, ns string, svc tailcfg.ServiceName) (bool, error) { - domain, err := dnsNameForService(ctx, lc, svc) +func hasCerts(ctx context.Context, cl client.Client, ns string, svc tailcfg.ServiceName, pg *tsapi.ProxyGroup) (bool, error) { + domain, err := dnsNameForService(ctx, cl, svc, pg, ns) if err != nil { return false, fmt.Errorf("failed to get DNS name for service: %w", err) } diff --git a/cmd/k8s-operator/ingress-for-pg_test.go b/cmd/k8s-operator/ingress-for-pg_test.go index f285bd8ee947d..3c9c839177bb5 100644 --- a/cmd/k8s-operator/ingress-for-pg_test.go +++ b/cmd/k8s-operator/ingress-for-pg_test.go @@ -28,7 +28,6 @@ import ( "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" @@ -563,16 +562,18 @@ func TestIngressPGReconciler_HTTPEndpoint(t *testing.T) { } // Add the Tailscale Service to prefs to have the Ingress recognised as ready. - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: "operator-ns", - Labels: pgSecretLabels("test-pg", kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:my-svc"],"Config":{"NodeID":"node-foo"}}`), - }, + mustUpdate(t, fc, "operator-ns", "test-pg-0", func(o *corev1.Secret) { + var p prefs + var err error + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:my-svc"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) // Reconcile and re-fetch Ingress. @@ -686,17 +687,19 @@ func TestIngressPGReconciler_HTTPRedirect(t *testing.T) { t.Fatal(err) } - // Add the Tailscale Service to prefs to have the Ingress recognised as ready - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: "operator-ns", - Labels: pgSecretLabels("test-pg", kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:my-svc"],"Config":{"NodeID":"node-foo"}}`), - }, + // Add the Tailscale Service to prefs to have the Ingress recognised as ready. + mustUpdate(t, fc, "operator-ns", "test-pg-0", func(o *corev1.Secret) { + var p prefs + var err error + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:my-svc"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) // Reconcile and re-fetch Ingress @@ -819,17 +822,19 @@ func TestIngressPGReconciler_HTTPEndpointAndRedirectConflict(t *testing.T) { t.Fatal(err) } - // Add the Tailscale Service to prefs to have the Ingress recognised as ready - mustCreate(t, fc, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pg-0", - Namespace: "operator-ns", - Labels: pgSecretLabels("test-pg", kubetypes.LabelSecretTypeState), - }, - Data: map[string][]byte{ - "_current-profile": []byte("profile-foo"), - "profile-foo": []byte(`{"AdvertiseServices":["svc:my-svc"],"Config":{"NodeID":"node-foo"}}`), - }, + // Add the Tailscale Service to prefs to have the Ingress recognised as ready. + mustUpdate(t, fc, "operator-ns", "test-pg-0", func(o *corev1.Secret) { + var p prefs + var err error + if err = json.Unmarshal(o.Data["test"], &p); err != nil { + t.Errorf("failed to unmarshal preferences: %v", err) + } + + p.AdvertiseServices = []string{"svc:my-svc"} + o.Data["test"], err = json.Marshal(p) + if err != nil { + t.Errorf("failed to marshal preferences: %v", err) + } }) // Reconcile and re-fetch Ingress @@ -1110,6 +1115,7 @@ func verifyTailscaledConfig(t *testing.T, fc client.Client, pgName string, expec func createPGResources(t *testing.T, fc client.Client, pgName string) { t.Helper() + // Pre-create the ProxyGroup pg := &tsapi.ProxyGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -1146,6 +1152,30 @@ func createPGResources(t *testing.T, fc client.Client, pgName string) { }, } mustCreate(t, fc, pgCfgSecret) + + pr := prefs{} + pr.Config.UserProfile.LoginName = "test.ts.net" + pr.Config.NodeID = "test" + + p, err := json.Marshal(pr) + if err != nil { + t.Fatalf("marshaling prefs: %v", err) + } + + // Pre-create a state secret for the ProxyGroup + pgStateSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgStateSecretName(pgName, 0), + Namespace: "operator-ns", + Labels: pgSecretLabels(pgName, kubetypes.LabelSecretTypeState), + }, + Data: map[string][]byte{ + currentProfileKey: []byte("test"), + "test": p, + }, + } + mustCreate(t, fc, pgStateSecret) + pg.Status.Conditions = []metav1.Condition{ { Type: string(tsapi.ProxyGroupAvailable), @@ -1180,14 +1210,6 @@ func setupIngressTest(t *testing.T) (*HAIngressReconciler, client.Client, *fakeT t.Fatal(err) } - lc := &fakeLocalClient{ - status: &ipnstate.Status{ - CurrentTailnet: &ipnstate.TailnetStatus{ - MagicDNSSuffix: "ts.net", - }, - }, - } - ingPGR := &HAIngressReconciler{ Client: fc, tsClient: ft, @@ -1196,7 +1218,6 @@ func setupIngressTest(t *testing.T) (*HAIngressReconciler, client.Client, *fakeT tsnetServer: fakeTsnetServer, logger: zl.Sugar(), recorder: record.NewFakeRecorder(10), - lc: lc, ingressClassName: tsIngressClass.Name, } diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index 81f62d4775671..ef55d27481266 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -441,7 +441,6 @@ func runReconcilers(opts reconcilerOpts) { defaultTags: strings.Split(opts.proxyTags, ","), Client: mgr.GetClient(), logger: opts.log.Named("ingress-pg-reconciler"), - lc: lc, operatorID: id, tsNamespace: opts.tailscaleNamespace, ingressClassName: opts.ingressClassName, @@ -467,7 +466,6 @@ func runReconcilers(opts reconcilerOpts) { defaultTags: strings.Split(opts.proxyTags, ","), Client: mgr.GetClient(), logger: opts.log.Named("service-pg-reconciler"), - lc: lc, clock: tstime.DefaultClock{}, operatorID: id, tsNamespace: opts.tailscaleNamespace, @@ -686,7 +684,6 @@ func runReconcilers(opts reconcilerOpts) { logger: opts.log.Named("kube-apiserver-ts-service-reconciler"), tsClient: opts.tsClient, tsNamespace: opts.tailscaleNamespace, - lc: lc, defaultTags: strings.Split(opts.proxyTags, ","), operatorID: id, clock: tstime.DefaultClock{}, diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index 13c3d7b715e50..bfdfc3dca1765 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -119,20 +119,15 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ return reconcile.Result{}, fmt.Errorf("failed to get tailscale.com ProxyGroup: %w", err) } - tailscaleClient := r.tsClient - if pg.Spec.Tailnet != "" { - tc, err := clientForTailnet(ctx, r.Client, r.tsNamespace, pg.Spec.Tailnet) - if err != nil { - oldPGStatus := pg.Status.DeepCopy() - nrr := ¬ReadyReason{ - reason: reasonProxyGroupTailnetUnavailable, - message: err.Error(), - } - - return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, make(map[string][]netip.AddrPort))) + tailscaleClient, loginUrl, err := r.getClientAndLoginURL(ctx, pg.Spec.Tailnet) + if err != nil { + oldPGStatus := pg.Status.DeepCopy() + nrr := ¬ReadyReason{ + reason: reasonProxyGroupTailnetUnavailable, + message: fmt.Errorf("failed to get tailscale client and loginUrl: %w", err).Error(), } - tailscaleClient = tc + return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, make(map[string][]netip.AddrPort))) } if markedForDeletion(pg) { @@ -162,7 +157,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ } oldPGStatus := pg.Status.DeepCopy() - staticEndpoints, nrr, err := r.reconcilePG(ctx, tailscaleClient, pg, logger) + staticEndpoints, nrr, err := r.reconcilePG(ctx, tailscaleClient, loginUrl, pg, logger) return reconcile.Result{}, errors.Join(err, r.maybeUpdateStatus(ctx, logger, pg, oldPGStatus, nrr, staticEndpoints)) } @@ -170,7 +165,7 @@ func (r *ProxyGroupReconciler) Reconcile(ctx context.Context, req reconcile.Requ // for deletion. It is separated out from Reconcile to make a clear separation // between reconciling the ProxyGroup, and posting the status of its created // resources onto the ProxyGroup status field. -func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (map[string][]netip.AddrPort, *notReadyReason, error) { +func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, tailscaleClient tsClient, loginUrl string, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger) (map[string][]netip.AddrPort, *notReadyReason, error) { if !slices.Contains(pg.Finalizers, FinalizerName) { // This log line is printed exactly once during initial provisioning, // because once the finalizer is in place this block gets skipped. So, @@ -211,7 +206,7 @@ func (r *ProxyGroupReconciler) reconcilePG(ctx context.Context, tailscaleClient return notReady(reasonProxyGroupInvalid, fmt.Sprintf("invalid ProxyGroup spec: %v", err)) } - staticEndpoints, nrr, err := r.maybeProvision(ctx, tailscaleClient, pg, proxyClass) + staticEndpoints, nrr, err := r.maybeProvision(ctx, tailscaleClient, loginUrl, pg, proxyClass) if err != nil { return nil, nrr, err } @@ -297,7 +292,7 @@ func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGrou return errors.Join(errs...) } -func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { +func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, loginUrl string, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { logger := r.logger(pg.Name) r.mu.Lock() r.ensureAddedToGaugeForProxyGroup(pg) @@ -320,7 +315,7 @@ func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClie } } - staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, tailscaleClient, pg, proxyClass, svcToNodePorts) + staticEndpoints, err := r.ensureConfigSecretsCreated(ctx, tailscaleClient, loginUrl, pg, proxyClass, svcToNodePorts) if err != nil { var selectorErr *FindStaticEndpointErr if errors.As(err, &selectorErr) { @@ -631,7 +626,7 @@ func (r *ProxyGroupReconciler) ensureNodePortServiceCreated(ctx context.Context, // tailnet devices when the number of replicas specified is reduced. func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass) error { logger := r.logger(pg.Name) - metadata, err := r.getNodeMetadata(ctx, pg) + metadata, err := getNodeMetadata(ctx, pg, r.Client, r.tsNamespace) if err != nil { return err } @@ -689,7 +684,7 @@ func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, tai func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup) (bool, error) { logger := r.logger(pg.Name) - metadata, err := r.getNodeMetadata(ctx, pg) + metadata, err := getNodeMetadata(ctx, pg, r.Client, r.tsNamespace) if err != nil { return false, err } @@ -735,6 +730,7 @@ func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, tailscal func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( ctx context.Context, tailscaleClient tsClient, + loginUrl string, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass, svcToNodePorts map[string]uint16, @@ -870,8 +866,8 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( } } - if r.loginServer != "" { - cfg.ServerURL = &r.loginServer + if loginUrl != "" { + cfg.ServerURL = new(loginUrl) } if proxyClass != nil && proxyClass.Spec.TailscaleConfig != nil { @@ -899,7 +895,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( return nil, err } - configs, err := pgTailscaledConfig(pg, proxyClass, i, authKey, endpoints[nodePortSvcName], existingAdvertiseServices, r.loginServer) + configs, err := pgTailscaledConfig(pg, loginUrl, proxyClass, i, authKey, endpoints[nodePortSvcName], existingAdvertiseServices) if err != nil { return nil, fmt.Errorf("error creating tailscaled config: %w", err) } @@ -1056,7 +1052,7 @@ func (r *ProxyGroupReconciler) ensureRemovedFromGaugeForProxyGroup(pg *tsapi.Pro gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) } -func pgTailscaledConfig(pg *tsapi.ProxyGroup, pc *tsapi.ProxyClass, idx int32, authKey *string, staticEndpoints []netip.AddrPort, oldAdvertiseServices []string, loginServer string) (tailscaledConfigs, error) { +func pgTailscaledConfig(pg *tsapi.ProxyGroup, loginServer string, pc *tsapi.ProxyClass, idx int32, authKey *string, staticEndpoints []netip.AddrPort, oldAdvertiseServices []string) (tailscaledConfigs, error) { conf := &ipn.ConfigVAlpha{ Version: "alpha0", AcceptDNS: "false", @@ -1107,10 +1103,10 @@ func extractAdvertiseServicesConfig(cfgSecret *corev1.Secret) ([]string, error) // some pods have failed to write state. // // The returned metadata will contain an entry for each state Secret that exists. -func (r *ProxyGroupReconciler) getNodeMetadata(ctx context.Context, pg *tsapi.ProxyGroup) (metadata []nodeMetadata, _ error) { +func getNodeMetadata(ctx context.Context, pg *tsapi.ProxyGroup, cl client.Client, tsNamespace string) (metadata []nodeMetadata, _ error) { // List all state Secrets owned by this ProxyGroup. secrets := &corev1.SecretList{} - if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeState))); err != nil { + if err := cl.List(ctx, secrets, client.InNamespace(tsNamespace), client.MatchingLabels(pgSecretLabels(pg.Name, kubetypes.LabelSecretTypeState))); err != nil { return nil, fmt.Errorf("failed to list state Secrets: %w", err) } for _, secret := range secrets.Items { @@ -1134,7 +1130,7 @@ func (r *ProxyGroupReconciler) getNodeMetadata(ctx context.Context, pg *tsapi.Pr } pod := &corev1.Pod{} - if err := r.Get(ctx, client.ObjectKey{Namespace: r.tsNamespace, Name: fmt.Sprintf("%s-%d", pg.Name, ordinal)}, pod); err != nil && !apierrors.IsNotFound(err) { + if err := cl.Get(ctx, client.ObjectKey{Namespace: tsNamespace, Name: fmt.Sprintf("%s-%d", pg.Name, ordinal)}, pod); err != nil && !apierrors.IsNotFound(err) { return nil, err } else if err == nil { nm.podUID = string(pod.UID) @@ -1153,7 +1149,7 @@ func (r *ProxyGroupReconciler) getNodeMetadata(ctx context.Context, pg *tsapi.Pr // getRunningProxies will return status for all proxy Pods whose state Secret // has an up to date Pod UID and at least a hostname. func (r *ProxyGroupReconciler) getRunningProxies(ctx context.Context, pg *tsapi.ProxyGroup, staticEndpoints map[string][]netip.AddrPort) (devices []tsapi.TailnetDevice, _ error) { - metadata, err := r.getNodeMetadata(ctx, pg) + metadata, err := getNodeMetadata(ctx, pg, r.Client, r.tsNamespace) if err != nil { return nil, err } @@ -1197,6 +1193,29 @@ func (r *ProxyGroupReconciler) getRunningProxies(ctx context.Context, pg *tsapi. return devices, nil } +// getClientAndLoginURL returns the appropriate Tailscale client and resolved login URL +// for the given tailnet name. If no tailnet is specified, returns the default client +// and login server. Applies fallback to the operator's login server if the tailnet +// doesn't specify a custom login URL. +func (r *ProxyGroupReconciler) getClientAndLoginURL(ctx context.Context, tailnetName string) (tsClient, + string, error) { + if tailnetName == "" { + return r.tsClient, r.loginServer, nil + } + + tc, loginUrl, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tailnetName) + if err != nil { + return nil, "", err + } + + // Apply fallback if tailnet doesn't specify custom login URL + if loginUrl == "" { + loginUrl = r.loginServer + } + + return tc, loginUrl, nil +} + type nodeMetadata struct { ordinal int stateSecret *corev1.Secret diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 85aab2e8a0d2a..f98e3624a5e9f 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -198,14 +198,9 @@ func IsHTTPSEnabledOnTailnet(tsnetServer tsnetServer) bool { // Provision ensures that the StatefulSet for the given service is running and // up to date. func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) (*corev1.Service, error) { - tailscaleClient := a.tsClient - if sts.Tailnet != "" { - tc, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, sts.Tailnet) - if err != nil { - return nil, err - } - - tailscaleClient = tc + tailscaleClient, loginUrl, err := a.getClientAndLoginURL(ctx, sts.Tailnet) + if err != nil { + return nil, fmt.Errorf("failed to get tailscale client and loginUrl: %w", err) } // Do full reconcile. @@ -227,7 +222,7 @@ func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.Suga } sts.ProxyClass = proxyClass - secretNames, err := a.provisionSecrets(ctx, tailscaleClient, logger, sts, hsvc) + secretNames, err := a.provisionSecrets(ctx, tailscaleClient, loginUrl, sts, hsvc, logger) if err != nil { return nil, fmt.Errorf("failed to create or get API key secret: %w", err) } @@ -248,13 +243,36 @@ func (a *tailscaleSTSReconciler) Provision(ctx context.Context, logger *zap.Suga return hsvc, nil } +// getClientAndLoginURL returns the appropriate Tailscale client and resolved login URL +// for the given tailnet name. If no tailnet is specified, returns the default client +// and login server. Applies fallback to the operator's login server if the tailnet +// doesn't specify a custom login URL. +func (a *tailscaleSTSReconciler) getClientAndLoginURL(ctx context.Context, tailnetName string) (tsClient, + string, error) { + if tailnetName == "" { + return a.tsClient, a.loginServer, nil + } + + tc, loginUrl, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, tailnetName) + if err != nil { + return nil, "", err + } + + // Apply fallback if tailnet doesn't specify custom login URL + if loginUrl == "" { + loginUrl = a.loginServer + } + + return tc, loginUrl, nil +} + // Cleanup removes all resources associated that were created by Provision with // the given labels. It returns true when all resources have been removed, // otherwise it returns false and the caller should retry later. func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, tailnet string, logger *zap.SugaredLogger, labels map[string]string, typ string) (done bool, _ error) { tailscaleClient := a.tsClient if tailnet != "" { - tc, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, tailnet) + tc, _, err := clientForTailnet(ctx, a.Client, a.operatorNamespace, tailnet) if err != nil { logger.Errorf("failed to get tailscale client: %v", err) return false, nil @@ -385,7 +403,7 @@ func (a *tailscaleSTSReconciler) reconcileHeadlessService(ctx context.Context, l return createOrUpdate(ctx, a.Client, a.operatorNamespace, hsvc, func(svc *corev1.Service) { svc.Spec = hsvc.Spec }) } -func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, tailscaleClient tsClient, logger *zap.SugaredLogger, stsC *tailscaleSTSConfig, hsvc *corev1.Service) ([]string, error) { +func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, tailscaleClient tsClient, loginUrl string, stsC *tailscaleSTSConfig, hsvc *corev1.Service, logger *zap.SugaredLogger) ([]string, error) { secretNames := make([]string, stsC.Replicas) // Start by ensuring we have Secrets for the desired number of replicas. This will handle both creating and scaling @@ -434,7 +452,7 @@ func (a *tailscaleSTSReconciler) provisionSecrets(ctx context.Context, tailscale } } - configs, err := tailscaledConfig(stsC, authKey, orig, hostname) + configs, err := tailscaledConfig(stsC, loginUrl, authKey, orig, hostname) if err != nil { return nil, fmt.Errorf("error creating tailscaled config: %w", err) } @@ -1067,7 +1085,7 @@ func isMainContainer(c *corev1.Container) bool { // tailscaledConfig takes a proxy config, a newly generated auth key if generated and a Secret with the previous proxy // state and auth key and returns tailscaled config files for currently supported proxy versions. -func tailscaledConfig(stsC *tailscaleSTSConfig, newAuthkey string, oldSecret *corev1.Secret, hostname string) (tailscaledConfigs, error) { +func tailscaledConfig(stsC *tailscaleSTSConfig, loginUrl string, newAuthkey string, oldSecret *corev1.Secret, hostname string) (tailscaledConfigs, error) { conf := &ipn.ConfigVAlpha{ Version: "alpha0", AcceptDNS: "false", @@ -1106,6 +1124,10 @@ func tailscaledConfig(stsC *tailscaleSTSConfig, newAuthkey string, oldSecret *co conf.AuthKey = key } + if loginUrl != "" { + conf.ServerURL = new(loginUrl) + } + capVerConfigs := make(map[tailcfg.CapabilityVersion]ipn.ConfigVAlpha) capVerConfigs[107] = *conf diff --git a/cmd/k8s-operator/svc-for-pg.go b/cmd/k8s-operator/svc-for-pg.go index e0383824a6313..3e58db1b6cb0f 100644 --- a/cmd/k8s-operator/svc-for-pg.go +++ b/cmd/k8s-operator/svc-for-pg.go @@ -27,6 +27,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" tsoperator "tailscale.com/k8s-operator" @@ -41,13 +42,10 @@ import ( ) const ( - svcPGFinalizerName = "tailscale.com/service-pg-finalizer" - + svcPGFinalizerName = "tailscale.com/service-pg-finalizer" reasonIngressSvcInvalid = "IngressSvcInvalid" - reasonIngressSvcValid = "IngressSvcValid" reasonIngressSvcConfigured = "IngressSvcConfigured" reasonIngressSvcNoBackendsConfigured = "IngressSvcNoBackendsConfigured" - reasonIngressSvcCreationFailed = "IngressSvcCreationFailed" ) var gaugePGServiceResources = clientmetric.NewGauge(kubetypes.MetricServicePGResourceCount) @@ -61,7 +59,6 @@ type HAServiceReconciler struct { logger *zap.SugaredLogger tsClient tsClient tsNamespace string - lc localClient defaultTags []string operatorID string // stableID of the operator's Tailscale device @@ -100,12 +97,41 @@ func (r *HAServiceReconciler) Reconcile(ctx context.Context, req reconcile.Reque return res, fmt.Errorf("failed to get Service: %w", err) } + pgName := svc.Annotations[AnnotationProxyGroup] + if pgName == "" { + logger.Infof("[unexpected] no ProxyGroup annotation, skipping Tailscale Service provisioning") + return res, nil + } + + logger = logger.With("ProxyGroup", pgName) + + pg := &tsapi.ProxyGroup{} + err = r.Get(ctx, client.ObjectKey{Name: pgName}, pg) + switch { + case apierrors.IsNotFound(err): + logger.Infof("ProxyGroup %q does not exist, it may have been deleted. Reconciliation for service %q will be skipped until the ProxyGroup is found", pgName, svc.Name) + r.recorder.Event(svc, corev1.EventTypeWarning, "ProxyGroupNotFound", "ProxyGroup not found") + return res, nil + case err != nil: + return res, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) + } + + if !tsoperator.ProxyGroupAvailable(pg) { + logger.Infof("ProxyGroup is not (yet) ready") + return res, nil + } + + tailscaleClient, err := clientFromProxyGroup(ctx, r.Client, pg, r.tsNamespace, r.tsClient) + if err != nil { + return res, fmt.Errorf("failed to get tailscale client: %w", err) + } + hostname := nameForService(svc) logger = logger.With("hostname", hostname) if !svc.DeletionTimestamp.IsZero() || !r.isTailscaleService(svc) { logger.Debugf("Service is being deleted or is (no longer) referring to Tailscale ingress/egress, ensuring any created resources are cleaned up") - _, err = r.maybeCleanup(ctx, hostname, svc, logger) + _, err = r.maybeCleanup(ctx, hostname, svc, logger, tailscaleClient) return res, err } @@ -113,7 +139,7 @@ func (r *HAServiceReconciler) Reconcile(ctx context.Context, req reconcile.Reque // is the case, we reconcile the Ingress one more time to ensure that concurrent updates to the Tailscale Service in a // multi-cluster Ingress setup have not resulted in another actor overwriting our Tailscale Service update. needsRequeue := false - needsRequeue, err = r.maybeProvision(ctx, hostname, svc, logger) + needsRequeue, err = r.maybeProvision(ctx, hostname, svc, pg, logger, tailscaleClient) if err != nil { if strings.Contains(err.Error(), optimisticLockErrorMsg) { logger.Infof("optimistic lock error, retrying: %s", err) @@ -136,7 +162,7 @@ func (r *HAServiceReconciler) Reconcile(ctx context.Context, req reconcile.Reque // If a Tailscale Service exists, but does not have an owner reference from any operator, we error // out assuming that this is an owner reference created by an unknown actor. // Returns true if the operation resulted in a Tailscale Service update. -func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname string, svc *corev1.Service, pg *tsapi.ProxyGroup, logger *zap.SugaredLogger, tsClient tsClient) (svcsChanged bool, err error) { oldSvcStatus := svc.Status.DeepCopy() defer func() { if !apiequality.Semantic.DeepEqual(oldSvcStatus, &svc.Status) { @@ -145,30 +171,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin } }() - pgName := svc.Annotations[AnnotationProxyGroup] - if pgName == "" { - logger.Infof("[unexpected] no ProxyGroup annotation, skipping Tailscale Service provisioning") - return false, nil - } - - logger = logger.With("ProxyGroup", pgName) - - pg := &tsapi.ProxyGroup{} - if err := r.Get(ctx, client.ObjectKey{Name: pgName}, pg); err != nil { - if apierrors.IsNotFound(err) { - msg := fmt.Sprintf("ProxyGroup %q does not exist", pgName) - logger.Warnf(msg) - r.recorder.Event(svc, corev1.EventTypeWarning, "ProxyGroupNotFound", msg) - return false, nil - } - return false, fmt.Errorf("getting ProxyGroup %q: %w", pgName, err) - } - if !tsoperator.ProxyGroupAvailable(pg) { - logger.Infof("ProxyGroup is not (yet) ready") - return false, nil - } - - if err := r.validateService(ctx, svc, pg); err != nil { + if err = r.validateService(ctx, svc, pg); err != nil { r.recorder.Event(svc, corev1.EventTypeWarning, reasonIngressSvcInvalid, err.Error()) tsoperator.SetServiceCondition(svc, tsapi.IngressSvcValid, metav1.ConditionFalse, reasonIngressSvcInvalid, err.Error(), r.clock, logger) return false, nil @@ -198,7 +201,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin // that in edge cases (a single update changed both hostname and removed // ProxyGroup annotation) the Tailscale Service is more likely to be // (eventually) removed. - svcsChanged, err = r.maybeCleanupProxyGroup(ctx, pgName, logger) + svcsChanged, err = r.maybeCleanupProxyGroup(ctx, pg.Name, logger, tsClient) if err != nil { return false, fmt.Errorf("failed to cleanup Tailscale Service resources for ProxyGroup: %w", err) } @@ -206,7 +209,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin // 2. Ensure that there isn't a Tailscale Service with the same hostname // already created and not owned by this Service. serviceName := tailcfg.ServiceName("svc:" + hostname) - existingTSSvc, err := r.tsClient.GetVIPService(ctx, serviceName) + existingTSSvc, err := tsClient.GetVIPService(ctx, serviceName) if err != nil && !isErrorTailscaleServiceNotFound(err) { return false, fmt.Errorf("error getting Tailscale Service %q: %w", hostname, err) } @@ -248,13 +251,13 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin !reflect.DeepEqual(tsSvc.Tags, existingTSSvc.Tags) || !ownersAreSetAndEqual(tsSvc, existingTSSvc) { logger.Infof("Ensuring Tailscale Service exists and is up to date") - if err := r.tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { + if err := tsClient.CreateOrUpdateVIPService(ctx, tsSvc); err != nil { return false, fmt.Errorf("error creating Tailscale Service: %w", err) } existingTSSvc = tsSvc } - cm, cfgs, err := ingressSvcsConfigs(ctx, r.Client, pgName, r.tsNamespace) + cm, cfgs, err := ingressSvcsConfigs(ctx, r.Client, pg.Name, r.tsNamespace) if err != nil { return false, fmt.Errorf("error retrieving ingress services configuration: %w", err) } @@ -264,7 +267,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin } if existingTSSvc.Addrs == nil { - existingTSSvc, err = r.tsClient.GetVIPService(ctx, tsSvc.Name) + existingTSSvc, err = tsClient.GetVIPService(ctx, tsSvc.Name) if err != nil { return false, fmt.Errorf("error getting Tailscale Service: %w", err) } @@ -329,7 +332,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin return false, fmt.Errorf("failed to update tailscaled config: %w", err) } - count, err := r.numberPodsAdvertising(ctx, pgName, serviceName) + count, err := r.numberPodsAdvertising(ctx, pg.Name, serviceName) if err != nil { return false, fmt.Errorf("failed to get number of advertised Pods: %w", err) } @@ -345,7 +348,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin conditionReason := reasonIngressSvcNoBackendsConfigured conditionMessage := fmt.Sprintf("%d/%d proxy backends ready and advertising", count, pgReplicas(pg)) if count != 0 { - dnsName, err := r.dnsNameForService(ctx, serviceName) + dnsName, err := dnsNameForService(ctx, r.Client, serviceName, pg, r.tsNamespace) if err != nil { return false, fmt.Errorf("error getting DNS name for Service: %w", err) } @@ -371,7 +374,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin // Service is being deleted or is unexposed. The cleanup is safe for a multi-cluster setup- the Tailscale Service is only // deleted if it does not contain any other owner references. If it does the cleanup only removes the owner reference // corresponding to this Service. -func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger) (svcChanged bool, err error) { +func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger, tsClient tsClient) (svcChanged bool, err error) { logger.Debugf("Ensuring any resources for Service are cleaned up") ix := slices.Index(svc.Finalizers, svcPGFinalizerName) if ix < 0 { @@ -389,7 +392,7 @@ func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, serviceName := tailcfg.ServiceName("svc:" + hostname) // 1. Clean up the Tailscale Service. - svcChanged, err = cleanupTailscaleService(ctx, r.tsClient, serviceName, r.operatorID, logger) + svcChanged, err = cleanupTailscaleService(ctx, tsClient, serviceName, r.operatorID, logger) if err != nil { return false, fmt.Errorf("error deleting Tailscale Service: %w", err) } @@ -422,14 +425,14 @@ func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, // Tailscale Services that are associated with the provided ProxyGroup and no longer managed this operator's instance are deleted, if not owned by other operator instances, else the owner reference is cleaned up. // Returns true if the operation resulted in existing Tailscale Service updates (owner reference removal). -func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyGroupName string, logger *zap.SugaredLogger) (svcsChanged bool, err error) { +func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyGroupName string, logger *zap.SugaredLogger, tsClient tsClient) (svcsChanged bool, err error) { cm, config, err := ingressSvcsConfigs(ctx, r.Client, proxyGroupName, r.tsNamespace) if err != nil { return false, fmt.Errorf("failed to get ingress service config: %s", err) } svcList := &corev1.ServiceList{} - if err := r.Client.List(ctx, svcList, client.MatchingFields{indexIngressProxyGroup: proxyGroupName}); err != nil { + if err = r.Client.List(ctx, svcList, client.MatchingFields{indexIngressProxyGroup: proxyGroupName}); err != nil { return false, fmt.Errorf("failed to find Services for ProxyGroup %q: %w", proxyGroupName, err) } @@ -450,7 +453,7 @@ func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG return false, fmt.Errorf("failed to update tailscaled config services: %w", err) } - svcsChanged, err = cleanupTailscaleService(ctx, r.tsClient, tailcfg.ServiceName(tsSvcName), r.operatorID, logger) + svcsChanged, err = cleanupTailscaleService(ctx, tsClient, tailcfg.ServiceName(tsSvcName), r.operatorID, logger) if err != nil { return false, fmt.Errorf("deleting Tailscale Service %q: %w", tsSvcName, err) } @@ -510,15 +513,6 @@ func (r *HAServiceReconciler) shouldExposeClusterIP(svc *corev1.Service) bool { return isTailscaleLoadBalancerService(svc, r.isDefaultLoadBalancer) || hasExposeAnnotation(svc) } -// tailnetCertDomain returns the base domain (TCD) of the current tailnet. -func (r *HAServiceReconciler) tailnetCertDomain(ctx context.Context) (string, error) { - st, err := r.lc.StatusWithoutPeers(ctx) - if err != nil { - return "", fmt.Errorf("error getting tailscale status: %w", err) - } - return st.CurrentTailnet.MagicDNSSuffix, nil -} - // cleanupTailscaleService deletes any Tailscale Service by the provided name if it is not owned by operator instances other than this one. // If a Tailscale Service is found, but contains other owner references, only removes this operator's owner reference. // If a Tailscale Service by the given name is not found or does not contain this operator's owner reference, do nothing. @@ -571,10 +565,10 @@ func cleanupTailscaleService(ctx context.Context, tsClient tsClient, name tailcf return true, tsClient.CreateOrUpdateVIPService(ctx, svc) } -func (a *HAServiceReconciler) backendRoutesSetup(ctx context.Context, serviceName, replicaName, pgName string, wantsCfg *ingressservices.Config, logger *zap.SugaredLogger) (bool, error) { +func (r *HAServiceReconciler) backendRoutesSetup(ctx context.Context, serviceName, replicaName string, wantsCfg *ingressservices.Config, logger *zap.SugaredLogger) (bool, error) { logger.Debugf("checking backend routes for service '%s'", serviceName) pod := &corev1.Pod{} - err := a.Get(ctx, client.ObjectKey{Namespace: a.tsNamespace, Name: replicaName}, pod) + err := r.Get(ctx, client.ObjectKey{Namespace: r.tsNamespace, Name: replicaName}, pod) if apierrors.IsNotFound(err) { logger.Debugf("Pod %q not found", replicaName) return false, nil @@ -583,7 +577,7 @@ func (a *HAServiceReconciler) backendRoutesSetup(ctx context.Context, serviceNam return false, fmt.Errorf("failed to get Pod: %w", err) } secret := &corev1.Secret{} - err = a.Get(ctx, client.ObjectKey{Namespace: a.tsNamespace, Name: replicaName}, secret) + err = r.Get(ctx, client.ObjectKey{Namespace: r.tsNamespace, Name: replicaName}, secret) if apierrors.IsNotFound(err) { logger.Debugf("Secret %q not found", replicaName) return false, nil @@ -638,17 +632,17 @@ func isCurrentStatus(gotCfgs ingressservices.Status, pod *corev1.Pod, logger *za return true, nil } -func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, svc *corev1.Service, pgName string, serviceName tailcfg.ServiceName, cfg *ingressservices.Config, shouldBeAdvertised bool, logger *zap.SugaredLogger) (err error) { +func (r *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Context, svc *corev1.Service, pgName string, serviceName tailcfg.ServiceName, cfg *ingressservices.Config, shouldBeAdvertised bool, logger *zap.SugaredLogger) (err error) { logger.Debugf("checking advertisement for service '%s'", serviceName) // Get all config Secrets for this ProxyGroup. // Get all Pods secrets := &corev1.SecretList{} - if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil { + if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil { return fmt.Errorf("failed to list config Secrets: %w", err) } if svc != nil && shouldBeAdvertised { - shouldBeAdvertised, err = a.checkEndpointsReady(ctx, svc, logger) + shouldBeAdvertised, err = r.checkEndpointsReady(ctx, svc, logger) if err != nil { return fmt.Errorf("failed to check readiness of Service '%s' endpoints: %w", svc.Name, err) } @@ -680,7 +674,7 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con logger.Infof("[unexpected] unable to determine replica name from config Secret name %q, unable to determine if backend routing has been configured", secret.Name) return nil } - ready, err := a.backendRoutesSetup(ctx, serviceName.String(), replicaName, pgName, cfg, logger) + ready, err := r.backendRoutesSetup(ctx, serviceName.String(), replicaName, cfg, logger) if err != nil { return fmt.Errorf("error checking backend routes: %w", err) } @@ -699,7 +693,7 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con updated = true } if updated { - if err := a.Update(ctx, &secret); err != nil { + if err := r.Update(ctx, &secret); err != nil { return fmt.Errorf("error updating ProxyGroup config Secret: %w", err) } } @@ -707,10 +701,10 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con return nil } -func (a *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName string, serviceName tailcfg.ServiceName) (int, error) { +func (r *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName string, serviceName tailcfg.ServiceName) (int, error) { // Get all state Secrets for this ProxyGroup. secrets := &corev1.SecretList{} - if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeState))); err != nil { + if err := r.List(ctx, secrets, client.InNamespace(r.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeState))); err != nil { return 0, fmt.Errorf("failed to list ProxyGroup %q state Secrets: %w", pgName, err) } @@ -732,12 +726,28 @@ func (a *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName } // dnsNameForService returns the DNS name for the given Tailscale Service name. -func (r *HAServiceReconciler) dnsNameForService(ctx context.Context, svc tailcfg.ServiceName) (string, error) { +func dnsNameForService(ctx context.Context, cl client.Client, svc tailcfg.ServiceName, pg *tsapi.ProxyGroup, namespace string) (string, error) { s := svc.WithoutPrefix() - tcd, err := r.tailnetCertDomain(ctx) - if err != nil { - return "", fmt.Errorf("error determining DNS name base: %w", err) + + md, err := getNodeMetadata(ctx, pg, cl, namespace) + switch { + case err != nil: + return "", fmt.Errorf("error getting node metadata: %w", err) + case len(md) == 0: + return "", fmt.Errorf("failed to find node metadata for ProxyGroup %q", pg.Name) + } + + // To determine the appropriate magic DNS name we take the first dns name we can find that is not empty and + // contains a period. + idx := slices.IndexFunc(md, func(metadata nodeMetadata) bool { + return metadata.dnsName != "" && strings.ContainsRune(metadata.dnsName, '.') + }) + if idx == -1 { + return "", fmt.Errorf("failed to find dns name for ProxyGroup %q", pg.Name) } + + tcd := strings.SplitN(md[idx].dnsName, ".", 2)[1] + return s + "." + tcd, nil } diff --git a/cmd/k8s-operator/svc-for-pg_test.go b/cmd/k8s-operator/svc-for-pg_test.go index d01f8e983ad75..07a2393115330 100644 --- a/cmd/k8s-operator/svc-for-pg_test.go +++ b/cmd/k8s-operator/svc-for-pg_test.go @@ -22,7 +22,7 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "tailscale.com/ipn/ipnstate" + tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/ingressservices" @@ -195,14 +195,6 @@ func setupServiceTest(t *testing.T) (*HAServiceReconciler, *corev1.Secret, clien t.Fatal(err) } - lc := &fakeLocalClient{ - status: &ipnstate.Status{ - CurrentTailnet: &ipnstate.TailnetStatus{ - MagicDNSSuffix: "ts.net", - }, - }, - } - cl := tstest.NewClock(tstest.ClockOpts{}) svcPGR := &HAServiceReconciler{ Client: fc, @@ -212,7 +204,6 @@ func setupServiceTest(t *testing.T) (*HAServiceReconciler, *corev1.Secret, clien tsNamespace: "operator-ns", logger: zl.Sugar(), recorder: record.NewFakeRecorder(10), - lc: lc, } return svcPGR, pgStateSecret, fc, ft, cl @@ -280,15 +271,12 @@ func TestValidateService(t *testing.T) { func TestServicePGReconciler_MultiCluster(t *testing.T) { var ft *fakeTSClient - var lc localClient for i := 0; i <= 10; i++ { pgr, stateSecret, fc, fti, _ := setupServiceTest(t) if i == 0 { ft = fti - lc = pgr.lc } else { pgr.tsClient = ft - pgr.lc = lc } svc, _ := setupTestService(t, "test-multi-cluster", "", "4.3.2.1", fc, stateSecret) diff --git a/cmd/k8s-operator/tailnet.go b/cmd/k8s-operator/tailnet.go index 57c749bec31ee..439489f750665 100644 --- a/cmd/k8s-operator/tailnet.go +++ b/cmd/k8s-operator/tailnet.go @@ -20,19 +20,19 @@ import ( tsapi "tailscale.com/k8s-operator/apis/v1alpha1" ) -func clientForTailnet(ctx context.Context, cl client.Client, namespace, name string) (tsClient, error) { +func clientForTailnet(ctx context.Context, cl client.Client, namespace, name string) (tsClient, string, error) { var tn tsapi.Tailnet if err := cl.Get(ctx, client.ObjectKey{Name: name}, &tn); err != nil { - return nil, fmt.Errorf("failed to get tailnet %q: %w", name, err) + return nil, "", fmt.Errorf("failed to get tailnet %q: %w", name, err) } if !operatorutils.TailnetIsReady(&tn) { - return nil, fmt.Errorf("tailnet %q is not ready", name) + return nil, "", fmt.Errorf("tailnet %q is not ready", name) } var secret corev1.Secret if err := cl.Get(ctx, client.ObjectKey{Name: tn.Spec.Credentials.SecretName, Namespace: namespace}, &secret); err != nil { - return nil, fmt.Errorf("failed to get Secret %q in namespace %q: %w", tn.Spec.Credentials.SecretName, namespace, err) + return nil, "", fmt.Errorf("failed to get Secret %q in namespace %q: %w", tn.Spec.Credentials.SecretName, namespace, err) } baseURL := ipn.DefaultControlURL @@ -54,5 +54,18 @@ func clientForTailnet(ctx context.Context, cl client.Client, namespace, name str ts.HTTPClient = httpClient ts.BaseURL = baseURL - return ts, nil + return ts, baseURL, nil +} + +func clientFromProxyGroup(ctx context.Context, cl client.Client, pg *tsapi.ProxyGroup, namespace string, def tsClient) (tsClient, error) { + if pg.Spec.Tailnet == "" { + return def, nil + } + + tailscaleClient, _, err := clientForTailnet(ctx, cl, namespace, pg.Spec.Tailnet) + if err != nil { + return nil, err + } + + return tailscaleClient, nil } diff --git a/cmd/k8s-operator/testutils_test.go b/cmd/k8s-operator/testutils_test.go index 54b7ead55f7ff..feed98d507a4e 100644 --- a/cmd/k8s-operator/testutils_test.go +++ b/cmd/k8s-operator/testutils_test.go @@ -30,9 +30,9 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "tailscale.com/internal/client/tailscale" "tailscale.com/ipn" - "tailscale.com/ipn/ipnstate" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/kubetypes" "tailscale.com/tailcfg" @@ -988,18 +988,3 @@ func (c *fakeTSClient) DeleteVIPService(ctx context.Context, name tailcfg.Servic } return nil } - -type fakeLocalClient struct { - status *ipnstate.Status -} - -func (f *fakeLocalClient) StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) { - if f.status == nil { - return &ipnstate.Status{ - Self: &ipnstate.PeerStatus{ - DNSName: "test-node.test.ts.net.", - }, - }, nil - } - return f.status, nil -} diff --git a/cmd/k8s-operator/tsrecorder.go b/cmd/k8s-operator/tsrecorder.go index 60ed24a7006b1..3857908f2bc1c 100644 --- a/cmd/k8s-operator/tsrecorder.go +++ b/cmd/k8s-operator/tsrecorder.go @@ -99,14 +99,9 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return reconcile.Result{}, nil } - tailscaleClient := r.tsClient - if tsr.Spec.Tailnet != "" { - tc, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tsr.Spec.Tailnet) - if err != nil { - return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderTailnetUnavailable, err.Error()) - } - - tailscaleClient = tc + tailscaleClient, loginUrl, err := r.getClientAndLoginURL(ctx, tsr.Spec.Tailnet) + if err != nil { + return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderTailnetUnavailable, err.Error()) } if markedForDeletion(tsr) { @@ -149,7 +144,7 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return setStatusReady(tsr, metav1.ConditionFalse, reasonRecorderInvalid, message) } - if err = r.maybeProvision(ctx, tailscaleClient, tsr); err != nil { + if err = r.maybeProvision(ctx, tailscaleClient, loginUrl, tsr); err != nil { reason := reasonRecorderCreationFailed message := fmt.Sprintf("failed creating Recorder: %s", err) if strings.Contains(err.Error(), optimisticLockErrorMsg) { @@ -167,7 +162,30 @@ func (r *RecorderReconciler) Reconcile(ctx context.Context, req reconcile.Reques return setStatusReady(tsr, metav1.ConditionTrue, reasonRecorderCreated, reasonRecorderCreated) } -func (r *RecorderReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, tsr *tsapi.Recorder) error { +// getClientAndLoginURL returns the appropriate Tailscale client and resolved login URL +// for the given tailnet name. If no tailnet is specified, returns the default client +// and login server. Applies fallback to the operator's login server if the tailnet +// doesn't specify a custom login URL. +func (r *RecorderReconciler) getClientAndLoginURL(ctx context.Context, tailnetName string) (tsClient, + string, error) { + if tailnetName == "" { + return r.tsClient, r.loginServer, nil + } + + tc, loginUrl, err := clientForTailnet(ctx, r.Client, r.tsNamespace, tailnetName) + if err != nil { + return nil, "", err + } + + // Apply fallback if tailnet doesn't specify custom login URL + if loginUrl == "" { + loginUrl = r.loginServer + } + + return tc, loginUrl, nil +} + +func (r *RecorderReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, loginUrl string, tsr *tsapi.Recorder) error { logger := r.logger(tsr.Name) r.mu.Lock() @@ -234,7 +252,7 @@ func (r *RecorderReconciler) maybeProvision(ctx context.Context, tailscaleClient return fmt.Errorf("error creating RoleBinding: %w", err) } - ss := tsrStatefulSet(tsr, r.tsNamespace, r.loginServer) + ss := tsrStatefulSet(tsr, r.tsNamespace, loginUrl) _, err = createOrUpdate(ctx, r.Client, r.tsNamespace, ss, func(s *appsv1.StatefulSet) { s.ObjectMeta.Labels = ss.ObjectMeta.Labels s.ObjectMeta.Annotations = ss.ObjectMeta.Annotations From 43ad51d9474aeca3ba0a0b8117bc00f6c88b9572 Mon Sep 17 00:00:00 2001 From: Mike O'Driscoll Date: Tue, 10 Mar 2026 15:23:54 -0400 Subject: [PATCH 192/202] util/linuxfw: fix nil pointer panic in connmark rules without IPv6 (#18946) (#18947) When IPv6 is unavailable on a system, AddConnmarkSaveRule() and DelConnmarkSaveRule() would panic with a nil pointer dereference. Both methods directly iterated over []iptablesInterface{i.ipt4, i.ipt6} without checking if ipt6 was nil. Use `getTables()` instead to properly retrieve the available tables on a given system Fixes #3310 (cherry picked from commit 021de2e1bc8d5d4ab66d4f4f5c560dc585ae3ae0) Signed-off-by: Mike O'Driscoll --- util/linuxfw/fake.go | 18 ++-- util/linuxfw/iptables_runner.go | 8 +- util/linuxfw/iptables_runner_test.go | 140 +++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 12 deletions(-) diff --git a/util/linuxfw/fake.go b/util/linuxfw/fake.go index 1886e25429537..deeae87603f8a 100644 --- a/util/linuxfw/fake.go +++ b/util/linuxfw/fake.go @@ -25,13 +25,15 @@ type fakeRule struct { func newFakeIPTables() *fakeIPTables { return &fakeIPTables{ n: map[string][]string{ - "filter/INPUT": nil, - "filter/OUTPUT": nil, - "filter/FORWARD": nil, - "nat/PREROUTING": nil, - "nat/OUTPUT": nil, - "nat/POSTROUTING": nil, - "mangle/FORWARD": nil, + "filter/INPUT": nil, + "filter/OUTPUT": nil, + "filter/FORWARD": nil, + "nat/PREROUTING": nil, + "nat/OUTPUT": nil, + "nat/POSTROUTING": nil, + "mangle/FORWARD": nil, + "mangle/PREROUTING": nil, + "mangle/OUTPUT": nil, }, } } @@ -81,7 +83,7 @@ func (n *fakeIPTables) Delete(table, chain string, args ...string) error { return nil } } - return fmt.Errorf("delete of unknown rule %q from %s", strings.Join(args, " "), k) + return errors.New("exitcode:1") } else { return fmt.Errorf("unknown table/chain %s", k) } diff --git a/util/linuxfw/iptables_runner.go b/util/linuxfw/iptables_runner.go index b8eb39f219be9..0d50bdd61de38 100644 --- a/util/linuxfw/iptables_runner.go +++ b/util/linuxfw/iptables_runner.go @@ -533,7 +533,7 @@ func (i *iptablesRunner) DelStatefulRule(tunname string) error { // proper routing table lookups for exit nodes and subnet routers. func (i *iptablesRunner) AddConnmarkSaveRule() error { // Check if rules already exist (idempotency) - for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + for _, ipt := range i.getTables() { rules, err := ipt.List("mangle", "PREROUTING") if err != nil { continue @@ -551,7 +551,7 @@ func (i *iptablesRunner) AddConnmarkSaveRule() error { // mangle/PREROUTING: Restore mark from conntrack for ESTABLISHED/RELATED connections // This runs BEFORE routing decision and rp_filter check - for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + for _, ipt := range i.getTables() { args := []string{ "-m", "conntrack", "--ctstate", "ESTABLISHED,RELATED", @@ -566,7 +566,7 @@ func (i *iptablesRunner) AddConnmarkSaveRule() error { } // mangle/OUTPUT: Save mark to conntrack for NEW connections with non-zero marks - for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + for _, ipt := range i.getTables() { args := []string{ "-m", "conntrack", "--ctstate", "NEW", @@ -587,7 +587,7 @@ func (i *iptablesRunner) AddConnmarkSaveRule() error { // DelConnmarkSaveRule removes conntrack marking rules added by AddConnmarkSaveRule. func (i *iptablesRunner) DelConnmarkSaveRule() error { - for _, ipt := range []iptablesInterface{i.ipt4, i.ipt6} { + for _, ipt := range i.getTables() { // Delete PREROUTING rule args := []string{ "-m", "conntrack", diff --git a/util/linuxfw/iptables_runner_test.go b/util/linuxfw/iptables_runner_test.go index 0dcade35188fc..77c753004a770 100644 --- a/util/linuxfw/iptables_runner_test.go +++ b/util/linuxfw/iptables_runner_test.go @@ -364,3 +364,143 @@ func checkSNATRuleCount(t *testing.T, iptr *iptablesRunner, ip netip.Addr, wants t.Fatalf("wants %d rules, got %d", wantsRules, len(rules)) } } + +func TestAddAndDelConnmarkSaveRule(t *testing.T) { + preroutingArgs := []string{ + "-m", "conntrack", + "--ctstate", "ESTABLISHED,RELATED", + "-j", "CONNMARK", + "--restore-mark", + "--nfmask", "0xff0000", + "--ctmask", "0xff0000", + } + + outputArgs := []string{ + "-m", "conntrack", + "--ctstate", "NEW", + "-m", "mark", + "!", "--mark", "0x0/0xff0000", + "-j", "CONNMARK", + "--save-mark", + "--nfmask", "0xff0000", + "--ctmask", "0xff0000", + } + + t.Run("with_ipv6", func(t *testing.T) { + iptr := newFakeIPTablesRunner() + + // Add connmark rules + if err := iptr.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule failed: %v", err) + } + + // Verify rules exist in both IPv4 and IPv6 + for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} { + if exists, err := proto.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking PREROUTING rule: %v", err) + } else if !exists { + t.Errorf("PREROUTING connmark rule doesn't exist") + } + + if exists, err := proto.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking OUTPUT rule: %v", err) + } else if !exists { + t.Errorf("OUTPUT connmark rule doesn't exist") + } + } + + // Test idempotency - calling AddConnmarkSaveRule again should not fail or duplicate + if err := iptr.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule (second call) failed: %v", err) + } + + // Verify rules still exist and weren't duplicated + for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} { + preroutingRules, err := proto.List("mangle", "PREROUTING") + if err != nil { + t.Fatalf("error listing PREROUTING rules: %v", err) + } + connmarkCount := 0 + for _, rule := range preroutingRules { + if strings.Contains(rule, "CONNMARK") && strings.Contains(rule, "restore-mark") { + connmarkCount++ + } + } + if connmarkCount != 1 { + t.Errorf("expected 1 PREROUTING connmark rule, got %d", connmarkCount) + } + } + + // Delete connmark rules + if err := iptr.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule failed: %v", err) + } + + // Verify rules are deleted + for _, proto := range []iptablesInterface{iptr.ipt4, iptr.ipt6} { + if exists, err := proto.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking PREROUTING rule: %v", err) + } else if exists { + t.Errorf("PREROUTING connmark rule still exists after deletion") + } + + if exists, err := proto.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking OUTPUT rule: %v", err) + } else if exists { + t.Errorf("OUTPUT connmark rule still exists after deletion") + } + } + + // Test idempotency of deletion + if err := iptr.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule (second call) failed: %v", err) + } + }) + + t.Run("without_ipv6", func(t *testing.T) { + // Create an iptables runner with only IPv4 (simulating system without IPv6) + iptr := &iptablesRunner{ + ipt4: newFakeIPTables(), + ipt6: nil, // IPv6 not available + v6Available: false, + v6NATAvailable: false, + v6FilterAvailable: false, + } + + // Add connmark rules should NOT panic with nil ipt6 + if err := iptr.AddConnmarkSaveRule(); err != nil { + t.Fatalf("AddConnmarkSaveRule failed with IPv6 disabled: %v", err) + } + + // Verify rules exist ONLY in IPv4 + if exists, err := iptr.ipt4.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking IPv4 PREROUTING rule: %v", err) + } else if !exists { + t.Errorf("IPv4 PREROUTING connmark rule doesn't exist") + } + + if exists, err := iptr.ipt4.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking IPv4 OUTPUT rule: %v", err) + } else if !exists { + t.Errorf("IPv4 OUTPUT connmark rule doesn't exist") + } + + // Delete connmark rules should NOT panic with nil ipt6 + if err := iptr.DelConnmarkSaveRule(); err != nil { + t.Fatalf("DelConnmarkSaveRule failed with IPv6 disabled: %v", err) + } + + // Verify rules are deleted from IPv4 + if exists, err := iptr.ipt4.Exists("mangle", "PREROUTING", preroutingArgs...); err != nil { + t.Fatalf("error checking IPv4 PREROUTING rule: %v", err) + } else if exists { + t.Errorf("IPv4 PREROUTING connmark rule still exists after deletion") + } + + if exists, err := iptr.ipt4.Exists("mangle", "OUTPUT", outputArgs...); err != nil { + t.Fatalf("error checking IPv4 OUTPUT rule: %v", err) + } else if exists { + t.Errorf("IPv4 OUTPUT connmark rule still exists after deletion") + } + }) +} From c25843e176e8bb8ce48b809293ce624a06e17600 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Wed, 11 Mar 2026 12:50:02 +0000 Subject: [PATCH 193/202] cmd/{containerboot,k8s-operator}: reissue auth keys for broken proxies (#16450) (#18962) Adds logic for containerboot to signal that it can't auth, so the operator can reissue a new auth key. This only applies when running with a config file and with a kube state store. If the operator sees reissue_authkey in a state Secret, it will create a new auth key iff the config has no auth key or its auth key matches the value of reissue_authkey from the state Secret. This is to ensure we don't reissue auth keys in a tight loop if the proxy is slow to start or failing for some other reason. The reissue logic also uses a burstable rate limiter to ensure there's no way a terminally misconfigured or buggy operator can automatically generate new auth keys in a tight loop. Additional implementation details (ChaosInTheCRD): - Added `ipn.NotifyInitialHealthState` to ipn watcher, to ensure that `n.Health` is populated when notify's are returned. - on auth failure, containerboot: - Disconnects from control server - Sets reissue_authkey marker in state Secret with the failing key - Polls config file for new auth key (10 minute timeout) - Restarts after receiving new key to apply it - modified operator's reissue logic slightly: - Deletes old device from tailnet before creating new key - Rate limiting: 1 key per 30s with initial burst equal to replica count - In-flight tracking (authKeyReissuing map) prevents duplicate API calls across reconcile loops Updates #14080 Change-Id: I6982f8e741932a6891f2f48a2936f7f6a455317f (cherry picked from commit 969927c47c3d4de05e90f5b26a6d8d931c5ceed4) (cherry picked from commit 95a135ead10db7378d7cf22aa21deba7e5ce1bc7) Signed-off-by: Tom Proctor Co-authored-by: Tom Proctor --- cmd/containerboot/kube.go | 148 ++++++++++++-- cmd/containerboot/kube_test.go | 72 ++++++- cmd/containerboot/main.go | 66 +++++- cmd/containerboot/main_test.go | 189 +++++++++++++++-- cmd/k8s-operator/operator.go | 3 + cmd/k8s-operator/proxygroup.go | 216 +++++++++++++++----- cmd/k8s-operator/proxygroup_test.go | 303 +++++++++++++++++++++++----- cmd/k8s-operator/sts.go | 14 +- cmd/k8s-operator/testutils_test.go | 4 +- cmd/k8s-operator/tsrecorder_test.go | 2 +- kube/kubetypes/types.go | 16 +- 11 files changed, 876 insertions(+), 157 deletions(-) diff --git a/cmd/containerboot/kube.go b/cmd/containerboot/kube.go index 4943bddba7ad4..73f5819b406db 100644 --- a/cmd/containerboot/kube.go +++ b/cmd/containerboot/kube.go @@ -14,9 +14,12 @@ import ( "net/http" "net/netip" "os" + "path/filepath" "strings" "time" + "github.com/fsnotify/fsnotify" + "tailscale.com/client/local" "tailscale.com/ipn" "tailscale.com/kube/egressservices" "tailscale.com/kube/ingressservices" @@ -26,9 +29,11 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/util/backoff" - "tailscale.com/util/set" ) +const fieldManager = "tailscale-container" +const kubeletMountedConfigLn = "..data" + // kubeClient is a wrapper around Tailscale's internal kube client that knows how to talk to the kube API server. We use // this rather than any of the upstream Kubernetes client libaries to avoid extra imports. type kubeClient struct { @@ -46,7 +51,7 @@ func newKubeClient(root string, stateSecret string) (*kubeClient, error) { var err error kc, err := kubeclient.New("tailscale-container") if err != nil { - return nil, fmt.Errorf("Error creating kube client: %w", err) + return nil, fmt.Errorf("error creating kube client: %w", err) } if (root != "/") || os.Getenv("TS_KUBERNETES_READ_API_SERVER_ADDRESS_FROM_ENV") == "true" { // Derive the API server address from the environment variables @@ -63,7 +68,7 @@ func (kc *kubeClient) storeDeviceID(ctx context.Context, deviceID tailcfg.Stable kubetypes.KeyDeviceID: []byte(deviceID), }, } - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // storeDeviceEndpoints writes device's tailnet IPs and MagicDNS name to fields 'device_ips', 'device_fqdn' of client's @@ -84,7 +89,7 @@ func (kc *kubeClient) storeDeviceEndpoints(ctx context.Context, fqdn string, add kubetypes.KeyDeviceIPs: deviceIPs, }, } - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // storeHTTPSEndpoint writes an HTTPS endpoint exposed by this device via 'tailscale serve' to the client's state @@ -96,7 +101,7 @@ func (kc *kubeClient) storeHTTPSEndpoint(ctx context.Context, ep string) error { kubetypes.KeyHTTPSEndpoint: []byte(ep), }, } - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // deleteAuthKey deletes the 'authkey' field of the given kube @@ -122,7 +127,7 @@ func (kc *kubeClient) deleteAuthKey(ctx context.Context) error { // resetContainerbootState resets state from previous runs of containerboot to // ensure the operator doesn't use stale state when a Pod is first recreated. -func (kc *kubeClient) resetContainerbootState(ctx context.Context, podUID string) error { +func (kc *kubeClient) resetContainerbootState(ctx context.Context, podUID string, tailscaledConfigAuthkey string) error { existingSecret, err := kc.GetSecret(ctx, kc.stateSecret) switch { case kubeclient.IsNotFoundErr(err): @@ -131,32 +136,135 @@ func (kc *kubeClient) resetContainerbootState(ctx context.Context, podUID string case err != nil: return fmt.Errorf("failed to read state Secret %q to reset state: %w", kc.stateSecret, err) } + s := &kubeapi.Secret{ Data: map[string][]byte{ kubetypes.KeyCapVer: fmt.Appendf(nil, "%d", tailcfg.CurrentCapabilityVersion), + + // TODO(tomhjp): Perhaps shouldn't clear device ID and use a different signal, as this could leak tailnet devices. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, }, } if podUID != "" { s.Data[kubetypes.KeyPodUID] = []byte(podUID) } - toClear := set.SetOf([]string{ - kubetypes.KeyDeviceID, - kubetypes.KeyDeviceFQDN, - kubetypes.KeyDeviceIPs, - kubetypes.KeyHTTPSEndpoint, - egressservices.KeyEgressServices, - ingressservices.IngressConfigKey, - }) - for key := range existingSecret.Data { - if toClear.Contains(key) { - // It's fine to leave the key in place as a debugging breadcrumb, - // it should get a new value soon. - s.Data[key] = nil + // Only clear reissue_authkey if the operator has actioned it. + brokenAuthkey, ok := existingSecret.Data[kubetypes.KeyReissueAuthkey] + if ok && tailscaledConfigAuthkey != "" && string(brokenAuthkey) != tailscaledConfigAuthkey { + s.Data[kubetypes.KeyReissueAuthkey] = nil + } + + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) +} + +func (kc *kubeClient) setAndWaitForAuthKeyReissue(ctx context.Context, client *local.Client, cfg *settings, tailscaledConfigAuthKey string) error { + err := client.DisconnectControl(ctx) + if err != nil { + return fmt.Errorf("error disconnecting from control: %w", err) + } + + err = kc.setReissueAuthKey(ctx, tailscaledConfigAuthKey) + if err != nil { + return fmt.Errorf("failed to set reissue_authkey in Kubernetes Secret: %w", err) + } + + err = kc.waitForAuthKeyReissue(ctx, cfg.TailscaledConfigFilePath, tailscaledConfigAuthKey, 10*time.Minute) + if err != nil { + return fmt.Errorf("failed to receive new auth key: %w", err) + } + + return nil +} + +func (kc *kubeClient) setReissueAuthKey(ctx context.Context, authKey string) error { + s := &kubeapi.Secret{ + Data: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte(authKey), + }, + } + + log.Printf("Requesting a new auth key from operator") + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) +} + +func (kc *kubeClient) waitForAuthKeyReissue(ctx context.Context, configPath string, oldAuthKey string, maxWait time.Duration) error { + log.Printf("Waiting for operator to provide new auth key (max wait: %v)", maxWait) + + ctx, cancel := context.WithTimeout(ctx, maxWait) + defer cancel() + + tailscaledCfgDir := filepath.Dir(configPath) + toWatch := filepath.Join(tailscaledCfgDir, kubeletMountedConfigLn) + + var ( + pollTicker <-chan time.Time + eventChan <-chan fsnotify.Event + ) + + pollInterval := 5 * time.Second + + // Try to use fsnotify for faster notification + if w, err := fsnotify.NewWatcher(); err != nil { + log.Printf("auth key reissue: fsnotify unavailable, using polling: %v", err) + } else if err := w.Add(tailscaledCfgDir); err != nil { + w.Close() + log.Printf("auth key reissue: fsnotify watch failed, using polling: %v", err) + } else { + defer w.Close() + log.Printf("auth key reissue: watching for config changes via fsnotify") + eventChan = w.Events + } + + // still keep polling if using fsnotify, for logging and in case fsnotify fails + pt := time.NewTicker(pollInterval) + defer pt.Stop() + pollTicker = pt.C + + start := time.Now() + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("timeout waiting for auth key reissue after %v", maxWait) + case <-pollTicker: // Waits for polling tick, continues when received + case event := <-eventChan: + if event.Name != toWatch { + continue + } + } + + newAuthKey := authkeyFromTailscaledConfig(configPath) + if newAuthKey != "" && newAuthKey != oldAuthKey { + log.Printf("New auth key received from operator after %v", time.Since(start).Round(time.Second)) + + if err := kc.clearReissueAuthKeyRequest(ctx); err != nil { + log.Printf("Warning: failed to clear reissue request: %v", err) + } + + return nil + } + + if eventChan == nil && pollTicker != nil { + log.Printf("Waiting for new auth key from operator (%v elapsed)", time.Since(start).Round(time.Second)) } } +} - return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, "tailscale-container") +// clearReissueAuthKeyRequest removes the reissue_authkey marker from the Secret +// to signal to the operator that we've successfully received the new key. +func (kc *kubeClient) clearReissueAuthKeyRequest(ctx context.Context) error { + s := &kubeapi.Secret{ + Data: map[string][]byte{ + kubetypes.KeyReissueAuthkey: nil, + }, + } + return kc.StrategicMergePatchSecret(ctx, kc.stateSecret, s, fieldManager) } // waitForConsistentState waits for tailscaled to finish writing state if it diff --git a/cmd/containerboot/kube_test.go b/cmd/containerboot/kube_test.go index bc80e9cdf2cb3..6acaa60e1588e 100644 --- a/cmd/containerboot/kube_test.go +++ b/cmd/containerboot/kube_test.go @@ -248,25 +248,42 @@ func TestResetContainerbootState(t *testing.T) { capver := fmt.Appendf(nil, "%d", tailcfg.CurrentCapabilityVersion) for name, tc := range map[string]struct { podUID string + authkey string initial map[string][]byte expected map[string][]byte }{ "empty_initial": { podUID: "1234", + authkey: "new-authkey", initial: map[string][]byte{}, expected: map[string][]byte{ kubetypes.KeyCapVer: capver, kubetypes.KeyPodUID: []byte("1234"), + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, }, }, "empty_initial_no_pod_uid": { initial: map[string][]byte{}, expected: map[string][]byte{ kubetypes.KeyCapVer: capver, + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, }, }, "only_relevant_keys_updated": { - podUID: "1234", + podUID: "1234", + authkey: "new-authkey", initial: map[string][]byte{ kubetypes.KeyCapVer: []byte("1"), kubetypes.KeyPodUID: []byte("5678"), @@ -295,6 +312,57 @@ func TestResetContainerbootState(t *testing.T) { // Tailscaled keys not included in patch. }, }, + "new_authkey_issued": { + initial: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("old-authkey"), + }, + authkey: "new-authkey", + expected: map[string][]byte{ + kubetypes.KeyCapVer: capver, + kubetypes.KeyReissueAuthkey: nil, + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, + }, + }, + "authkey_not_yet_updated": { + initial: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("old-authkey"), + }, + authkey: "old-authkey", + expected: map[string][]byte{ + kubetypes.KeyCapVer: capver, + // reissue_authkey not cleared. + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, + }, + }, + "authkey_deleted_from_config": { + initial: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("old-authkey"), + }, + authkey: "", + expected: map[string][]byte{ + kubetypes.KeyCapVer: capver, + // reissue_authkey not cleared. + // Cleared keys. + kubetypes.KeyDeviceID: nil, + kubetypes.KeyDeviceFQDN: nil, + kubetypes.KeyDeviceIPs: nil, + kubetypes.KeyHTTPSEndpoint: nil, + egressservices.KeyEgressServices: nil, + ingressservices.IngressConfigKey: nil, + }, + }, } { t.Run(name, func(t *testing.T) { var actual map[string][]byte @@ -309,7 +377,7 @@ func TestResetContainerbootState(t *testing.T) { return nil }, }} - if err := kc.resetContainerbootState(context.Background(), tc.podUID); err != nil { + if err := kc.resetContainerbootState(context.Background(), tc.podUID, tc.authkey); err != nil { t.Fatalf("resetContainerbootState() error = %v", err) } if diff := cmp.Diff(tc.expected, actual); diff != "" { diff --git a/cmd/containerboot/main.go b/cmd/containerboot/main.go index 6b192b41605f1..c020ab0a94402 100644 --- a/cmd/containerboot/main.go +++ b/cmd/containerboot/main.go @@ -137,7 +137,9 @@ import ( "golang.org/x/sys/unix" "tailscale.com/client/tailscale" + "tailscale.com/health" "tailscale.com/ipn" + "tailscale.com/ipn/conffile" kubeutils "tailscale.com/k8s-operator" healthz "tailscale.com/kube/health" "tailscale.com/kube/kubetypes" @@ -207,6 +209,11 @@ func run() error { bootCtx, cancel := context.WithTimeout(ctx, 60*time.Second) defer cancel() + var tailscaledConfigAuthkey string + if isOneStepConfig(cfg) { + tailscaledConfigAuthkey = authkeyFromTailscaledConfig(cfg.TailscaledConfigFilePath) + } + var kc *kubeClient if cfg.KubeSecret != "" { kc, err = newKubeClient(cfg.Root, cfg.KubeSecret) @@ -220,7 +227,7 @@ func run() error { // hasKubeStateStore because although we know we're in kube, that // doesn't guarantee the state store is properly configured. if hasKubeStateStore(cfg) { - if err := kc.resetContainerbootState(bootCtx, cfg.PodUID); err != nil { + if err := kc.resetContainerbootState(bootCtx, cfg.PodUID, tailscaledConfigAuthkey); err != nil { return fmt.Errorf("error clearing previous state from Secret: %w", err) } } @@ -300,7 +307,7 @@ func run() error { } } - w, err := client.WatchIPNBus(bootCtx, ipn.NotifyInitialNetMap|ipn.NotifyInitialPrefs|ipn.NotifyInitialState) + w, err := client.WatchIPNBus(bootCtx, ipn.NotifyInitialNetMap|ipn.NotifyInitialPrefs|ipn.NotifyInitialState|ipn.NotifyInitialHealthState) if err != nil { return fmt.Errorf("failed to watch tailscaled for updates: %w", err) } @@ -366,8 +373,23 @@ authLoop: if isOneStepConfig(cfg) { // This could happen if this is the first time tailscaled was run for this // device and the auth key was not passed via the configfile. - return fmt.Errorf("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file.") + if hasKubeStateStore(cfg) { + log.Printf("Auth key missing or invalid (NeedsLogin state), disconnecting from control and requesting new key from operator") + + err := kc.setAndWaitForAuthKeyReissue(bootCtx, client, cfg, tailscaledConfigAuthkey) + if err != nil { + return fmt.Errorf("failed to get a reissued authkey: %w", err) + } + + log.Printf("Successfully received new auth key, restarting to apply configuration") + + // we don't return an error here since we have handled the reissue gracefully. + return nil + } + + return errors.New("invalid state: tailscaled daemon started with a config file, but tailscale is not logged in: ensure you pass a valid auth key in the config file") } + if err := authTailscale(); err != nil { return fmt.Errorf("failed to auth tailscale: %w", err) } @@ -385,6 +407,27 @@ authLoop: log.Printf("tailscaled in state %q, waiting", *n.State) } } + + if n.Health != nil { + // This can happen if the config has an auth key but it's invalid, + // for example if it was single-use and already got used, but the + // device state was lost. + if _, ok := n.Health.Warnings[health.LoginStateWarnable.Code]; ok { + if isOneStepConfig(cfg) && hasKubeStateStore(cfg) { + log.Printf("Auth key failed to authenticate (may be expired or single-use), disconnecting from control and requesting new key from operator") + + err := kc.setAndWaitForAuthKeyReissue(bootCtx, client, cfg, tailscaledConfigAuthkey) + if err != nil { + return fmt.Errorf("failed to get a reissued authkey: %w", err) + } + + // we don't return an error here since we have handled the reissue gracefully. + log.Printf("Successfully received new auth key, restarting to apply configuration") + + return nil + } + } + } } w.Close() @@ -410,9 +453,9 @@ authLoop: // We were told to only auth once, so any secret-bound // authkey is no longer needed. We don't strictly need to // wipe it, but it's good hygiene. - log.Printf("Deleting authkey from kube secret") + log.Printf("Deleting authkey from Kubernetes Secret") if err := kc.deleteAuthKey(ctx); err != nil { - return fmt.Errorf("deleting authkey from kube secret: %w", err) + return fmt.Errorf("deleting authkey from Kubernetes Secret: %w", err) } } @@ -423,8 +466,10 @@ authLoop: // If tailscaled config was read from a mounted file, watch the file for updates and reload. cfgWatchErrChan := make(chan error) + cfgWatchCtx, cfgWatchCancel := context.WithCancel(ctx) + defer cfgWatchCancel() if cfg.TailscaledConfigFilePath != "" { - go watchTailscaledConfigChanges(ctx, cfg.TailscaledConfigFilePath, client, cfgWatchErrChan) + go watchTailscaledConfigChanges(cfgWatchCtx, cfg.TailscaledConfigFilePath, client, cfgWatchErrChan) } var ( @@ -524,6 +569,7 @@ runLoop: case err := <-cfgWatchErrChan: return fmt.Errorf("failed to watch tailscaled config: %w", err) case n := <-notifyChan: + // TODO: (ChaosInTheCRD) Add node removed check when supported by ipn if n.State != nil && *n.State != ipn.Running { // Something's gone wrong and we've left the authenticated state. // Our container image never recovered gracefully from this, and the @@ -980,3 +1026,11 @@ func serviceIPsFromNetMap(nm *netmap.NetworkMap, fqdn dnsname.FQDN) []netip.Pref return prefixes } + +func authkeyFromTailscaledConfig(path string) string { + if cfg, err := conffile.Load(path); err == nil && cfg.Parsed.AuthKey != nil { + return *cfg.Parsed.AuthKey + } + + return "" +} diff --git a/cmd/containerboot/main_test.go b/cmd/containerboot/main_test.go index 58ab757950612..f1d892a19d118 100644 --- a/cmd/containerboot/main_test.go +++ b/cmd/containerboot/main_test.go @@ -31,6 +31,7 @@ import ( "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" + "tailscale.com/health" "tailscale.com/ipn" "tailscale.com/kube/egressservices" "tailscale.com/kube/kubeclient" @@ -41,6 +42,8 @@ import ( "tailscale.com/types/ptr" ) +const configFileAuthKey = "some-auth-key" + func TestContainerBoot(t *testing.T) { boot := filepath.Join(t.TempDir(), "containerboot") if err := exec.Command("go", "build", "-ldflags", "-X main.testSleepDuration=1ms", "-o", boot, "tailscale.com/cmd/containerboot").Run(); err != nil { @@ -77,6 +80,10 @@ func TestContainerBoot(t *testing.T) { // phase (simulates our fake tailscaled doing it). UpdateKubeSecret map[string]string + // Update files with these paths/contents at the beginning of the phase + // (simulates the operator updating mounted config files). + UpdateFiles map[string]string + // WantFiles files that should exist in the container and their // contents. WantFiles map[string]string @@ -781,6 +788,127 @@ func TestContainerBoot(t *testing.T) { }, } }, + "sets_reissue_authkey_if_needs_login": func(env *testEnv) testCase { + newAuthKey := "new-reissued-auth-key" + return testCase{ + Env: map[string]string{ + "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR": filepath.Join(env.d, "etc/tailscaled/"), + "KUBERNETES_SERVICE_HOST": env.kube.Host, + "KUBERNETES_SERVICE_PORT_HTTPS": env.kube.Port, + }, + Phases: []phase{ + { + UpdateFiles: map[string]string{ + "etc/tailscaled/..data": "", + }, + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled/cap-95.hujson", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + }, { + Notify: &ipn.Notify{ + State: new(ipn.NeedsLogin), + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + kubetypes.KeyReissueAuthkey: configFileAuthKey, + }, + WantLog: "watching for config changes via fsnotify", + }, { + UpdateFiles: map[string]string{ + "etc/tailscaled/cap-95.hujson": fmt.Sprintf(`{"Version":"alpha0","AuthKey":"%s"}`, newAuthKey), + "etc/tailscaled/..data": "updated", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + WantExitCode: new(0), + WantLog: "Successfully received new auth key, restarting to apply configuration", + }, + }, + } + }, + "sets_reissue_authkey_if_auth_fails": func(env *testEnv) testCase { + newAuthKey := "new-reissued-auth-key" + return testCase{ + Env: map[string]string{ + "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR": filepath.Join(env.d, "etc/tailscaled/"), + "KUBERNETES_SERVICE_HOST": env.kube.Host, + "KUBERNETES_SERVICE_PORT_HTTPS": env.kube.Port, + }, + Phases: []phase{ + { + UpdateFiles: map[string]string{ + "etc/tailscaled/..data": "", + }, + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled/cap-95.hujson", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + }, { + Notify: &ipn.Notify{ + Health: &health.State{ + Warnings: map[health.WarnableCode]health.UnhealthyState{ + health.LoginStateWarnable.Code: {}, + }, + }, + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + kubetypes.KeyReissueAuthkey: configFileAuthKey, + }, + WantLog: "watching for config changes via fsnotify", + }, { + UpdateFiles: map[string]string{ + "etc/tailscaled/cap-95.hujson": fmt.Sprintf(`{"Version":"alpha0","AuthKey":"%s"}`, newAuthKey), + "etc/tailscaled/..data": "updated", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + }, + WantExitCode: new(0), + WantLog: "Successfully received new auth key, restarting to apply configuration", + }, + }, + } + }, + "clears_reissue_authkey_on_change": func(env *testEnv) testCase { + return testCase{ + Env: map[string]string{ + "TS_EXPERIMENTAL_VERSIONED_CONFIG_DIR": filepath.Join(env.d, "etc/tailscaled/"), + "KUBERNETES_SERVICE_HOST": env.kube.Host, + "KUBERNETES_SERVICE_PORT_HTTPS": env.kube.Port, + }, + KubeSecret: map[string]string{ + kubetypes.KeyReissueAuthkey: "some-older-authkey", + "foo": "bar", // Check not everything is cleared. + }, + Phases: []phase{ + { + WantCmds: []string{ + "/usr/bin/tailscaled --socket=/tmp/tailscaled.sock --state=kube:tailscale --statedir=/tmp --tun=userspace-networking --config=/etc/tailscaled/cap-95.hujson", + }, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + "foo": "bar", + }, + }, { + Notify: runningNotify, + WantKubeSecret: map[string]string{ + kubetypes.KeyCapVer: capver, + "foo": "bar", + kubetypes.KeyDeviceFQDN: "test-node.test.ts.net.", + kubetypes.KeyDeviceID: "myID", + kubetypes.KeyDeviceIPs: `["100.64.0.1"]`, + }, + }, + }, + } + }, "metrics_enabled": func(env *testEnv) testCase { return testCase{ Env: map[string]string{ @@ -1134,19 +1262,22 @@ func TestContainerBoot(t *testing.T) { for k, v := range p.UpdateKubeSecret { env.kube.SetSecret(k, v) } + for path, content := range p.UpdateFiles { + fullPath := filepath.Join(env.d, path) + if err := os.WriteFile(fullPath, []byte(content), 0700); err != nil { + t.Fatalf("phase %d: updating file %q: %v", i, path, err) + } + // Explicitly update mtime to ensure fsnotify detects the change. + // Without this, file operations can be buffered and fsnotify events may not trigger. + now := time.Now() + if err := os.Chtimes(fullPath, now, now); err != nil { + t.Fatalf("phase %d: updating mtime for %q: %v", i, path, err) + } + } env.lapi.Notify(p.Notify) if p.Signal != nil { cmd.Process.Signal(*p.Signal) } - if p.WantLog != "" { - err := tstest.WaitFor(2*time.Second, func() error { - waitLogLine(t, time.Second, cbOut, p.WantLog) - return nil - }) - if err != nil { - t.Fatal(err) - } - } if p.WantExitCode != nil { state, err := cmd.Process.Wait() @@ -1156,14 +1287,19 @@ func TestContainerBoot(t *testing.T) { if state.ExitCode() != *p.WantExitCode { t.Fatalf("phase %d: want exit code %d, got %d", i, *p.WantExitCode, state.ExitCode()) } + } - // Early test return, we don't expect the successful startup log message. - return + if p.WantLog != "" { + err := tstest.WaitFor(5*time.Second, func() error { + waitLogLine(t, 5*time.Second, cbOut, p.WantLog) + return nil + }) + if err != nil { + t.Fatal(err) + } } - wantCmds = append(wantCmds, p.WantCmds...) - waitArgs(t, 2*time.Second, env.d, env.argFile, strings.Join(wantCmds, "\n")) - err := tstest.WaitFor(2*time.Second, func() error { + err := tstest.WaitFor(5*time.Second, func() error { if p.WantKubeSecret != nil { got := env.kube.Secret() if diff := cmp.Diff(got, p.WantKubeSecret); diff != "" { @@ -1180,6 +1316,16 @@ func TestContainerBoot(t *testing.T) { if err != nil { t.Fatalf("test: %q phase %d: %v", name, i, err) } + + // if we provide a wanted exit code, we expect that the process is finished, + // so should return from the test. + if p.WantExitCode != nil { + return + } + + wantCmds = append(wantCmds, p.WantCmds...) + waitArgs(t, 2*time.Second, env.d, env.argFile, strings.Join(wantCmds, "\n")) + err = tstest.WaitFor(2*time.Second, func() error { for path, want := range p.WantFiles { gotBs, err := os.ReadFile(filepath.Join(env.d, path)) @@ -1393,6 +1539,13 @@ func (lc *localAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { default: panic(fmt.Sprintf("unsupported method %q", r.Method)) } + // In the localAPI ServeHTTP method + case "/localapi/v0/disconnect-control": + if r.Method != "POST" { + panic(fmt.Sprintf("unsupported method %q", r.Method)) + } + w.WriteHeader(http.StatusOK) + return default: panic(fmt.Sprintf("unsupported path %q", r.URL.Path)) } @@ -1593,7 +1746,11 @@ func (k *kubeServer) serveSecret(w http.ResponseWriter, r *http.Request) { panic(fmt.Sprintf("json decode failed: %v. Body:\n\n%s", err, string(bs))) } for key, val := range req.Data { - k.secret[key] = string(val) + if val == nil { + delete(k.secret, key) + } else { + k.secret[key] = string(val) + } } default: panic(fmt.Sprintf("unknown content type %q", r.Header.Get("Content-Type"))) @@ -1661,7 +1818,7 @@ func newTestEnv(t *testing.T) testEnv { kube.Start(t) t.Cleanup(kube.Close) - tailscaledConf := &ipn.ConfigVAlpha{AuthKey: ptr.To("foo"), Version: "alpha0"} + tailscaledConf := &ipn.ConfigVAlpha{AuthKey: new(configFileAuthKey), Version: "alpha0"} serveConf := ipn.ServeConfig{TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}} serveConfWithServices := ipn.ServeConfig{ TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}}, diff --git a/cmd/k8s-operator/operator.go b/cmd/k8s-operator/operator.go index ef55d27481266..1060c6f3da9e7 100644 --- a/cmd/k8s-operator/operator.go +++ b/cmd/k8s-operator/operator.go @@ -20,6 +20,7 @@ import ( "github.com/go-logr/zapr" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "golang.org/x/time/rate" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -723,6 +724,8 @@ func runReconcilers(opts reconcilerOpts) { tsFirewallMode: opts.proxyFirewallMode, defaultProxyClass: opts.defaultProxyClass, loginServer: opts.tsServer.ControlURL, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), }) if err != nil { startlog.Fatalf("could not create ProxyGroup reconciler: %v", err) diff --git a/cmd/k8s-operator/proxygroup.go b/cmd/k8s-operator/proxygroup.go index bfdfc3dca1765..2aef6ff9e8226 100644 --- a/cmd/k8s-operator/proxygroup.go +++ b/cmd/k8s-operator/proxygroup.go @@ -16,10 +16,12 @@ import ( "sort" "strings" "sync" + "time" dockerref "github.com/distribution/reference" "go.uber.org/zap" xslices "golang.org/x/exp/slices" + "golang.org/x/time/rate" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -95,10 +97,12 @@ type ProxyGroupReconciler struct { defaultProxyClass string loginServer string - mu sync.Mutex // protects following - egressProxyGroups set.Slice[types.UID] // for egress proxygroups gauge - ingressProxyGroups set.Slice[types.UID] // for ingress proxygroups gauge - apiServerProxyGroups set.Slice[types.UID] // for kube-apiserver proxygroups gauge + mu sync.Mutex // protects following + egressProxyGroups set.Slice[types.UID] // for egress proxygroups gauge + ingressProxyGroups set.Slice[types.UID] // for ingress proxygroups gauge + apiServerProxyGroups set.Slice[types.UID] // for kube-apiserver proxygroups gauge + authKeyRateLimits map[string]*rate.Limiter // per-ProxyGroup rate limiters for auth key re-issuance. + authKeyReissuing map[string]bool } func (r *ProxyGroupReconciler) logger(name string) *zap.SugaredLogger { @@ -295,7 +299,7 @@ func (r *ProxyGroupReconciler) validate(ctx context.Context, pg *tsapi.ProxyGrou func (r *ProxyGroupReconciler) maybeProvision(ctx context.Context, tailscaleClient tsClient, loginUrl string, pg *tsapi.ProxyGroup, proxyClass *tsapi.ProxyClass) (map[string][]netip.AddrPort, *notReadyReason, error) { logger := r.logger(pg.Name) r.mu.Lock() - r.ensureAddedToGaugeForProxyGroup(pg) + r.ensureStateAddedForProxyGroup(pg) r.mu.Unlock() svcToNodePorts := make(map[string]uint16) @@ -632,13 +636,13 @@ func (r *ProxyGroupReconciler) cleanupDanglingResources(ctx context.Context, tai } for _, m := range metadata { - if m.ordinal+1 <= int(pgReplicas(pg)) { + if m.ordinal+1 <= pgReplicas(pg) { continue } // Dangling resource, delete the config + state Secrets, as well as // deleting the device from the tailnet. - if err := r.deleteTailnetDevice(ctx, tailscaleClient, m.tsID, logger); err != nil { + if err := r.ensureDeviceDeleted(ctx, tailscaleClient, m.tsID, logger); err != nil { return err } if err := r.Delete(ctx, m.stateSecret); err != nil && !apierrors.IsNotFound(err) { @@ -690,7 +694,7 @@ func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, tailscaleClient } for _, m := range metadata { - if err := r.deleteTailnetDevice(ctx, tailscaleClient, m.tsID, logger); err != nil { + if err := r.ensureDeviceDeleted(ctx, tailscaleClient, m.tsID, logger); err != nil { return false, err } } @@ -706,12 +710,12 @@ func (r *ProxyGroupReconciler) maybeCleanup(ctx context.Context, tailscaleClient logger.Infof("cleaned up ProxyGroup resources") r.mu.Lock() - r.ensureRemovedFromGaugeForProxyGroup(pg) + r.ensureStateRemovedForProxyGroup(pg) r.mu.Unlock() return true, nil } -func (r *ProxyGroupReconciler) deleteTailnetDevice(ctx context.Context, tailscaleClient tsClient, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { +func (r *ProxyGroupReconciler) ensureDeviceDeleted(ctx context.Context, tailscaleClient tsClient, id tailcfg.StableNodeID, logger *zap.SugaredLogger) error { logger.Debugf("deleting device %s from control", string(id)) if err := tailscaleClient.DeleteDevice(ctx, string(id)); err != nil { errResp := &tailscale.ErrResponse{} @@ -738,6 +742,7 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( logger := r.logger(pg.Name) endpoints = make(map[string][]netip.AddrPort, pgReplicas(pg)) // keyed by Service name. for i := range pgReplicas(pg) { + logger = logger.With("Pod", fmt.Sprintf("%s-%d", pg.Name, i)) cfgSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: pgConfigSecretName(pg.Name, i), @@ -755,38 +760,9 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( return nil, err } - var authKey *string - if existingCfgSecret == nil { - logger.Debugf("Creating authkey for new ProxyGroup proxy") - tags := pg.Spec.Tags.Stringify() - if len(tags) == 0 { - tags = r.defaultTags - } - key, err := newAuthKey(ctx, tailscaleClient, tags) - if err != nil { - return nil, err - } - authKey = &key - } - - if authKey == nil { - // Get state Secret to check if it's already authed. - stateSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: pgStateSecretName(pg.Name, i), - Namespace: r.tsNamespace, - }, - } - if err = r.Get(ctx, client.ObjectKeyFromObject(stateSecret), stateSecret); err != nil && !apierrors.IsNotFound(err) { - return nil, err - } - - if shouldRetainAuthKey(stateSecret) && existingCfgSecret != nil { - authKey, err = authKeyFromSecret(existingCfgSecret) - if err != nil { - return nil, fmt.Errorf("error retrieving auth key from existing config Secret: %w", err) - } - } + authKey, err := r.getAuthKey(ctx, tailscaleClient, pg, existingCfgSecret, i, logger) + if err != nil { + return nil, err } nodePortSvcName := pgNodePortServiceName(pg.Name, i) @@ -922,11 +898,137 @@ func (r *ProxyGroupReconciler) ensureConfigSecretsCreated( return nil, err } } + } return endpoints, nil } +// getAuthKey returns an auth key for the proxy, or nil if none is needed. +// A new key is created if the config Secret doesn't exist yet, or if the +// proxy has requested a reissue via its state Secret. An existing key is +// retained while the device hasn't authed or a reissue is in progress. +func (r *ProxyGroupReconciler) getAuthKey(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, existingCfgSecret *corev1.Secret, ordinal int32, logger *zap.SugaredLogger) (*string, error) { + // Get state Secret to check if it's already authed or has requested + // a fresh auth key. + stateSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgStateSecretName(pg.Name, ordinal), + Namespace: r.tsNamespace, + }, + } + if err := r.Get(ctx, client.ObjectKeyFromObject(stateSecret), stateSecret); err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + + var createAuthKey bool + var cfgAuthKey *string + if existingCfgSecret == nil { + createAuthKey = true + } else { + var err error + cfgAuthKey, err = authKeyFromSecret(existingCfgSecret) + if err != nil { + return nil, fmt.Errorf("error retrieving auth key from existing config Secret: %w", err) + } + } + + if !createAuthKey { + var err error + createAuthKey, err = r.shouldReissueAuthKey(ctx, tailscaleClient, pg, stateSecret, cfgAuthKey) + if err != nil { + return nil, err + } + } + + var authKey *string + if createAuthKey { + logger.Debugf("creating auth key for ProxyGroup proxy %q", stateSecret.Name) + + tags := pg.Spec.Tags.Stringify() + if len(tags) == 0 { + tags = r.defaultTags + } + key, err := newAuthKey(ctx, tailscaleClient, tags) + if err != nil { + return nil, err + } + authKey = &key + } else { + // Retain auth key if the device hasn't authed yet, or if a + // reissue is in progress (device_id is stale during reissue). + _, reissueRequested := stateSecret.Data[kubetypes.KeyReissueAuthkey] + if !deviceAuthed(stateSecret) || reissueRequested { + authKey = cfgAuthKey + } + } + + return authKey, nil +} + +// shouldReissueAuthKey returns true if the proxy needs a new auth key. It +// tracks in-flight reissues via authKeyReissuing to avoid duplicate API calls +// across reconciles. +func (r *ProxyGroupReconciler) shouldReissueAuthKey(ctx context.Context, tailscaleClient tsClient, pg *tsapi.ProxyGroup, stateSecret *corev1.Secret, cfgAuthKey *string) (shouldReissue bool, err error) { + r.mu.Lock() + reissuing := r.authKeyReissuing[stateSecret.Name] + r.mu.Unlock() + + if reissuing { + // Check if reissue is complete by seeing if request was cleared + _, requestStillPresent := stateSecret.Data[kubetypes.KeyReissueAuthkey] + if !requestStillPresent { + // Containerboot cleared the request, reissue is complete + r.mu.Lock() + r.authKeyReissuing[stateSecret.Name] = false + r.mu.Unlock() + r.log.Debugf("auth key reissue completed for %q", stateSecret.Name) + return false, nil + } + + // Reissue still in-flight; waiting for containerboot to pick up new key + r.log.Debugf("auth key already in process of re-issuance, waiting for secret to be updated") + return false, nil + } + + defer func() { + r.mu.Lock() + r.authKeyReissuing[stateSecret.Name] = shouldReissue + r.mu.Unlock() + }() + + brokenAuthkey, ok := stateSecret.Data[kubetypes.KeyReissueAuthkey] + if !ok { + // reissue hasn't been requested since the key in the secret hasn't been populated + return false, nil + } + + empty := cfgAuthKey == nil || *cfgAuthKey == "" + broken := cfgAuthKey != nil && *cfgAuthKey == string(brokenAuthkey) + + // A new key has been written but the proxy hasn't picked it up yet. + if !empty && !broken { + return false, nil + } + + lim := r.authKeyRateLimits[pg.Name] + if !lim.Allow() { + r.log.Debugf("auth key re-issuance rate limit exceeded, limit: %.2f, burst: %d, tokens: %.2f", + lim.Limit(), lim.Burst(), lim.Tokens()) + return false, fmt.Errorf("auth key re-issuance rate limit exceeded for ProxyGroup %q, will retry with backoff", pg.Name) + } + + r.log.Infof("Proxy failing to auth; attempting cleanup and new key") + if tsID := stateSecret.Data[kubetypes.KeyDeviceID]; len(tsID) > 0 { + id := tailcfg.StableNodeID(tsID) + if err := r.ensureDeviceDeleted(ctx, tailscaleClient, id, r.log); err != nil { + return false, err + } + } + + return true, nil +} + type FindStaticEndpointErr struct { msg string } @@ -1020,9 +1122,9 @@ func getStaticEndpointAddress(a *corev1.NodeAddress, port uint16) *netip.AddrPor return ptr.To(netip.AddrPortFrom(addr, port)) } -// ensureAddedToGaugeForProxyGroup ensures the gauge metric for the ProxyGroup resource is updated when the ProxyGroup -// is created. r.mu must be held. -func (r *ProxyGroupReconciler) ensureAddedToGaugeForProxyGroup(pg *tsapi.ProxyGroup) { +// ensureStateAddedForProxyGroup ensures the gauge metric for the ProxyGroup resource is updated when the ProxyGroup +// is created, and initialises per-ProxyGroup rate limits on re-issuing auth keys. r.mu must be held. +func (r *ProxyGroupReconciler) ensureStateAddedForProxyGroup(pg *tsapi.ProxyGroup) { switch pg.Spec.Type { case tsapi.ProxyGroupTypeEgress: r.egressProxyGroups.Add(pg.UID) @@ -1034,11 +1136,24 @@ func (r *ProxyGroupReconciler) ensureAddedToGaugeForProxyGroup(pg *tsapi.ProxyGr gaugeEgressProxyGroupResources.Set(int64(r.egressProxyGroups.Len())) gaugeIngressProxyGroupResources.Set(int64(r.ingressProxyGroups.Len())) gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) + + if _, ok := r.authKeyRateLimits[pg.Name]; !ok { + // Allow every replica to have its auth key re-issued quickly the first + // time, but with an overall limit of 1 every 30s after a burst. + r.authKeyRateLimits[pg.Name] = rate.NewLimiter(rate.Every(30*time.Second), int(pgReplicas(pg))) + } + + for i := range pgReplicas(pg) { + rep := pgStateSecretName(pg.Name, i) + if _, ok := r.authKeyReissuing[rep]; !ok { + r.authKeyReissuing[rep] = false + } + } } -// ensureRemovedFromGaugeForProxyGroup ensures the gauge metric for the ProxyGroup resource type is updated when the -// ProxyGroup is deleted. r.mu must be held. -func (r *ProxyGroupReconciler) ensureRemovedFromGaugeForProxyGroup(pg *tsapi.ProxyGroup) { +// ensureStateRemovedForProxyGroup ensures the gauge metric for the ProxyGroup resource type is updated when the +// ProxyGroup is deleted, and deletes the per-ProxyGroup rate limiter to free memory. r.mu must be held. +func (r *ProxyGroupReconciler) ensureStateRemovedForProxyGroup(pg *tsapi.ProxyGroup) { switch pg.Spec.Type { case tsapi.ProxyGroupTypeEgress: r.egressProxyGroups.Remove(pg.UID) @@ -1050,6 +1165,7 @@ func (r *ProxyGroupReconciler) ensureRemovedFromGaugeForProxyGroup(pg *tsapi.Pro gaugeEgressProxyGroupResources.Set(int64(r.egressProxyGroups.Len())) gaugeIngressProxyGroupResources.Set(int64(r.ingressProxyGroups.Len())) gaugeAPIServerProxyGroupResources.Set(int64(r.apiServerProxyGroups.Len())) + delete(r.authKeyRateLimits, pg.Name) } func pgTailscaledConfig(pg *tsapi.ProxyGroup, loginServer string, pc *tsapi.ProxyClass, idx int32, authKey *string, staticEndpoints []netip.AddrPort, oldAdvertiseServices []string) (tailscaledConfigs, error) { @@ -1110,7 +1226,7 @@ func getNodeMetadata(ctx context.Context, pg *tsapi.ProxyGroup, cl client.Client return nil, fmt.Errorf("failed to list state Secrets: %w", err) } for _, secret := range secrets.Items { - var ordinal int + var ordinal int32 if _, err := fmt.Sscanf(secret.Name, pg.Name+"-%d", &ordinal); err != nil { return nil, fmt.Errorf("unexpected secret %s was labelled as owned by the ProxyGroup %s: %w", secret.Name, pg.Name, err) } @@ -1217,7 +1333,7 @@ func (r *ProxyGroupReconciler) getClientAndLoginURL(ctx context.Context, tailnet } type nodeMetadata struct { - ordinal int + ordinal int32 stateSecret *corev1.Secret podUID string // or empty if the Pod no longer exists. tsID tailcfg.StableNodeID diff --git a/cmd/k8s-operator/proxygroup_test.go b/cmd/k8s-operator/proxygroup_test.go index c58bd2bb71dc5..b27f5e67aa043 100644 --- a/cmd/k8s-operator/proxygroup_test.go +++ b/cmd/k8s-operator/proxygroup_test.go @@ -6,15 +6,19 @@ package main import ( + "context" "encoding/json" "fmt" "net/netip" + "reflect" "slices" + "strings" "testing" "time" "github.com/google/go-cmp/cmp" "go.uber.org/zap" + "golang.org/x/time/rate" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -28,7 +32,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "tailscale.com/client/tailscale" "tailscale.com/ipn" - kube "tailscale.com/k8s-operator" tsoperator "tailscale.com/k8s-operator" tsapi "tailscale.com/k8s-operator/apis/v1alpha1" "tailscale.com/kube/k8s-proxy/conf" @@ -638,10 +641,12 @@ func TestProxyGroupWithStaticEndpoints(t *testing.T) { tsFirewallMode: "auto", defaultProxyClass: "default-pc", - Client: fc, - tsClient: tsClient, - recorder: fr, - clock: cl, + Client: fc, + tsClient: tsClient, + recorder: fr, + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } for i, r := range tt.reconciles { @@ -781,11 +786,13 @@ func TestProxyGroupWithStaticEndpoints(t *testing.T) { tsFirewallMode: "auto", defaultProxyClass: "default-pc", - Client: fc, - tsClient: tsClient, - recorder: fr, - log: zl.Sugar().With("TestName", tt.name).With("Reconcile", "cleanup"), - clock: cl, + Client: fc, + tsClient: tsClient, + recorder: fr, + log: zl.Sugar().With("TestName", tt.name).With("Reconcile", "cleanup"), + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } if err := fc.Delete(t.Context(), pg); err != nil { @@ -842,12 +849,15 @@ func TestProxyGroup(t *testing.T) { tsFirewallMode: "auto", defaultProxyClass: "default-pc", - Client: fc, - tsClient: tsClient, - recorder: fr, - log: zl.Sugar(), - clock: cl, + Client: fc, + tsClient: tsClient, + recorder: fr, + log: zl.Sugar(), + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } + crd := &apiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: serviceMonitorCRD}} opts := configOpts{ proxyType: "proxygroup", @@ -864,7 +874,7 @@ func TestProxyGroup(t *testing.T) { tsoperator.SetProxyGroupCondition(pg, tsapi.ProxyGroupReady, metav1.ConditionFalse, reasonProxyGroupCreating, "the ProxyGroup's ProxyClass \"default-pc\" is not yet in a ready state, waiting...", 1, cl, zl.Sugar()) expectEqual(t, fc, pg) expectProxyGroupResources(t, fc, pg, false, pc) - if kube.ProxyGroupAvailable(pg) { + if tsoperator.ProxyGroupAvailable(pg) { t.Fatal("expected ProxyGroup to not be available") } }) @@ -892,7 +902,7 @@ func TestProxyGroup(t *testing.T) { tsoperator.SetProxyGroupCondition(pg, tsapi.ProxyGroupAvailable, metav1.ConditionFalse, reasonProxyGroupCreating, "0/2 ProxyGroup pods running", 0, cl, zl.Sugar()) expectEqual(t, fc, pg) expectProxyGroupResources(t, fc, pg, true, pc) - if kube.ProxyGroupAvailable(pg) { + if tsoperator.ProxyGroupAvailable(pg) { t.Fatal("expected ProxyGroup to not be available") } if expected := 1; reconciler.egressProxyGroups.Len() != expected { @@ -936,7 +946,7 @@ func TestProxyGroup(t *testing.T) { tsoperator.SetProxyGroupCondition(pg, tsapi.ProxyGroupAvailable, metav1.ConditionTrue, reasonProxyGroupAvailable, "2/2 ProxyGroup pods running", 0, cl, zl.Sugar()) expectEqual(t, fc, pg) expectProxyGroupResources(t, fc, pg, true, pc) - if !kube.ProxyGroupAvailable(pg) { + if !tsoperator.ProxyGroupAvailable(pg) { t.Fatal("expected ProxyGroup to be available") } }) @@ -1046,12 +1056,14 @@ func TestProxyGroupTypes(t *testing.T) { zl, _ := zap.NewDevelopment() reconciler := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zl.Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zl.Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } t.Run("egress_type", func(t *testing.T) { @@ -1286,12 +1298,14 @@ func TestKubeAPIServerStatusConditionFlow(t *testing.T) { WithStatusSubresource(pg). Build() r := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zap.Must(zap.NewDevelopment()).Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zap.Must(zap.NewDevelopment()).Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } expectReconciled(t, r, "", pg.Name) @@ -1339,12 +1353,14 @@ func TestKubeAPIServerType_DoesNotOverwriteServicesConfig(t *testing.T) { Build() reconciler := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zap.Must(zap.NewDevelopment()).Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zap.Must(zap.NewDevelopment()).Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } pg := &tsapi.ProxyGroup{ @@ -1368,10 +1384,10 @@ func TestKubeAPIServerType_DoesNotOverwriteServicesConfig(t *testing.T) { cfg := conf.VersionedConfig{ Version: "v1alpha1", ConfigV1Alpha1: &conf.ConfigV1Alpha1{ - AuthKey: ptr.To("secret-authkey"), - State: ptr.To(fmt.Sprintf("kube:%s", pgPodName(pg.Name, 0))), - App: ptr.To(kubetypes.AppProxyGroupKubeAPIServer), - LogLevel: ptr.To("debug"), + AuthKey: new("new-authkey"), + State: new(fmt.Sprintf("kube:%s", pgPodName(pg.Name, 0))), + App: new(kubetypes.AppProxyGroupKubeAPIServer), + LogLevel: new("debug"), Hostname: ptr.To("test-k8s-apiserver-0"), APIServerProxy: &conf.APIServerProxyConfig{ @@ -1424,12 +1440,14 @@ func TestIngressAdvertiseServicesConfigPreserved(t *testing.T) { WithStatusSubresource(&tsapi.ProxyGroup{}). Build() reconciler := &ProxyGroupReconciler{ - tsNamespace: tsNamespace, - tsProxyImage: testProxyImage, - Client: fc, - log: zap.Must(zap.NewDevelopment()).Sugar(), - tsClient: &fakeTSClient{}, - clock: tstest.NewClock(tstest.ClockOpts{}), + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + Client: fc, + log: zap.Must(zap.NewDevelopment()).Sugar(), + tsClient: &fakeTSClient{}, + clock: tstest.NewClock(tstest.ClockOpts{}), + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } existingServices := []string{"svc1", "svc2"} @@ -1654,6 +1672,197 @@ func TestValidateProxyGroup(t *testing.T) { } } +func TestProxyGroupGetAuthKey(t *testing.T) { + pg := &tsapi.ProxyGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Finalizers: []string{"tailscale.com/finalizer"}, + }, + Spec: tsapi.ProxyGroupSpec{ + Type: tsapi.ProxyGroupTypeEgress, + Replicas: new(int32(1)), + }, + } + tsClient := &fakeTSClient{} + + // Variables to reference in test cases. + existingAuthKey := new("existing-auth-key") + newAuthKey := new("new-authkey") + configWith := func(authKey *string) map[string][]byte { + value := []byte("{}") + if authKey != nil { + value = fmt.Appendf(nil, `{"AuthKey": "%s"}`, *authKey) + } + return map[string][]byte{ + tsoperator.TailscaledConfigFileName(pgMinCapabilityVersion): value, + } + } + + initTest := func() (*ProxyGroupReconciler, client.WithWatch) { + fc := fake.NewClientBuilder(). + WithScheme(tsapi.GlobalScheme). + WithObjects(pg). + WithStatusSubresource(pg). + Build() + zl, _ := zap.NewDevelopment() + fr := record.NewFakeRecorder(1) + cl := tstest.NewClock(tstest.ClockOpts{}) + reconciler := &ProxyGroupReconciler{ + tsNamespace: tsNamespace, + tsProxyImage: testProxyImage, + defaultTags: []string{"tag:test-tag"}, + tsFirewallMode: "auto", + + Client: fc, + tsClient: tsClient, + recorder: fr, + log: zl.Sugar(), + clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), + } + reconciler.ensureStateAddedForProxyGroup(pg) + + return reconciler, fc + } + + // Config Secret: exists or not, has key or not. + // State Secret: has device ID or not, requested reissue or not. + for name, tc := range map[string]struct { + configData map[string][]byte + stateData map[string][]byte + expectedAuthKey *string + expectReissue bool + }{ + "no_secrets_needs_new": { + expectedAuthKey: newAuthKey, // New ProxyGroup or manually cleared Pod. + }, + "no_config_secret_state_authed_ok": { + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + }, + expectedAuthKey: newAuthKey, // Always create an auth key if we're creating the config Secret. + }, + "config_secret_without_key_state_authed_with_reissue_needs_new": { + configData: configWith(nil), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + kubetypes.KeyReissueAuthkey: []byte(""), + }, + expectedAuthKey: newAuthKey, + expectReissue: true, // Device is authed but reissue was requested. + }, + "config_secret_with_key_state_with_reissue_stale_ok": { + configData: configWith(existingAuthKey), + stateData: map[string][]byte{ + kubetypes.KeyReissueAuthkey: []byte("some-older-authkey"), + }, + expectedAuthKey: existingAuthKey, // Config's auth key is different from the one marked for reissue. + }, + "config_secret_with_key_state_with_reissue_existing_key_needs_new": { + configData: configWith(existingAuthKey), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + kubetypes.KeyReissueAuthkey: []byte(*existingAuthKey), + }, + expectedAuthKey: newAuthKey, + expectReissue: true, // Current config's auth key is marked for reissue. + }, + "config_secret_without_key_no_state_ok": { + configData: configWith(nil), + expectedAuthKey: nil, // Proxy will set reissue_authkey and then next reconcile will reissue. + }, + "config_secret_without_key_state_authed_ok": { + configData: configWith(nil), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + }, + expectedAuthKey: nil, // Device is already authed. + }, + "config_secret_with_key_state_authed_ok": { + configData: configWith(existingAuthKey), + stateData: map[string][]byte{ + kubetypes.KeyDeviceID: []byte("nodeid-0"), + }, + expectedAuthKey: nil, // Auth key getting removed because device is authed. + }, + "config_secret_with_key_no_state_keeps_existing": { + configData: configWith(existingAuthKey), + expectedAuthKey: existingAuthKey, // No state, waiting for containerboot to try the auth key. + }, + } { + t.Run(name, func(t *testing.T) { + tsClient.deleted = tsClient.deleted[:0] // Reset deleted devices for each test case. + reconciler, fc := initTest() + var cfgSecret *corev1.Secret + if tc.configData != nil { + cfgSecret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgConfigSecretName(pg.Name, 0), + Namespace: tsNamespace, + }, + Data: tc.configData, + } + } + if tc.stateData != nil { + mustCreate(t, fc, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: pgStateSecretName(pg.Name, 0), + Namespace: tsNamespace, + }, + Data: tc.stateData, + }) + } + + authKey, err := reconciler.getAuthKey(t.Context(), tsClient, pg, cfgSecret, 0, reconciler.log.With("TestName", t.Name())) + if err != nil { + t.Fatalf("unexpected error getting auth key: %v", err) + } + if !reflect.DeepEqual(authKey, tc.expectedAuthKey) { + deref := func(s *string) string { + if s == nil { + return "" + } + return *s + } + t.Errorf("expected auth key %v, got %v", deref(tc.expectedAuthKey), deref(authKey)) + } + + // Use the device deletion as a proxy for the fact the new auth key + // was due to a reissue. + switch { + case tc.expectReissue && len(tsClient.deleted) != 1: + t.Errorf("expected 1 deleted device, got %v", tsClient.deleted) + case !tc.expectReissue && len(tsClient.deleted) != 0: + t.Errorf("expected no deleted devices, got %v", tsClient.deleted) + } + + if tc.expectReissue { + // Trigger the rate limit in a tight loop. Up to 100 iterations + // to allow for CI that is extremely slow, but should happen on + // first try for any reasonable machine. + stateSecretName := pgStateSecretName(pg.Name, 0) + for range 100 { + //NOTE: (ChaosInTheCRD) we added some protection here to avoid + // trying to reissue when already reissung. This overrides it. + reconciler.mu.Lock() + reconciler.authKeyReissuing[stateSecretName] = false + reconciler.mu.Unlock() + _, err := reconciler.getAuthKey(context.Background(), tsClient, pg, cfgSecret, 0, + reconciler.log.With("TestName", t.Name())) + if err != nil { + if !strings.Contains(err.Error(), "rate limit exceeded") { + t.Fatalf("unexpected error getting auth key: %v", err) + } + return // Expected rate limit error. + } + } + t.Fatal("expected rate limit error, but got none") + } + }) + } +} + func proxyClassesForLEStagingTest() (*tsapi.ProxyClass, *tsapi.ProxyClass, *tsapi.ProxyClass) { pcLEStaging := &tsapi.ProxyClass{ ObjectMeta: metav1.ObjectMeta{ @@ -1904,6 +2113,8 @@ func TestProxyGroupLetsEncryptStaging(t *testing.T) { tsClient: &fakeTSClient{}, log: zl.Sugar(), clock: cl, + authKeyRateLimits: make(map[string]*rate.Limiter), + authKeyReissuing: make(map[string]bool), } expectReconciled(t, reconciler, "", pg.Name) diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index f98e3624a5e9f..ea38ddece2749 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -1116,7 +1116,7 @@ func tailscaledConfig(stsC *tailscaleSTSConfig, loginUrl string, newAuthkey stri if newAuthkey != "" { conf.AuthKey = &newAuthkey - } else if shouldRetainAuthKey(oldSecret) { + } else if !deviceAuthed(oldSecret) { key, err := authKeyFromSecret(oldSecret) if err != nil { return nil, fmt.Errorf("error retrieving auth key from Secret: %w", err) @@ -1169,6 +1169,8 @@ func latestConfigFromSecret(s *corev1.Secret) (*ipn.ConfigVAlpha, error) { return conf, nil } +// authKeyFromSecret returns the auth key from the latest config version if +// found, or else nil. func authKeyFromSecret(s *corev1.Secret) (key *string, err error) { conf, err := latestConfigFromSecret(s) if err != nil { @@ -1185,13 +1187,13 @@ func authKeyFromSecret(s *corev1.Secret) (key *string, err error) { return key, nil } -// shouldRetainAuthKey returns true if the state stored in a proxy's state Secret suggests that auth key should be -// retained (because the proxy has not yet successfully authenticated). -func shouldRetainAuthKey(s *corev1.Secret) bool { +// deviceAuthed returns true if the state stored in a proxy's state Secret +// suggests that the proxy has successfully authenticated. +func deviceAuthed(s *corev1.Secret) bool { if s == nil { - return false // nothing to retain here + return false // No state Secret means no device state. } - return len(s.Data["device_id"]) == 0 // proxy has not authed yet + return len(s.Data["device_id"]) > 0 } func shouldAcceptRoutes(pc *tsapi.ProxyClass) bool { diff --git a/cmd/k8s-operator/testutils_test.go b/cmd/k8s-operator/testutils_test.go index feed98d507a4e..8e055e0dd164e 100644 --- a/cmd/k8s-operator/testutils_test.go +++ b/cmd/k8s-operator/testutils_test.go @@ -529,7 +529,7 @@ func expectedSecret(t *testing.T, cl client.Client, opts configOpts) *corev1.Sec AcceptDNS: "false", Hostname: &opts.hostname, Locked: "false", - AuthKey: ptr.To("secret-authkey"), + AuthKey: new("new-authkey"), AcceptRoutes: "false", AppConnector: &ipn.AppConnectorPrefs{Advertise: false}, NoStatefulFiltering: "true", @@ -862,7 +862,7 @@ func (c *fakeTSClient) CreateKey(ctx context.Context, caps tailscale.KeyCapabili Created: time.Now(), Capabilities: caps, } - return "secret-authkey", k, nil + return "new-authkey", k, nil } func (c *fakeTSClient) Device(ctx context.Context, deviceID string, fields *tailscale.DeviceFieldsOpts) (*tailscale.Device, error) { diff --git a/cmd/k8s-operator/tsrecorder_test.go b/cmd/k8s-operator/tsrecorder_test.go index bea734d865f66..5d315f8c52e93 100644 --- a/cmd/k8s-operator/tsrecorder_test.go +++ b/cmd/k8s-operator/tsrecorder_test.go @@ -285,7 +285,7 @@ func expectRecorderResources(t *testing.T, fc client.WithWatch, tsr *tsapi.Recor } for replica := range replicas { - auth := tsrAuthSecret(tsr, tsNamespace, "secret-authkey", replica) + auth := tsrAuthSecret(tsr, tsNamespace, "new-authkey", replica) state := tsrStateSecret(tsr, tsNamespace, replica) if shouldExist { diff --git a/kube/kubetypes/types.go b/kube/kubetypes/types.go index 187f54f3481f8..9f1b29064acca 100644 --- a/kube/kubetypes/types.go +++ b/kube/kubetypes/types.go @@ -38,17 +38,17 @@ const ( // Keys that containerboot writes to state file that can be used to determine its state. // fields set in Tailscale state Secret. These are mostly used by the Tailscale Kubernetes operator to determine // the state of this tailscale device. - KeyDeviceID string = "device_id" // node stable ID of the device - KeyDeviceFQDN string = "device_fqdn" // device's tailnet hostname - KeyDeviceIPs string = "device_ips" // device's tailnet IPs - KeyPodUID string = "pod_uid" // Pod UID - // KeyCapVer contains Tailscale capability version of this proxy instance. - KeyCapVer string = "tailscale_capver" + KeyDeviceID = "device_id" // node stable ID of the device + KeyDeviceFQDN = "device_fqdn" // device's tailnet hostname + KeyDeviceIPs = "device_ips" // device's tailnet IPs + KeyPodUID = "pod_uid" // Pod UID + KeyCapVer = "tailscale_capver" // tailcfg.CurrentCapabilityVersion of this proxy instance. + KeyReissueAuthkey = "reissue_authkey" // Proxies will set this to the authkey that failed, or "no-authkey", if they can't log in. // KeyHTTPSEndpoint is a name of a field that can be set to the value of any HTTPS endpoint currently exposed by // this device to the tailnet. This is used by the Kubernetes operator Ingress proxy to communicate to the operator // that cluster workloads behind the Ingress can now be accessed via the given DNS name over HTTPS. - KeyHTTPSEndpoint string = "https_endpoint" - ValueNoHTTPS string = "no-https" + KeyHTTPSEndpoint = "https_endpoint" + ValueNoHTTPS = "no-https" // Pod's IPv4 address header key as returned by containerboot health check endpoint. PodIPv4Header string = "Pod-IPv4" From 17a4f58b5c457d350b1e357c8b1e3a9e7188416b Mon Sep 17 00:00:00 2001 From: Nick O'Neill Date: Tue, 17 Mar 2026 15:05:14 -0700 Subject: [PATCH 194/202] VERISON.txt this is v1.96.2 Signed-off-by: Nick O'Neill --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 024f4b4caab94..4e28d4dc5e3c1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.96.1 +1.96.2 From 044221b8c6d40ac0f1b5e8134aeec503dfcd3902 Mon Sep 17 00:00:00 2001 From: Tom Meadows Date: Thu, 19 Mar 2026 12:49:36 +0100 Subject: [PATCH 195/202] kube/certs: discover TLS domains from TCP TerminateTLS handlers (#19020) (#19021) After #18179 switched to L4 TCPForward, EnsureCertLoops found no domains since it only checked service.Web entries. Certs were never provisioned, leaving kube-apiserver ProxyGroups stuck at 0/N ready. Fixes #19019 (cherry picked from commit a565833998c71517594187befe8a6837520eb4b0) Signed-off-by: Raj Singh Co-authored-by: Raj Singh --- kube/certs/certs.go | 7 +++++++ kube/certs/certs_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/kube/certs/certs.go b/kube/certs/certs.go index dd8fd7d799ac6..4c8ac88b6b624 100644 --- a/kube/certs/certs.go +++ b/kube/certs/certs.go @@ -53,6 +53,7 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig) currentDomains := make(map[string]bool) const httpsPort = "443" for _, service := range sc.Services { + // L7 Web handlers (HA Ingress). for hostPort := range service.Web { domain, port, err := net.SplitHostPort(string(hostPort)) if err != nil { @@ -63,6 +64,12 @@ func (cm *CertManager) EnsureCertLoops(ctx context.Context, sc *ipn.ServeConfig) } currentDomains[domain] = true } + // L4 TCP handlers with TLS termination (kube-apiserver proxy). + for _, handler := range service.TCP { + if handler != nil && handler.TerminateTLS != "" { + currentDomains[handler.TerminateTLS] = true + } + } } cm.mu.Lock() defer cm.mu.Unlock() diff --git a/kube/certs/certs_test.go b/kube/certs/certs_test.go index 91196f5760f72..f3662f6c39ad4 100644 --- a/kube/certs/certs_test.go +++ b/kube/certs/certs_test.go @@ -127,6 +127,43 @@ func TestEnsureCertLoops(t *testing.T) { initialGoroutines: 2, // initially two loops (one per service) updatedGoroutines: 1, // one loop after removing service2 }, + { + name: "tcp_terminate_tls", + initialConfig: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:my-apiserver": { + TCP: map[uint16]*ipn.TCPPortHandler{ + 443: { + TCPForward: "localhost:80", + TerminateTLS: "my-apiserver.tailnetxyz.ts.net", + }, + }, + }, + }, + }, + initialGoroutines: 1, + }, + { + name: "tcp_terminate_tls_and_web", + initialConfig: &ipn.ServeConfig{ + Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{ + "svc:my-apiserver": { + TCP: map[uint16]*ipn.TCPPortHandler{ + 443: { + TCPForward: "localhost:80", + TerminateTLS: "my-apiserver.tailnetxyz.ts.net", + }, + }, + }, + "svc:my-app": { + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "my-app.tailnetxyz.ts.net:443": {}, + }, + }, + }, + }, + initialGoroutines: 2, + }, { name: "add_domain", initialConfig: &ipn.ServeConfig{ @@ -171,6 +208,7 @@ func TestEnsureCertLoops(t *testing.T) { CertDomains: []string{ "my-app.tailnetxyz.ts.net", "my-other-app.tailnetxyz.ts.net", + "my-apiserver.tailnetxyz.ts.net", }, }, }, From a330ce96f396059d584e7888ba42e68197704987 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Thu, 19 Mar 2026 08:54:55 -0500 Subject: [PATCH 196/202] net/dns: use the correct separator for multiple servers in the same NRPT rule on Windows If an NRPT rule lists more than one server, those servers should be separated by a semicolon (";"), rather than a semicolon followed by a space ("; "). Otherwise, Windows fails to parse the created registry value, and DNS resolution may fail. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/06088ca3-4cf1-48fa-8837-ca8d853ee1e8 Fixes #19040 Updates #15404 (enabled MagicDNS IPv6 by default, adding a second server and triggering the issue) Signed-off-by: Nick Khyl (cherry picked from commit 0d8d3831b9ca3aa507cbb28223a285340ea3df7b) --- net/dns/nrpt_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/dns/nrpt_windows.go b/net/dns/nrpt_windows.go index 1e1462e9ef908..6d9cf71b7a427 100644 --- a/net/dns/nrpt_windows.go +++ b/net/dns/nrpt_windows.go @@ -318,7 +318,7 @@ func (db *nrptRuleDatabase) writeNRPTRule(ruleID string, servers, doms []string) } defer key.Close() - if err := writeNRPTValues(key, strings.Join(servers, "; "), doms); err != nil { + if err := writeNRPTValues(key, strings.Join(servers, ";"), doms); err != nil { return err } } From 41061fabb67b0905e8d398e9db6c99d4f2dfa034 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 19 Mar 2026 16:00:19 +0000 Subject: [PATCH 197/202] feature/featuretags: skip TestAllOmitBuildTagsDeclared when not in a git repo This test was failing on Alpine's CI which had 'git' but wasn't in a git repo: https://github.com/tailscale/tailscale/commit/036b6a12621306da8368b167deb9858d4a8d6ce9#commitcomment-180001647 Updates #12614 Change-Id: Ic1b8856aaf020788a2a57e48738851e13ea85a93 Signed-off-by: Brad Fitzpatrick (cherry picked from commit ac19bd5e7a1791af621dfd04d98f627817bbefc9) --- feature/featuretags/featuretags_test.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/feature/featuretags/featuretags_test.go b/feature/featuretags/featuretags_test.go index 19c9722a6f300..c8a9f77ae5e10 100644 --- a/feature/featuretags/featuretags_test.go +++ b/feature/featuretags/featuretags_test.go @@ -5,9 +5,7 @@ package featuretags import ( "maps" - "os" "os/exec" - "path/filepath" "regexp" "slices" "strings" @@ -91,19 +89,18 @@ func TestRequiredBy(t *testing.T) { // Verify that all "ts_omit_foo" build tags are declared in featuretags.go func TestAllOmitBuildTagsDeclared(t *testing.T) { - dir, err := os.Getwd() + if _, err := exec.LookPath("git"); err != nil { + t.Skipf("git not found in PATH; skipping test") + } + root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() if err != nil { - t.Fatal(err) + t.Skipf("not in a git repository; skipping test") } - root := filepath.Join(dir, "..", "..") cmd := exec.Command("git", "grep", "ts_omit_") - cmd.Dir = root + cmd.Dir = strings.TrimSpace(string(root)) out, err := cmd.CombinedOutput() if err != nil { - if _, err := exec.LookPath("git"); err != nil { - t.Skipf("git not found in PATH; skipping test") - } t.Fatalf("git grep failed: %v\nOutput:\n%s", err, out) } rx := regexp.MustCompile(`\bts_omit_[\w_]+\b`) From bf309e40002f482217865f1ef47803f5e6768614 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Thu, 19 Mar 2026 12:34:44 -0500 Subject: [PATCH 198/202] VERSION.txt: this is v1.96.3 Signed-off-by: Nick Khyl --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 4e28d4dc5e3c1..52f38f56e84a6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.96.2 +1.96.3 From bb055ff5ffcb1f402a0de925233cbce7caf99071 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Wed, 25 Mar 2026 13:09:36 -0500 Subject: [PATCH 199/202] go.toolchain.*: bump for mips and synology segmentation violation fixes Updates #19039 Updates tailscale/go#160 Updates tailscale/go#162 Updates golang/go#77730 Updates golang/go#77930 Signed-off-by: Nick Khyl (cherry picked from commit 33da8a8d6829dfb8e888feaa3cbbd97cbba741bd) --- go.toolchain.next.rev | 2 +- go.toolchain.rev | 2 +- go.toolchain.rev.sri | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.toolchain.next.rev b/go.toolchain.next.rev index ea3d3c773f779..205355c4745f6 100644 --- a/go.toolchain.next.rev +++ b/go.toolchain.next.rev @@ -1 +1 @@ -5b5cb0db47535a0a8d2f450cb1bf83af8e70f164 +f4de14a515221e27c0d79446b423849a6546e3a6 diff --git a/go.toolchain.rev b/go.toolchain.rev index 0b07150d516a0..205355c4745f6 100644 --- a/go.toolchain.rev +++ b/go.toolchain.rev @@ -1 +1 @@ -5cce30e20c1fc6d8463b0a99acdd9777c4ad124b +f4de14a515221e27c0d79446b423849a6546e3a6 diff --git a/go.toolchain.rev.sri b/go.toolchain.rev.sri index 39306739de25f..86b2083ff8624 100644 --- a/go.toolchain.rev.sri +++ b/go.toolchain.rev.sri @@ -1 +1 @@ -sha256-nYXUQfKPoHgKCvK5BCh0BKOgPh6n90XX+iUNETLETBA= +sha256-qmX68/Ml/jvf+sD9qykdx9QhSbkYaF8xJMFtd3iLHI8= From 41cb72f27119f95b859335f3ffc3434d6ca55e23 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Wed, 25 Mar 2026 14:45:12 -0500 Subject: [PATCH 200/202] VERSION.txt: this is v1.96.4 Signed-off-by: Nick Khyl --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 52f38f56e84a6..064da1ffbecb1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.96.3 +1.96.4 From d15a5654aaed4fe0c0d17b10efe4e4fa71fb4bdb Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 22 Mar 2026 15:47:23 +0000 Subject: [PATCH 201/202] release/dist/unixpkgs: include tailscale-online.target in packages The tailscale-online.target and tailscale-wait-online.service systemd units were added in 30e12310f1 but never included in the release packaging (tarballs, debs, rpms). Updates #11504 Change-Id: I93e03e1330a7ff8facf845c7ca062ed2f0d35eaa Signed-off-by: Brad Fitzpatrick (cherry picked from commit 2b1030a4317bbb5c728688680f61eb6d9df52e55) --- release/dist/unixpkgs/pkgs.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/release/dist/unixpkgs/pkgs.go b/release/dist/unixpkgs/pkgs.go index d251ff621f98a..6e140d580b904 100644 --- a/release/dist/unixpkgs/pkgs.go +++ b/release/dist/unixpkgs/pkgs.go @@ -140,6 +140,12 @@ func (t *tgzTarget) Build(b *dist.Build) ([]string, error) { if err := addFile(filepath.Join(tailscaledDir, "tailscaled.defaults"), filepath.Join(dir, "tailscaled.defaults"), 0644); err != nil { return nil, err } + if err := addFile(filepath.Join(tailscaledDir, "tailscale-online.target"), filepath.Join(dir, "tailscale-online.target"), 0644); err != nil { + return nil, err + } + if err := addFile(filepath.Join(tailscaledDir, "tailscale-wait-online.service"), filepath.Join(dir, "tailscale-wait-online.service"), 0644); err != nil { + return nil, err + } } if err := tw.Close(); err != nil { return nil, err @@ -223,6 +229,16 @@ func (t *debTarget) Build(b *dist.Build) ([]string, error) { Source: filepath.Join(tailscaledDir, "tailscaled.service"), Destination: "/lib/systemd/system/tailscaled.service", }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-online.target"), + Destination: "/lib/systemd/system/tailscale-online.target", + }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-wait-online.service"), + Destination: "/lib/systemd/system/tailscale-wait-online.service", + }, &files.Content{ Type: files.TypeConfigNoReplace, Source: filepath.Join(tailscaledDir, "tailscaled.defaults"), @@ -360,6 +376,16 @@ func (t *rpmTarget) Build(b *dist.Build) ([]string, error) { Source: filepath.Join(tailscaledDir, "tailscaled.service"), Destination: "/lib/systemd/system/tailscaled.service", }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-online.target"), + Destination: "/lib/systemd/system/tailscale-online.target", + }, + &files.Content{ + Type: files.TypeFile, + Source: filepath.Join(tailscaledDir, "tailscale-wait-online.service"), + Destination: "/lib/systemd/system/tailscale-wait-online.service", + }, &files.Content{ Type: files.TypeConfigNoReplace, Source: filepath.Join(tailscaledDir, "tailscaled.defaults"), From 88200dbb4515f7396384b8f1c9e0a80566d18834 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Fri, 27 Mar 2026 14:20:32 -0400 Subject: [PATCH 202/202] VERSION.txt: this is v1.96.5 Signed-off-by: Jonathan Nobels --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 064da1ffbecb1..27a0f1d276599 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.96.4 +1.96.5