diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 58c5e26ac..9f0bffa89 100644 --- a/cmd/derper/depaware.txt +++ b/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+ diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index dcd4924ef..328a49980 100644 --- a/cmd/tailscale/depaware.txt +++ b/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+ diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/debug.go index 4d81894bb..ba1cac965 100644 --- a/cmd/tailscaled/debug.go +++ b/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() diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index ee7c79a2d..f59f16401 100644 --- a/control/controlclient/direct.go +++ b/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 diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go index fb220fd0b..762a2eba1 100644 --- a/control/controlhttp/client.go +++ b/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 } diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index 560a58676..4b3bc1495 100644 --- a/derp/derphttp/derphttp_client.go +++ b/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 diff --git a/health/health.go b/health/health.go index d1d779933..61455a33e 100644 --- a/health/health.go +++ b/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 { diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index a69c0573e..954d90618 100644 --- a/ipn/ipnlocal/local.go +++ b/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{}) } diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index 6d1a6726c..610e6e2df 100644 --- a/logpolicy/logpolicy.go +++ b/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 } diff --git a/net/dnsfallback/dnsfallback.go b/net/dnsfallback/dnsfallback.go index 59ca5f624..6b3ac864e 100644 --- a/net/dnsfallback/dnsfallback.go +++ b/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 { diff --git a/net/tlsdial/tlsdial.go b/net/tlsdial/tlsdial.go index ddd2ee064..087d2fbce 100644 --- a/net/tlsdial/tlsdial.go +++ b/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) }, } diff --git a/net/tlsdial/tlsdial_test.go b/net/tlsdial/tlsdial_test.go index 7aaf3b36a..26814ebbd 100644 --- a/net/tlsdial/tlsdial_test.go +++ b/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) diff --git a/tsd/tsd.go b/tsd/tsd.go index c209a0166..d9e8a5e94 100644 --- a/tsd/tsd.go +++ b/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 diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index 3d5ec99f9..4ab429ff6 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/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" diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index 3d5ec99f9..4ab429ff6 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/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" diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index 3d5ec99f9..4ab429ff6 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/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" diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index 3d5ec99f9..4ab429ff6 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/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" diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index 240c58c13..7607f75d2 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/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" diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go index b6cecb55d..66235d22e 100644 --- a/wgengine/magicsock/derp.go +++ b/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)