2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2021-02-18 16:58:13 +00:00
|
|
|
|
|
|
|
// Package health is a registry for other packages to report & check
|
|
|
|
// overall health status of the node.
|
|
|
|
package health
|
|
|
|
|
|
|
|
import (
|
2021-03-16 05:20:48 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2021-12-21 21:52:50 +00:00
|
|
|
"net/http"
|
2021-10-22 16:12:00 +00:00
|
|
|
"runtime"
|
2021-03-16 05:20:48 +00:00
|
|
|
"sort"
|
2021-02-18 16:58:13 +00:00
|
|
|
"sync"
|
2021-04-27 00:08:05 +00:00
|
|
|
"sync/atomic"
|
2021-02-25 05:29:51 +00:00
|
|
|
"time"
|
|
|
|
|
2022-01-24 18:52:57 +00:00
|
|
|
"tailscale.com/envknob"
|
2021-02-25 05:29:51 +00:00
|
|
|
"tailscale.com/tailcfg"
|
2024-04-25 20:24:49 +00:00
|
|
|
"tailscale.com/types/opt"
|
|
|
|
"tailscale.com/util/mak"
|
2021-11-02 21:30:48 +00:00
|
|
|
"tailscale.com/util/multierr"
|
2022-11-28 18:34:35 +00:00
|
|
|
"tailscale.com/util/set"
|
2021-02-18 16:58:13 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-04-25 20:24:49 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
debugHandler map[string]http.Handler
|
|
|
|
)
|
|
|
|
|
|
|
|
// Global is a global health tracker for the process.
|
|
|
|
//
|
tsd, ipnlocal, etc: add tsd.System.HealthTracker, start some plumbing
This adds a health.Tracker to tsd.System, accessible via
a new tsd.System.HealthTracker method.
In the future, that new method will return a tsd.System-specific
HealthTracker, so multiple tsnet.Servers in the same process are
isolated. For now, though, it just always returns the temporary
health.Global value. That permits incremental plumbing over a number
of changes. When the second to last health.Global reference is gone,
then the tsd.System.HealthTracker implementation can return a private
Tracker.
The primary plumbing this does is adding it to LocalBackend and its
dozen and change health calls. A few misc other callers are also
plumbed. Subsequent changes will flesh out other parts of the tree
(magicsock, controlclient, etc).
Updates #11874
Updates #4136
Change-Id: Id51e73cfc8a39110425b6dc19d18b3975eac75ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 03:29:20 +00:00
|
|
|
// 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.
|
2024-04-25 20:24:49 +00:00
|
|
|
var Global = new(Tracker)
|
|
|
|
|
|
|
|
type Tracker struct {
|
2021-02-25 05:29:51 +00:00
|
|
|
// mu guards everything in this var block.
|
|
|
|
mu sync.Mutex
|
|
|
|
|
2024-04-25 21:25:48 +00:00
|
|
|
warnables []*Warnable // keys ever set
|
|
|
|
warnableVal map[*Warnable]error
|
|
|
|
|
|
|
|
sysErr map[Subsystem]error // subsystem => err (or nil for no error)
|
|
|
|
watchers set.HandleSet[func(Subsystem, error)] // opt func to run if error state changes
|
|
|
|
timer *time.Timer
|
2021-02-25 05:29:51 +00:00
|
|
|
|
|
|
|
inMapPoll bool
|
|
|
|
inMapPollSince time.Time
|
|
|
|
lastMapPollEndedAt time.Time
|
|
|
|
lastStreamedMapResponse time.Time
|
|
|
|
derpHomeRegion int
|
2023-11-16 21:43:36 +00:00
|
|
|
derpHomeless bool
|
2024-04-25 20:24:49 +00:00
|
|
|
derpRegionConnected map[int]bool
|
|
|
|
derpRegionHealthProblem map[int]string
|
|
|
|
derpRegionLastFrame map[int]time.Time
|
2021-02-25 05:29:51 +00:00
|
|
|
lastMapRequestHeard time.Time // time we got a 200 from control for a MapRequest
|
|
|
|
ipnState string
|
|
|
|
ipnWantRunning bool
|
2024-04-25 20:24:49 +00:00
|
|
|
anyInterfaceUp opt.Bool // empty means unknown (assume true)
|
2021-04-28 17:36:54 +00:00
|
|
|
udp4Unbound bool
|
2021-09-18 19:59:55 +00:00
|
|
|
controlHealth []string
|
2022-06-03 17:52:07 +00:00
|
|
|
lastLoginErr error
|
2022-10-18 20:54:07 +00:00
|
|
|
localLogConfigErr error
|
2024-04-25 20:24:49 +00:00
|
|
|
tlsConnectionErrors map[string]error // map[ServerName]error
|
|
|
|
}
|
2021-02-18 16:58:13 +00:00
|
|
|
|
2021-03-16 05:20:48 +00:00
|
|
|
// Subsystem is the name of a subsystem whose health can be monitored.
|
|
|
|
type Subsystem string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// SysOverall is the name representing the overall health of
|
|
|
|
// the system, rather than one particular subsystem.
|
|
|
|
SysOverall = Subsystem("overall")
|
|
|
|
|
2021-04-28 17:36:54 +00:00
|
|
|
// SysRouter is the name of the wgengine/router subsystem.
|
2021-03-16 05:20:48 +00:00
|
|
|
SysRouter = Subsystem("router")
|
|
|
|
|
2021-04-03 02:31:58 +00:00
|
|
|
// SysDNS is the name of the net/dns subsystem.
|
|
|
|
SysDNS = Subsystem("dns")
|
|
|
|
|
2021-11-18 23:52:21 +00:00
|
|
|
// SysDNSOS is the name of the net/dns OSConfigurator subsystem.
|
|
|
|
SysDNSOS = Subsystem("dns-os")
|
|
|
|
|
2022-02-15 14:59:15 +00:00
|
|
|
// SysDNSManager is the name of the net/dns manager subsystem.
|
|
|
|
SysDNSManager = Subsystem("dns-manager")
|
2023-01-04 18:36:07 +00:00
|
|
|
|
|
|
|
// SysTKA is the name of the tailnet key authority subsystem.
|
|
|
|
SysTKA = Subsystem("tailnet-lock")
|
2022-11-13 15:32:37 +00:00
|
|
|
)
|
2022-02-15 14:59:15 +00:00
|
|
|
|
2024-04-25 21:25:48 +00:00
|
|
|
// NewWarnable returns a new warnable item that the caller can mark as health or
|
|
|
|
// in warning state via Tracker.SetWarnable.
|
|
|
|
//
|
|
|
|
// NewWarnable is generally called in init and stored in a package global. It
|
|
|
|
// can be used by multiple Trackers.
|
|
|
|
func NewWarnable(opts ...WarnableOpt) *Warnable {
|
2022-11-13 15:32:37 +00:00
|
|
|
w := new(Warnable)
|
|
|
|
for _, o := range opts {
|
|
|
|
o.mod(w)
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
}
|
2022-11-02 20:13:26 +00:00
|
|
|
|
2022-11-13 15:32:37 +00:00
|
|
|
// WarnableOpt is an option passed to NewWarnable.
|
|
|
|
type WarnableOpt interface {
|
|
|
|
mod(*Warnable)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMapDebugFlag returns a WarnableOpt for NewWarnable that makes the returned
|
|
|
|
// Warnable report itself to the coordination server as broken with this
|
|
|
|
// string in MapRequest.DebugFlag when Set to a non-nil value.
|
|
|
|
func WithMapDebugFlag(name string) WarnableOpt {
|
|
|
|
return warnOptFunc(func(w *Warnable) {
|
|
|
|
w.debugFlag = name
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-03 00:23:58 +00:00
|
|
|
// WithConnectivityImpact returns an option which makes a Warnable annotated as
|
|
|
|
// something that could be breaking external network connectivity on the
|
|
|
|
// machine. This will make the warnable returned by OverallError alongside
|
|
|
|
// network connectivity errors.
|
|
|
|
func WithConnectivityImpact() WarnableOpt {
|
|
|
|
return warnOptFunc(func(w *Warnable) {
|
|
|
|
w.hasConnectivityImpact = true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-13 15:32:37 +00:00
|
|
|
type warnOptFunc func(*Warnable)
|
|
|
|
|
|
|
|
func (f warnOptFunc) mod(w *Warnable) { f(w) }
|
|
|
|
|
|
|
|
// Warnable is a health check item that may or may not be in a bad warning state.
|
2024-04-25 21:25:48 +00:00
|
|
|
// The caller of NewWarnable is responsible for calling Tracker.SetWarnable to update the state.
|
2022-11-13 15:32:37 +00:00
|
|
|
type Warnable struct {
|
|
|
|
debugFlag string // optional MapRequest.DebugFlag to send when unhealthy
|
|
|
|
|
2024-01-03 00:23:58 +00:00
|
|
|
// If true, this warning is related to configuration of networking stack
|
|
|
|
// on the machine that impacts connectivity.
|
|
|
|
hasConnectivityImpact bool
|
2022-11-13 15:32:37 +00:00
|
|
|
}
|
|
|
|
|
2024-04-26 03:26:49 +00:00
|
|
|
// nil reports whether t is nil.
|
|
|
|
// It exists to accept nil *Tracker receivers on all methods
|
|
|
|
// to at least not crash. But because a nil receiver indicates
|
|
|
|
// some lost Tracker plumbing, we want to capture stack trace
|
|
|
|
// samples when it occurs.
|
|
|
|
func (t *Tracker) nil() bool {
|
|
|
|
if t != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// TODO(bradfitz): open source our "unexpected" package
|
|
|
|
// and use it here to capture samples of stacks where
|
|
|
|
// t is nil.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-11-13 15:32:37 +00:00
|
|
|
// Set updates the Warnable's state.
|
|
|
|
// If non-nil, it's considered unhealthy.
|
2024-04-25 21:25:48 +00:00
|
|
|
func (t *Tracker) SetWarnable(w *Warnable, err error) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 21:25:48 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
l0 := len(t.warnableVal)
|
|
|
|
mak.Set(&t.warnableVal, w, err)
|
|
|
|
if len(t.warnableVal) != l0 {
|
|
|
|
t.warnables = append(t.warnables, w)
|
2022-11-13 15:32:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AppendWarnableDebugFlags appends to base any health items that are currently in failed
|
|
|
|
// state and were created with MapDebugFlag.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) AppendWarnableDebugFlags(base []string) []string {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return base
|
|
|
|
}
|
|
|
|
|
2022-11-13 15:32:37 +00:00
|
|
|
ret := base
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2024-04-25 21:25:48 +00:00
|
|
|
for w, err := range t.warnableVal {
|
2022-11-13 15:32:37 +00:00
|
|
|
if w.debugFlag == "" {
|
|
|
|
continue
|
|
|
|
}
|
2024-04-25 21:25:48 +00:00
|
|
|
if err != nil {
|
2022-11-13 15:32:37 +00:00
|
|
|
ret = append(ret, w.debugFlag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(ret[len(base):]) // sort the new ones
|
|
|
|
return ret
|
|
|
|
}
|
2021-03-16 05:20:48 +00:00
|
|
|
|
2021-02-18 16:58:13 +00:00
|
|
|
// RegisterWatcher adds a function that will be called if an
|
|
|
|
// error changes state either to unhealthy or from unhealthy. It is
|
|
|
|
// not called on transition from unknown to healthy. It must be non-nil
|
|
|
|
// and is run in its own goroutine. The returned func unregisters it.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) RegisterWatcher(cb func(key Subsystem, err error)) (unregister func()) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return func() {}
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
if t.watchers == nil {
|
|
|
|
t.watchers = set.HandleSet[func(Subsystem, error)]{}
|
|
|
|
}
|
|
|
|
handle := t.watchers.Add(cb)
|
|
|
|
if t.timer == nil {
|
|
|
|
t.timer = time.AfterFunc(time.Minute, t.timerSelfCheck)
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
2021-02-18 16:58:13 +00:00
|
|
|
return func() {
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
delete(t.watchers, handle)
|
|
|
|
if len(t.watchers) == 0 && t.timer != nil {
|
|
|
|
t.timer.Stop()
|
|
|
|
t.timer = nil
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
2021-02-18 16:58:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 02:31:58 +00:00
|
|
|
// SetRouterHealth sets the state of the wgengine/router.Router.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetRouterHealth(err error) { t.setErr(SysRouter, err) }
|
2021-02-18 16:58:13 +00:00
|
|
|
|
|
|
|
// RouterHealth returns the wgengine/router.Router error state.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) RouterHealth() error { return t.get(SysRouter) }
|
2021-02-18 16:58:13 +00:00
|
|
|
|
2021-04-03 02:31:58 +00:00
|
|
|
// SetDNSHealth sets the state of the net/dns.Manager
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetDNSHealth(err error) { t.setErr(SysDNS, err) }
|
2021-04-03 02:31:58 +00:00
|
|
|
|
|
|
|
// DNSHealth returns the net/dns.Manager error state.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) DNSHealth() error { return t.get(SysDNS) }
|
2021-04-03 02:31:58 +00:00
|
|
|
|
2021-11-18 23:52:21 +00:00
|
|
|
// SetDNSOSHealth sets the state of the net/dns.OSConfigurator
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetDNSOSHealth(err error) { t.setErr(SysDNSOS, err) }
|
2021-11-18 23:52:21 +00:00
|
|
|
|
2022-02-15 14:59:15 +00:00
|
|
|
// SetDNSManagerHealth sets the state of the Linux net/dns manager's
|
|
|
|
// discovery of the /etc/resolv.conf situation.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetDNSManagerHealth(err error) { t.setErr(SysDNSManager, err) }
|
2022-02-15 14:59:15 +00:00
|
|
|
|
2021-11-18 23:52:21 +00:00
|
|
|
// DNSOSHealth returns the net/dns.OSConfigurator error state.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) DNSOSHealth() error { return t.get(SysDNSOS) }
|
2021-11-18 23:52:21 +00:00
|
|
|
|
2023-01-04 18:36:07 +00:00
|
|
|
// SetTKAHealth sets the health of the tailnet key authority.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetTKAHealth(err error) { t.setErr(SysTKA, err) }
|
2023-01-04 18:36:07 +00:00
|
|
|
|
|
|
|
// TKAHealth returns the tailnet key authority error state.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) TKAHealth() error { return t.get(SysTKA) }
|
2023-01-04 18:36:07 +00:00
|
|
|
|
2022-10-18 20:54:07 +00:00
|
|
|
// SetLocalLogConfigHealth sets the error state of this client's local log configuration.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetLocalLogConfigHealth(err error) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.localLogConfigErr = err
|
2022-10-18 20:54:07 +00:00
|
|
|
}
|
|
|
|
|
2023-02-01 19:29:44 +00:00
|
|
|
// SetTLSConnectionError sets the error state for connections to a specific
|
|
|
|
// host. Setting the error to nil will clear any previously-set error.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetTLSConnectionError(host string, err error) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2023-02-01 19:29:44 +00:00
|
|
|
if err == nil {
|
2024-04-25 20:24:49 +00:00
|
|
|
delete(t.tlsConnectionErrors, host)
|
2023-02-01 19:29:44 +00:00
|
|
|
} else {
|
2024-04-25 20:24:49 +00:00
|
|
|
mak.Set(&t.tlsConnectionErrors, host, err)
|
2023-02-01 19:29:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 21:52:50 +00:00
|
|
|
func RegisterDebugHandler(typ string, h http.Handler) {
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
2024-04-25 20:24:49 +00:00
|
|
|
mak.Set(&debugHandler, typ, h)
|
2021-12-21 21:52:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func DebugHandler(typ string) http.Handler {
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
return debugHandler[typ]
|
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) get(key Subsystem) error {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return nil
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
return t.sysErr[key]
|
2021-02-18 16:58:13 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) setErr(key Subsystem, err error) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.setLocked(key, err)
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) setLocked(key Subsystem, err error) {
|
|
|
|
if t.sysErr == nil {
|
|
|
|
t.sysErr = map[Subsystem]error{}
|
|
|
|
}
|
|
|
|
old, ok := t.sysErr[key]
|
2021-02-18 16:58:13 +00:00
|
|
|
if !ok && err == nil {
|
|
|
|
// Initial happy path.
|
2024-04-25 20:24:49 +00:00
|
|
|
t.sysErr[key] = nil
|
|
|
|
t.selfCheckLocked()
|
2021-02-18 16:58:13 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if ok && (old == nil) == (err == nil) {
|
|
|
|
// No change in overall error status (nil-vs-not), so
|
|
|
|
// don't run callbacks, but exact error might've
|
|
|
|
// changed, so note it.
|
|
|
|
if err != nil {
|
2024-04-25 20:24:49 +00:00
|
|
|
t.sysErr[key] = err
|
2021-02-18 16:58:13 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.sysErr[key] = err
|
|
|
|
t.selfCheckLocked()
|
|
|
|
for _, cb := range t.watchers {
|
2021-02-18 16:58:13 +00:00
|
|
|
go cb(key, err)
|
|
|
|
}
|
|
|
|
}
|
2021-02-25 05:29:51 +00:00
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetControlHealth(problems []string) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.controlHealth = problems
|
|
|
|
t.selfCheckLocked()
|
2021-09-18 19:59:55 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 05:29:51 +00:00
|
|
|
// GotStreamedMapResponse notes that we got a tailcfg.MapResponse
|
|
|
|
// message in streaming mode, even if it's just a keep-alive message.
|
2023-08-30 15:57:55 +00:00
|
|
|
//
|
|
|
|
// This also notes that a map poll is in progress. To unset that, call
|
|
|
|
// SetOutOfPollNetMap().
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) GotStreamedMapResponse() {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.lastStreamedMapResponse = time.Now()
|
|
|
|
if !t.inMapPoll {
|
|
|
|
t.inMapPoll = true
|
|
|
|
t.inMapPollSince = time.Now()
|
2023-08-30 15:57:55 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-30 15:57:55 +00:00
|
|
|
// SetOutOfPollNetMap records that the client is no longer in
|
|
|
|
// an HTTP map request long poll to the control plane.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetOutOfPollNetMap() {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
if !t.inMapPoll {
|
2021-02-25 05:29:51 +00:00
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.inMapPoll = false
|
|
|
|
t.lastMapPollEndedAt = time.Now()
|
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
2021-12-16 16:06:32 +00:00
|
|
|
// GetInPollNetMap reports whether the client has an open
|
|
|
|
// HTTP long poll open to the control plane.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) GetInPollNetMap() bool {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return false
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
return t.inMapPoll
|
2021-12-16 16:06:32 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 05:29:51 +00:00
|
|
|
// SetMagicSockDERPHome notes what magicsock's view of its home DERP is.
|
2023-11-16 21:43:36 +00:00
|
|
|
//
|
|
|
|
// The homeless parameter is whether magicsock is running in DERP-disconnected
|
|
|
|
// mode, without discovering and maintaining a connection to its home DERP.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetMagicSockDERPHome(region int, homeless bool) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.derpHomeRegion = region
|
|
|
|
t.derpHomeless = homeless
|
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NoteMapRequestHeard notes whenever we successfully sent a map request
|
|
|
|
// to control for which we received a 200 response.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) NoteMapRequestHeard(mr *tailcfg.MapRequest) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2021-02-25 05:29:51 +00:00
|
|
|
// TODO: extract mr.HostInfo.NetInfo.PreferredDERP, compare
|
|
|
|
// against SetMagicSockDERPHome and
|
|
|
|
// SetDERPRegionConnectedState
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
t.lastMapRequestHeard = time.Now()
|
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetDERPRegionConnectedState(region int, connected bool) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
mak.Set(&t.derpRegionConnected, region, connected)
|
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
2021-09-02 02:27:22 +00:00
|
|
|
// SetDERPRegionHealth sets or clears any problem associated with the
|
|
|
|
// provided DERP region.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetDERPRegionHealth(region int, problem string) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2021-09-02 02:27:22 +00:00
|
|
|
if problem == "" {
|
2024-04-25 20:24:49 +00:00
|
|
|
delete(t.derpRegionHealthProblem, region)
|
2021-09-02 02:27:22 +00:00
|
|
|
} else {
|
2024-04-25 20:24:49 +00:00
|
|
|
mak.Set(&t.derpRegionHealthProblem, region, problem)
|
2021-09-02 02:27:22 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.selfCheckLocked()
|
2021-09-02 02:27:22 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 20:03:15 +00:00
|
|
|
// NoteDERPRegionReceivedFrame is called to note that a frame was received from
|
|
|
|
// the given DERP region at the current time.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) NoteDERPRegionReceivedFrame(region int) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
mak.Set(&t.derpRegionLastFrame, region, time.Now())
|
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
2023-12-08 20:03:15 +00:00
|
|
|
// GetDERPRegionReceivedTime returns the last time that a frame was received
|
|
|
|
// from the given DERP region, or the zero time if no communication with that
|
|
|
|
// region has occurred.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) GetDERPRegionReceivedTime(region int) time.Time {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return time.Time{}
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
return t.derpRegionLastFrame[region]
|
2023-12-08 20:03:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 05:29:51 +00:00
|
|
|
// state is an ipn.State.String() value: "Running", "Stopped", "NeedsLogin", etc.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetIPNState(state string, wantRunning bool) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.ipnState = state
|
|
|
|
t.ipnWantRunning = wantRunning
|
|
|
|
t.selfCheckLocked()
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
|
|
|
|
2021-03-23 04:41:53 +00:00
|
|
|
// SetAnyInterfaceUp sets whether any network interface is up.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetAnyInterfaceUp(up bool) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.anyInterfaceUp.Set(up)
|
|
|
|
t.selfCheckLocked()
|
2021-03-23 04:41:53 +00:00
|
|
|
}
|
|
|
|
|
2021-04-28 17:36:54 +00:00
|
|
|
// SetUDP4Unbound sets whether the udp4 bind failed completely.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetUDP4Unbound(unbound bool) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.udp4Unbound = unbound
|
|
|
|
t.selfCheckLocked()
|
2021-04-28 17:36:54 +00:00
|
|
|
}
|
|
|
|
|
2022-06-03 17:52:07 +00:00
|
|
|
// SetAuthRoutineInError records the latest error encountered as a result of a
|
|
|
|
// login attempt. Providing a nil error indicates successful login, or that
|
|
|
|
// being logged in w/coordination is not currently desired.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) SetAuthRoutineInError(err error) {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
t.lastLoginErr = err
|
2022-06-03 17:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) timerSelfCheck() {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2021-04-27 00:08:05 +00:00
|
|
|
checkReceiveFuncs()
|
2024-04-25 20:24:49 +00:00
|
|
|
t.selfCheckLocked()
|
|
|
|
if t.timer != nil {
|
|
|
|
t.timer.Reset(time.Minute)
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) selfCheckLocked() {
|
|
|
|
if t.ipnState == "" {
|
2021-03-16 05:20:48 +00:00
|
|
|
// Don't check yet.
|
|
|
|
return
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.setLocked(SysOverall, t.overallErrorLocked())
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
|
|
|
|
2021-09-02 02:27:22 +00:00
|
|
|
// OverallError returns a summary of the health state.
|
|
|
|
//
|
|
|
|
// If there are multiple problems, the error will be of type
|
2021-11-02 21:30:48 +00:00
|
|
|
// multierr.Error.
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) OverallError() error {
|
2024-04-26 03:26:49 +00:00
|
|
|
if t.nil() {
|
|
|
|
return nil
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
return t.overallErrorLocked()
|
2021-09-02 02:27:22 +00:00
|
|
|
}
|
|
|
|
|
2022-09-14 19:49:39 +00:00
|
|
|
var fakeErrForTesting = envknob.RegisterString("TS_DEBUG_FAKE_HEALTH_ERROR")
|
2021-09-16 18:24:44 +00:00
|
|
|
|
2024-04-25 21:25:48 +00:00
|
|
|
// networkErrorfLocked creates an error that indicates issues with outgoing network
|
2024-01-03 00:23:58 +00:00
|
|
|
// connectivity. Any active warnings related to network connectivity will
|
|
|
|
// automatically be appended to it.
|
2024-04-25 21:25:48 +00:00
|
|
|
//
|
|
|
|
// t.mu must be held.
|
|
|
|
func (t *Tracker) networkErrorfLocked(format string, a ...any) error {
|
2024-01-03 00:23:58 +00:00
|
|
|
errs := []error{
|
|
|
|
fmt.Errorf(format, a...),
|
|
|
|
}
|
2024-04-25 21:25:48 +00:00
|
|
|
for _, w := range t.warnables {
|
2024-01-03 00:23:58 +00:00
|
|
|
if !w.hasConnectivityImpact {
|
|
|
|
continue
|
|
|
|
}
|
2024-04-25 21:25:48 +00:00
|
|
|
if err := t.warnableVal[w]; err != nil {
|
2024-01-03 00:23:58 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(errs) == 1 {
|
|
|
|
return errs[0]
|
|
|
|
}
|
|
|
|
return multierr.New(errs...)
|
|
|
|
}
|
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
var errNetworkDown = errors.New("network down")
|
|
|
|
var errNotInMapPoll = errors.New("not in map poll")
|
2024-01-03 00:23:58 +00:00
|
|
|
var errNoDERPHome = errors.New("no DERP home")
|
2024-04-25 20:24:49 +00:00
|
|
|
var errNoUDP4Bind = errors.New("no udp4 bind")
|
2024-01-03 00:23:58 +00:00
|
|
|
|
2024-04-25 20:24:49 +00:00
|
|
|
func (t *Tracker) overallErrorLocked() error {
|
|
|
|
if v, ok := t.anyInterfaceUp.Get(); ok && !v {
|
2024-01-03 00:23:58 +00:00
|
|
|
return errNetworkDown
|
2021-03-23 04:41:53 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if t.localLogConfigErr != nil {
|
|
|
|
return t.localLogConfigErr
|
2022-10-18 20:54:07 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if !t.ipnWantRunning {
|
|
|
|
return fmt.Errorf("state=%v, wantRunning=%v", t.ipnState, t.ipnWantRunning)
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if t.lastLoginErr != nil {
|
|
|
|
return fmt.Errorf("not logged in, last login error=%v", t.lastLoginErr)
|
2022-06-03 17:52:07 +00:00
|
|
|
}
|
2021-03-16 05:20:48 +00:00
|
|
|
now := time.Now()
|
2024-04-25 20:24:49 +00:00
|
|
|
if !t.inMapPoll && (t.lastMapPollEndedAt.IsZero() || now.Sub(t.lastMapPollEndedAt) > 10*time.Second) {
|
2024-01-03 00:23:58 +00:00
|
|
|
return errNotInMapPoll
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
|
|
|
const tooIdle = 2*time.Minute + 5*time.Second
|
2024-04-25 20:24:49 +00:00
|
|
|
if d := now.Sub(t.lastStreamedMapResponse).Round(time.Second); d > tooIdle {
|
2024-04-25 21:25:48 +00:00
|
|
|
return t.networkErrorfLocked("no map response in %v", d)
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if !t.derpHomeless {
|
|
|
|
rid := t.derpHomeRegion
|
2023-11-16 21:43:36 +00:00
|
|
|
if rid == 0 {
|
2024-01-03 00:23:58 +00:00
|
|
|
return errNoDERPHome
|
2023-11-16 21:43:36 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if !t.derpRegionConnected[rid] {
|
2024-04-25 21:25:48 +00:00
|
|
|
return t.networkErrorfLocked("not connected to home DERP region %v", rid)
|
2023-11-16 21:43:36 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if d := now.Sub(t.derpRegionLastFrame[rid]).Round(time.Second); d > tooIdle {
|
2024-04-25 21:25:48 +00:00
|
|
|
return t.networkErrorfLocked("haven't heard from home DERP region %v in %v", rid, d)
|
2023-11-16 21:43:36 +00:00
|
|
|
}
|
2021-03-16 05:20:48 +00:00
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
if t.udp4Unbound {
|
2024-01-03 00:23:58 +00:00
|
|
|
return errNoUDP4Bind
|
2021-04-28 17:36:54 +00:00
|
|
|
}
|
2021-03-16 05:20:48 +00:00
|
|
|
|
|
|
|
// TODO: use
|
2024-04-25 20:24:49 +00:00
|
|
|
_ = t.inMapPollSince
|
|
|
|
_ = t.lastMapPollEndedAt
|
|
|
|
_ = t.lastStreamedMapResponse
|
|
|
|
_ = t.lastMapRequestHeard
|
2021-03-16 05:20:48 +00:00
|
|
|
|
|
|
|
var errs []error
|
2021-04-27 00:08:05 +00:00
|
|
|
for _, recv := range receiveFuncs {
|
|
|
|
if recv.missing {
|
|
|
|
errs = append(errs, fmt.Errorf("%s is not running", recv.name))
|
|
|
|
}
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
for sys, err := range t.sysErr {
|
2021-03-16 05:20:48 +00:00
|
|
|
if err == nil || sys == SysOverall {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
errs = append(errs, fmt.Errorf("%v: %w", sys, err))
|
|
|
|
}
|
2024-04-25 21:25:48 +00:00
|
|
|
for _, w := range t.warnables {
|
|
|
|
if err := t.warnableVal[w]; err != nil {
|
2022-11-13 15:32:37 +00:00
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
for regionID, problem := range t.derpRegionHealthProblem {
|
2021-09-02 02:27:22 +00:00
|
|
|
errs = append(errs, fmt.Errorf("derp%d: %v", regionID, problem))
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
for _, s := range t.controlHealth {
|
2021-09-18 19:59:55 +00:00
|
|
|
errs = append(errs, errors.New(s))
|
|
|
|
}
|
2022-09-17 03:24:28 +00:00
|
|
|
if err := envknob.ApplyDiskConfigError(); err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
}
|
2024-04-25 20:24:49 +00:00
|
|
|
for serverName, err := range t.tlsConnectionErrors {
|
2023-02-01 19:29:44 +00:00
|
|
|
errs = append(errs, fmt.Errorf("TLS connection error for %q: %w", serverName, err))
|
|
|
|
}
|
2022-09-14 19:49:39 +00:00
|
|
|
if e := fakeErrForTesting(); len(errs) == 0 && e != "" {
|
2021-09-16 18:24:44 +00:00
|
|
|
return errors.New(e)
|
|
|
|
}
|
2021-03-16 05:20:48 +00:00
|
|
|
sort.Slice(errs, func(i, j int) bool {
|
|
|
|
// Not super efficient (stringifying these in a sort), but probably max 2 or 3 items.
|
|
|
|
return errs[i].Error() < errs[j].Error()
|
|
|
|
})
|
2021-11-02 21:30:48 +00:00
|
|
|
return multierr.New(errs...)
|
2021-02-25 05:29:51 +00:00
|
|
|
}
|
2021-04-27 00:08:05 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
ReceiveIPv4 = ReceiveFuncStats{name: "ReceiveIPv4"}
|
2021-04-28 17:43:51 +00:00
|
|
|
ReceiveIPv6 = ReceiveFuncStats{name: "ReceiveIPv6"}
|
2021-04-27 00:08:05 +00:00
|
|
|
ReceiveDERP = ReceiveFuncStats{name: "ReceiveDERP"}
|
|
|
|
|
2021-04-28 17:43:51 +00:00
|
|
|
receiveFuncs = []*ReceiveFuncStats{&ReceiveIPv4, &ReceiveIPv6, &ReceiveDERP}
|
2021-04-27 00:08:05 +00:00
|
|
|
)
|
|
|
|
|
2021-10-22 16:12:00 +00:00
|
|
|
func init() {
|
|
|
|
if runtime.GOOS == "js" {
|
|
|
|
receiveFuncs = receiveFuncs[2:] // ignore IPv4 and IPv6
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 00:08:05 +00:00
|
|
|
// ReceiveFuncStats tracks the calls made to a wireguard-go receive func.
|
|
|
|
type ReceiveFuncStats struct {
|
|
|
|
// name is the name of the receive func.
|
|
|
|
name string
|
|
|
|
// numCalls is the number of times the receive func has ever been called.
|
|
|
|
// It is required because it is possible for a receive func's wireguard-go goroutine
|
|
|
|
// to be active even though the receive func isn't.
|
|
|
|
// The wireguard-go goroutine alternates between calling the receive func and
|
|
|
|
// processing what the func returned.
|
|
|
|
numCalls uint64 // accessed atomically
|
|
|
|
// prevNumCalls is the value of numCalls last time the health check examined it.
|
|
|
|
prevNumCalls uint64
|
|
|
|
// inCall indicates whether the receive func is currently running.
|
|
|
|
inCall uint32 // bool, accessed atomically
|
|
|
|
// missing indicates whether the receive func is not running.
|
|
|
|
missing bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ReceiveFuncStats) Enter() {
|
|
|
|
atomic.AddUint64(&s.numCalls, 1)
|
|
|
|
atomic.StoreUint32(&s.inCall, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ReceiveFuncStats) Exit() {
|
|
|
|
atomic.StoreUint32(&s.inCall, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkReceiveFuncs() {
|
|
|
|
for _, recv := range receiveFuncs {
|
|
|
|
recv.missing = false
|
|
|
|
prev := recv.prevNumCalls
|
|
|
|
numCalls := atomic.LoadUint64(&recv.numCalls)
|
|
|
|
recv.prevNumCalls = numCalls
|
|
|
|
if numCalls > prev {
|
|
|
|
// OK: the function has gotten called since last we checked
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if atomic.LoadUint32(&recv.inCall) == 1 {
|
|
|
|
// OK: the function is active, probably blocked due to inactivity
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Not OK: The function is not active, and not accumulating new calls.
|
|
|
|
// It is probably MIA.
|
|
|
|
recv.missing = true
|
|
|
|
}
|
|
|
|
}
|