Compare commits

...

6 Commits

Author SHA1 Message Date
Irbe Krumina 3e64dc69e3
Merge 2fa7d1d836 into 71e9258ad9 2024-04-27 02:44:53 +03:00
Jonathan Nobels 71e9258ad9
ipn/ipnlocal: fix null dereference for early suggested exit node queries (#11885)
Fixes tailscale/corp#19558

A request for the suggested exit nodes that occurs too early in the
VPN lifecycle would result in a null deref of the netmap and/or
the netcheck report.  This checks both and errors out.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
2024-04-26 14:35:11 -07:00
Brad Fitzpatrick 745931415c health, all: remove health.Global, finish plumbing health.Tracker
Updates #11874
Updates #4136

Change-Id: I414470f71d90be9889d44c3afd53956d9f26cd61
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 12:03:11 -07:00
Brad Fitzpatrick a4a282cd49 control/controlclient: plumb health.Tracker
Updates #11874
Updates #4136

Change-Id: Ia941153bd83523f0c8b56852010f5231d774d91a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 10:12:33 -07:00
Brad Fitzpatrick 6d69fc137f ipn/{ipnlocal,localapi},wgengine{,/magicsock}: plumb health.Tracker
Down to 25 health.Global users. After this remains controlclient &
net/dns & wgengine/router.

Updates #11874
Updates #4136

Change-Id: I6dd1856e3d9bf523bdd44b60fb3b8f7501d5dc0d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 09:43:28 -07:00
Irbe Krumina 2fa7d1d836 cmd/k8s-operator/deploy/manifests: check if IPv6 module is loaded before using it
Before attempting to enable IPv6 forwarding in the proxy init container
check if the relevant module is found, else the container crashes
on hosts that don't have it.

Updates#11860

Signed-off-by: Irbe Krumina <irbe@tailscale.com>
2024-04-25 07:34:49 +01:00
53 changed files with 257 additions and 193 deletions

View File

@ -138,6 +138,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/types/structs from tailscale.com/ipn+ tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/types/tkatype from tailscale.com/client/tailscale+ tailscale.com/types/tkatype from tailscale.com/client/tailscale+
tailscale.com/types/views from tailscale.com/ipn+ tailscale.com/types/views from tailscale.com/ipn+
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/net/netmon+ tailscale.com/util/clientmetric from tailscale.com/net/netmon+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/cloudenv from tailscale.com/hostinfo+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy

View File

@ -14,10 +14,8 @@ spec:
- name: sysctler - name: sysctler
securityContext: securityContext:
privileged: true privileged: true
command: ["/bin/sh"] command: ["/bin/sh", "-c"]
args: args: [sysctl -w net.ipv4.ip_forward=1; if sysctl net.ipv6.conf.all.forwarding; then sysctl -w net.ipv6.conf.all.forwarding=1; fi]
- -c
- sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1
resources: resources:
requests: requests:
cpu: 1m cpu: 1m

View File

@ -189,8 +189,8 @@ func expectedSTS(t *testing.T, cl client.Client, opts configOpts) *appsv1.Statef
{ {
Name: "sysctler", Name: "sysctler",
Image: "tailscale/tailscale", Image: "tailscale/tailscale",
Command: []string{"/bin/sh"}, Command: []string{"/bin/sh", "-c"},
Args: []string{"-c", "sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1"}, Args: []string{"sysctl -w net.ipv4.ip_forward=1; if sysctl net.ipv6.conf.all.forwarding; then sysctl -w net.ipv6.conf.all.forwarding=1; fi"},
SecurityContext: &corev1.SecurityContext{ SecurityContext: &corev1.SecurityContext{
Privileged: ptr.To(true), Privileged: ptr.To(true),
}, },

View File

@ -142,6 +142,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/structs from tailscale.com/ipn+ tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/types/tkatype from tailscale.com/types/key+ tailscale.com/types/tkatype from tailscale.com/types/key+
tailscale.com/types/views from tailscale.com/tailcfg+ tailscale.com/types/views from tailscale.com/tailcfg+
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+ tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+ tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy+ tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy+

View File

@ -358,6 +358,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/structs from tailscale.com/control/controlclient+ tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/types/tkatype from tailscale.com/tka+ tailscale.com/types/tkatype from tailscale.com/tka+
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+ tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
tailscale.com/util/cibuild from tailscale.com/health
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+ tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+ tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
tailscale.com/util/cmpver from tailscale.com/net/dns+ tailscale.com/util/cmpver from tailscale.com/net/dns+

View File

@ -358,7 +358,7 @@ func run() (err error) {
sys.Set(netMon) sys.Set(netMon)
} }
pol := logpolicy.New(logtail.CollectionNode, netMon, nil /* use log.Printf */) pol := logpolicy.New(logtail.CollectionNode, netMon, sys.HealthTracker(), nil /* use log.Printf */)
pol.SetVerbosityLevel(args.verbose) pol.SetVerbosityLevel(args.verbose)
logPol = pol logPol = pol
defer func() { defer func() {
@ -651,6 +651,7 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
conf := wgengine.Config{ conf := wgengine.Config{
ListenPort: args.port, ListenPort: args.port,
NetMon: sys.NetMon.Get(), NetMon: sys.NetMon.Get(),
HealthTracker: sys.HealthTracker(),
Dialer: sys.Dialer.Get(), Dialer: sys.Dialer.Get(),
SetSubsystem: sys.Set, SetSubsystem: sys.Set,
ControlKnobs: sys.ControlKnobs(), ControlKnobs: sys.ControlKnobs(),
@ -676,7 +677,7 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
// configuration being unavailable (from the noop // configuration being unavailable (from the noop
// manager). More in Issue 4017. // manager). More in Issue 4017.
// TODO(bradfitz): add a Synology-specific DNS manager. // TODO(bradfitz): add a Synology-specific DNS manager.
conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name conf.DNS, err = dns.NewOSConfigurator(logf, sys.HealthTracker(), "") // empty interface name
if err != nil { if err != nil {
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err) return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
} }
@ -698,13 +699,13 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
return false, err return false, err
} }
r, err := router.New(logf, dev, sys.NetMon.Get()) r, err := router.New(logf, dev, sys.NetMon.Get(), sys.HealthTracker())
if err != nil { if err != nil {
dev.Close() dev.Close()
return false, fmt.Errorf("creating router: %w", err) return false, fmt.Errorf("creating router: %w", err)
} }
d, err := dns.NewOSConfigurator(logf, devName) d, err := dns.NewOSConfigurator(logf, sys.HealthTracker(), devName)
if err != nil { if err != nil {
dev.Close() dev.Close()
r.Close() r.Close()

View File

@ -104,9 +104,10 @@ func newIPN(jsConfig js.Value) map[string]any {
sys.Set(store) sys.Set(store)
dialer := &tsdial.Dialer{Logf: logf} dialer := &tsdial.Dialer{Logf: logf}
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Dialer: dialer, Dialer: dialer,
SetSubsystem: sys.Set, SetSubsystem: sys.Set,
ControlKnobs: sys.ControlKnobs(), ControlKnobs: sys.ControlKnobs(),
HealthTracker: sys.HealthTracker(),
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -12,7 +12,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"tailscale.com/health"
"tailscale.com/logtail/backoff" "tailscale.com/logtail/backoff"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
@ -195,7 +194,7 @@ func NewNoStart(opts Options) (_ *Auto, err error) {
c.mapCtx, c.mapCancel = context.WithCancel(context.Background()) c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
c.mapCtx = sockstats.WithSockStats(c.mapCtx, sockstats.LabelControlClientAuto, opts.Logf) c.mapCtx = sockstats.WithSockStats(c.mapCtx, sockstats.LabelControlClientAuto, opts.Logf)
c.unregisterHealthWatch = health.Global.RegisterWatcher(direct.ReportHealthChange) c.unregisterHealthWatch = opts.HealthTracker.RegisterWatcher(direct.ReportHealthChange)
return c, nil return c, nil
} }
@ -316,7 +315,7 @@ func (c *Auto) authRoutine() {
} }
if goal == nil { if goal == nil {
health.Global.SetAuthRoutineInError(nil) c.direct.health.SetAuthRoutineInError(nil)
// Wait for user to Login or Logout. // Wait for user to Login or Logout.
<-ctx.Done() <-ctx.Done()
c.logf("[v1] authRoutine: context done.") c.logf("[v1] authRoutine: context done.")
@ -343,7 +342,7 @@ func (c *Auto) authRoutine() {
f = "TryLogin" f = "TryLogin"
} }
if err != nil { if err != nil {
health.Global.SetAuthRoutineInError(err) c.direct.health.SetAuthRoutineInError(err)
report(err, f) report(err, f)
bo.BackOff(ctx, err) bo.BackOff(ctx, err)
continue continue
@ -373,7 +372,7 @@ func (c *Auto) authRoutine() {
} }
// success // success
health.Global.SetAuthRoutineInError(nil) c.direct.health.SetAuthRoutineInError(nil)
c.mu.Lock() c.mu.Lock()
c.urlToVisit = "" c.urlToVisit = ""
c.loggedIn = true c.loggedIn = true
@ -503,11 +502,11 @@ func (c *Auto) mapRoutine() {
c.logf("[v1] mapRoutine: context done.") c.logf("[v1] mapRoutine: context done.")
continue continue
} }
health.Global.SetOutOfPollNetMap() c.direct.health.SetOutOfPollNetMap()
err := c.direct.PollNetMap(ctx, mrs) err := c.direct.PollNetMap(ctx, mrs)
health.Global.SetOutOfPollNetMap() c.direct.health.SetOutOfPollNetMap()
c.mu.Lock() c.mu.Lock()
c.inMapPoll = false c.inMapPoll = false
if c.state == StateSynchronized { if c.state == StateSynchronized {

View File

@ -69,6 +69,7 @@ type Direct struct {
clock tstime.Clock clock tstime.Clock
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // or nil netMon *netmon.Monitor // or nil
health *health.Tracker
discoPubKey key.DiscoPublic discoPubKey key.DiscoPublic
getMachinePrivKey func() (key.MachinePrivate, error) getMachinePrivKey func() (key.MachinePrivate, error)
debugFlags []string debugFlags []string
@ -119,10 +120,11 @@ type Options struct {
Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
DiscoPublicKey key.DiscoPublic DiscoPublicKey key.DiscoPublic
Logf logger.Logf Logf logger.Logf
HTTPTestClient *http.Client // optional HTTP client to use (for tests only) HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only) NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only)
DebugFlags []string // debug settings to send to control DebugFlags []string // debug settings to send to control
NetMon *netmon.Monitor // optional network monitor NetMon *netmon.Monitor // optional network monitor
HealthTracker *health.Tracker
PopBrowserURL func(url string) // optional func to open browser PopBrowserURL func(url string) // optional func to open browser
OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status
OnControlTime func(time.Time) // optional func to notify callers of new time from control OnControlTime func(time.Time) // optional func to notify callers of new time from control
@ -248,7 +250,7 @@ func NewDirect(opts Options) (*Direct, error) {
tr := http.DefaultTransport.(*http.Transport).Clone() tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr) tshttpproxy.SetTransportGetProxyConnectHeader(tr)
tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), health.Global, tr.TLSClientConfig) tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), opts.HealthTracker, tr.TLSClientConfig)
tr.DialContext = dnscache.Dialer(opts.Dialer.SystemDial, dnsCache) tr.DialContext = dnscache.Dialer(opts.Dialer.SystemDial, dnsCache)
tr.DialTLSContext = dnscache.TLSDialer(opts.Dialer.SystemDial, dnsCache, tr.TLSClientConfig) tr.DialTLSContext = dnscache.TLSDialer(opts.Dialer.SystemDial, dnsCache, tr.TLSClientConfig)
tr.ForceAttemptHTTP2 = true tr.ForceAttemptHTTP2 = true
@ -271,6 +273,7 @@ func NewDirect(opts Options) (*Direct, error) {
discoPubKey: opts.DiscoPublicKey, discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags, debugFlags: opts.DebugFlags,
netMon: opts.NetMon, netMon: opts.NetMon,
health: opts.HealthTracker,
skipIPForwardingCheck: opts.SkipIPForwardingCheck, skipIPForwardingCheck: opts.SkipIPForwardingCheck,
pinger: opts.Pinger, pinger: opts.Pinger,
popBrowser: opts.PopBrowserURL, popBrowser: opts.PopBrowserURL,
@ -894,10 +897,10 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) { ipForwardingBroken(hi.RoutableIPs, c.netMon.InterfaceState()) {
extraDebugFlags = append(extraDebugFlags, "warn-ip-forwarding-off") extraDebugFlags = append(extraDebugFlags, "warn-ip-forwarding-off")
} }
if health.Global.RouterHealth() != nil { if c.health.RouterHealth() != nil {
extraDebugFlags = append(extraDebugFlags, "warn-router-unhealthy") extraDebugFlags = append(extraDebugFlags, "warn-router-unhealthy")
} }
extraDebugFlags = health.Global.AppendWarnableDebugFlags(extraDebugFlags) extraDebugFlags = c.health.AppendWarnableDebugFlags(extraDebugFlags)
if hostinfo.DisabledEtcAptSource() { if hostinfo.DisabledEtcAptSource() {
extraDebugFlags = append(extraDebugFlags, "warn-etc-apt-source-disabled") extraDebugFlags = append(extraDebugFlags, "warn-etc-apt-source-disabled")
} }
@ -970,7 +973,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
} }
defer res.Body.Close() defer res.Body.Close()
health.Global.NoteMapRequestHeard(request) c.health.NoteMapRequestHeard(request)
watchdogTimer.Reset(watchdogTimeout) watchdogTimer.Reset(watchdogTimeout)
if nu == nil { if nu == nil {
@ -1041,7 +1044,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
metricMapResponseMessages.Add(1) metricMapResponseMessages.Add(1)
if isStreaming { if isStreaming {
health.Global.GotStreamedMapResponse() c.health.GotStreamedMapResponse()
} }
if pr := resp.PingRequest; pr != nil && c.isUniquePingRequest(pr) { if pr := resp.PingRequest; pr != nil && c.isUniquePingRequest(pr) {
@ -1450,14 +1453,15 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
} }
c.logf("[v1] creating new noise client") c.logf("[v1] creating new noise client")
nc, err := NewNoiseClient(NoiseOpts{ nc, err := NewNoiseClient(NoiseOpts{
PrivKey: k, PrivKey: k,
ServerPubKey: serverNoiseKey, ServerPubKey: serverNoiseKey,
ServerURL: c.serverURL, ServerURL: c.serverURL,
Dialer: c.dialer, Dialer: c.dialer,
DNSCache: c.dnsCache, DNSCache: c.dnsCache,
Logf: c.logf, Logf: c.logf,
NetMon: c.netMon, NetMon: c.netMon,
DialPlan: dp, HealthTracker: c.health,
DialPlan: dp,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -19,6 +19,7 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"tailscale.com/control/controlbase" "tailscale.com/control/controlbase"
"tailscale.com/control/controlhttp" "tailscale.com/control/controlhttp"
"tailscale.com/health"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
@ -174,6 +175,7 @@ type NoiseClient struct {
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor netMon *netmon.Monitor
health *health.Tracker
// mu only protects the following variables. // mu only protects the following variables.
mu sync.Mutex mu sync.Mutex
@ -204,6 +206,8 @@ type NoiseOpts struct {
// network interface state. This field can be nil; if so, the current // network interface state. This field can be nil; if so, the current
// state will be looked up dynamically. // state will be looked up dynamically.
NetMon *netmon.Monitor NetMon *netmon.Monitor
// HealthTracker, if non-nil, is the health tracker to use.
HealthTracker *health.Tracker
// DialPlan, if set, is a function that should return an explicit plan // DialPlan, if set, is a function that should return an explicit plan
// on how to connect to the server. // on how to connect to the server.
DialPlan func() *tailcfg.ControlDialPlan DialPlan func() *tailcfg.ControlDialPlan
@ -247,6 +251,7 @@ func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error) {
dialPlan: opts.DialPlan, dialPlan: opts.DialPlan,
logf: opts.Logf, logf: opts.Logf,
netMon: opts.NetMon, netMon: opts.NetMon,
health: opts.HealthTracker,
} }
// Create the HTTP/2 Transport using a net/http.Transport // Create the HTTP/2 Transport using a net/http.Transport
@ -453,6 +458,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
DialPlan: dialPlan, DialPlan: dialPlan,
Logf: nc.logf, Logf: nc.logf,
NetMon: nc.netMon, NetMon: nc.netMon,
HealthTracker: nc.health,
Clock: tstime.StdClock{}, Clock: tstime.StdClock{},
}).Dial(ctx) }).Dial(ctx)
if err != nil { if err != nil {

View File

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

View File

@ -8,6 +8,7 @@ import (
"net/url" "net/url"
"time" "time"
"tailscale.com/health"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
@ -79,6 +80,9 @@ type Dialer struct {
NetMon *netmon.Monitor NetMon *netmon.Monitor
// HealthTracker, if non-nil, is the health tracker to use.
HealthTracker *health.Tracker
// DialPlan, if set, contains instructions from the control server on // DialPlan, if set, contains instructions from the control server on
// how to connect to it. If present, we will try the methods in this // how to connect to it. If present, we will try the methods in this
// plan before falling back to DNS. // plan before falling back to DNS.

View File

@ -9,6 +9,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os"
"runtime" "runtime"
"sort" "sort"
"sync" "sync"
@ -18,6 +19,7 @@ import (
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/opt" "tailscale.com/types/opt"
"tailscale.com/util/cibuild"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/multierr" "tailscale.com/util/multierr"
"tailscale.com/util/set" "tailscale.com/util/set"
@ -28,15 +30,9 @@ var (
debugHandler map[string]http.Handler debugHandler map[string]http.Handler
) )
// Global is a global health tracker for the process. // Tracker tracks the health of various Tailscale subsystems,
// // comparing each subsystems' state with each other to make sure
// TODO(bradfitz): finish moving all reference to this plumb it (ultimately out // they're consistent based on the user's intended state.
// 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 { type Tracker struct {
// mu guards everything in this var block. // mu guards everything in this var block.
mu sync.Mutex mu sync.Mutex
@ -152,6 +148,11 @@ func (t *Tracker) nil() bool {
if t != nil { if t != nil {
return false return false
} }
if cibuild.On() {
stack := make([]byte, 1<<10)
stack = stack[:runtime.Stack(stack, false)]
fmt.Fprintf(os.Stderr, "## WARNING: (non-fatal) nil health.Tracker (being strict in CI):\n%s\n", stack)
}
// TODO(bradfitz): open source our "unexpected" package // TODO(bradfitz): open source our "unexpected" package
// and use it here to capture samples of stacks where // and use it here to capture samples of stacks where
// t is nil. // t is nil.

View File

@ -327,6 +327,16 @@ type LocalBackend struct {
outgoingFiles map[string]*ipn.OutgoingFile outgoingFiles map[string]*ipn.OutgoingFile
} }
// HealthTracker returns the health tracker for the backend.
func (b *LocalBackend) HealthTracker() *health.Tracker {
return b.health
}
// NetMon returns the network monitor for the backend.
func (b *LocalBackend) NetMon() *netmon.Monitor {
return b.sys.NetMon.Get()
}
type updateStatus struct { type updateStatus struct {
started bool started bool
} }
@ -405,7 +415,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
} }
netMon := sys.NetMon.Get() netMon := sys.NetMon.Get()
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon) b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon, sys.HealthTracker())
if err != nil { if err != nil {
log.Printf("error setting up sockstat logger: %v", err) log.Printf("error setting up sockstat logger: %v", err)
} }
@ -1753,6 +1763,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
DiscoPublicKey: discoPublic, DiscoPublicKey: discoPublic,
DebugFlags: debugFlags, DebugFlags: debugFlags,
NetMon: b.sys.NetMon.Get(), NetMon: b.sys.NetMon.Get(),
HealthTracker: b.health,
Pinger: b, Pinger: b,
PopBrowserURL: b.tellClientToBrowseToURL, PopBrowserURL: b.tellClientToBrowseToURL,
OnClientVersion: b.onClientVersion, OnClientVersion: b.onClientVersion,
@ -6242,6 +6253,7 @@ func mayDeref[T any](p *T) (v T) {
} }
var ErrNoPreferredDERP = errors.New("no preferred DERP, try again later") var ErrNoPreferredDERP = errors.New("no preferred DERP, try again later")
var ErrCannotSuggestExitNode = errors.New("unable to suggest an exit node, try again later")
// SuggestExitNode computes a suggestion based on the current netmap and last netcheck report. If // SuggestExitNode computes a suggestion based on the current netmap and last netcheck report. If
// there are multiple equally good options, one is selected at random, so the result is not stable. To be // there are multiple equally good options, one is selected at random, so the result is not stable. To be
@ -6255,6 +6267,9 @@ func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionRes
lastReport := b.MagicConn().GetLastNetcheckReport(b.ctx) lastReport := b.MagicConn().GetLastNetcheckReport(b.ctx)
netMap := b.netMap netMap := b.netMap
b.mu.Unlock() b.mu.Unlock()
if lastReport == nil || netMap == nil {
return response, ErrCannotSuggestExitNode
}
seed := time.Now().UnixNano() seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed)) r := rand.New(rand.NewSource(seed))
return suggestExitNode(lastReport, netMap, r) return suggestExitNode(lastReport, netMap, r)

View File

@ -20,7 +20,6 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"tailscale.com/health"
"tailscale.com/health/healthmsg" "tailscale.com/health/healthmsg"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
@ -59,11 +58,11 @@ type tkaState struct {
// b.mu must be held. // b.mu must be held.
func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) { func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
if b.tka == nil && !b.capTailnetLock { if b.tka == nil && !b.capTailnetLock {
health.Global.SetTKAHealth(nil) b.health.SetTKAHealth(nil)
return return
} }
if b.tka == nil { if b.tka == nil {
health.Global.SetTKAHealth(nil) b.health.SetTKAHealth(nil)
return // TKA not enabled. return // TKA not enabled.
} }
@ -117,9 +116,9 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
// Check that we ourselves are not locked out, report a health issue if so. // Check that we ourselves are not locked out, report a health issue if so.
if nm.SelfNode.Valid() && b.tka.authority.NodeKeyAuthorized(nm.SelfNode.Key(), nm.SelfNode.KeySignature().AsSlice()) != nil { if nm.SelfNode.Valid() && b.tka.authority.NodeKeyAuthorized(nm.SelfNode.Key(), nm.SelfNode.KeySignature().AsSlice()) != nil {
health.Global.SetTKAHealth(errors.New(healthmsg.LockedOut)) b.health.SetTKAHealth(errors.New(healthmsg.LockedOut))
} else { } else {
health.Global.SetTKAHealth(nil) b.health.SetTKAHealth(nil)
} }
} }
@ -188,7 +187,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
b.logf("Disablement failed, leaving TKA enabled. Error: %v", err) b.logf("Disablement failed, leaving TKA enabled. Error: %v", err)
} else { } else {
isEnabled = false isEnabled = false
health.Global.SetTKAHealth(nil) b.health.SetTKAHealth(nil)
} }
} else { } else {
return fmt.Errorf("[bug] unreachable invariant of wantEnabled w/ isEnabled") return fmt.Errorf("[bug] unreachable invariant of wantEnabled w/ isEnabled")

View File

@ -199,7 +199,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
defer onDone() defer onDone()
if strings.HasPrefix(r.URL.Path, "/localapi/") { if strings.HasPrefix(r.URL.Path, "/localapi/") {
lah := localapi.NewHandler(lb, s.logf, s.netMon, s.backendLogID) lah := localapi.NewHandler(lb, s.logf, s.backendLogID)
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci) lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
lah.PermitCert = s.connCanFetchCerts(ci) lah.PermitCert = s.connCanFetchCerts(ci)
lah.ConnIdentity = ci lah.ConnIdentity = ci

View File

@ -140,7 +140,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
} }
checkSTUN4 := func(derpNode *tailcfg.DERPNode) { checkSTUN4 := func(derpNode *tailcfg.DERPNode) {
u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(h.logf, h.netMon)).ListenPacket(ctx, "udp4", ":0") u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(h.logf, h.b.NetMon())).ListenPacket(ctx, "udp4", ":0")
if err != nil { if err != nil {
st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err)) st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err))
return return
@ -249,7 +249,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
serverPubKeys := make(map[key.NodePublic]bool) serverPubKeys := make(map[key.NodePublic]bool)
for i := range 5 { for i := range 5 {
func() { func() {
rc := derphttp.NewRegionClient(fakePrivKey, h.logf, h.netMon, func() *tailcfg.DERPRegion { rc := derphttp.NewRegionClient(fakePrivKey, h.logf, h.b.NetMon(), func() *tailcfg.DERPRegion {
return &tailcfg.DERPRegion{ return &tailcfg.DERPRegion{
RegionID: reg.RegionID, RegionID: reg.RegionID,
RegionCode: reg.RegionCode, RegionCode: reg.RegionCode,

View File

@ -36,7 +36,6 @@ import (
"tailscale.com/clientupdate" "tailscale.com/clientupdate"
"tailscale.com/drive" "tailscale.com/drive"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnauth"
@ -156,8 +155,8 @@ var (
// NewHandler creates a new LocalAPI HTTP handler. All parameters except netMon // NewHandler creates a new LocalAPI HTTP handler. All parameters except netMon
// are required (if non-nil it's used to do faster interface lookups). // are required (if non-nil it's used to do faster interface lookups).
func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, netMon *netmon.Monitor, logID logid.PublicID) *Handler { func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler {
return &Handler{b: b, logf: logf, netMon: netMon, backendLogID: logID, clock: tstime.StdClock{}} return &Handler{b: b, logf: logf, backendLogID: logID, clock: tstime.StdClock{}}
} }
type Handler struct { type Handler struct {
@ -188,7 +187,6 @@ type Handler struct {
b *ipnlocal.LocalBackend b *ipnlocal.LocalBackend
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
backendLogID logid.PublicID backendLogID logid.PublicID
clock tstime.Clock clock tstime.Clock
} }
@ -358,7 +356,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
} }
hi, _ := json.Marshal(hostinfo.New()) hi, _ := json.Marshal(hostinfo.New())
h.logf("user bugreport hostinfo: %s", hi) h.logf("user bugreport hostinfo: %s", hi)
if err := health.Global.OverallError(); err != nil { if err := h.b.HealthTracker().OverallError(); err != nil {
h.logf("user bugreport health: %s", err.Error()) h.logf("user bugreport health: %s", err.Error())
} else { } else {
h.logf("user bugreport health: ok") h.logf("user bugreport health: ok")
@ -748,7 +746,7 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) {
done := make(chan bool, 1) done := make(chan bool, 1)
var c *portmapper.Client var c *portmapper.Client
c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), h.netMon, debugKnobs, h.b.ControlKnobs(), func() { c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), h.b.NetMon(), debugKnobs, h.b.ControlKnobs(), func() {
logf("portmapping changed.") logf("portmapping changed.")
logf("have mapping: %v", c.HaveMapping()) logf("have mapping: %v", c.HaveMapping())

View File

@ -17,6 +17,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"tailscale.com/health"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail" "tailscale.com/logtail"
"tailscale.com/logtail/filch" "tailscale.com/logtail/filch"
@ -93,7 +94,7 @@ func SockstatLogID(logID logid.PublicID) logid.PrivateID {
// The returned Logger is not yet enabled, and must be shut down with Shutdown when it is no longer needed. // The returned Logger is not yet enabled, and must be shut down with Shutdown when it is no longer needed.
// Logs will be uploaded to the log server using a new log ID derived from the provided backend logID. // Logs will be uploaded to the log server using a new log ID derived from the provided backend logID.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (*Logger, error) { func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor, health *health.Tracker) (*Logger, error) {
if !sockstats.IsAvailable { if !sockstats.IsAvailable {
return nil, nil return nil, nil
} }
@ -113,7 +114,7 @@ func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *ne
logger := &Logger{ logger := &Logger{
logf: logf, logf: logf,
filch: filch, filch: filch,
tr: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon, logf), tr: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon, health, logf),
} }
logger.logger = logtail.NewLogger(logtail.Config{ logger.logger = logtail.NewLogger(logtail.Config{
BaseURL: logpolicy.LogURL(), BaseURL: logpolicy.LogURL(),

View File

@ -23,7 +23,7 @@ func TestResourceCleanup(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
lg, err := NewLogger(td, logger.Discard, id.Public(), nil) lg, err := NewLogger(td, logger.Discard, id.Public(), nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -453,13 +453,13 @@ func tryFixLogStateLocation(dir, cmdname string, logf logger.Logf) {
// The logf parameter is optional; if non-nil, information logs (e.g. when // The logf parameter is optional; if non-nil, information logs (e.g. when
// migrating state) are sent to that logger, and global changes to the log // migrating state) are sent to that logger, and global changes to the log
// package are avoided. If nil, logs will be printed using log.Printf. // package are avoided. If nil, logs will be printed using log.Printf.
func New(collection string, netMon *netmon.Monitor, logf logger.Logf) *Policy { func New(collection string, netMon *netmon.Monitor, health *health.Tracker, logf logger.Logf) *Policy {
return NewWithConfigPath(collection, "", "", netMon, logf) return NewWithConfigPath(collection, "", "", netMon, health, logf)
} }
// NewWithConfigPath is identical to New, but uses the specified directory and // NewWithConfigPath is identical to New, but uses the specified directory and
// command name. If either is empty, it derives them automatically. // command name. If either is empty, it derives them automatically.
func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor, logf logger.Logf) *Policy { func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor, health *health.Tracker, logf logger.Logf) *Policy {
var lflags int var lflags int
if term.IsTerminal(2) || runtime.GOOS == "windows" { if term.IsTerminal(2) || runtime.GOOS == "windows" {
lflags = 0 lflags = 0
@ -555,7 +555,7 @@ func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor,
PrivateID: newc.PrivateID, PrivateID: newc.PrivateID,
Stderr: logWriter{console}, Stderr: logWriter{console},
CompressLogs: true, CompressLogs: true,
HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost, netMon, logf)}, HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost, netMon, health, logf)},
} }
if collection == logtail.CollectionNode { if collection == logtail.CollectionNode {
conf.MetricsDelta = clientmetric.EncodeLogTailMetricsDelta conf.MetricsDelta = clientmetric.EncodeLogTailMetricsDelta
@ -570,7 +570,7 @@ func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor,
logf("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.") logf("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.")
conf.BaseURL = val conf.BaseURL = val
u, _ := url.Parse(val) u, _ := url.Parse(val)
conf.HTTPC = &http.Client{Transport: NewLogtailTransport(u.Host, netMon, logf)} conf.HTTPC = &http.Client{Transport: NewLogtailTransport(u.Host, netMon, health, logf)}
} }
filchOptions := filch.Options{ filchOptions := filch.Options{
@ -742,7 +742,7 @@ func dialContext(ctx context.Context, netw, addr string, netMon *netmon.Monitor,
// //
// The logf parameter is optional; if non-nil, logs are printed using the // The logf parameter is optional; if non-nil, logs are printed using the
// provided function; if nil, log.Printf will be used instead. // provided function; if nil, log.Printf will be used instead.
func NewLogtailTransport(host string, netMon *netmon.Monitor, logf logger.Logf) http.RoundTripper { func NewLogtailTransport(host string, netMon *netmon.Monitor, health *health.Tracker, logf logger.Logf) http.RoundTripper {
if testenv.InTest() { if testenv.InTest() {
return noopPretendSuccessTransport{} return noopPretendSuccessTransport{}
} }
@ -783,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.TLSNextProto = map[string]func(authority string, c *tls.Conn) http.RoundTripper{}
} }
tr.TLSClientConfig = tlsdial.Config(host, health.Global, tr.TLSClientConfig) tr.TLSClientConfig = tlsdial.Config(host, health, tr.TLSClientConfig)
return tr return tr
} }

View File

@ -21,6 +21,7 @@ import (
"sync" "sync"
"time" "time"
"tailscale.com/health"
"tailscale.com/net/dns/resolvconffile" "tailscale.com/net/dns/resolvconffile"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -116,8 +117,9 @@ func restartResolved() error {
// The caller must call Down before program shutdown // The caller must call Down before program shutdown
// or as cleanup if the program terminates unexpectedly. // or as cleanup if the program terminates unexpectedly.
type directManager struct { type directManager struct {
logf logger.Logf logf logger.Logf
fs wholeFileFS health *health.Tracker
fs wholeFileFS
// renameBroken is set if fs.Rename to or from /etc/resolv.conf // renameBroken is set if fs.Rename to or from /etc/resolv.conf
// fails. This can happen in some container runtimes, where // fails. This can happen in some container runtimes, where
// /etc/resolv.conf is bind-mounted from outside the container, // /etc/resolv.conf is bind-mounted from outside the container,
@ -140,14 +142,15 @@ type directManager struct {
} }
//lint:ignore U1000 used in manager_{freebsd,openbsd}.go //lint:ignore U1000 used in manager_{freebsd,openbsd}.go
func newDirectManager(logf logger.Logf) *directManager { func newDirectManager(logf logger.Logf, health *health.Tracker) *directManager {
return newDirectManagerOnFS(logf, directFS{}) return newDirectManagerOnFS(logf, health, directFS{})
} }
func newDirectManagerOnFS(logf logger.Logf, fs wholeFileFS) *directManager { func newDirectManagerOnFS(logf logger.Logf, health *health.Tracker, fs wholeFileFS) *directManager {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
m := &directManager{ m := &directManager{
logf: logf, logf: logf,
health: health,
fs: fs, fs: fs,
ctx: ctx, ctx: ctx,
ctxClose: cancel, ctxClose: cancel,

View File

@ -78,7 +78,7 @@ func (m *directManager) checkForFileTrample() {
return return
} }
if bytes.Equal(cur, want) { if bytes.Equal(cur, want) {
health.Global.SetWarnable(warnTrample, nil) m.health.SetWarnable(warnTrample, nil)
if lastWarn != nil { if lastWarn != nil {
m.mu.Lock() m.mu.Lock()
m.lastWarnContents = nil m.lastWarnContents = nil
@ -101,7 +101,7 @@ func (m *directManager) checkForFileTrample() {
show = show[:1024] show = show[:1024]
} }
m.logf("trample: resolv.conf changed from what we expected. did some other program interfere? current contents: %q", show) m.logf("trample: resolv.conf changed from what we expected. did some other program interfere? current contents: %q", show)
health.Global.SetWarnable(warnTrample, errors.New("Linux DNS config not ideal. /etc/resolv.conf overwritten. See https://tailscale.com/s/dns-fight")) m.health.SetWarnable(warnTrample, errors.New("Linux DNS config not ideal. /etc/resolv.conf overwritten. See https://tailscale.com/s/dns-fight"))
} }
func (m *directManager) closeInotifyOnDone(ctx context.Context, in *gonotify.Inotify) { func (m *directManager) closeInotifyOnDone(ctx context.Context, in *gonotify.Inotify) {

View File

@ -42,7 +42,8 @@ const maxActiveQueries = 256
// Manager manages system DNS settings. // Manager manages system DNS settings.
type Manager struct { type Manager struct {
logf logger.Logf logf logger.Logf
health *health.Tracker
activeQueriesAtomic int32 activeQueriesAtomic int32
@ -55,7 +56,7 @@ type Manager struct {
// NewManagers created a new manager from the given config. // NewManagers created a new manager from the given config.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func NewManager(logf logger.Logf, oscfg OSConfigurator, netMon *netmon.Monitor, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs) *Manager { func NewManager(logf logger.Logf, oscfg OSConfigurator, netMon *netmon.Monitor, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs) *Manager {
if dialer == nil { if dialer == nil {
panic("nil Dialer") panic("nil Dialer")
} }
@ -64,6 +65,7 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, netMon *netmon.Monitor,
logf: logf, logf: logf,
resolver: resolver.New(logf, netMon, linkSel, dialer, knobs), resolver: resolver.New(logf, netMon, linkSel, dialer, knobs),
os: oscfg, os: oscfg,
health: health,
} }
m.ctx, m.ctxCancel = context.WithCancel(context.Background()) m.ctx, m.ctxCancel = context.WithCancel(context.Background())
m.logf("using %T", m.os) m.logf("using %T", m.os)
@ -94,10 +96,10 @@ func (m *Manager) Set(cfg Config) error {
return err return err
} }
if err := m.os.SetDNS(ocfg); err != nil { if err := m.os.SetDNS(ocfg); err != nil {
health.Global.SetDNSOSHealth(err) m.health.SetDNSOSHealth(err)
return err return err
} }
health.Global.SetDNSOSHealth(nil) m.health.SetDNSOSHealth(nil)
return nil return nil
} }
@ -248,7 +250,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
// This is currently (2022-10-13) expected on certain iOS and macOS // This is currently (2022-10-13) expected on certain iOS and macOS
// builds. // builds.
} else { } else {
health.Global.SetDNSOSHealth(err) m.health.SetDNSOSHealth(err)
return resolver.Config{}, OSConfig{}, err return resolver.Config{}, OSConfig{}, err
} }
} }
@ -453,12 +455,12 @@ func (m *Manager) FlushCaches() error {
// in case the Tailscale daemon terminated without closing the router. // in case the Tailscale daemon terminated without closing the router.
// No other state needs to be instantiated before this runs. // No other state needs to be instantiated before this runs.
func CleanUp(logf logger.Logf, interfaceName string) { func CleanUp(logf logger.Logf, interfaceName string) {
oscfg, err := NewOSConfigurator(logf, interfaceName) oscfg, err := NewOSConfigurator(logf, nil, interfaceName)
if err != nil { if err != nil {
logf("creating dns cleanup: %v", err) logf("creating dns cleanup: %v", err)
return return
} }
dns := NewManager(logf, oscfg, nil, &tsdial.Dialer{Logf: logf}, nil, nil) dns := NewManager(logf, oscfg, nil, nil, &tsdial.Dialer{Logf: logf}, nil, nil)
if err := dns.Down(); err != nil { if err := dns.Down(); err != nil {
logf("dns down: %v", err) logf("dns down: %v", err)
} }

View File

@ -8,11 +8,12 @@ import (
"os" "os"
"go4.org/mem" "go4.org/mem"
"tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/mak" "tailscale.com/util/mak"
) )
func NewOSConfigurator(logf logger.Logf, ifName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, ifName string) (OSConfigurator, error) {
return &darwinConfigurator{logf: logf, ifName: ifName}, nil return &darwinConfigurator{logf: logf, ifName: ifName}, nil
} }

View File

@ -5,11 +5,11 @@
package dns package dns
import "tailscale.com/types/logger" import (
"tailscale.com/health"
"tailscale.com/types/logger"
)
func NewOSConfigurator(logger.Logf, string) (OSConfigurator, error) { func NewOSConfigurator(logger.Logf, *health.Tracker, string) (OSConfigurator, error) {
// TODO(dmytro): on darwin, we should use a macOS-specific method such as scutil.
// This is currently not implemented. Editing /etc/resolv.conf does not work,
// as most applications use the system resolver, which disregards it.
return NewNoopManager() return NewNoopManager()
} }

View File

@ -7,13 +7,14 @@ import (
"fmt" "fmt"
"os" "os"
"tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ string) (OSConfigurator, error) {
bs, err := os.ReadFile("/etc/resolv.conf") bs, err := os.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) { if os.IsNotExist(err) {
return newDirectManager(logf), nil return newDirectManager(logf, health), nil
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err) return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
@ -23,16 +24,16 @@ func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
case "resolvconf": case "resolvconf":
switch resolvconfStyle() { switch resolvconfStyle() {
case "": case "":
return newDirectManager(logf), nil return newDirectManager(logf, health), nil
case "debian": case "debian":
return newDebianResolvconfManager(logf) return newDebianResolvconfManager(logf)
case "openresolv": case "openresolv":
return newOpenresolvManager(logf) return newOpenresolvManager(logf)
default: default:
logf("[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager", resolvconfStyle()) logf("[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager", resolvconfStyle())
return newDirectManager(logf), nil return newDirectManager(logf, health), nil
} }
default: default:
return newDirectManager(logf), nil return newDirectManager(logf, health), nil
} }
} }

View File

@ -31,7 +31,7 @@ func (kv kv) String() string {
var publishOnce sync.Once var publishOnce sync.Once
func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurator, err error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName string) (ret OSConfigurator, err error) {
env := newOSConfigEnv{ env := newOSConfigEnv{
fs: directFS{}, fs: directFS{},
dbusPing: dbusPing, dbusPing: dbusPing,
@ -40,7 +40,7 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat
nmVersionBetween: nmVersionBetween, nmVersionBetween: nmVersionBetween,
resolvconfStyle: resolvconfStyle, resolvconfStyle: resolvconfStyle,
} }
mode, err := dnsMode(logf, env) mode, err := dnsMode(logf, health, env)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -52,9 +52,9 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat
logf("dns: using %q mode", mode) logf("dns: using %q mode", mode)
switch mode { switch mode {
case "direct": case "direct":
return newDirectManagerOnFS(logf, env.fs), nil return newDirectManagerOnFS(logf, health, env.fs), nil
case "systemd-resolved": case "systemd-resolved":
return newResolvedManager(logf, interfaceName) return newResolvedManager(logf, health, interfaceName)
case "network-manager": case "network-manager":
return newNMManager(interfaceName) return newNMManager(interfaceName)
case "debian-resolvconf": case "debian-resolvconf":
@ -63,7 +63,7 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat
return newOpenresolvManager(logf) return newOpenresolvManager(logf)
default: default:
logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode) logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode)
return newDirectManagerOnFS(logf, env.fs), nil return newDirectManagerOnFS(logf, health, env.fs), nil
} }
} }
@ -77,7 +77,7 @@ type newOSConfigEnv struct {
resolvconfStyle func() string resolvconfStyle func() string
} }
func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) { func dnsMode(logf logger.Logf, health *health.Tracker, env newOSConfigEnv) (ret string, err error) {
var debug []kv var debug []kv
dbg := func(k, v string) { dbg := func(k, v string) {
debug = append(debug, kv{k, v}) debug = append(debug, kv{k, v})
@ -271,7 +271,7 @@ func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) {
return "direct", nil return "direct", nil
} }
health.Global.SetDNSManagerHealth(errors.New("systemd-resolved and NetworkManager are wired together incorrectly; MagicDNS will probably not work. For more info, see https://tailscale.com/s/resolved-nm")) health.SetDNSManagerHealth(errors.New("systemd-resolved and NetworkManager are wired together incorrectly; MagicDNS will probably not work. For more info, see https://tailscale.com/s/resolved-nm"))
dbg("nm-safe", "no") dbg("nm-safe", "no")
return "systemd-resolved", nil return "systemd-resolved", nil
default: default:

View File

@ -286,7 +286,7 @@ func TestLinuxDNSMode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var logBuf tstest.MemLogger var logBuf tstest.MemLogger
got, err := dnsMode(logBuf.Logf, tt.env) got, err := dnsMode(logBuf.Logf, nil, tt.env)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -19,8 +20,8 @@ func (kv kv) String() string {
return fmt.Sprintf("%s=%s", kv.k, kv.v) return fmt.Sprintf("%s=%s", kv.k, kv.v)
} }
func NewOSConfigurator(logf logger.Logf, interfaceName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName string) (OSConfigurator, error) {
return newOSConfigurator(logf, interfaceName, return newOSConfigurator(logf, health, interfaceName,
newOSConfigEnv{ newOSConfigEnv{
rcIsResolvd: rcIsResolvd, rcIsResolvd: rcIsResolvd,
fs: directFS{}, fs: directFS{},
@ -33,7 +34,7 @@ type newOSConfigEnv struct {
rcIsResolvd func(resolvConfContents []byte) bool rcIsResolvd func(resolvConfContents []byte) bool
} }
func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEnv) (ret OSConfigurator, err error) { func newOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName string, env newOSConfigEnv) (ret OSConfigurator, err error) {
var debug []kv var debug []kv
dbg := func(k, v string) { dbg := func(k, v string) {
debug = append(debug, kv{k, v}) debug = append(debug, kv{k, v})
@ -48,7 +49,7 @@ func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEn
bs, err := env.fs.ReadFile(resolvConf) bs, err := env.fs.ReadFile(resolvConf)
if os.IsNotExist(err) { if os.IsNotExist(err) {
dbg("rc", "missing") dbg("rc", "missing")
return newDirectManager(logf), nil return newDirectManager(logf, health), nil
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err) return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
@ -60,7 +61,7 @@ func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEn
} }
dbg("resolvd", "missing") dbg("resolvd", "missing")
return newDirectManager(logf), nil return newDirectManager(logf, health), nil
} }
func rcIsResolvd(resolvConfContents []byte) bool { func rcIsResolvd(resolvConfContents []byte) bool {

View File

@ -87,7 +87,7 @@ func TestDNSOverTCP(t *testing.T) {
SearchDomains: fqdns("coffee.shop"), SearchDomains: fqdns("coffee.shop"),
}, },
} }
m := NewManager(t.Logf, &f, nil, new(tsdial.Dialer), nil, nil) m := NewManager(t.Logf, &f, nil, nil, new(tsdial.Dialer), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver) m.resolver.TestOnlySetHook(f.SetResolver)
m.Set(Config{ m.Set(Config{
Hosts: hosts( Hosts: hosts(
@ -172,7 +172,7 @@ func TestDNSOverTCP_TooLarge(t *testing.T) {
SearchDomains: fqdns("coffee.shop"), SearchDomains: fqdns("coffee.shop"),
}, },
} }
m := NewManager(log, &f, nil, new(tsdial.Dialer), nil, nil) m := NewManager(log, &f, nil, nil, new(tsdial.Dialer), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver) m.resolver.TestOnlySetHook(f.SetResolver)
m.Set(Config{ m.Set(Config{
Hosts: hosts("andrew.ts.com.", "1.2.3.4"), Hosts: hosts("andrew.ts.com.", "1.2.3.4"),

View File

@ -613,7 +613,7 @@ func TestManager(t *testing.T) {
SplitDNS: test.split, SplitDNS: test.split,
BaseConfig: test.bs, BaseConfig: test.bs,
} }
m := NewManager(t.Logf, &f, nil, new(tsdial.Dialer), nil, nil) m := NewManager(t.Logf, &f, nil, nil, new(tsdial.Dialer), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver) m.resolver.TestOnlySetHook(f.SetResolver)
if err := m.Set(test.in); err != nil { if err := m.Set(test.in); err != nil {

View File

@ -23,6 +23,7 @@ import (
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/atomicfile" "tailscale.com/atomicfile"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
@ -44,11 +45,11 @@ type windowsManager struct {
closing bool closing bool
} }
func NewOSConfigurator(logf logger.Logf, interfaceName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName string) (OSConfigurator, error) {
ret := &windowsManager{ ret := &windowsManager{
logf: logf, logf: logf,
guid: interfaceName, guid: interfaceName,
wslManager: newWSLManager(logf), wslManager: newWSLManager(logf, health),
} }
if isWindows10OrBetter() { if isWindows10OrBetter() {

View File

@ -84,7 +84,7 @@ func TestManagerWindowsGPCopy(t *testing.T) {
} }
defer delIfKey() defer delIfKey()
cfg, err := NewOSConfigurator(logf, fakeInterface.String()) cfg, err := NewOSConfigurator(logf, nil, fakeInterface.String())
if err != nil { if err != nil {
t.Fatalf("NewOSConfigurator: %v\n", err) t.Fatalf("NewOSConfigurator: %v\n", err)
} }
@ -213,7 +213,7 @@ func runTest(t *testing.T, isLocal bool) {
} }
defer delIfKey() defer delIfKey()
cfg, err := NewOSConfigurator(logf, fakeInterface.String()) cfg, err := NewOSConfigurator(logf, nil, fakeInterface.String())
if err != nil { if err != nil {
t.Fatalf("NewOSConfigurator: %v\n", err) t.Fatalf("NewOSConfigurator: %v\n", err)
} }

View File

@ -63,13 +63,14 @@ type resolvedManager struct {
ctx context.Context ctx context.Context
cancel func() // terminate the context, for close cancel func() // terminate the context, for close
logf logger.Logf logf logger.Logf
ifidx int health *health.Tracker
ifidx int
configCR chan changeRequest // tracks OSConfigs changes and error responses configCR chan changeRequest // tracks OSConfigs changes and error responses
} }
func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManager, error) { func newResolvedManager(logf logger.Logf, health *health.Tracker, interfaceName string) (*resolvedManager, error) {
iface, err := net.InterfaceByName(interfaceName) iface, err := net.InterfaceByName(interfaceName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -82,8 +83,9 @@ func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManage
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
logf: logf, logf: logf,
ifidx: iface.Index, health: health,
ifidx: iface.Index,
configCR: make(chan changeRequest), configCR: make(chan changeRequest),
} }
@ -163,7 +165,7 @@ func (m *resolvedManager) run(ctx context.Context) {
// Reset backoff and SetNSOSHealth after successful on reconnect. // Reset backoff and SetNSOSHealth after successful on reconnect.
bo.BackOff(ctx, nil) bo.BackOff(ctx, nil)
health.Global.SetDNSOSHealth(nil) m.health.SetDNSOSHealth(nil)
return nil return nil
} }
@ -241,7 +243,7 @@ func (m *resolvedManager) run(ctx context.Context) {
// Set health while holding the lock, because this will // Set health while holding the lock, because this will
// graciously serialize the resync's health outcome with a // graciously serialize the resync's health outcome with a
// concurrent SetDNS call. // concurrent SetDNS call.
health.Global.SetDNSOSHealth(err) m.health.SetDNSOSHealth(err)
if err != nil { if err != nil {
m.logf("failed to configure systemd-resolved: %v", err) m.logf("failed to configure systemd-resolved: %v", err)
} }

View File

@ -16,6 +16,7 @@ import (
"time" "time"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
) )
@ -54,12 +55,14 @@ func wslDistros() ([]string, error) {
// wslManager is a DNS manager for WSL2 linux distributions. // wslManager is a DNS manager for WSL2 linux distributions.
// It configures /etc/wsl.conf and /etc/resolv.conf. // It configures /etc/wsl.conf and /etc/resolv.conf.
type wslManager struct { type wslManager struct {
logf logger.Logf logf logger.Logf
health *health.Tracker
} }
func newWSLManager(logf logger.Logf) *wslManager { func newWSLManager(logf logger.Logf, health *health.Tracker) *wslManager {
m := &wslManager{ m := &wslManager{
logf: logf, logf: logf,
health: health,
} }
return m return m
} }
@ -73,7 +76,7 @@ func (wm *wslManager) SetDNS(cfg OSConfig) error {
} }
managers := make(map[string]*directManager) managers := make(map[string]*directManager)
for _, distro := range distros { for _, distro := range distros {
managers[distro] = newDirectManagerOnFS(wm.logf, wslFS{ managers[distro] = newDirectManagerOnFS(wm.logf, wm.health, wslFS{
user: "root", user: "root",
distro: distro, distro: distro,
}) })

View File

@ -139,14 +139,6 @@ func (s *System) ProxyMapper() *proxymap.Mapper {
// HealthTracker returns the system health tracker. // HealthTracker returns the system health tracker.
func (s *System) HealthTracker() *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 return &s.healthTracker
} }

View File

@ -31,6 +31,7 @@ import (
"tailscale.com/client/tailscale" "tailscale.com/client/tailscale"
"tailscale.com/control/controlclient" "tailscale.com/control/controlclient"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
@ -233,7 +234,7 @@ func (s *Server) Loopback() (addr string, proxyCred, localAPICred string, err er
// out the CONNECT code from tailscaled/proxy.go that uses // out the CONNECT code from tailscaled/proxy.go that uses
// httputil.ReverseProxy and adding auth support. // httputil.ReverseProxy and adding auth support.
go func() { go func() {
lah := localapi.NewHandler(s.lb, s.logf, s.netMon, s.logid) lah := localapi.NewHandler(s.lb, s.logf, s.logid)
lah.PermitWrite = true lah.PermitWrite = true
lah.PermitRead = true lah.PermitRead = true
lah.RequiredPassword = s.localAPICred lah.RequiredPassword = s.localAPICred
@ -504,7 +505,8 @@ func (s *Server) start() (reterr error) {
return fmt.Errorf("%v is not a directory", s.rootPath) return fmt.Errorf("%v is not a directory", s.rootPath)
} }
if err := s.startLogger(&closePool); err != nil { sys := new(tsd.System)
if err := s.startLogger(&closePool, sys.HealthTracker()); err != nil {
return err return err
} }
@ -514,14 +516,14 @@ func (s *Server) start() (reterr error) {
} }
closePool.add(s.netMon) closePool.add(s.netMon)
sys := new(tsd.System)
s.dialer = &tsdial.Dialer{Logf: logf} // mutated below (before used) s.dialer = &tsdial.Dialer{Logf: logf} // mutated below (before used)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
ListenPort: s.Port, ListenPort: s.Port,
NetMon: s.netMon, NetMon: s.netMon,
Dialer: s.dialer, Dialer: s.dialer,
SetSubsystem: sys.Set, SetSubsystem: sys.Set,
ControlKnobs: sys.ControlKnobs(), ControlKnobs: sys.ControlKnobs(),
HealthTracker: sys.HealthTracker(),
}) })
if err != nil { if err != nil {
return err return err
@ -606,7 +608,7 @@ func (s *Server) start() (reterr error) {
go s.printAuthURLLoop() go s.printAuthURLLoop()
// Run the localapi handler, to allow fetching LetsEncrypt certs. // Run the localapi handler, to allow fetching LetsEncrypt certs.
lah := localapi.NewHandler(lb, logf, s.netMon, s.logid) lah := localapi.NewHandler(lb, logf, s.logid)
lah.PermitWrite = true lah.PermitWrite = true
lah.PermitRead = true lah.PermitRead = true
@ -626,7 +628,7 @@ func (s *Server) start() (reterr error) {
return nil return nil
} }
func (s *Server) startLogger(closePool *closeOnErrorPool) error { func (s *Server) startLogger(closePool *closeOnErrorPool, health *health.Tracker) error {
if testenv.InTest() { if testenv.InTest() {
return nil return nil
} }
@ -657,7 +659,7 @@ func (s *Server) startLogger(closePool *closeOnErrorPool) error {
Stderr: io.Discard, // log everything to Buffer Stderr: io.Discard, // log everything to Buffer
Buffer: s.logbuffer, Buffer: s.logbuffer,
CompressLogs: true, CompressLogs: true,
HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, s.netMon, s.logf)}, HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, s.netMon, health, s.logf)},
MetricsDelta: clientmetric.EncodeLogTailMetricsDelta, MetricsDelta: clientmetric.EncodeLogTailMetricsDelta,
} }
s.logtail = logtail.NewLogger(c, s.logf) s.logtail = logtail.NewLogger(c, s.logf)

View File

@ -165,7 +165,7 @@ func (c *Conn) maybeSetNearestDERP(report *netcheck.Report) (preferredDERP int)
if testenv.InTest() && !checkControlHealthDuringNearestDERPInTests { if testenv.InTest() && !checkControlHealthDuringNearestDERPInTests {
connectedToControl = true connectedToControl = true
} else { } else {
connectedToControl = health.Global.GetInPollNetMap() connectedToControl = c.health.GetInPollNetMap()
} }
if !connectedToControl { if !connectedToControl {
c.mu.Lock() c.mu.Lock()
@ -201,12 +201,12 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) {
defer c.mu.Unlock() defer c.mu.Unlock()
if !c.wantDerpLocked() { if !c.wantDerpLocked() {
c.myDerp = 0 c.myDerp = 0
health.Global.SetMagicSockDERPHome(0, c.homeless) c.health.SetMagicSockDERPHome(0, c.homeless)
return false return false
} }
if c.homeless { if c.homeless {
c.myDerp = 0 c.myDerp = 0
health.Global.SetMagicSockDERPHome(0, c.homeless) c.health.SetMagicSockDERPHome(0, c.homeless)
return false return false
} }
if derpNum == c.myDerp { if derpNum == c.myDerp {
@ -217,7 +217,7 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) {
metricDERPHomeChange.Add(1) metricDERPHomeChange.Add(1)
} }
c.myDerp = derpNum c.myDerp = derpNum
health.Global.SetMagicSockDERPHome(derpNum, c.homeless) c.health.SetMagicSockDERPHome(derpNum, c.homeless)
if c.privateKey.IsZero() { if c.privateKey.IsZero() {
// No private key yet, so DERP connections won't come up anyway. // No private key yet, so DERP connections won't come up anyway.
@ -400,7 +400,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netip.AddrPort, peer key.NodePublic) cha
} }
return derpMap.Regions[regionID] return derpMap.Regions[regionID]
}) })
dc.HealthTracker = health.Global dc.HealthTracker = c.health
dc.SetCanAckPings(true) dc.SetCanAckPings(true)
dc.NotePreferred(c.myDerp == regionID) dc.NotePreferred(c.myDerp == regionID)
@ -526,8 +526,8 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netip.AddrPort, d
return n return n
} }
defer health.Global.SetDERPRegionConnectedState(regionID, false) defer c.health.SetDERPRegionConnectedState(regionID, false)
defer health.Global.SetDERPRegionHealth(regionID, "") defer c.health.SetDERPRegionHealth(regionID, "")
// peerPresent is the set of senders we know are present on this // peerPresent is the set of senders we know are present on this
// connection, based on messages we've received from the server. // connection, based on messages we've received from the server.
@ -539,7 +539,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netip.AddrPort, d
for { for {
msg, connGen, err := dc.RecvDetail() msg, connGen, err := dc.RecvDetail()
if err != nil { if err != nil {
health.Global.SetDERPRegionConnectedState(regionID, false) c.health.SetDERPRegionConnectedState(regionID, false)
// Forget that all these peers have routes. // Forget that all these peers have routes.
for peer := range peerPresent { for peer := range peerPresent {
delete(peerPresent, peer) delete(peerPresent, peer)
@ -577,14 +577,14 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netip.AddrPort, d
now := time.Now() now := time.Now()
if lastPacketTime.IsZero() || now.Sub(lastPacketTime) > frameReceiveRecordRate { if lastPacketTime.IsZero() || now.Sub(lastPacketTime) > frameReceiveRecordRate {
health.Global.NoteDERPRegionReceivedFrame(regionID) c.health.NoteDERPRegionReceivedFrame(regionID)
lastPacketTime = now lastPacketTime = now
} }
switch m := msg.(type) { switch m := msg.(type) {
case derp.ServerInfoMessage: case derp.ServerInfoMessage:
health.Global.SetDERPRegionConnectedState(regionID, true) c.health.SetDERPRegionConnectedState(regionID, true)
health.Global.SetDERPRegionHealth(regionID, "") // until declared otherwise c.health.SetDERPRegionHealth(regionID, "") // until declared otherwise
c.logf("magicsock: derp-%d connected; connGen=%v", regionID, connGen) c.logf("magicsock: derp-%d connected; connGen=%v", regionID, connGen)
continue continue
case derp.ReceivedPacket: case derp.ReceivedPacket:
@ -624,7 +624,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netip.AddrPort, d
}() }()
continue continue
case derp.HealthMessage: case derp.HealthMessage:
health.Global.SetDERPRegionHealth(regionID, m.Problem) c.health.SetDERPRegionHealth(regionID, m.Problem)
continue continue
case derp.PeerGoneMessage: case derp.PeerGoneMessage:
switch m.Reason { switch m.Reason {

View File

@ -91,6 +91,7 @@ type Conn struct {
testOnlyPacketListener nettype.PacketListener testOnlyPacketListener nettype.PacketListener
noteRecvActivity func(key.NodePublic) // or nil, see Options.NoteRecvActivity noteRecvActivity func(key.NodePublic) // or nil, see Options.NoteRecvActivity
netMon *netmon.Monitor // or nil netMon *netmon.Monitor // or nil
health *health.Tracker // or nil
controlKnobs *controlknobs.Knobs // or nil controlKnobs *controlknobs.Knobs // or nil
// ================================================================ // ================================================================
@ -369,9 +370,13 @@ type Options struct {
NoteRecvActivity func(key.NodePublic) NoteRecvActivity func(key.NodePublic)
// NetMon is the network monitor to use. // NetMon is the network monitor to use.
// With one, the portmapper won't be used. // If nil, the portmapper won't be used.
NetMon *netmon.Monitor NetMon *netmon.Monitor
// HealthTracker optionally specifies the health tracker to
// report errors and warnings to.
HealthTracker *health.Tracker
// ControlKnobs are the set of control knobs to use. // ControlKnobs are the set of control knobs to use.
// If nil, they're ignored and not updated. // If nil, they're ignored and not updated.
ControlKnobs *controlknobs.Knobs ControlKnobs *controlknobs.Knobs
@ -463,6 +468,7 @@ func NewConn(opts Options) (*Conn, error) {
c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP) c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP)
} }
c.netMon = opts.NetMon c.netMon = opts.NetMon
c.health = opts.HealthTracker
c.onPortUpdate = opts.OnPortUpdate c.onPortUpdate = opts.OnPortUpdate
c.getPeerByKey = opts.PeerByKeyFunc c.getPeerByKey = opts.PeerByKeyFunc
@ -666,7 +672,7 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) {
// NOTE(andrew-d): I don't love that we're depending on the // NOTE(andrew-d): I don't love that we're depending on the
// health package here, but I'd rather do that and not store // health package here, but I'd rather do that and not store
// the exact same state in two different places. // the exact same state in two different places.
GetLastDERPActivity: health.Global.GetDERPRegionReceivedTime, GetLastDERPActivity: c.health.GetDERPRegionReceivedTime,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -2471,7 +2477,7 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur
} }
ruc.setConnLocked(pconn, network, c.bind.BatchSize()) ruc.setConnLocked(pconn, network, c.bind.BatchSize())
if network == "udp4" { if network == "udp4" {
health.Global.SetUDP4Unbound(false) c.health.SetUDP4Unbound(false)
} }
return nil return nil
} }
@ -2482,7 +2488,7 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur
// we get a link change and we can try binding again. // we get a link change and we can try binding again.
ruc.setConnLocked(newBlockForeverConn(), "", c.bind.BatchSize()) ruc.setConnLocked(newBlockForeverConn(), "", c.bind.BatchSize())
if network == "udp4" { if network == "udp4" {
health.Global.SetUDP4Unbound(true) c.health.SetUDP4Unbound(true)
} }
return fmt.Errorf("failed to bind any ports (tried %v)", ports) return fmt.Errorf("failed to bind any ports (tried %v)", ports)
} }

View File

@ -3113,21 +3113,23 @@ func TestMaybeSetNearestDERP(t *testing.T) {
} }
for _, tt := range testCases { for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ht := new(health.Tracker)
c := newConn() c := newConn()
c.logf = t.Logf c.logf = t.Logf
c.myDerp = tt.old c.myDerp = tt.old
c.derpMap = derpMap c.derpMap = derpMap
c.health = ht
report := &netcheck.Report{PreferredDERP: tt.reportDERP} report := &netcheck.Report{PreferredDERP: tt.reportDERP}
oldConnected := health.Global.GetInPollNetMap() oldConnected := ht.GetInPollNetMap()
if tt.connectedToControl != oldConnected { if tt.connectedToControl != oldConnected {
if tt.connectedToControl { if tt.connectedToControl {
health.Global.GotStreamedMapResponse() ht.GotStreamedMapResponse()
t.Cleanup(health.Global.SetOutOfPollNetMap) t.Cleanup(ht.SetOutOfPollNetMap)
} else { } else {
health.Global.SetOutOfPollNetMap() ht.SetOutOfPollNetMap()
t.Cleanup(health.Global.GotStreamedMapResponse) t.Cleanup(ht.GotStreamedMapResponse)
} }
} }

View File

@ -16,6 +16,7 @@ import (
"sync" "sync"
"time" "time"
"tailscale.com/health"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail" "tailscale.com/logtail"
"tailscale.com/net/connstats" "tailscale.com/net/connstats"
@ -92,7 +93,7 @@ var testClient *http.Client
// The IP protocol and source port are always zero. // The IP protocol and source port are always zero.
// The sock is used to populated the PhysicalTraffic field in Message. // The sock is used to populated the PhysicalTraffic field in Message.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor) error { func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor, health *health.Tracker) error {
nl.mu.Lock() nl.mu.Lock()
defer nl.mu.Unlock() defer nl.mu.Unlock()
if nl.logger != nil { if nl.logger != nil {
@ -101,7 +102,7 @@ func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID lo
// Startup a log stream to Tailscale's logging service. // Startup a log stream to Tailscale's logging service.
logf := log.Printf logf := log.Printf
httpc := &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon, logf)} httpc := &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon, health, logf)}
if testClient != nil { if testClient != nil {
httpc = testClient httpc = testClient
} }

View File

@ -237,7 +237,7 @@ func interfaceFromLUID(luid winipcfg.LUID, flags winipcfg.GAAFlags) (*winipcfg.I
var networkCategoryWarning = health.NewWarnable(health.WithMapDebugFlag("warn-network-category-unhealthy")) var networkCategoryWarning = health.NewWarnable(health.WithMapDebugFlag("warn-network-category-unhealthy"))
func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) { func configureInterface(cfg *Config, tun *tun.NativeTun, health *health.Tracker) (retErr error) {
var mtu = tstun.DefaultTUNMTU() var mtu = tstun.DefaultTUNMTU()
luid := winipcfg.LUID(tun.LUID()) luid := winipcfg.LUID(tun.LUID())
iface, err := interfaceFromLUID(luid, iface, err := interfaceFromLUID(luid,
@ -268,10 +268,10 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
for i := range tries { for i := range tries {
found, err := setPrivateNetwork(luid) found, err := setPrivateNetwork(luid)
if err != nil { if err != nil {
health.Global.SetWarnable(networkCategoryWarning, fmt.Errorf("set-network-category: %w", err)) health.SetWarnable(networkCategoryWarning, fmt.Errorf("set-network-category: %w", err))
log.Printf("setPrivateNetwork(try=%d): %v", i, err) log.Printf("setPrivateNetwork(try=%d): %v", i, err)
} else { } else {
health.Global.SetWarnable(networkCategoryWarning, nil) health.SetWarnable(networkCategoryWarning, nil)
if found { if found {
if i > 0 { if i > 0 {
log.Printf("setPrivateNetwork(try=%d): success", i) log.Printf("setPrivateNetwork(try=%d): success", i)

View File

@ -10,6 +10,7 @@ import (
"reflect" "reflect"
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
@ -44,9 +45,9 @@ type Router interface {
// //
// If netMon is nil, it's not used. It's currently (2021-07-20) only // If netMon is nil, it's not used. It's currently (2021-07-20) only
// used on Linux in some situations. // used on Linux in some situations.
func New(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor) (Router, error) { func New(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
logf = logger.WithPrefix(logf, "router: ") logf = logger.WithPrefix(logf, "router: ")
return newUserspaceRouter(logf, tundev, netMon) return newUserspaceRouter(logf, tundev, netMon, health)
} }
// CleanUp restores the system network configuration to its original state // CleanUp restores the system network configuration to its original state

View File

@ -5,12 +5,13 @@ package router
import ( import (
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
return newUserspaceBSDRouter(logf, tundev, netMon) return newUserspaceBSDRouter(logf, tundev, netMon, health)
} }
func cleanUp(logger.Logf, string) { func cleanUp(logger.Logf, string) {

View File

@ -10,11 +10,12 @@ import (
"runtime" "runtime"
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
return nil, fmt.Errorf("unsupported OS %q", runtime.GOOS) return nil, fmt.Errorf("unsupported OS %q", runtime.GOOS)
} }

View File

@ -5,6 +5,7 @@ package router
import ( import (
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -14,8 +15,8 @@ import (
// Work is currently underway for an in-kernel FreeBSD implementation of wireguard // Work is currently underway for an in-kernel FreeBSD implementation of wireguard
// https://svnweb.freebsd.org/base?view=revision&revision=357986 // https://svnweb.freebsd.org/base?view=revision&revision=357986
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
return newUserspaceBSDRouter(logf, tundev, netMon) return newUserspaceBSDRouter(logf, tundev, netMon, health)
} }
func cleanUp(logf logger.Logf, interfaceName string) { func cleanUp(logf logger.Logf, interfaceName string) {

View File

@ -22,6 +22,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
@ -69,7 +70,7 @@ type linuxRouter struct {
magicsockPortV6 uint16 magicsockPortV6 uint16
} }
func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
tunname, err := tunDev.Name() tunname, err := tunDev.Name()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -886,7 +886,7 @@ func newLinuxRootTest(t *testing.T) *linuxTest {
mon.Start() mon.Start()
lt.mon = mon lt.mon = mon
r, err := newUserspaceRouter(logf, lt.tun, mon) r, err := newUserspaceRouter(logf, lt.tun, mon, nil)
if err != nil { if err != nil {
lt.Close() lt.Close()
t.Fatal(err) t.Fatal(err)

View File

@ -12,6 +12,7 @@ import (
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"go4.org/netipx" "go4.org/netipx"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/set" "tailscale.com/util/set"
@ -30,7 +31,7 @@ type openbsdRouter struct {
routes set.Set[netip.Prefix] routes set.Set[netip.Prefix]
} }
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
tunname, err := tundev.Name() tunname, err := tundev.Name()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -14,6 +14,7 @@ import (
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"go4.org/netipx" "go4.org/netipx"
"tailscale.com/health"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -23,12 +24,13 @@ import (
type userspaceBSDRouter struct { type userspaceBSDRouter struct {
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor netMon *netmon.Monitor
health *health.Tracker
tunname string tunname string
local []netip.Prefix local []netip.Prefix
routes map[netip.Prefix]bool routes map[netip.Prefix]bool
} }
func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
tunname, err := tundev.Name() tunname, err := tundev.Name()
if err != nil { if err != nil {
return nil, err return nil, err
@ -37,6 +39,7 @@ func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.M
return &userspaceBSDRouter{ return &userspaceBSDRouter{
logf: logf, logf: logf,
netMon: netMon, netMon: netMon,
health: health,
tunname: tunname, tunname: tunname,
}, nil }, nil
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/health"
"tailscale.com/logtail/backoff" "tailscale.com/logtail/backoff"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
@ -31,12 +32,13 @@ import (
type winRouter struct { type winRouter struct {
logf func(fmt string, args ...any) logf func(fmt string, args ...any)
netMon *netmon.Monitor // may be nil netMon *netmon.Monitor // may be nil
health *health.Tracker
nativeTun *tun.NativeTun nativeTun *tun.NativeTun
routeChangeCallback *winipcfg.RouteChangeCallback routeChangeCallback *winipcfg.RouteChangeCallback
firewall *firewallTweaker firewall *firewallTweaker
} }
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor) (Router, error) { func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
nativeTun := tundev.(*tun.NativeTun) nativeTun := tundev.(*tun.NativeTun)
luid := winipcfg.LUID(nativeTun.LUID()) luid := winipcfg.LUID(nativeTun.LUID())
guid, err := luid.GUID() guid, err := luid.GUID()
@ -47,6 +49,7 @@ func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Moni
return &winRouter{ return &winRouter{
logf: logf, logf: logf,
netMon: netMon, netMon: netMon,
health: health,
nativeTun: nativeTun, nativeTun: nativeTun,
firewall: &firewallTweaker{ firewall: &firewallTweaker{
logf: logger.WithPrefix(logf, "firewall: "), logf: logger.WithPrefix(logf, "firewall: "),
@ -80,7 +83,7 @@ func (r *winRouter) Set(cfg *Config) error {
} }
r.firewall.set(localAddrs, cfg.Routes, cfg.LocalRoutes) r.firewall.set(localAddrs, cfg.Routes, cfg.LocalRoutes)
err := configureInterface(cfg, r.nativeTun) err := configureInterface(cfg, r.nativeTun, r.health)
if err != nil { if err != nil {
r.logf("ConfigureInterface: %v", err) r.logf("ConfigureInterface: %v", err)
return err return err

View File

@ -98,6 +98,7 @@ type userspaceEngine struct {
dns *dns.Manager dns *dns.Manager
magicConn *magicsock.Conn magicConn *magicsock.Conn
netMon *netmon.Monitor netMon *netmon.Monitor
health *health.Tracker
netMonOwned bool // whether we created netMon (and thus need to close it) netMonOwned bool // whether we created netMon (and thus need to close it)
netMonUnregister func() // unsubscribes from changes; used regardless of netMonOwned netMonUnregister func() // unsubscribes from changes; used regardless of netMonOwned
birdClient BIRDClient // or nil birdClient BIRDClient // or nil
@ -188,6 +189,9 @@ type Config struct {
// If nil, a new network monitor is created. // If nil, a new network monitor is created.
NetMon *netmon.Monitor NetMon *netmon.Monitor
// HealthTracker, if non-nil, is the health tracker to use.
HealthTracker *health.Tracker
// Dialer is the dialer to use for outbound connections. // Dialer is the dialer to use for outbound connections.
// If nil, a new Dialer is created // If nil, a new Dialer is created
Dialer *tsdial.Dialer Dialer *tsdial.Dialer
@ -310,6 +314,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
birdClient: conf.BIRDClient, birdClient: conf.BIRDClient,
controlKnobs: conf.ControlKnobs, controlKnobs: conf.ControlKnobs,
reconfigureVPN: conf.ReconfigureVPN, reconfigureVPN: conf.ReconfigureVPN,
health: conf.HealthTracker,
} }
if e.birdClient != nil { if e.birdClient != nil {
@ -336,7 +341,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
tunName, _ := conf.Tun.Name() tunName, _ := conf.Tun.Name()
conf.Dialer.SetTUNName(tunName) conf.Dialer.SetTUNName(tunName)
conf.Dialer.SetNetMon(e.netMon) conf.Dialer.SetNetMon(e.netMon)
e.dns = dns.NewManager(logf, conf.DNS, e.netMon, conf.Dialer, fwdDNSLinkSelector{e, tunName}, conf.ControlKnobs) e.dns = dns.NewManager(logf, conf.DNS, e.netMon, e.health, conf.Dialer, fwdDNSLinkSelector{e, tunName}, conf.ControlKnobs)
// TODO: there's probably a better place for this // TODO: there's probably a better place for this
sockstats.SetNetMon(e.netMon) sockstats.SetNetMon(e.netMon)
@ -372,6 +377,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
IdleFunc: e.tundev.IdleDuration, IdleFunc: e.tundev.IdleDuration,
NoteRecvActivity: e.noteRecvActivity, NoteRecvActivity: e.noteRecvActivity,
NetMon: e.netMon, NetMon: e.netMon,
HealthTracker: e.health,
ControlKnobs: conf.ControlKnobs, ControlKnobs: conf.ControlKnobs,
OnPortUpdate: onPortUpdate, OnPortUpdate: onPortUpdate,
PeerByKeyFunc: e.PeerByKey, PeerByKeyFunc: e.PeerByKey,
@ -960,7 +966,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
nid := cfg.NetworkLogging.NodeID nid := cfg.NetworkLogging.NodeID
tid := cfg.NetworkLogging.DomainID tid := cfg.NetworkLogging.DomainID
e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public()) e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public())
if err := e.networkLogger.Startup(cfg.NodeID, nid, tid, e.tundev, e.magicConn, e.netMon); err != nil { if err := e.networkLogger.Startup(cfg.NodeID, nid, tid, e.tundev, e.magicConn, e.netMon, e.health); err != nil {
e.logf("wgengine: Reconfig: error starting up network logger: %v", err) e.logf("wgengine: Reconfig: error starting up network logger: %v", err)
} }
e.networkLogger.ReconfigRoutes(routerCfg) e.networkLogger.ReconfigRoutes(routerCfg)
@ -970,7 +976,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
e.logf("wgengine: Reconfig: configuring router") e.logf("wgengine: Reconfig: configuring router")
e.networkLogger.ReconfigRoutes(routerCfg) e.networkLogger.ReconfigRoutes(routerCfg)
err := e.router.Set(routerCfg) err := e.router.Set(routerCfg)
health.Global.SetRouterHealth(err) e.health.SetRouterHealth(err)
if err != nil { if err != nil {
return err return err
} }
@ -979,7 +985,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
// assigned address. // assigned address.
e.logf("wgengine: Reconfig: configuring DNS") e.logf("wgengine: Reconfig: configuring DNS")
err = e.dns.Set(*dnsCfg) err = e.dns.Set(*dnsCfg)
health.Global.SetDNSHealth(err) e.health.SetDNSHealth(err)
if err != nil { if err != nil {
return err return err
} }
@ -1183,7 +1189,7 @@ func (e *userspaceEngine) linkChange(delta *netmon.ChangeDelta) {
e.logf("[v1] LinkChange: minor") e.logf("[v1] LinkChange: minor")
} }
health.Global.SetAnyInterfaceUp(up) e.health.SetAnyInterfaceUp(up)
e.magicConn.SetNetworkUp(up) e.magicConn.SetNetworkUp(up)
if !up || changed { if !up || changed {
if err := e.dns.FlushCaches(); err != nil { if err := e.dns.FlushCaches(); err != nil {