Explorar o código

tsd, ipnlocal, etc: add tsd.System.HealthTracker, start some plumbing

This adds a health.Tracker to tsd.System, accessible via
a new tsd.System.HealthTracker method.

In the future, that new method will return a tsd.System-specific
HealthTracker, so multiple tsnet.Servers in the same process are
isolated. For now, though, it just always returns the temporary
health.Global value. That permits incremental plumbing over a number
of changes. When the second to last health.Global reference is gone,
then the tsd.System.HealthTracker implementation can return a private
Tracker.

The primary plumbing this does is adding it to LocalBackend and its
dozen and change health calls. A few misc other callers are also
plumbed. Subsequent changes will flesh out other parts of the tree
(magicsock, controlclient, etc).

Updates #11874
Updates #4136

Change-Id: Id51e73cfc8a39110425b6dc19d18b3975eac75ce
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick hai 1 ano
pai
achega
723c775dbb

+ 1 - 1
cmd/derper/depaware.txt

@@ -89,7 +89,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
         tailscale.com/disco                                          from tailscale.com/derp
         tailscale.com/drive                                          from tailscale.com/client/tailscale+
         tailscale.com/envknob                                        from tailscale.com/client/tailscale+
-        tailscale.com/health                                         from tailscale.com/net/tlsdial
+        tailscale.com/health                                         from tailscale.com/net/tlsdial+
         tailscale.com/hostinfo                                       from tailscale.com/net/interfaces+
         tailscale.com/ipn                                            from tailscale.com/client/tailscale
         tailscale.com/ipn/ipnstate                                   from tailscale.com/client/tailscale+

+ 1 - 1
cmd/tailscale/depaware.txt

@@ -88,7 +88,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
         tailscale.com/disco                                          from tailscale.com/derp
         tailscale.com/drive                                          from tailscale.com/client/tailscale+
         tailscale.com/envknob                                        from tailscale.com/client/tailscale+
-        tailscale.com/health                                         from tailscale.com/net/tlsdial
+        tailscale.com/health                                         from tailscale.com/net/tlsdial+
         tailscale.com/health/healthmsg                               from tailscale.com/cmd/tailscale/cli
         tailscale.com/hostinfo                                       from tailscale.com/client/web+
         tailscale.com/ipn                                            from tailscale.com/client/tailscale+

+ 4 - 0
cmd/tailscaled/debug.go

@@ -21,6 +21,7 @@ import (
 	"time"
 
 	"tailscale.com/derp/derphttp"
+	"tailscale.com/health"
 	"tailscale.com/ipn"
 	"tailscale.com/net/interfaces"
 	"tailscale.com/net/netmon"
@@ -157,6 +158,7 @@ func getURL(ctx context.Context, urlStr string) error {
 }
 
 func checkDerp(ctx context.Context, derpRegion string) (err error) {
+	ht := new(health.Tracker)
 	req, err := http.NewRequestWithContext(ctx, "GET", ipn.DefaultControlURL+"/derpmap/default", nil)
 	if err != nil {
 		return fmt.Errorf("create derp map request: %w", err)
@@ -195,6 +197,8 @@ func checkDerp(ctx context.Context, derpRegion string) (err error) {
 
 	c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion)
 	c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion)
+	c1.HealthTracker = ht
+	c2.HealthTracker = ht
 	defer func() {
 		if err != nil {
 			c1.Close()

+ 1 - 1
control/controlclient/direct.go

@@ -248,7 +248,7 @@ func NewDirect(opts Options) (*Direct, error) {
 		tr := http.DefaultTransport.(*http.Transport).Clone()
 		tr.Proxy = tshttpproxy.ProxyFromEnvironment
 		tshttpproxy.SetTransportGetProxyConnectHeader(tr)
-		tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), tr.TLSClientConfig)
+		tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), health.Global, tr.TLSClientConfig)
 		tr.DialContext = dnscache.Dialer(opts.Dialer.SystemDial, dnsCache)
 		tr.DialTLSContext = dnscache.TLSDialer(opts.Dialer.SystemDial, dnsCache, tr.TLSClientConfig)
 		tr.ForceAttemptHTTP2 = true

+ 2 - 1
control/controlhttp/client.go

@@ -38,6 +38,7 @@ import (
 
 	"tailscale.com/control/controlbase"
 	"tailscale.com/envknob"
+	"tailscale.com/health"
 	"tailscale.com/net/dnscache"
 	"tailscale.com/net/dnsfallback"
 	"tailscale.com/net/netutil"
@@ -433,7 +434,7 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
 	// Disable HTTP2, since h2 can't do protocol switching.
 	tr.TLSClientConfig.NextProtos = []string{}
 	tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
-	tr.TLSClientConfig = tlsdial.Config(a.Hostname, tr.TLSClientConfig)
+	tr.TLSClientConfig = tlsdial.Config(a.Hostname, health.Global, tr.TLSClientConfig)
 	if !tr.TLSClientConfig.InsecureSkipVerify {
 		panic("unexpected") // should be set by tlsdial.Config
 	}

+ 8 - 5
derp/derphttp/derphttp_client.go

@@ -31,6 +31,7 @@ import (
 	"go4.org/mem"
 	"tailscale.com/derp"
 	"tailscale.com/envknob"
+	"tailscale.com/health"
 	"tailscale.com/net/dnscache"
 	"tailscale.com/net/netmon"
 	"tailscale.com/net/netns"
@@ -51,10 +52,11 @@ import (
 // Send/Recv will completely re-establish the connection (unless Close
 // has been called).
 type Client struct {
-	TLSConfig *tls.Config        // optional; nil means default
-	DNSCache  *dnscache.Resolver // optional; nil means no caching
-	MeshKey   string             // optional; for trusted clients
-	IsProber  bool               // optional; for probers to optional declare themselves as such
+	TLSConfig     *tls.Config        // optional; nil means default
+	HealthTracker *health.Tracker    // optional; used if non-nil only
+	DNSCache      *dnscache.Resolver // optional; nil means no caching
+	MeshKey       string             // optional; for trusted clients
+	IsProber      bool               // optional; for probers to optional declare themselves as such
 
 	// WatchConnectionChanges is whether the client wishes to subscribe to
 	// notifications about clients connecting & disconnecting.
@@ -115,6 +117,7 @@ func (c *Client) String() string {
 // NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
 // To trigger a connection, use Connect.
 // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
+// The healthTracker parameter is also optional.
 func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client {
 	ctx, cancel := context.WithCancel(context.Background())
 	c := &Client{
@@ -612,7 +615,7 @@ func (c *Client) dialRegion(ctx context.Context, reg *tailcfg.DERPRegion) (net.C
 }
 
 func (c *Client) tlsClient(nc net.Conn, node *tailcfg.DERPNode) *tls.Conn {
-	tlsConf := tlsdial.Config(c.tlsServerName(node), c.TLSConfig)
+	tlsConf := tlsdial.Config(c.tlsServerName(node), c.HealthTracker, c.TLSConfig)
 	if node != nil {
 		if node.InsecureForTests {
 			tlsConf.InsecureSkipVerify = true

+ 5 - 2
health/health.go

@@ -30,8 +30,11 @@ var (
 
 // Global is a global health tracker for the process.
 //
-// TODO(bradfitz): move this to tsd.System so a process can have multiple
-// tsnet/etc instances with their own health trackers.
+// TODO(bradfitz): finish moving all reference to this plumb it (ultimately out
+// from tsd.System) so a process can have multiple tsnet/etc instances with
+// their own health trackers. But for now (2024-04-25), the tsd.System value
+// given out is just this one, until that's the only remaining Global reference
+// remaining.
 var Global = new(Tracker)
 
 type Tracker struct {

+ 17 - 15
ipn/ipnlocal/local.go

@@ -170,6 +170,7 @@ type LocalBackend struct {
 	keyLogf               logger.Logf        // for printing list of peers on change
 	statsLogf             logger.Logf        // for printing peers stats on change
 	sys                   *tsd.System
+	health                *health.Tracker // always non-nil
 	e                     wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
 	store                 ipn.StateStore  // non-nil; TODO(bradfitz): remove; use sys
 	dialer                *tsdial.Dialer  // non-nil; TODO(bradfitz): remove; use sys
@@ -386,6 +387,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
 		keyLogf:             logger.LogOnChange(logf, 5*time.Minute, clock.Now),
 		statsLogf:           logger.LogOnChange(logf, 5*time.Minute, clock.Now),
 		sys:                 sys,
+		health:              sys.HealthTracker(),
 		conf:                sys.InitialConfig,
 		e:                   e,
 		dialer:              dialer,
@@ -426,7 +428,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
 	b.linkChange(&netmon.ChangeDelta{New: netMon.InterfaceState()})
 	b.unregisterNetMon = netMon.RegisterChangeCallback(b.linkChange)
 
-	b.unregisterHealthWatch = health.Global.RegisterWatcher(b.onHealthChange)
+	b.unregisterHealthWatch = b.health.RegisterWatcher(b.onHealthChange)
 
 	if tunWrap, ok := b.sys.Tun.GetOK(); ok {
 		tunWrap.PeerAPIPort = b.GetPeerAPIPort
@@ -625,7 +627,7 @@ 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(b.netMap, b.pm.CurrentPrefs())
-	updateExitNodeUsageWarning(b.pm.CurrentPrefs(), delta.New)
+	updateExitNodeUsageWarning(b.pm.CurrentPrefs(), delta.New, b.health)
 
 	if peerAPIListenAsync && b.netMap != nil && b.state == ipn.Running {
 		want := b.netMap.GetAddresses().Len()
@@ -761,7 +763,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
 				}
 			}
 		}
-		if err := health.Global.OverallError(); err != nil {
+		if err := b.health.OverallError(); err != nil {
 			switch e := err.(type) {
 			case multierr.Error:
 				for _, err := range e.Errors() {
@@ -820,7 +822,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
 
 	sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
 		ss.OS = version.OS()
-		ss.Online = health.Global.GetInPollNetMap()
+		ss.Online = b.health.GetInPollNetMap()
 		if b.netMap != nil {
 			ss.InNetworkMap = true
 			if hi := b.netMap.SelfNode.Hostinfo(); hi.Valid() {
@@ -1221,7 +1223,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
 	if st.NetMap != nil {
 		if envknob.NoLogsNoSupport() && st.NetMap.HasCap(tailcfg.CapabilityDataPlaneAuditLogs) {
 			msg := "tailnet requires logging to be enabled. Remove --no-logs-no-support from tailscaled command line."
-			health.Global.SetLocalLogConfigHealth(errors.New(msg))
+			b.health.SetLocalLogConfigHealth(errors.New(msg))
 			// Connecting to this tailnet without logging is forbidden; boot us outta here.
 			b.mu.Lock()
 			prefs.WantRunning = false
@@ -1851,10 +1853,10 @@ func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs ipn.P
 
 		if packetFilterPermitsUnlockedNodes(b.peers, packetFilter) {
 			err := errors.New("server sent invalid packet filter permitting traffic to unlocked nodes; rejecting all packets for safety")
-			health.Global.SetWarnable(warnInvalidUnsignedNodes, err)
+			b.health.SetWarnable(warnInvalidUnsignedNodes, err)
 			packetFilter = nil
 		} else {
-			health.Global.SetWarnable(warnInvalidUnsignedNodes, nil)
+			b.health.SetWarnable(warnInvalidUnsignedNodes, nil)
 		}
 	}
 	if prefs.Valid() {
@@ -3048,7 +3050,7 @@ var warnExitNodeUsage = health.NewWarnable(health.WithConnectivityImpact())
 
 // updateExitNodeUsageWarning updates a warnable meant to notify users of
 // configuration issues that could break exit node usage.
-func updateExitNodeUsageWarning(p ipn.PrefsView, state *interfaces.State) {
+func updateExitNodeUsageWarning(p ipn.PrefsView, state *interfaces.State, health *health.Tracker) {
 	var result error
 	if p.ExitNodeIP().IsValid() || p.ExitNodeID() != "" {
 		warn, _ := netutil.CheckReversePathFiltering(state)
@@ -3057,7 +3059,7 @@ func updateExitNodeUsageWarning(p ipn.PrefsView, state *interfaces.State) {
 			result = fmt.Errorf("%s: %v, %s", healthmsg.WarnExitNodeUsage, warn, comment)
 		}
 	}
-	health.Global.SetWarnable(warnExitNodeUsage, result)
+	health.SetWarnable(warnExitNodeUsage, result)
 }
 
 func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error {
@@ -4254,7 +4256,7 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State, unlock unlock
 
 	// prefs may change irrespective of state; WantRunning should be explicitly
 	// set before potential early return even if the state is unchanged.
-	health.Global.SetIPNState(newState.String(), prefs.Valid() && prefs.WantRunning())
+	b.health.SetIPNState(newState.String(), prefs.Valid() && prefs.WantRunning())
 	if oldState == newState {
 		return
 	}
@@ -4692,9 +4694,9 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
 	b.pauseOrResumeControlClientLocked()
 
 	if nm != nil {
-		health.Global.SetControlHealth(nm.ControlHealth)
+		b.health.SetControlHealth(nm.ControlHealth)
 	} else {
-		health.Global.SetControlHealth(nil)
+		b.health.SetControlHealth(nil)
 	}
 
 	// Determine if file sharing is enabled
@@ -5679,9 +5681,9 @@ var warnSSHSELinux = health.NewWarnable()
 
 func (b *LocalBackend) updateSELinuxHealthWarning() {
 	if hostinfo.IsSELinuxEnforcing() {
-		health.Global.SetWarnable(warnSSHSELinux, errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
+		b.health.SetWarnable(warnSSHSELinux, errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
 	} else {
-		health.Global.SetWarnable(warnSSHSELinux, nil)
+		b.health.SetWarnable(warnSSHSELinux, nil)
 	}
 }
 
@@ -5908,7 +5910,7 @@ func (b *LocalBackend) resetForProfileChangeLockedOnEntry(unlock unlockOnce) err
 	b.lastServeConfJSON = mem.B(nil)
 	b.serveConfig = ipn.ServeConfigView{}
 	b.enterStateLockedOnEntry(ipn.NoState, unlock) // Reset state; releases b.mu
-	health.Global.SetLocalLogConfigHealth(nil)
+	b.health.SetLocalLogConfigHealth(nil)
 	return b.Start(ipn.Options{})
 }
 

+ 2 - 1
logpolicy/logpolicy.go

@@ -30,6 +30,7 @@ import (
 	"golang.org/x/term"
 	"tailscale.com/atomicfile"
 	"tailscale.com/envknob"
+	"tailscale.com/health"
 	"tailscale.com/log/filelogger"
 	"tailscale.com/logtail"
 	"tailscale.com/logtail/filch"
@@ -782,7 +783,7 @@ func NewLogtailTransport(host string, netMon *netmon.Monitor, logf logger.Logf)
 		tr.TLSNextProto = map[string]func(authority string, c *tls.Conn) http.RoundTripper{}
 	}
 
-	tr.TLSClientConfig = tlsdial.Config(host, tr.TLSClientConfig)
+	tr.TLSClientConfig = tlsdial.Config(host, health.Global, tr.TLSClientConfig)
 
 	return tr
 }

+ 13 - 9
net/dnsfallback/dnsfallback.go

@@ -28,6 +28,7 @@ import (
 
 	"tailscale.com/atomicfile"
 	"tailscale.com/envknob"
+	"tailscale.com/health"
 	"tailscale.com/net/dns/recursive"
 	"tailscale.com/net/netmon"
 	"tailscale.com/net/netns"
@@ -64,9 +65,10 @@ func MakeLookupFunc(logf logger.Logf, netMon *netmon.Monitor) func(ctx context.C
 // fallbackResolver contains the state and configuration for a DNS resolution
 // function.
 type fallbackResolver struct {
-	logf   logger.Logf
-	netMon *netmon.Monitor // or nil
-	sf     singleflight.Group[string, resolveResult]
+	logf          logger.Logf
+	netMon        *netmon.Monitor // or nil
+	healthTracker *health.Tracker // or nil
+	sf            singleflight.Group[string, resolveResult]
 
 	// for tests
 	waitForCompare bool
@@ -79,7 +81,7 @@ func (fr *fallbackResolver) Lookup(ctx context.Context, host string) ([]netip.Ad
 	// recursive resolver. (tailscale/corp#15261) In the future, we might
 	// change the default (the opt.Bool being unset) to mean enabled.
 	if disableRecursiveResolver() || !optRecursiveResolver().EqualBool(true) {
-		return lookup(ctx, host, fr.logf, fr.netMon)
+		return lookup(ctx, host, fr.logf, fr.healthTracker, fr.netMon)
 	}
 
 	addrsCh := make(chan []netip.Addr, 1)
@@ -99,7 +101,7 @@ func (fr *fallbackResolver) Lookup(ctx context.Context, host string) ([]netip.Ad
 		go fr.compareWithRecursive(ctx, addrsCh, host)
 	}
 
-	addrs, err := lookup(ctx, host, fr.logf, fr.netMon)
+	addrs, err := lookup(ctx, host, fr.logf, fr.healthTracker, fr.netMon)
 	if err != nil {
 		addrsCh <- nil
 		return nil, err
@@ -207,7 +209,7 @@ func (fr *fallbackResolver) compareWithRecursive(
 	}
 }
 
-func lookup(ctx context.Context, host string, logf logger.Logf, netMon *netmon.Monitor) ([]netip.Addr, error) {
+func lookup(ctx context.Context, host string, logf logger.Logf, ht *health.Tracker, netMon *netmon.Monitor) ([]netip.Addr, error) {
 	if ip, err := netip.ParseAddr(host); err == nil && ip.IsValid() {
 		return []netip.Addr{ip}, nil
 	}
@@ -255,7 +257,7 @@ func lookup(ctx context.Context, host string, logf logger.Logf, netMon *netmon.M
 		logf("trying bootstrapDNS(%q, %q) for %q ...", cand.dnsName, cand.ip, host)
 		ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
 		defer cancel()
-		dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host, logf, netMon)
+		dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host, logf, ht, netMon)
 		if err != nil {
 			logf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err)
 			continue
@@ -274,14 +276,16 @@ func lookup(ctx context.Context, host string, logf logger.Logf, netMon *netmon.M
 
 // serverName and serverIP of are, say, "derpN.tailscale.com".
 // queryName is the name being sought (e.g. "controlplane.tailscale.com"), passed as hint.
-func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf, netMon *netmon.Monitor) (dnsMap, error) {
+//
+// ht may be nil.
+func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf, ht *health.Tracker, netMon *netmon.Monitor) (dnsMap, error) {
 	dialer := netns.NewDialer(logf, netMon)
 	tr := http.DefaultTransport.(*http.Transport).Clone()
 	tr.Proxy = tshttpproxy.ProxyFromEnvironment
 	tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
 		return dialer.DialContext(ctx, "tcp", net.JoinHostPort(serverIP.String(), "443"))
 	}
-	tr.TLSClientConfig = tlsdial.Config(serverName, tr.TLSClientConfig)
+	tr.TLSClientConfig = tlsdial.Config(serverName, ht, tr.TLSClientConfig)
 	c := &http.Client{Transport: tr}
 	req, err := http.NewRequestWithContext(ctx, "GET", "https://"+serverName+"/bootstrap-dns?q="+url.QueryEscape(queryName), nil)
 	if err != nil {

+ 11 - 8
net/tlsdial/tlsdial.go

@@ -46,7 +46,8 @@ var tlsdialWarningPrinted sync.Map // map[string]bool
 // Config returns a tls.Config for connecting to a server.
 // If base is non-nil, it's cloned as the base config before
 // being configured and returned.
-func Config(host string, base *tls.Config) *tls.Config {
+// If ht is non-nil, it's used to report health errors.
+func Config(host string, ht *health.Tracker, base *tls.Config) *tls.Config {
 	var conf *tls.Config
 	if base == nil {
 		conf = new(tls.Config)
@@ -78,12 +79,14 @@ func Config(host string, base *tls.Config) *tls.Config {
 	conf.VerifyConnection = func(cs tls.ConnectionState) error {
 		// Perform some health checks on this certificate before we do
 		// any verification.
-		if certIsSelfSigned(cs.PeerCertificates[0]) {
-			// Self-signed certs are never valid.
-			health.Global.SetTLSConnectionError(cs.ServerName, fmt.Errorf("certificate is self-signed"))
-		} else {
-			// Ensure we clear any error state for this ServerName.
-			health.Global.SetTLSConnectionError(cs.ServerName, nil)
+		if ht != nil {
+			if certIsSelfSigned(cs.PeerCertificates[0]) {
+				// Self-signed certs are never valid.
+				ht.SetTLSConnectionError(cs.ServerName, fmt.Errorf("certificate is self-signed"))
+			} else {
+				// Ensure we clear any error state for this ServerName.
+				ht.SetTLSConnectionError(cs.ServerName, nil)
+			}
 		}
 
 		// First try doing x509 verification with the system's
@@ -204,7 +207,7 @@ func NewTransport() *http.Transport {
 				return nil, err
 			}
 			var d tls.Dialer
-			d.Config = Config(host, nil)
+			d.Config = Config(host, nil, nil)
 			return d.DialContext(ctx, network, addr)
 		},
 	}

+ 4 - 1
net/tlsdial/tlsdial_test.go

@@ -15,6 +15,8 @@ import (
 	"runtime"
 	"sync/atomic"
 	"testing"
+
+	"tailscale.com/health"
 )
 
 func resetOnce() {
@@ -105,7 +107,8 @@ func TestFallbackRootWorks(t *testing.T) {
 		},
 		DisableKeepAlives: true, // for test cleanup ease
 	}
-	tr.TLSClientConfig = Config("tlsdial.test", tr.TLSClientConfig)
+	ht := new(health.Tracker)
+	tr.TLSClientConfig = Config("tlsdial.test", ht, tr.TLSClientConfig)
 	c := &http.Client{Transport: tr}
 
 	ctr0 := atomic.LoadInt32(&counterFallbackOK)

+ 16 - 0
tsd/tsd.go

@@ -23,6 +23,7 @@ import (
 
 	"tailscale.com/control/controlknobs"
 	"tailscale.com/drive"
+	"tailscale.com/health"
 	"tailscale.com/ipn"
 	"tailscale.com/ipn/conffile"
 	"tailscale.com/net/dns"
@@ -63,6 +64,8 @@ type System struct {
 
 	controlKnobs controlknobs.Knobs
 	proxyMap     proxymap.Mapper
+
+	healthTracker health.Tracker
 }
 
 // NetstackImpl is the interface that *netstack.Impl implements.
@@ -134,6 +137,19 @@ func (s *System) ProxyMapper() *proxymap.Mapper {
 	return &s.proxyMap
 }
 
+// HealthTracker returns the system health tracker.
+func (s *System) HealthTracker() *health.Tracker {
+	// TODO(bradfitz): plumb the tsd.System.HealthTracker() value
+	// everywhere and then then remove this use of the global
+	// and remove health.Global entirely. But for now we keep
+	// the two in sync during plumbing.
+	const stillPlumbing = true
+	if stillPlumbing {
+		return health.Global
+	}
+	return &s.healthTracker
+}
+
 // SubSystem represents some subsystem of the Tailscale node daemon.
 //
 // A subsystem can be set to a value, and then later retrieved. A subsystem

+ 1 - 0
tstest/integration/tailscaled_deps_test_darwin.go

@@ -17,6 +17,7 @@ import (
 	_ "tailscale.com/derp/derphttp"
 	_ "tailscale.com/drive/driveimpl"
 	_ "tailscale.com/envknob"
+	_ "tailscale.com/health"
 	_ "tailscale.com/ipn"
 	_ "tailscale.com/ipn/conffile"
 	_ "tailscale.com/ipn/ipnlocal"

+ 1 - 0
tstest/integration/tailscaled_deps_test_freebsd.go

@@ -17,6 +17,7 @@ import (
 	_ "tailscale.com/derp/derphttp"
 	_ "tailscale.com/drive/driveimpl"
 	_ "tailscale.com/envknob"
+	_ "tailscale.com/health"
 	_ "tailscale.com/ipn"
 	_ "tailscale.com/ipn/conffile"
 	_ "tailscale.com/ipn/ipnlocal"

+ 1 - 0
tstest/integration/tailscaled_deps_test_linux.go

@@ -17,6 +17,7 @@ import (
 	_ "tailscale.com/derp/derphttp"
 	_ "tailscale.com/drive/driveimpl"
 	_ "tailscale.com/envknob"
+	_ "tailscale.com/health"
 	_ "tailscale.com/ipn"
 	_ "tailscale.com/ipn/conffile"
 	_ "tailscale.com/ipn/ipnlocal"

+ 1 - 0
tstest/integration/tailscaled_deps_test_openbsd.go

@@ -17,6 +17,7 @@ import (
 	_ "tailscale.com/derp/derphttp"
 	_ "tailscale.com/drive/driveimpl"
 	_ "tailscale.com/envknob"
+	_ "tailscale.com/health"
 	_ "tailscale.com/ipn"
 	_ "tailscale.com/ipn/conffile"
 	_ "tailscale.com/ipn/ipnlocal"

+ 1 - 0
tstest/integration/tailscaled_deps_test_windows.go

@@ -24,6 +24,7 @@ import (
 	_ "tailscale.com/derp/derphttp"
 	_ "tailscale.com/drive/driveimpl"
 	_ "tailscale.com/envknob"
+	_ "tailscale.com/health"
 	_ "tailscale.com/ipn"
 	_ "tailscale.com/ipn/conffile"
 	_ "tailscale.com/ipn/ipnlocal"

+ 1 - 0
wgengine/magicsock/derp.go

@@ -400,6 +400,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netip.AddrPort, peer key.NodePublic) cha
 		}
 		return derpMap.Regions[regionID]
 	})
+	dc.HealthTracker = health.Global
 
 	dc.SetCanAckPings(true)
 	dc.NotePreferred(c.myDerp == regionID)