ソースを参照

health: make it omittable

Saves 86 KB.

And stop depending on expvar and usermetrics when disabled,
in prep to removing all the expvar/metrics/tsweb stuff.

Updates #12614

Change-Id: I35d2479ddd1d39b615bab32b1fa940ae8cbf9b11
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 5 ヶ月 前
コミット
447cbdd1d0

+ 1 - 1
cmd/k8s-operator/depaware.txt

@@ -741,7 +741,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
         tailscale.com/logpolicy                                      from tailscale.com/ipn/ipnlocal+
         tailscale.com/logtail                                        from tailscale.com/control/controlclient+
         tailscale.com/logtail/filch                                  from tailscale.com/log/sockstatlog+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/net/tstun+
         tailscale.com/net/ace                                        from tailscale.com/control/controlhttp
         tailscale.com/net/bakedroots                                 from tailscale.com/net/tlsdial+
      💣 tailscale.com/net/batching                                   from tailscale.com/wgengine/magicsock

+ 1 - 1
cmd/tailscale/depaware.txt

@@ -112,7 +112,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
         tailscale.com/ipn/ipnstate                                   from tailscale.com/client/local+
         tailscale.com/kube/kubetypes                                 from tailscale.com/envknob
         tailscale.com/licenses                                       from tailscale.com/client/web+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/tsweb+
         tailscale.com/net/ace                                        from tailscale.com/cmd/tailscale/cli+
         tailscale.com/net/bakedroots                                 from tailscale.com/net/tlsdial
         tailscale.com/net/captivedetection                           from tailscale.com/net/netcheck

+ 3 - 3
cmd/tailscaled/depaware-min.txt

@@ -76,7 +76,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/logpolicy                                      from tailscale.com/cmd/tailscaled+
         tailscale.com/logtail                                        from tailscale.com/cmd/tailscaled+
         tailscale.com/logtail/filch                                  from tailscale.com/log/sockstatlog+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/net/tstun+
         tailscale.com/net/ace                                        from tailscale.com/control/controlhttp
         tailscale.com/net/bakedroots                                 from tailscale.com/net/tlsdial
      💣 tailscale.com/net/batching                                   from tailscale.com/wgengine/magicsock
@@ -179,7 +179,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/util/syspolicy/policyclient                    from tailscale.com/cmd/tailscaled+
         tailscale.com/util/syspolicy/ptype                           from tailscale.com/ipn/ipnlocal+
         tailscale.com/util/testenv                                   from tailscale.com/control/controlclient+
-        tailscale.com/util/usermetric                                from tailscale.com/health+
+        tailscale.com/util/usermetric                                from tailscale.com/ipn/ipnlocal+
         tailscale.com/util/vizerror                                  from tailscale.com/tailcfg+
         tailscale.com/util/winutil                                   from tailscale.com/ipn/ipnauth
         tailscale.com/util/zstdframe                                 from tailscale.com/control/controlclient
@@ -324,7 +324,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         encoding/json                                                from expvar+
         encoding/pem                                                 from crypto/tls+
         errors                                                       from bufio+
-        expvar                                                       from tailscale.com/health+
+        expvar                                                       from tailscale.com/metrics+
         flag                                                         from tailscale.com/cmd/tailscaled+
         fmt                                                          from compress/flate+
         hash                                                         from crypto+

+ 3 - 3
cmd/tailscaled/depaware-minbox.txt

@@ -99,7 +99,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/logpolicy                                      from tailscale.com/cmd/tailscaled+
         tailscale.com/logtail                                        from tailscale.com/cmd/tailscaled+
         tailscale.com/logtail/filch                                  from tailscale.com/log/sockstatlog+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/net/tstun+
         tailscale.com/net/ace                                        from tailscale.com/control/controlhttp+
         tailscale.com/net/bakedroots                                 from tailscale.com/net/tlsdial
      💣 tailscale.com/net/batching                                   from tailscale.com/wgengine/magicsock
@@ -206,7 +206,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/util/syspolicy/policyclient                    from tailscale.com/cmd/tailscaled+
         tailscale.com/util/syspolicy/ptype                           from tailscale.com/ipn/ipnlocal+
         tailscale.com/util/testenv                                   from tailscale.com/control/controlclient+
-        tailscale.com/util/usermetric                                from tailscale.com/health+
+        tailscale.com/util/usermetric                                from tailscale.com/ipn/ipnlocal+
         tailscale.com/util/vizerror                                  from tailscale.com/tailcfg+
         tailscale.com/util/winutil                                   from tailscale.com/ipn/ipnauth
         tailscale.com/util/zstdframe                                 from tailscale.com/control/controlclient
@@ -353,7 +353,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         encoding/json                                                from expvar+
         encoding/pem                                                 from crypto/tls+
         errors                                                       from bufio+
-        expvar                                                       from tailscale.com/health+
+        expvar                                                       from tailscale.com/metrics+
         flag                                                         from tailscale.com/cmd/tailscaled+
         fmt                                                          from compress/flate+
         hash                                                         from crypto+

+ 1 - 1
cmd/tailscaled/depaware.txt

@@ -321,7 +321,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/logpolicy                                      from tailscale.com/cmd/tailscaled+
         tailscale.com/logtail                                        from tailscale.com/cmd/tailscaled+
         tailscale.com/logtail/filch                                  from tailscale.com/log/sockstatlog+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/net/tstun+
         tailscale.com/net/ace                                        from tailscale.com/control/controlhttp
         tailscale.com/net/bakedroots                                 from tailscale.com/net/tlsdial+
      💣 tailscale.com/net/batching                                   from tailscale.com/wgengine/magicsock+

+ 1 - 1
cmd/tsidp/depaware.txt

@@ -169,7 +169,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
         tailscale.com/logpolicy                                      from tailscale.com/ipn/ipnlocal+
         tailscale.com/logtail                                        from tailscale.com/control/controlclient+
         tailscale.com/logtail/filch                                  from tailscale.com/log/sockstatlog+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/net/tstun+
         tailscale.com/net/ace                                        from tailscale.com/control/controlhttp
         tailscale.com/net/bakedroots                                 from tailscale.com/ipn/ipnlocal+
      💣 tailscale.com/net/batching                                   from tailscale.com/wgengine/magicsock

+ 13 - 0
feature/buildfeatures/feature_health_disabled.go

@@ -0,0 +1,13 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Code generated by gen.go; DO NOT EDIT.
+
+//go:build ts_omit_health
+
+package buildfeatures
+
+// HasHealth is whether the binary was built with support for modular feature "Health checking support".
+// Specifically, it's whether the binary was NOT built with the "ts_omit_health" build tag.
+// It's a const so it can be used for dead code elimination.
+const HasHealth = false

+ 13 - 0
feature/buildfeatures/feature_health_enabled.go

@@ -0,0 +1,13 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Code generated by gen.go; DO NOT EDIT.
+
+//go:build !ts_omit_health
+
+package buildfeatures
+
+// HasHealth is whether the binary was built with support for modular feature "Health checking support".
+// Specifically, it's whether the binary was NOT built with the "ts_omit_health" build tag.
+// It's a const so it can be used for dead code elimination.
+const HasHealth = true

+ 1 - 0
feature/featuretags/featuretags.go

@@ -150,6 +150,7 @@ var Features = map[FeatureTag]FeatureMeta{
 		Desc: "Generic Receive Offload support (performance)",
 		Deps: []FeatureTag{"netstack"},
 	},
+	"health":        {Sym: "Health", Desc: "Health checking support"},
 	"hujsonconf":    {Sym: "HuJSONConf", Desc: "HuJSON config file support"},
 	"iptables":      {Sym: "IPTables", Desc: "Linux iptables support"},
 	"kube":          {Sym: "Kube", Desc: "Kubernetes integration"},

+ 41 - 45
health/health.go

@@ -8,7 +8,6 @@ package health
 import (
 	"context"
 	"errors"
-	"expvar"
 	"fmt"
 	"maps"
 	"net/http"
@@ -20,14 +19,13 @@ import (
 	"time"
 
 	"tailscale.com/envknob"
-	"tailscale.com/metrics"
+	"tailscale.com/feature/buildfeatures"
 	"tailscale.com/tailcfg"
 	"tailscale.com/tstime"
 	"tailscale.com/types/opt"
 	"tailscale.com/util/cibuild"
 	"tailscale.com/util/eventbus"
 	"tailscale.com/util/mak"
-	"tailscale.com/util/usermetric"
 	"tailscale.com/version"
 )
 
@@ -132,12 +130,15 @@ type Tracker struct {
 	lastLoginErr                error
 	localLogConfigErr           error
 	tlsConnectionErrors         map[string]error // map[ServerName]error
-	metricHealthMessage         *metrics.MultiLabelMap[metricHealthMessageLabel]
+	metricHealthMessage         any              // nil or *metrics.MultiLabelMap[metricHealthMessageLabel]
 }
 
 // NewTracker contructs a new [Tracker] and attaches the given eventbus.
 // NewTracker will panic is no eventbus is given.
 func NewTracker(bus *eventbus.Bus) *Tracker {
+	if !buildfeatures.HasHealth {
+		return &Tracker{}
+	}
 	if bus == nil {
 		panic("no eventbus set")
 	}
@@ -221,6 +222,9 @@ const legacyErrorArgKey = "LegacyError"
 // temporarily (2024-06-14) while we migrate the old health infrastructure based
 // on Subsystems to the new Warnables architecture.
 func (s Subsystem) Warnable() *Warnable {
+	if !buildfeatures.HasHealth {
+		return &noopWarnable
+	}
 	w, ok := subsystemsWarnables[s]
 	if !ok {
 		panic(fmt.Sprintf("health: no Warnable for Subsystem %q", s))
@@ -230,10 +234,15 @@ func (s Subsystem) Warnable() *Warnable {
 
 var registeredWarnables = map[WarnableCode]*Warnable{}
 
+var noopWarnable Warnable
+
 // Register registers a new Warnable with the health package and returns it.
 // Register panics if the Warnable was already registered, because Warnables
 // should be unique across the program.
 func Register(w *Warnable) *Warnable {
+	if !buildfeatures.HasHealth {
+		return &noopWarnable
+	}
 	if registeredWarnables[w.Code] != nil {
 		panic(fmt.Sprintf("health: a Warnable with code %q was already registered", w.Code))
 	}
@@ -245,6 +254,9 @@ func Register(w *Warnable) *Warnable {
 // unregister removes a Warnable from the health package. It should only be used
 // for testing purposes.
 func unregister(w *Warnable) {
+	if !buildfeatures.HasHealth {
+		return
+	}
 	if registeredWarnables[w.Code] == nil {
 		panic(fmt.Sprintf("health: attempting to unregister Warnable %q that was not registered", w.Code))
 	}
@@ -317,6 +329,9 @@ func StaticMessage(s string) func(Args) string {
 // some lost Tracker plumbing, we want to capture stack trace
 // samples when it occurs.
 func (t *Tracker) nil() bool {
+	if !buildfeatures.HasHealth {
+		return true
+	}
 	if t != nil {
 		return false
 	}
@@ -385,37 +400,10 @@ func (w *Warnable) IsVisible(ws *warningState, clockNow func() time.Time) bool {
 	return clockNow().Sub(ws.BrokenSince) >= w.TimeToVisible
 }
 
-// SetMetricsRegistry sets up the metrics for the Tracker. It takes
-// a usermetric.Registry and registers the metrics there.
-func (t *Tracker) SetMetricsRegistry(reg *usermetric.Registry) {
-	if reg == nil || t.metricHealthMessage != nil {
-		return
-	}
-
-	t.metricHealthMessage = usermetric.NewMultiLabelMapWithRegistry[metricHealthMessageLabel](
-		reg,
-		"tailscaled_health_messages",
-		"gauge",
-		"Number of health messages broken down by type.",
-	)
-
-	t.metricHealthMessage.Set(metricHealthMessageLabel{
-		Type: MetricLabelWarning,
-	}, expvar.Func(func() any {
-		if t.nil() {
-			return 0
-		}
-		t.mu.Lock()
-		defer t.mu.Unlock()
-		t.updateBuiltinWarnablesLocked()
-		return int64(len(t.stringsLocked()))
-	}))
-}
-
 // IsUnhealthy reports whether the current state is unhealthy because the given
 // warnable is set.
 func (t *Tracker) IsUnhealthy(w *Warnable) bool {
-	if t.nil() {
+	if !buildfeatures.HasHealth || t.nil() {
 		return false
 	}
 	t.mu.Lock()
@@ -429,7 +417,7 @@ func (t *Tracker) IsUnhealthy(w *Warnable) bool {
 // SetUnhealthy takes ownership of args. The args can be nil if no additional information is
 // needed for the unhealthy state.
 func (t *Tracker) SetUnhealthy(w *Warnable, args Args) {
-	if t.nil() {
+	if !buildfeatures.HasHealth || t.nil() {
 		return
 	}
 	t.mu.Lock()
@@ -438,7 +426,7 @@ func (t *Tracker) SetUnhealthy(w *Warnable, args Args) {
 }
 
 func (t *Tracker) setUnhealthyLocked(w *Warnable, args Args) {
-	if w == nil {
+	if !buildfeatures.HasHealth || w == nil {
 		return
 	}
 
@@ -489,7 +477,7 @@ func (t *Tracker) setUnhealthyLocked(w *Warnable, args Args) {
 
 // SetHealthy removes any warningState for the given Warnable.
 func (t *Tracker) SetHealthy(w *Warnable) {
-	if t.nil() {
+	if !buildfeatures.HasHealth || t.nil() {
 		return
 	}
 	t.mu.Lock()
@@ -498,7 +486,7 @@ func (t *Tracker) SetHealthy(w *Warnable) {
 }
 
 func (t *Tracker) setHealthyLocked(w *Warnable) {
-	if t.warnableVal[w] == nil {
+	if !buildfeatures.HasHealth || t.warnableVal[w] == nil {
 		// Nothing to remove
 		return
 	}
@@ -1009,7 +997,7 @@ func (t *Tracker) OverallError() error {
 // each Warning to show a localized version of them instead. This function is
 // here for legacy compatibility purposes and is deprecated.
 func (t *Tracker) Strings() []string {
-	if t.nil() {
+	if !buildfeatures.HasHealth || t.nil() {
 		return nil
 	}
 	t.mu.Lock()
@@ -1018,6 +1006,9 @@ func (t *Tracker) Strings() []string {
 }
 
 func (t *Tracker) stringsLocked() []string {
+	if !buildfeatures.HasHealth {
+		return nil
+	}
 	result := []string{}
 	for w, ws := range t.warnableVal {
 		if !w.IsVisible(ws, t.now) {
@@ -1078,6 +1069,9 @@ var fakeErrForTesting = envknob.RegisterString("TS_DEBUG_FAKE_HEALTH_ERROR")
 // updateBuiltinWarnablesLocked performs a number of checks on the state of the backend,
 // and adds/removes Warnings from the Tracker as needed.
 func (t *Tracker) updateBuiltinWarnablesLocked() {
+	if !buildfeatures.HasHealth {
+		return
+	}
 	t.updateWarmingUpWarnableLocked()
 
 	if w, show := t.showUpdateWarnable(); show {
@@ -1316,11 +1310,17 @@ func (s *ReceiveFuncStats) Name() string {
 }
 
 func (s *ReceiveFuncStats) Enter() {
+	if !buildfeatures.HasHealth {
+		return
+	}
 	s.numCalls.Add(1)
 	s.inCall.Store(true)
 }
 
 func (s *ReceiveFuncStats) Exit() {
+	if !buildfeatures.HasHealth {
+		return
+	}
 	s.inCall.Store(false)
 }
 
@@ -1329,7 +1329,7 @@ func (s *ReceiveFuncStats) Exit() {
 //
 // If t is nil, it returns nil.
 func (t *Tracker) ReceiveFuncStats(which ReceiveFunc) *ReceiveFuncStats {
-	if t == nil {
+	if !buildfeatures.HasHealth || t == nil {
 		return nil
 	}
 	t.initOnce.Do(t.doOnceInit)
@@ -1337,6 +1337,9 @@ func (t *Tracker) ReceiveFuncStats(which ReceiveFunc) *ReceiveFuncStats {
 }
 
 func (t *Tracker) doOnceInit() {
+	if !buildfeatures.HasHealth {
+		return
+	}
 	for i := range t.MagicSockReceiveFuncs {
 		f := &t.MagicSockReceiveFuncs[i]
 		f.name = (ReceiveFunc(i)).String()
@@ -1385,10 +1388,3 @@ func (t *Tracker) LastNoiseDialWasRecent() bool {
 	t.lastNoiseDial = now
 	return dur < 2*time.Minute
 }
-
-const MetricLabelWarning = "warning"
-
-type metricHealthMessageLabel struct {
-	// TODO: break down by warnable.severity as well?
-	Type string
-}

+ 11 - 2
health/health_test.go

@@ -15,6 +15,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
+	"tailscale.com/metrics"
 	"tailscale.com/tailcfg"
 	"tailscale.com/tstest"
 	"tailscale.com/tstime"
@@ -497,7 +498,11 @@ func TestHealthMetric(t *testing.T) {
 			tr.applyUpdates = tt.apply
 			tr.latestVersion = tt.cv
 			tr.SetMetricsRegistry(&usermetric.Registry{})
-			if val := tr.metricHealthMessage.Get(metricHealthMessageLabel{Type: MetricLabelWarning}).String(); val != strconv.Itoa(tt.wantMetricCount) {
+			m, ok := tr.metricHealthMessage.(*metrics.MultiLabelMap[metricHealthMessageLabel])
+			if !ok {
+				t.Fatal("metricHealthMessage has wrong type or is nil")
+			}
+			if val := m.Get(metricHealthMessageLabel{Type: MetricLabelWarning}).String(); val != strconv.Itoa(tt.wantMetricCount) {
 				t.Fatalf("metric value: %q, want: %q", val, strconv.Itoa(tt.wantMetricCount))
 			}
 			for _, w := range tr.CurrentState().Warnings {
@@ -634,7 +639,11 @@ func TestControlHealth(t *testing.T) {
 		var r usermetric.Registry
 		ht.SetMetricsRegistry(&r)
 
-		got := ht.metricHealthMessage.Get(metricHealthMessageLabel{
+		m, ok := ht.metricHealthMessage.(*metrics.MultiLabelMap[metricHealthMessageLabel])
+		if !ok {
+			t.Fatal("metricHealthMessage has wrong type or is nil")
+		}
+		got := m.Get(metricHealthMessageLabel{
 			Type: MetricLabelWarning,
 		}).String()
 		want := strconv.Itoa(

+ 2 - 1
health/state.go

@@ -9,6 +9,7 @@ import (
 	"encoding/json"
 	"time"
 
+	"tailscale.com/feature/buildfeatures"
 	"tailscale.com/tailcfg"
 )
 
@@ -120,7 +121,7 @@ func (w *Warnable) unhealthyState(ws *warningState) *UnhealthyState {
 // The returned State is a snapshot of shared memory, and the caller should not
 // mutate the returned value.
 func (t *Tracker) CurrentState() *State {
-	if t.nil() {
+	if !buildfeatures.HasHealth || t.nil() {
 		return &State{}
 	}
 

+ 52 - 0
health/usermetrics.go

@@ -0,0 +1,52 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ts_omit_health && !ts_omit_usermetrics
+
+package health
+
+import (
+	"expvar"
+
+	"tailscale.com/feature/buildfeatures"
+	"tailscale.com/util/usermetric"
+)
+
+const MetricLabelWarning = "warning"
+
+type metricHealthMessageLabel struct {
+	// TODO: break down by warnable.severity as well?
+	Type string
+}
+
+// SetMetricsRegistry sets up the metrics for the Tracker. It takes
+// a usermetric.Registry and registers the metrics there.
+func (t *Tracker) SetMetricsRegistry(reg *usermetric.Registry) {
+	if !buildfeatures.HasHealth {
+		return
+	}
+
+	if reg == nil || t.metricHealthMessage != nil {
+		return
+	}
+
+	m := usermetric.NewMultiLabelMapWithRegistry[metricHealthMessageLabel](
+		reg,
+		"tailscaled_health_messages",
+		"gauge",
+		"Number of health messages broken down by type.",
+	)
+
+	m.Set(metricHealthMessageLabel{
+		Type: MetricLabelWarning,
+	}, expvar.Func(func() any {
+		if t.nil() {
+			return 0
+		}
+		t.mu.Lock()
+		defer t.mu.Unlock()
+		t.updateBuiltinWarnablesLocked()
+		return int64(len(t.stringsLocked()))
+	}))
+	t.metricHealthMessage = m
+}

+ 8 - 0
health/usermetrics_omit.go

@@ -0,0 +1,8 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build ts_omit_health || ts_omit_usermetrics
+
+package health
+
+func (t *Tracker) SetMetricsRegistry(any) {}

+ 217 - 171
health/warnings.go

@@ -8,234 +8,278 @@ import (
 	"runtime"
 	"time"
 
+	"tailscale.com/feature/buildfeatures"
 	"tailscale.com/version"
 )
 
+func condRegister(f func() *Warnable) *Warnable {
+	if !buildfeatures.HasHealth {
+		return nil
+	}
+	return f()
+}
+
 /**
 This file contains definitions for the Warnables maintained within this `health` package.
 */
 
 // updateAvailableWarnable is a Warnable that warns the user that an update is available.
-var updateAvailableWarnable = Register(&Warnable{
-	Code:     "update-available",
-	Title:    "Update available",
-	Severity: SeverityLow,
-	Text: func(args Args) string {
-		if version.IsMacAppStore() || version.IsAppleTV() || version.IsMacSys() || version.IsWindowsGUI() || runtime.GOOS == "android" {
-			return fmt.Sprintf("An update from version %s to %s is available.", args[ArgCurrentVersion], args[ArgAvailableVersion])
-		} else {
-			return fmt.Sprintf("An update from version %s to %s is available. Run `tailscale update` or `tailscale set --auto-update` to update now.", args[ArgCurrentVersion], args[ArgAvailableVersion])
-		}
-	},
+var updateAvailableWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "update-available",
+		Title:    "Update available",
+		Severity: SeverityLow,
+		Text: func(args Args) string {
+			if version.IsMacAppStore() || version.IsAppleTV() || version.IsMacSys() || version.IsWindowsGUI() || runtime.GOOS == "android" {
+				return fmt.Sprintf("An update from version %s to %s is available.", args[ArgCurrentVersion], args[ArgAvailableVersion])
+			} else {
+				return fmt.Sprintf("An update from version %s to %s is available. Run `tailscale update` or `tailscale set --auto-update` to update now.", args[ArgCurrentVersion], args[ArgAvailableVersion])
+			}
+		},
+	}
 })
 
 // securityUpdateAvailableWarnable is a Warnable that warns the user that an important security update is available.
-var securityUpdateAvailableWarnable = Register(&Warnable{
-	Code:     "security-update-available",
-	Title:    "Security update available",
-	Severity: SeverityMedium,
-	Text: func(args Args) string {
-		if version.IsMacAppStore() || version.IsAppleTV() || version.IsMacSys() || version.IsWindowsGUI() || runtime.GOOS == "android" {
-			return fmt.Sprintf("A security update from version %s to %s is available.", args[ArgCurrentVersion], args[ArgAvailableVersion])
-		} else {
-			return fmt.Sprintf("A security update from version %s to %s is available. Run `tailscale update` or `tailscale set --auto-update` to update now.", args[ArgCurrentVersion], args[ArgAvailableVersion])
-		}
-	},
+var securityUpdateAvailableWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "security-update-available",
+		Title:    "Security update available",
+		Severity: SeverityMedium,
+		Text: func(args Args) string {
+			if version.IsMacAppStore() || version.IsAppleTV() || version.IsMacSys() || version.IsWindowsGUI() || runtime.GOOS == "android" {
+				return fmt.Sprintf("A security update from version %s to %s is available.", args[ArgCurrentVersion], args[ArgAvailableVersion])
+			} else {
+				return fmt.Sprintf("A security update from version %s to %s is available. Run `tailscale update` or `tailscale set --auto-update` to update now.", args[ArgCurrentVersion], args[ArgAvailableVersion])
+			}
+		},
+	}
 })
 
 // unstableWarnable is a Warnable that warns the user that they are using an unstable version of Tailscale
 // so they won't be surprised by all the issues that may arise.
-var unstableWarnable = Register(&Warnable{
-	Code:     "is-using-unstable-version",
-	Title:    "Using an unstable version",
-	Severity: SeverityLow,
-	Text:     StaticMessage("This is an unstable version of Tailscale meant for testing and development purposes. Please report any issues to Tailscale."),
+var unstableWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "is-using-unstable-version",
+		Title:    "Using an unstable version",
+		Severity: SeverityLow,
+		Text:     StaticMessage("This is an unstable version of Tailscale meant for testing and development purposes. Please report any issues to Tailscale."),
+	}
 })
 
 // NetworkStatusWarnable is a Warnable that warns the user that the network is down.
-var NetworkStatusWarnable = Register(&Warnable{
-	Code:                "network-status",
-	Title:               "Network down",
-	Severity:            SeverityMedium,
-	Text:                StaticMessage("Tailscale cannot connect because the network is down. Check your Internet connection."),
-	ImpactsConnectivity: true,
-	TimeToVisible:       5 * time.Second,
+var NetworkStatusWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:                "network-status",
+		Title:               "Network down",
+		Severity:            SeverityMedium,
+		Text:                StaticMessage("Tailscale cannot connect because the network is down. Check your Internet connection."),
+		ImpactsConnectivity: true,
+		TimeToVisible:       5 * time.Second,
+	}
 })
 
 // IPNStateWarnable is a Warnable that warns the user that Tailscale is stopped.
-var IPNStateWarnable = Register(&Warnable{
-	Code:     "wantrunning-false",
-	Title:    "Tailscale off",
-	Severity: SeverityLow,
-	Text:     StaticMessage("Tailscale is stopped."),
+var IPNStateWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "wantrunning-false",
+		Title:    "Tailscale off",
+		Severity: SeverityLow,
+		Text:     StaticMessage("Tailscale is stopped."),
+	}
 })
 
 // localLogWarnable is a Warnable that warns the user that the local log is misconfigured.
-var localLogWarnable = Register(&Warnable{
-	Code:     "local-log-config-error",
-	Title:    "Local log misconfiguration",
-	Severity: SeverityLow,
-	Text: func(args Args) string {
-		return fmt.Sprintf("The local log is misconfigured: %v", args[ArgError])
-	},
+var localLogWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "local-log-config-error",
+		Title:    "Local log misconfiguration",
+		Severity: SeverityLow,
+		Text: func(args Args) string {
+			return fmt.Sprintf("The local log is misconfigured: %v", args[ArgError])
+		},
+	}
 })
 
 // LoginStateWarnable is a Warnable that warns the user that they are logged out,
 // and provides the last login error if available.
-var LoginStateWarnable = Register(&Warnable{
-	Code:     "login-state",
-	Title:    "Logged out",
-	Severity: SeverityMedium,
-	Text: func(args Args) string {
-		if args[ArgError] != "" {
-			return fmt.Sprintf("You are logged out. The last login error was: %v", args[ArgError])
-		} else {
-			return "You are logged out."
-		}
-	},
-	DependsOn: []*Warnable{IPNStateWarnable},
+var LoginStateWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "login-state",
+		Title:    "Logged out",
+		Severity: SeverityMedium,
+		Text: func(args Args) string {
+			if args[ArgError] != "" {
+				return fmt.Sprintf("You are logged out. The last login error was: %v", args[ArgError])
+			} else {
+				return "You are logged out."
+			}
+		},
+		DependsOn: []*Warnable{IPNStateWarnable},
+	}
 })
 
 // notInMapPollWarnable is a Warnable that warns the user that we are using a stale network map.
-var notInMapPollWarnable = Register(&Warnable{
-	Code:      "not-in-map-poll",
-	Title:     "Out of sync",
-	Severity:  SeverityMedium,
-	DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
-	Text:      StaticMessage("Unable to connect to the Tailscale coordination server to synchronize the state of your tailnet. Peer reachability might degrade over time."),
-	// 8 minutes reflects a maximum maintenance window for the coordination server.
-	TimeToVisible: 8 * time.Minute,
+var notInMapPollWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:      "not-in-map-poll",
+		Title:     "Out of sync",
+		Severity:  SeverityMedium,
+		DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
+		Text:      StaticMessage("Unable to connect to the Tailscale coordination server to synchronize the state of your tailnet. Peer reachability might degrade over time."),
+		// 8 minutes reflects a maximum maintenance window for the coordination server.
+		TimeToVisible: 8 * time.Minute,
+	}
 })
 
 // noDERPHomeWarnable is a Warnable that warns the user that Tailscale doesn't have a home DERP.
-var noDERPHomeWarnable = Register(&Warnable{
-	Code:                "no-derp-home",
-	Title:               "No home relay server",
-	Severity:            SeverityMedium,
-	DependsOn:           []*Warnable{NetworkStatusWarnable},
-	Text:                StaticMessage("Tailscale could not connect to any relay server. Check your Internet connection."),
-	ImpactsConnectivity: true,
-	TimeToVisible:       10 * time.Second,
+var noDERPHomeWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:                "no-derp-home",
+		Title:               "No home relay server",
+		Severity:            SeverityMedium,
+		DependsOn:           []*Warnable{NetworkStatusWarnable},
+		Text:                StaticMessage("Tailscale could not connect to any relay server. Check your Internet connection."),
+		ImpactsConnectivity: true,
+		TimeToVisible:       10 * time.Second,
+	}
 })
 
 // noDERPConnectionWarnable is a Warnable that warns the user that Tailscale couldn't connect to a specific DERP server.
-var noDERPConnectionWarnable = Register(&Warnable{
-	Code:     "no-derp-connection",
-	Title:    "Relay server unavailable",
-	Severity: SeverityMedium,
-	DependsOn: []*Warnable{
-		NetworkStatusWarnable,
-
-		// Technically noDERPConnectionWarnable could be used to warn about
-		// failure to connect to a specific DERP server (e.g. your home is derp1
-		// but you're trying to connect to a peer's derp4 and are unable) but as
-		// of 2024-09-25 we only use this for connecting to your home DERP, so
-		// we depend on noDERPHomeWarnable which is the ability to figure out
-		// what your DERP home even is.
-		noDERPHomeWarnable,
-	},
-	Text: func(args Args) string {
-		if n := args[ArgDERPRegionName]; n != "" {
-			return fmt.Sprintf("Tailscale could not connect to the '%s' relay server. Your Internet connection might be down, or the server might be temporarily unavailable.", n)
-		} else {
-			return fmt.Sprintf("Tailscale could not connect to the relay server with ID '%s'. Your Internet connection might be down, or the server might be temporarily unavailable.", args[ArgDERPRegionID])
-		}
-	},
-	ImpactsConnectivity: true,
-	TimeToVisible:       10 * time.Second,
+var noDERPConnectionWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "no-derp-connection",
+		Title:    "Relay server unavailable",
+		Severity: SeverityMedium,
+		DependsOn: []*Warnable{
+			NetworkStatusWarnable,
+
+			// Technically noDERPConnectionWarnable could be used to warn about
+			// failure to connect to a specific DERP server (e.g. your home is derp1
+			// but you're trying to connect to a peer's derp4 and are unable) but as
+			// of 2024-09-25 we only use this for connecting to your home DERP, so
+			// we depend on noDERPHomeWarnable which is the ability to figure out
+			// what your DERP home even is.
+			noDERPHomeWarnable,
+		},
+		Text: func(args Args) string {
+			if n := args[ArgDERPRegionName]; n != "" {
+				return fmt.Sprintf("Tailscale could not connect to the '%s' relay server. Your Internet connection might be down, or the server might be temporarily unavailable.", n)
+			} else {
+				return fmt.Sprintf("Tailscale could not connect to the relay server with ID '%s'. Your Internet connection might be down, or the server might be temporarily unavailable.", args[ArgDERPRegionID])
+			}
+		},
+		ImpactsConnectivity: true,
+		TimeToVisible:       10 * time.Second,
+	}
 })
 
 // derpTimeoutWarnable is a Warnable that warns the user that Tailscale hasn't
 // heard from the home DERP region for a while.
-var derpTimeoutWarnable = Register(&Warnable{
-	Code:     "derp-timed-out",
-	Title:    "Relay server timed out",
-	Severity: SeverityMedium,
-	DependsOn: []*Warnable{
-		NetworkStatusWarnable,
-		noDERPConnectionWarnable, // don't warn about it being stalled if we're not connected
-		noDERPHomeWarnable,       // same reason as noDERPConnectionWarnable's dependency
-	},
-	Text: func(args Args) string {
-		if n := args[ArgDERPRegionName]; n != "" {
-			return fmt.Sprintf("Tailscale hasn't heard from the '%s' relay server in %v. The server might be temporarily unavailable, or your Internet connection might be down.", n, args[ArgDuration])
-		} else {
-			return fmt.Sprintf("Tailscale hasn't heard from the home relay server (region ID '%v') in %v. The server might be temporarily unavailable, or your Internet connection might be down.", args[ArgDERPRegionID], args[ArgDuration])
-		}
-	},
+var derpTimeoutWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "derp-timed-out",
+		Title:    "Relay server timed out",
+		Severity: SeverityMedium,
+		DependsOn: []*Warnable{
+			NetworkStatusWarnable,
+			noDERPConnectionWarnable, // don't warn about it being stalled if we're not connected
+			noDERPHomeWarnable,       // same reason as noDERPConnectionWarnable's dependency
+		},
+		Text: func(args Args) string {
+			if n := args[ArgDERPRegionName]; n != "" {
+				return fmt.Sprintf("Tailscale hasn't heard from the '%s' relay server in %v. The server might be temporarily unavailable, or your Internet connection might be down.", n, args[ArgDuration])
+			} else {
+				return fmt.Sprintf("Tailscale hasn't heard from the home relay server (region ID '%v') in %v. The server might be temporarily unavailable, or your Internet connection might be down.", args[ArgDERPRegionID], args[ArgDuration])
+			}
+		},
+	}
 })
 
 // derpRegionErrorWarnable is a Warnable that warns the user that a DERP region is reporting an issue.
-var derpRegionErrorWarnable = Register(&Warnable{
-	Code:      "derp-region-error",
-	Title:     "Relay server error",
-	Severity:  SeverityLow,
-	DependsOn: []*Warnable{NetworkStatusWarnable},
-	Text: func(args Args) string {
-		return fmt.Sprintf("The relay server #%v is reporting an issue: %v", args[ArgDERPRegionID], args[ArgError])
-	},
+var derpRegionErrorWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:      "derp-region-error",
+		Title:     "Relay server error",
+		Severity:  SeverityLow,
+		DependsOn: []*Warnable{NetworkStatusWarnable},
+		Text: func(args Args) string {
+			return fmt.Sprintf("The relay server #%v is reporting an issue: %v", args[ArgDERPRegionID], args[ArgError])
+		},
+	}
 })
 
 // noUDP4BindWarnable is a Warnable that warns the user that Tailscale couldn't listen for incoming UDP connections.
-var noUDP4BindWarnable = Register(&Warnable{
-	Code:                "no-udp4-bind",
-	Title:               "NAT traversal setup failure",
-	Severity:            SeverityMedium,
-	DependsOn:           []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
-	Text:                StaticMessage("Tailscale couldn't listen for incoming UDP connections."),
-	ImpactsConnectivity: true,
+var noUDP4BindWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:                "no-udp4-bind",
+		Title:               "NAT traversal setup failure",
+		Severity:            SeverityMedium,
+		DependsOn:           []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
+		Text:                StaticMessage("Tailscale couldn't listen for incoming UDP connections."),
+		ImpactsConnectivity: true,
+	}
 })
 
 // mapResponseTimeoutWarnable is a Warnable that warns the user that Tailscale hasn't received a network map from the coordination server in a while.
-var mapResponseTimeoutWarnable = Register(&Warnable{
-	Code:      "mapresponse-timeout",
-	Title:     "Network map response timeout",
-	Severity:  SeverityMedium,
-	DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
-	Text: func(args Args) string {
-		return fmt.Sprintf("Tailscale hasn't received a network map from the coordination server in %s.", args[ArgDuration])
-	},
+var mapResponseTimeoutWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:      "mapresponse-timeout",
+		Title:     "Network map response timeout",
+		Severity:  SeverityMedium,
+		DependsOn: []*Warnable{NetworkStatusWarnable, IPNStateWarnable},
+		Text: func(args Args) string {
+			return fmt.Sprintf("Tailscale hasn't received a network map from the coordination server in %s.", args[ArgDuration])
+		},
+	}
 })
 
 // tlsConnectionFailedWarnable is a Warnable that warns the user that Tailscale could not establish an encrypted connection with a server.
-var tlsConnectionFailedWarnable = Register(&Warnable{
-	Code:      "tls-connection-failed",
-	Title:     "Encrypted connection failed",
-	Severity:  SeverityMedium,
-	DependsOn: []*Warnable{NetworkStatusWarnable},
-	Text: func(args Args) string {
-		return fmt.Sprintf("Tailscale could not establish an encrypted connection with '%q': %v", args[ArgServerName], args[ArgError])
-	},
+var tlsConnectionFailedWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:      "tls-connection-failed",
+		Title:     "Encrypted connection failed",
+		Severity:  SeverityMedium,
+		DependsOn: []*Warnable{NetworkStatusWarnable},
+		Text: func(args Args) string {
+			return fmt.Sprintf("Tailscale could not establish an encrypted connection with '%q': %v", args[ArgServerName], args[ArgError])
+		},
+	}
 })
 
 // magicsockReceiveFuncWarnable is a Warnable that warns the user that one of the Magicsock functions is not running.
-var magicsockReceiveFuncWarnable = Register(&Warnable{
-	Code:     "magicsock-receive-func-error",
-	Title:    "MagicSock function not running",
-	Severity: SeverityMedium,
-	Text: func(args Args) string {
-		return fmt.Sprintf("The MagicSock function %s is not running. You might experience connectivity issues.", args[ArgMagicsockFunctionName])
-	},
+var magicsockReceiveFuncWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "magicsock-receive-func-error",
+		Title:    "MagicSock function not running",
+		Severity: SeverityMedium,
+		Text: func(args Args) string {
+			return fmt.Sprintf("The MagicSock function %s is not running. You might experience connectivity issues.", args[ArgMagicsockFunctionName])
+		},
+	}
 })
 
 // testWarnable is a Warnable that is used within this package for testing purposes only.
-var testWarnable = Register(&Warnable{
-	Code:     "test-warnable",
-	Title:    "Test warnable",
-	Severity: SeverityLow,
-	Text: func(args Args) string {
-		return args[ArgError]
-	},
+var testWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "test-warnable",
+		Title:    "Test warnable",
+		Severity: SeverityLow,
+		Text: func(args Args) string {
+			return args[ArgError]
+		},
+	}
 })
 
 // applyDiskConfigWarnable is a Warnable that warns the user that there was an error applying the envknob config stored on disk.
-var applyDiskConfigWarnable = Register(&Warnable{
-	Code:     "apply-disk-config",
-	Title:    "Could not apply configuration",
-	Severity: SeverityMedium,
-	Text: func(args Args) string {
-		return fmt.Sprintf("An error occurred applying the Tailscale envknob configuration stored on disk: %v", args[ArgError])
-	},
+var applyDiskConfigWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "apply-disk-config",
+		Title:    "Could not apply configuration",
+		Severity: SeverityMedium,
+		Text: func(args Args) string {
+			return fmt.Sprintf("An error occurred applying the Tailscale envknob configuration stored on disk: %v", args[ArgError])
+		},
+	}
 })
 
 // warmingUpWarnableDuration is the duration for which the warmingUpWarnable is reported by the backend after the user
@@ -245,9 +289,11 @@ const warmingUpWarnableDuration = 5 * time.Second
 // warmingUpWarnable is a Warnable that is reported by the backend when it is starting up, for a maximum time of
 // warmingUpWarnableDuration. The GUIs use the presence of this Warnable to prevent showing any other warnings until
 // the backend is fully started.
-var warmingUpWarnable = Register(&Warnable{
-	Code:     "warming-up",
-	Title:    "Tailscale is starting",
-	Severity: SeverityLow,
-	Text:     StaticMessage("Tailscale is starting. Please wait."),
+var warmingUpWarnable = condRegister(func() *Warnable {
+	return &Warnable{
+		Code:     "warming-up",
+		Title:    "Tailscale is starting",
+		Severity: SeverityLow,
+		Text:     StaticMessage("Tailscale is starting. Please wait."),
+	}
 })

+ 1 - 1
tsnet/depaware.txt

@@ -165,7 +165,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
         tailscale.com/logpolicy                                      from tailscale.com/ipn/ipnlocal+
         tailscale.com/logtail                                        from tailscale.com/control/controlclient+
         tailscale.com/logtail/filch                                  from tailscale.com/log/sockstatlog+
-        tailscale.com/metrics                                        from tailscale.com/health+
+        tailscale.com/metrics                                        from tailscale.com/net/tstun+
         tailscale.com/net/ace                                        from tailscale.com/control/controlhttp
         tailscale.com/net/bakedroots                                 from tailscale.com/ipn/ipnlocal+
      💣 tailscale.com/net/batching                                   from tailscale.com/wgengine/magicsock

+ 1 - 1
wgengine/magicsock/magicsock.go

@@ -1710,7 +1710,7 @@ func (c *Conn) mkReceiveFunc(ruc *RebindingUDPConn, healthItem *health.ReceiveFu
 	var epCache epAddrEndpointCache
 
 	return func(buffs [][]byte, sizes []int, eps []conn.Endpoint) (_ int, retErr error) {
-		if healthItem != nil {
+		if buildfeatures.HasHealth && healthItem != nil {
 			healthItem.Enter()
 			defer healthItem.Exit()
 			defer func() {