mirror of
https://github.com/tailscale/tailscale.git
synced 2025-09-21 21:51:21 +02:00
The Tracker was using direct callbacks to ipnlocal. This PR moves those to be triggered via the eventbus. Additionally, the eventbus is now closed on exit from tailscaled explicitly, and health is now a SubSystem in tsd. Updates #15160 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
238 lines
7.2 KiB
Go
238 lines
7.2 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package tsd (short for "Tailscale Daemon") contains a System type that
|
|
// containing all the subsystems a Tailscale node (tailscaled or platform
|
|
// equivalent) uses.
|
|
//
|
|
// The goal of this package (as of 2023-05-03) is to eventually unify
|
|
// initialization across tailscaled, tailscaled as a Windows services, the mac
|
|
// GUI, tsnet, wasm, tests, and other places that wire up all the subsystems.
|
|
// And doing so without weird optional interface accessors on some subsystems
|
|
// that return other subsystems. It's all a work in progress.
|
|
//
|
|
// This package depends on nearly all parts of Tailscale, so it should not be
|
|
// imported by (or thus passed to) any package that does not want to depend on
|
|
// the world. In practice this means that only things like cmd/tailscaled,
|
|
// ipn/ipnlocal, and ipn/ipnserver should import this package.
|
|
package tsd
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"tailscale.com/control/controlknobs"
|
|
"tailscale.com/drive"
|
|
"tailscale.com/health"
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/ipn/conffile"
|
|
"tailscale.com/net/dns"
|
|
"tailscale.com/net/netmon"
|
|
"tailscale.com/net/tsdial"
|
|
"tailscale.com/net/tstun"
|
|
"tailscale.com/proxymap"
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/util/eventbus"
|
|
"tailscale.com/util/syspolicy/policyclient"
|
|
"tailscale.com/util/usermetric"
|
|
"tailscale.com/wgengine"
|
|
"tailscale.com/wgengine/magicsock"
|
|
"tailscale.com/wgengine/router"
|
|
)
|
|
|
|
// System contains all the subsystems of a Tailscale node (tailscaled, etc.)
|
|
//
|
|
// A valid System value must always have a non-nil Bus populated. Callers must
|
|
// ensure this before using the value further. Call [NewSystem] to obtain a
|
|
// value ready to use.
|
|
type System struct {
|
|
Bus SubSystem[*eventbus.Bus]
|
|
Dialer SubSystem[*tsdial.Dialer]
|
|
DNSManager SubSystem[*dns.Manager] // can get its *resolver.Resolver from DNSManager.Resolver
|
|
Engine SubSystem[wgengine.Engine]
|
|
NetMon SubSystem[*netmon.Monitor]
|
|
MagicSock SubSystem[*magicsock.Conn]
|
|
NetstackRouter SubSystem[bool] // using Netstack at all (either entirely or at least for subnets)
|
|
Router SubSystem[router.Router]
|
|
Tun SubSystem[*tstun.Wrapper]
|
|
StateStore SubSystem[ipn.StateStore]
|
|
Netstack SubSystem[NetstackImpl] // actually a *netstack.Impl
|
|
DriveForLocal SubSystem[drive.FileSystemForLocal]
|
|
DriveForRemote SubSystem[drive.FileSystemForRemote]
|
|
PolicyClient SubSystem[policyclient.Client]
|
|
HealthTracker SubSystem[*health.Tracker]
|
|
|
|
// InitialConfig is initial server config, if any.
|
|
// It is nil if the node is not in declarative mode.
|
|
// This value is never updated after startup.
|
|
// LocalBackend tracks the current config after any reloads.
|
|
InitialConfig *conffile.Config
|
|
|
|
// onlyNetstack is whether the Tun value is a fake TUN device
|
|
// and we're using netstack for everything.
|
|
onlyNetstack bool
|
|
|
|
controlKnobs controlknobs.Knobs
|
|
proxyMap proxymap.Mapper
|
|
|
|
userMetricsRegistry usermetric.Registry
|
|
}
|
|
|
|
// NewSystem constructs a new otherwise-empty [System] with a
|
|
// freshly-constructed event bus populated.
|
|
func NewSystem() *System { return NewSystemWithBus(eventbus.New()) }
|
|
|
|
// NewSystemWithBus constructs a new otherwise-empty [System] with an
|
|
// eventbus provided by the caller. The provided bus must not be nil.
|
|
// This is mainly intended for testing; for production use call [NewBus].
|
|
func NewSystemWithBus(bus *eventbus.Bus) *System {
|
|
if bus == nil {
|
|
panic("nil eventbus")
|
|
}
|
|
sys := new(System)
|
|
sys.Set(bus)
|
|
|
|
tracker := health.NewTracker(bus)
|
|
sys.Set(tracker)
|
|
|
|
return sys
|
|
}
|
|
|
|
// NetstackImpl is the interface that *netstack.Impl implements.
|
|
// It's an interface for circular dependency reasons: netstack.Impl
|
|
// references LocalBackend, and LocalBackend has a tsd.System.
|
|
type NetstackImpl interface {
|
|
UpdateNetstackIPs(*netmap.NetworkMap)
|
|
}
|
|
|
|
// Set is a convenience method to set a subsystem value.
|
|
// It panics if the type is unknown or has that type
|
|
// has already been set.
|
|
func (s *System) Set(v any) {
|
|
switch v := v.(type) {
|
|
case *eventbus.Bus:
|
|
s.Bus.Set(v)
|
|
case *netmon.Monitor:
|
|
s.NetMon.Set(v)
|
|
case *dns.Manager:
|
|
s.DNSManager.Set(v)
|
|
case *tsdial.Dialer:
|
|
s.Dialer.Set(v)
|
|
case wgengine.Engine:
|
|
s.Engine.Set(v)
|
|
case router.Router:
|
|
s.Router.Set(v)
|
|
case *tstun.Wrapper:
|
|
type ft interface {
|
|
IsFakeTun() bool
|
|
}
|
|
if _, ok := v.Unwrap().(ft); ok {
|
|
s.onlyNetstack = true
|
|
}
|
|
s.Tun.Set(v)
|
|
case *magicsock.Conn:
|
|
s.MagicSock.Set(v)
|
|
case ipn.StateStore:
|
|
s.StateStore.Set(v)
|
|
case NetstackImpl:
|
|
s.Netstack.Set(v)
|
|
case drive.FileSystemForLocal:
|
|
s.DriveForLocal.Set(v)
|
|
case drive.FileSystemForRemote:
|
|
s.DriveForRemote.Set(v)
|
|
case policyclient.Client:
|
|
s.PolicyClient.Set(v)
|
|
case *health.Tracker:
|
|
s.HealthTracker.Set(v)
|
|
default:
|
|
panic(fmt.Sprintf("unknown type %T", v))
|
|
}
|
|
}
|
|
|
|
// IsNetstackRouter reports whether Tailscale is either fully netstack based
|
|
// (without TUN) or is at least using netstack for routing.
|
|
func (s *System) IsNetstackRouter() bool {
|
|
if v, ok := s.NetstackRouter.GetOK(); ok && v {
|
|
return true
|
|
}
|
|
return s.IsNetstack()
|
|
}
|
|
|
|
// IsNetstack reports whether Tailscale is running as a netstack-based TUN-free engine.
|
|
func (s *System) IsNetstack() bool {
|
|
return s.onlyNetstack
|
|
}
|
|
|
|
// ControlKnobs returns the control knobs for this node.
|
|
func (s *System) ControlKnobs() *controlknobs.Knobs {
|
|
return &s.controlKnobs
|
|
}
|
|
|
|
// ProxyMapper returns the ephemeral ip:port mapper.
|
|
func (s *System) ProxyMapper() *proxymap.Mapper {
|
|
return &s.proxyMap
|
|
}
|
|
|
|
// UserMetricsRegistry returns the system usermetrics.
|
|
func (s *System) UserMetricsRegistry() *usermetric.Registry {
|
|
return &s.userMetricsRegistry
|
|
}
|
|
|
|
// PolicyClientOrDefault returns the policy client if set or a no-op default
|
|
// otherwise. It always returns a non-nil value.
|
|
func (s *System) PolicyClientOrDefault() policyclient.Client {
|
|
if client, ok := s.PolicyClient.GetOK(); ok {
|
|
return client
|
|
}
|
|
return policyclient.Get()
|
|
}
|
|
|
|
// SubSystem represents some subsystem of the Tailscale node daemon.
|
|
//
|
|
// A subsystem can be set to a value, and then later retrieved. A subsystem
|
|
// value tracks whether it's been set and, once set, doesn't allow the value to
|
|
// change.
|
|
type SubSystem[T any] struct {
|
|
set bool
|
|
v T
|
|
}
|
|
|
|
// Set sets p to v.
|
|
//
|
|
// It panics if p is already set to a different value.
|
|
//
|
|
// Set must not be called concurrently with other Sets or Gets.
|
|
func (p *SubSystem[T]) Set(v T) {
|
|
if p.set {
|
|
var oldVal any = p.v
|
|
var newVal any = v
|
|
if oldVal == newVal {
|
|
// Allow setting to the same value.
|
|
// Note we had to box them through "any" to force them to be comparable.
|
|
// We can't set the type constraint T to be "comparable" because the interfaces
|
|
// aren't comparable. (See https://github.com/golang/go/issues/52531 and
|
|
// https://github.com/golang/go/issues/52614 for some background)
|
|
return
|
|
}
|
|
|
|
var z *T
|
|
panic(fmt.Sprintf("%v is already set", reflect.TypeOf(z).Elem().String()))
|
|
}
|
|
p.v = v
|
|
p.set = true
|
|
}
|
|
|
|
// Get returns the value of p, panicking if it hasn't been set.
|
|
func (p *SubSystem[T]) Get() T {
|
|
if !p.set {
|
|
var z *T
|
|
panic(fmt.Sprintf("%v is not set", reflect.TypeOf(z).Elem().String()))
|
|
}
|
|
return p.v
|
|
}
|
|
|
|
// GetOK returns the value of p (if any) and whether it's been set.
|
|
func (p *SubSystem[T]) GetOK() (_ T, ok bool) {
|
|
return p.v, p.set
|
|
}
|