tsd: add package with System type to unify subsystem init, discovery

This is part of an effort to clean up tailscaled initialization between
tailscaled, tailscaled Windows service, tsnet, and the mac GUI.

Updates #8036

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/8064/head
Brad Fitzpatrick 1 year ago committed by Brad Fitzpatrick
parent 0d7303b798
commit 6e967446e4

@ -282,6 +282,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
tailscale.com/tka from tailscale.com/ipn/ipnlocal+ tailscale.com/tka from tailscale.com/ipn/ipnlocal+
W tailscale.com/tsconst from tailscale.com/net/interfaces W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
tailscale.com/tstime from tailscale.com/wgengine/magicsock tailscale.com/tstime from tailscale.com/wgengine/magicsock
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+ 💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
tailscale.com/tstime/rate from tailscale.com/wgengine/filter+ tailscale.com/tstime/rate from tailscale.com/wgengine/filter+

@ -50,6 +50,7 @@ import (
"tailscale.com/safesocket" "tailscale.com/safesocket"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/syncs" "tailscale.com/syncs"
"tailscale.com/tsd"
"tailscale.com/tsweb/varz" "tailscale.com/tsweb/varz"
"tailscale.com/types/flagtype" "tailscale.com/types/flagtype"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -330,12 +331,16 @@ var debugMux *http.ServeMux
func run() error { func run() error {
var logf logger.Logf = log.Printf var logf logger.Logf = log.Printf
sys := new(tsd.System)
netMon, err := netmon.New(func(format string, args ...any) { netMon, err := netmon.New(func(format string, args ...any) {
logf(format, args...) logf(format, args...)
}) })
if err != nil { if err != nil {
return fmt.Errorf("netmon.New: %w", err) return fmt.Errorf("netmon.New: %w", err)
} }
sys.Set(netMon)
pol := logpolicy.New(logtail.CollectionNode, netMon) pol := logpolicy.New(logtail.CollectionNode, netMon)
pol.SetVerbosityLevel(args.verbose) pol.SetVerbosityLevel(args.verbose)
@ -386,10 +391,10 @@ func run() error {
debugMux = newDebugMux() debugMux = newDebugMux()
} }
return startIPNServer(context.Background(), logf, pol.PublicID, netMon) return startIPNServer(context.Background(), logf, pol.PublicID, sys)
} }
func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) error { func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) error {
ln, err := safesocket.Listen(args.socketpath) ln, err := safesocket.Listen(args.socketpath)
if err != nil { if err != nil {
return fmt.Errorf("safesocket.Listen: %v", err) return fmt.Errorf("safesocket.Listen: %v", err)
@ -415,7 +420,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
} }
}() }()
srv := ipnserver.New(logf, logID, netMon) srv := ipnserver.New(logf, logID, sys.NetMon.Get())
if debugMux != nil { if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus) debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
} }
@ -433,7 +438,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return return
} }
} }
lb, err := getLocalBackend(ctx, logf, logID, netMon) lb, err := getLocalBackend(ctx, logf, logID, sys)
if err == nil { if err == nil {
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond)) logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
srv.SetLocalBackend(lb) srv.SetLocalBackend(lb)
@ -457,31 +462,28 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID,
return nil return nil
} }
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (_ *ipnlocal.LocalBackend, retErr error) { func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, sys *tsd.System) (_ *ipnlocal.LocalBackend, retErr error) {
if logPol != nil { if logPol != nil {
logPol.Logtail.SetNetMon(netMon) logPol.Logtail.SetNetMon(sys.NetMon.Get())
} }
socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr) socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr)
dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used) dialer := &tsdial.Dialer{Logf: logf} // mutated below (before used)
e, onlyNetstack, err := createEngine(logf, netMon, dialer) sys.Set(dialer)
onlyNetstack, err := createEngine(logf, sys)
if err != nil { if err != nil {
return nil, fmt.Errorf("createEngine: %w", err) return nil, fmt.Errorf("createEngine: %w", err)
} }
if _, ok := e.(wgengine.ResolvingEngine).GetResolver(); !ok {
panic("internal error: exit node resolver not wired up")
}
if debugMux != nil { if debugMux != nil {
if ig, ok := e.(wgengine.InternalsGetter); ok { if ms, ok := sys.MagicSock.GetOK(); ok {
if _, mc, _, ok := ig.GetInternals(); ok { debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug)
debugMux.HandleFunc("/debug/magicsock", mc.ServeHTTPDebug)
}
} }
go runDebugServer(debugMux, args.debug) go runDebugServer(debugMux, args.debug)
} }
ns, err := newNetstack(logf, dialer, e) ns, err := newNetstack(logf, sys)
if err != nil { if err != nil {
return nil, fmt.Errorf("newNetstack: %w", err) return nil, fmt.Errorf("newNetstack: %w", err)
} }
@ -489,6 +491,7 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
ns.ProcessSubnets = onlyNetstack || handleSubnetsInNetstack() ns.ProcessSubnets = onlyNetstack || handleSubnetsInNetstack()
if onlyNetstack { if onlyNetstack {
e := sys.Engine.Get()
dialer.UseNetstackForIP = func(ip netip.Addr) bool { dialer.UseNetstackForIP = func(ip netip.Addr) bool {
_, ok := e.PeerForIP(ip) _, ok := e.PeerForIP(ip)
return ok return ok
@ -519,16 +522,15 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
tshttpproxy.SetSelfProxy(addrs...) tshttpproxy.SetSelfProxy(addrs...)
} }
e = wgengine.NewWatchdog(e)
opts := ipnServerOpts() opts := ipnServerOpts()
store, err := store.New(logf, statePathOrDefault()) store, err := store.New(logf, statePathOrDefault())
if err != nil { if err != nil {
return nil, fmt.Errorf("store.New: %w", err) return nil, fmt.Errorf("store.New: %w", err)
} }
sys.Set(store)
lb, err := ipnlocal.NewLocalBackend(logf, logID, store, dialer, e, opts.LoginFlags) lb, err := ipnlocal.NewLocalBackend(logf, logID, sys, opts.LoginFlags)
if err != nil { if err != nil {
return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err) return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err)
} }
@ -554,21 +556,21 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID
// //
// onlyNetstack is true if the user has explicitly requested that we use netstack // onlyNetstack is true if the user has explicitly requested that we use netstack
// for all networking. // for all networking.
func createEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer) (e wgengine.Engine, onlyNetstack bool, err error) { func createEngine(logf logger.Logf, sys *tsd.System) (onlyNetstack bool, err error) {
if args.tunname == "" { if args.tunname == "" {
return nil, false, errors.New("no --tun value specified") return false, errors.New("no --tun value specified")
} }
var errs []error var errs []error
for _, name := range strings.Split(args.tunname, ",") { for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name) logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
e, onlyNetstack, err = tryEngine(logf, netMon, dialer, name) onlyNetstack, err = tryEngine(logf, sys, name)
if err == nil { if err == nil {
return e, onlyNetstack, nil return onlyNetstack, nil
} }
logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err) logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err)
errs = append(errs, err) errs = append(errs, err)
} }
return nil, false, multierr.New(errs...) return false, multierr.New(errs...)
} }
// handleSubnetsInNetstack reports whether netstack should handle subnet routers // handleSubnetsInNetstack reports whether netstack should handle subnet routers
@ -593,21 +595,23 @@ func handleSubnetsInNetstack() bool {
var tstunNew = tstun.New var tstunNew = tstun.New
func tryEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer, name string) (e wgengine.Engine, onlyNetstack bool, err error) { func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack bool, err error) {
conf := wgengine.Config{ conf := wgengine.Config{
ListenPort: args.port, ListenPort: args.port,
NetMon: netMon, NetMon: sys.NetMon.Get(),
Dialer: dialer, Dialer: sys.Dialer.Get(),
SetSubsystem: sys.Set,
} }
onlyNetstack = name == "userspace-networking" onlyNetstack = name == "userspace-networking"
netstackSubnetRouter := onlyNetstack // but mutated later on some platforms
netns.SetEnabled(!onlyNetstack) netns.SetEnabled(!onlyNetstack)
if args.birdSocketPath != "" && createBIRDClient != nil { if args.birdSocketPath != "" && createBIRDClient != nil {
log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath) log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
conf.BIRDClient, err = createBIRDClient(args.birdSocketPath) conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("createBIRDClient: %w", err) return false, fmt.Errorf("createBIRDClient: %w", err)
} }
} }
if onlyNetstack { if onlyNetstack {
@ -620,44 +624,55 @@ func tryEngine(logf logger.Logf, netMon *netmon.Monitor, dialer *tsdial.Dialer,
// 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, "") // empty interface name
if err != nil { if err != nil {
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err) return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
} }
} }
} else { } else {
dev, devName, err := tstunNew(logf, name) dev, devName, err := tstunNew(logf, name)
if err != nil { if err != nil {
tstun.Diagnose(logf, name, err) tstun.Diagnose(logf, name, err)
return nil, false, fmt.Errorf("tstun.New(%q): %w", name, err) return false, fmt.Errorf("tstun.New(%q): %w", name, err)
} }
conf.Tun = dev conf.Tun = dev
if strings.HasPrefix(name, "tap:") { if strings.HasPrefix(name, "tap:") {
conf.IsTAP = true conf.IsTAP = true
e, err := wgengine.NewUserspaceEngine(logf, conf) e, err := wgengine.NewUserspaceEngine(logf, conf)
return e, false, err if err != nil {
return false, err
}
sys.Set(e)
return false, err
} }
r, err := router.New(logf, dev, netMon) r, err := router.New(logf, dev, sys.NetMon.Get())
if err != nil { if err != nil {
dev.Close() dev.Close()
return nil, false, fmt.Errorf("creating router: %w", err) return false, fmt.Errorf("creating router: %w", err)
} }
sys.Set(r)
d, err := dns.NewOSConfigurator(logf, devName) d, err := dns.NewOSConfigurator(logf, devName)
if err != nil { if err != nil {
dev.Close() dev.Close()
r.Close() r.Close()
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err) return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
} }
conf.DNS = d conf.DNS = d
conf.Router = r conf.Router = r
if handleSubnetsInNetstack() { if handleSubnetsInNetstack() {
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router) conf.Router = netstack.NewSubnetRouterWrapper(conf.Router)
netstackSubnetRouter = true
} }
} }
e, err = wgengine.NewUserspaceEngine(logf, conf) e, err := wgengine.NewUserspaceEngine(logf, conf)
if err != nil { if err != nil {
return nil, onlyNetstack, err return onlyNetstack, err
} }
return e, onlyNetstack, nil e = wgengine.NewWatchdog(e)
sys.Set(e)
sys.NetstackRouter.Set(netstackSubnetRouter)
return onlyNetstack, nil
} }
func newDebugMux() *http.ServeMux { func newDebugMux() *http.ServeMux {
@ -687,12 +702,8 @@ func runDebugServer(mux *http.ServeMux, addr string) {
} }
} }
func newNetstack(logf logger.Logf, dialer *tsdial.Dialer, e wgengine.Engine) (*netstack.Impl, error) { func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
tunDev, magicConn, dns, ok := e.(wgengine.InternalsGetter).GetInternals() return netstack.Create(logf, sys.Tun.Get(), sys.Engine.Get(), sys.MagicSock.Get(), sys.Dialer.Get(), sys.DNSManager.Get())
if !ok {
return nil, fmt.Errorf("%T is not a wgengine.InternalsGetter", e)
}
return netstack.Create(logf, tunDev, e, magicConn, dialer, dns)
} }
// mustStartProxyListeners creates listeners for local SOCKS and HTTP // mustStartProxyListeners creates listeners for local SOCKS and HTTP

@ -47,6 +47,7 @@ import (
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
"tailscale.com/net/tstun" "tailscale.com/net/tstun"
"tailscale.com/tsd"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
@ -292,13 +293,15 @@ func beWindowsSubprocess() bool {
} }
}() }()
sys := new(tsd.System)
netMon, err := netmon.New(log.Printf) netMon, err := netmon.New(log.Printf)
if err != nil { if err != nil {
log.Printf("Could not create netMon: %v", err) log.Fatalf("Could not create netMon: %v", err)
netMon = nil
} }
sys.Set(netMon)
publicLogID, _ := logid.ParsePublicID(logID) publicLogID, _ := logid.ParsePublicID(logID)
err = startIPNServer(ctx, log.Printf, publicLogID, netMon) err = startIPNServer(ctx, log.Printf, publicLogID, sys)
if err != nil { if err != nil {
log.Fatalf("ipnserver: %v", err) log.Fatalf("ipnserver: %v", err)
} }

@ -37,6 +37,7 @@ import (
"tailscale.com/safesocket" "tailscale.com/safesocket"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/wgengine" "tailscale.com/wgengine"
"tailscale.com/wgengine/netstack" "tailscale.com/wgengine/netstack"
"tailscale.com/words" "tailscale.com/words"
@ -96,6 +97,8 @@ func newIPN(jsConfig js.Value) map[string]any {
logtail := logtail.NewLogger(c, log.Printf) logtail := logtail.NewLogger(c, log.Printf)
logf := logtail.Logf logf := logtail.Logf
sys := new(tsd.System)
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,
@ -103,12 +106,9 @@ func newIPN(jsConfig js.Value) map[string]any {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sys.Set(eng)
tunDev, magicConn, dnsManager, ok := eng.(wgengine.InternalsGetter).GetInternals() ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
if !ok {
log.Fatalf("%T is not a wgengine.InternalsGetter", eng)
}
ns, err := netstack.Create(logf, tunDev, eng, magicConn, dialer, dnsManager)
if err != nil { if err != nil {
log.Fatalf("netstack.Create: %v", err) log.Fatalf("netstack.Create: %v", err)
} }
@ -121,10 +121,11 @@ func newIPN(jsConfig js.Value) map[string]any {
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
return ns.DialContextTCP(ctx, dst) return ns.DialContextTCP(ctx, dst)
} }
sys.NetstackRouter.Set(true)
logid := lpc.PublicID logid := lpc.PublicID
srv := ipnserver.New(logf, logid, nil /* no netMon */) srv := ipnserver.New(logf, logid, nil /* no netMon */)
lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral) lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral)
if err != nil { if err != nil {
log.Fatalf("ipnlocal.NewLocalBackend: %v", err) log.Fatalf("ipnlocal.NewLocalBackend: %v", err)
} }

@ -60,6 +60,7 @@ import (
"tailscale.com/syncs" "tailscale.com/syncs"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tka" "tailscale.com/tka"
"tailscale.com/tsd"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/types/empty" "tailscale.com/types/empty"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -137,10 +138,11 @@ type LocalBackend struct {
logf logger.Logf // general logging logf logger.Logf // general logging
keyLogf logger.Logf // for printing list of peers on change keyLogf logger.Logf // for printing list of peers on change
statsLogf logger.Logf // for printing peers stats on change statsLogf logger.Logf // for printing peers stats on change
e wgengine.Engine sys *tsd.System
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
pm *profileManager pm *profileManager
store ipn.StateStore store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys
dialer *tsdial.Dialer // non-nil dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys
backendLogID logid.PublicID backendLogID logid.PublicID
unregisterNetMon func() unregisterNetMon func()
unregisterHealthWatch func() unregisterHealthWatch func()
@ -267,10 +269,10 @@ type clientGen func(controlclient.Options) (controlclient.Client, error)
// but is not actually running. // but is not actually running.
// //
// If dialer is nil, a new one is made. // If dialer is nil, a new one is made.
func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine, loginFlags controlclient.LoginFlags) (*LocalBackend, error) { func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, loginFlags controlclient.LoginFlags) (*LocalBackend, error) {
if e == nil { e := sys.Engine.Get()
panic("ipn.NewLocalBackend: engine must not be nil") store := sys.StateStore.Get()
} dialer := sys.Dialer.Get()
pm, err := newProfileManager(store, logf) pm, err := newProfileManager(store, logf)
if err != nil { if err != nil {
@ -301,10 +303,11 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
logf: logf, logf: logf,
keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now),
sys: sys,
e: e, e: e,
pm: pm,
store: store,
dialer: dialer, dialer: dialer,
store: store,
pm: pm,
backendLogID: logID, backendLogID: logID,
state: ipn.NoState, state: ipn.NoState,
portpoll: portpoll, portpoll: portpoll,
@ -313,7 +316,8 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
loginFlags: loginFlags, loginFlags: loginFlags,
} }
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, e.GetNetMon()) netMon := sys.NetMon.Get()
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, netMon)
if err != nil { if err != nil {
log.Printf("error setting up sockstat logger: %v", err) log.Printf("error setting up sockstat logger: %v", err)
} }
@ -330,7 +334,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
b.statusChanged = sync.NewCond(&b.statusLock) b.statusChanged = sync.NewCond(&b.statusLock)
b.e.SetStatusCallback(b.setWgengineStatus) b.e.SetStatusCallback(b.setWgengineStatus)
netMon := e.GetNetMon()
b.prevIfState = netMon.InterfaceState() b.prevIfState = netMon.InterfaceState()
// Call our linkChange code once with the current state, and // Call our linkChange code once with the current state, and
// then also whenever it changes: // then also whenever it changes:
@ -339,14 +342,9 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange) b.unregisterHealthWatch = health.RegisterWatcher(b.onHealthChange)
wiredPeerAPIPort := false if tunWrap, ok := b.sys.Tun.GetOK(); ok {
if ig, ok := e.(wgengine.InternalsGetter); ok { tunWrap.PeerAPIPort = b.GetPeerAPIPort
if tunWrap, _, _, ok := ig.GetInternals(); ok { } else {
tunWrap.PeerAPIPort = b.GetPeerAPIPort
wiredPeerAPIPort = true
}
}
if !wiredPeerAPIPort {
b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e) b.logf("[unexpected] failed to wire up PeerAPI port for engine %T", e)
} }
@ -464,6 +462,7 @@ func (b *LocalBackend) GetComponentDebugLogging(component string) time.Time {
} }
// Dialer returns the backend's dialer. // Dialer returns the backend's dialer.
// It is always non-nil.
func (b *LocalBackend) Dialer() *tsdial.Dialer { func (b *LocalBackend) Dialer() *tsdial.Dialer {
return b.dialer return b.dialer
} }
@ -644,7 +643,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
defer b.mu.Unlock() defer b.mu.Unlock()
sb.MutateStatus(func(s *ipnstate.Status) { sb.MutateStatus(func(s *ipnstate.Status) {
s.Version = version.Long() s.Version = version.Long()
s.TUN = !wgengine.IsNetstack(b.e) s.TUN = !b.sys.IsNetstack()
s.BackendState = b.state.String() s.BackendState = b.state.String()
s.AuthURL = b.authURLSticky s.AuthURL = b.authURLSticky
if err := health.OverallError(); err != nil { if err := health.OverallError(); err != nil {
@ -1315,8 +1314,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
hostinfo := hostinfo.New() hostinfo := hostinfo.New()
hostinfo.BackendLogID = b.backendLogID.String() hostinfo.BackendLogID = b.backendLogID.String()
hostinfo.FrontendLogID = opts.FrontendLogID hostinfo.FrontendLogID = opts.FrontendLogID
hostinfo.Userspace.Set(wgengine.IsNetstack(b.e)) hostinfo.Userspace.Set(b.sys.IsNetstack())
hostinfo.UserspaceRouter.Set(wgengine.IsNetstackRouter(b.e)) hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter())
if b.cc != nil { if b.cc != nil {
// TODO(apenwarr): avoid the need to reinit controlclient. // TODO(apenwarr): avoid the need to reinit controlclient.
@ -1401,7 +1400,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
var err error var err error
isNetstack := wgengine.IsNetstackRouter(b.e) isNetstack := b.sys.IsNetstackRouter()
debugFlags := controlDebugFlags debugFlags := controlDebugFlags
if isNetstack { if isNetstack {
debugFlags = append([]string{"netstack"}, debugFlags...) debugFlags = append([]string{"netstack"}, debugFlags...)
@ -1423,7 +1422,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
HTTPTestClient: httpTestClient, HTTPTestClient: httpTestClient,
DiscoPublicKey: discoPublic, DiscoPublicKey: discoPublic,
DebugFlags: debugFlags, DebugFlags: debugFlags,
NetMon: b.e.GetNetMon(), NetMon: b.sys.NetMon.Get(),
Pinger: b, Pinger: b,
PopBrowserURL: b.tellClientToBrowseToURL, PopBrowserURL: b.tellClientToBrowseToURL,
OnClientVersion: b.onClientVersion, OnClientVersion: b.onClientVersion,
@ -3317,14 +3316,12 @@ func (b *LocalBackend) initPeerAPIListener() {
directFileMode: b.directFileRoot != "", directFileMode: b.directFileRoot != "",
directFileDoFinalRename: b.directFileDoFinalRename, directFileDoFinalRename: b.directFileDoFinalRename,
} }
if re, ok := b.e.(wgengine.ResolvingEngine); ok { if dm, ok := b.sys.DNSManager.GetOK(); ok {
if r, ok := re.GetResolver(); ok { ps.resolver = dm.Resolver()
ps.resolver = r
}
} }
b.peerAPIServer = ps b.peerAPIServer = ps
isNetstack := wgengine.IsNetstack(b.e) isNetstack := b.sys.IsNetstack()
for i, a := range b.netMap.Addresses { for i, a := range b.netMap.Addresses {
var ln net.Listener var ln net.Listener
var err error var err error
@ -4040,7 +4037,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
b.setServeProxyHandlersLocked() b.setServeProxyHandlersLocked()
// don't listen on netmap addresses if we're in userspace mode // don't listen on netmap addresses if we're in userspace mode
if !wgengine.IsNetstack(b.e) { if !b.sys.IsNetstack() {
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts) b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
} }
} }
@ -4391,7 +4388,7 @@ func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
} }
func (b *LocalBackend) CheckIPForwarding() error { func (b *LocalBackend) CheckIPForwarding() error {
if wgengine.IsNetstackRouter(b.e) { if b.sys.IsNetstackRouter() {
return nil return nil
} }
@ -4537,13 +4534,9 @@ func (b *LocalBackend) DebugReSTUN() error {
} }
func (b *LocalBackend) magicConn() (*magicsock.Conn, error) { func (b *LocalBackend) magicConn() (*magicsock.Conn, error) {
ig, ok := b.e.(wgengine.InternalsGetter) mc, ok := b.sys.MagicSock.GetOK()
if !ok {
return nil, errors.New("engine isn't InternalsGetter")
}
_, mc, _, ok := ig.GetInternals()
if !ok { if !ok {
return nil, errors.New("failed to get internals") return nil, errors.New("failed to get magicsock from sys")
} }
return mc, nil return mc, nil
} }

@ -20,6 +20,7 @@ import (
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -501,13 +502,16 @@ func TestLazyMachineKeyGeneration(t *testing.T) {
tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true }) tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true })
var logf logger.Logf = logger.Discard var logf logger.Logf = logger.Discard
sys := new(tsd.System)
store := new(mem.Store) store := new(mem.Store)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys.Set(store)
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err) t.Fatalf("NewFakeUserspaceEngine: %v", err)
} }
t.Cleanup(eng.Close) t.Cleanup(eng.Close)
lb, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, eng, 0) sys.Set(eng)
lb, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }
@ -765,13 +769,16 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) {
func TestStatusWithoutPeers(t *testing.T) { func TestStatusWithoutPeers(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t) logf := tstest.WhileTestRunningLogger(t)
store := new(testStateStorage) store := new(testStateStorage)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys := new(tsd.System)
sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err) t.Fatalf("NewFakeUserspaceEngine: %v", err)
} }
sys.Set(e)
t.Cleanup(e.Close) t.Cleanup(e.Close)
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0) b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }

@ -12,6 +12,7 @@ import (
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -47,14 +48,17 @@ func TestLocalLogLines(t *testing.T) {
idA := logid(0xaa) idA := logid(0xaa)
// set up a LocalBackend, super bare bones. No functional data. // set up a LocalBackend, super bare bones. No functional data.
sys := new(tsd.System)
store := new(mem.Store) store := new(mem.Store)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(e.Close) t.Cleanup(e.Close)
sys.Set(e)
lb, err := NewLocalBackend(logf, idA, store, nil, e, 0) lb, err := NewLocalBackend(logf, idA, sys, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -50,7 +50,6 @@ import (
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/multierr" "tailscale.com/util/multierr"
"tailscale.com/version/distro" "tailscale.com/version/distro"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
) )
@ -469,7 +468,7 @@ func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net
} }
} }
if wgengine.IsNetstack(s.b.e) { if s.b.sys.IsNetstack() {
ipStr = "" ipStr = ""
} }
@ -1239,12 +1238,9 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req
http.Error(w, "denied; no debug access", http.StatusForbidden) http.Error(w, "denied; no debug access", http.StatusForbidden)
return return
} }
eng := h.ps.b.e if mc, ok := h.ps.b.sys.MagicSock.GetOK(); ok {
if ig, ok := eng.(wgengine.InternalsGetter); ok { mc.ServeHTTPDebug(w, r)
if _, mc, _, ok := ig.GetInternals(); ok { return
mc.ServeHTTPDebug(w, r)
return
}
} }
http.Error(w, "miswired", 500) http.Error(w, "miswired", 500)
} }

@ -143,7 +143,7 @@ func (s *serveListener) Run() {
} }
func (s *serveListener) shouldWarnAboutListenError(err error) bool { func (s *serveListener) shouldWarnAboutListenError(err error) bool {
if !s.b.e.GetNetMon().InterfaceState().HasIP(s.ap.Addr()) { if !s.b.sys.NetMon.Get().InterfaceState().HasIP(s.ap.Addr()) {
// Machine likely doesn't have IPv6 enabled (or the IP is still being // Machine likely doesn't have IPv6 enabled (or the IP is still being
// assigned). No need to warn. Notably, WSL2 (Issue 6303). // assigned). No need to warn. Notably, WSL2 (Issue 6303).
return false return false

@ -17,6 +17,7 @@ import (
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/empty" "tailscale.com/types/empty"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -297,14 +298,17 @@ func TestStateMachine(t *testing.T) {
c := qt.New(t) c := qt.New(t)
logf := tstest.WhileTestRunningLogger(t) logf := tstest.WhileTestRunningLogger(t)
sys := new(tsd.System)
store := new(testStateStorage) store := new(testStateStorage)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys.Set(store)
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err) t.Fatalf("NewFakeUserspaceEngine: %v", err)
} }
t.Cleanup(e.Close) t.Cleanup(e.Close)
sys.Set(e)
b, err := NewLocalBackend(logf, logid.PublicID{}, store, nil, e, 0) b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }
@ -941,13 +945,16 @@ func TestStateMachine(t *testing.T) {
func TestEditPrefsHasNoKeys(t *testing.T) { func TestEditPrefsHasNoKeys(t *testing.T) {
logf := tstest.WhileTestRunningLogger(t) logf := tstest.WhileTestRunningLogger(t)
e, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys := new(tsd.System)
sys.Set(new(mem.Store))
e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatalf("NewFakeUserspaceEngine: %v", err) t.Fatalf("NewFakeUserspaceEngine: %v", err)
} }
t.Cleanup(e.Close) t.Cleanup(e.Close)
sys.Set(e)
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, e, 0) b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }
@ -1023,10 +1030,14 @@ func TestWGEngineStatusRace(t *testing.T) {
t.Skip("test fails") t.Skip("test fails")
c := qt.New(t) c := qt.New(t)
logf := tstest.WhileTestRunningLogger(t) logf := tstest.WhileTestRunningLogger(t)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys := new(tsd.System)
sys.Set(new(mem.Store))
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
t.Cleanup(eng.Close) t.Cleanup(eng.Close)
b, err := NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), nil, eng, 0) sys.Set(eng)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
var cc *mockControl var cc *mockControl

@ -37,7 +37,7 @@ import (
type Server struct { type Server struct {
lb atomic.Pointer[ipnlocal.LocalBackend] lb atomic.Pointer[ipnlocal.LocalBackend]
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand netMon *netmon.Monitor // must be non-nil
backendLogID logid.PublicID backendLogID logid.PublicID
// resetOnZero is whether to call bs.Reset on transition from // resetOnZero is whether to call bs.Reset on transition from
// 1->0 active HTTP requests. That is, this is whether the backend is // 1->0 active HTTP requests. That is, this is whether the backend is
@ -410,14 +410,15 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
} }
// New returns a new Server. // New returns a new Server.
// The netMon parameter is optional; if non-nil it's used to do faster interface
// lookups.
// //
// To start it, use the Server.Run method. // To start it, use the Server.Run method.
// //
// At some point, either before or after Run, the Server's SetLocalBackend // At some point, either before or after Run, the Server's SetLocalBackend
// method must also be called before Server can do anything useful. // method must also be called before Server can do anything useful.
func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server { func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server {
if netMon == nil {
panic("nil netMon")
}
return &Server{ return &Server{
backendLogID: logID, backendLogID: logID,
logf: logf, logf: logf,

@ -47,8 +47,11 @@ func (t *fakeTUN) Write(b [][]byte, n int) (int, error) {
return 1, nil return 1, nil
} }
// FakeTUNName is the name of the fake TUN device.
const FakeTUNName = "FakeTUN"
func (t *fakeTUN) Flush() error { return nil } func (t *fakeTUN) Flush() error { return nil }
func (t *fakeTUN) MTU() (int, error) { return 1500, nil } func (t *fakeTUN) MTU() (int, error) { return 1500, nil }
func (t *fakeTUN) Name() (string, error) { return "FakeTUN", nil } func (t *fakeTUN) Name() (string, error) { return FakeTUNName, nil }
func (t *fakeTUN) Events() <-chan tun.Event { return t.evchan } func (t *fakeTUN) Events() <-chan tun.Event { return t.evchan }
func (t *fakeTUN) BatchSize() int { return 1 } func (t *fakeTUN) BatchSize() int { return 1 }

@ -38,6 +38,7 @@ import (
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tempfork/gliderlabs/ssh" "tailscale.com/tempfork/gliderlabs/ssh"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
@ -815,14 +816,14 @@ func TestSSHAuthFlow(t *testing.T) {
func TestSSH(t *testing.T) { func TestSSH(t *testing.T) {
var logf logger.Logf = t.Logf var logf logger.Logf = t.Logf
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0) sys := &tsd.System{}
eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys.Set(eng)
new(mem.Store), sys.Set(new(mem.Store))
new(tsdial.Dialer), lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0)
eng, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -0,0 +1,135 @@
// 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/ipn"
"tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/wgengine"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/router"
)
// System contains all the subsystems of a Tailscale node (tailscaled, etc.)
type System struct {
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]
}
// 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 *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:
s.Tun.Set(v)
case *magicsock.Conn:
s.MagicSock.Set(v)
case ipn.StateStore:
s.StateStore.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 {
name, _ := s.Tun.Get().Name()
return name == tstun.FakeTUNName
}
// 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
}

@ -47,6 +47,7 @@ import (
"tailscale.com/net/socks5" "tailscale.com/net/socks5"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/tsd"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
"tailscale.com/types/nettype" "tailscale.com/types/nettype"
@ -482,23 +483,21 @@ 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: 0, ListenPort: 0,
NetMon: s.netMon, NetMon: s.netMon,
Dialer: s.dialer, Dialer: s.dialer,
SetSubsystem: sys.Set,
}) })
if err != nil { if err != nil {
return err return err
} }
closePool.add(s.dialer) closePool.add(s.dialer)
sys.Set(eng)
tunDev, magicConn, dns, ok := eng.(wgengine.InternalsGetter).GetInternals() ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get())
if !ok {
return fmt.Errorf("%T is not a wgengine.InternalsGetter", eng)
}
ns, err := netstack.Create(logf, tunDev, eng, magicConn, s.dialer, dns)
if err != nil { if err != nil {
return fmt.Errorf("netstack.Create: %w", err) return fmt.Errorf("netstack.Create: %w", err)
} }
@ -522,12 +521,13 @@ func (s *Server) start() (reterr error) {
return err return err
} }
} }
sys.Set(s.Store)
loginFlags := controlclient.LoginDefault loginFlags := controlclient.LoginDefault
if s.Ephemeral { if s.Ephemeral {
loginFlags = controlclient.LoginEphemeral loginFlags = controlclient.LoginEphemeral
} }
lb, err := ipnlocal.NewLocalBackend(logf, s.logid, s.Store, s.dialer, eng, loginFlags) lb, err := ipnlocal.NewLocalBackend(logf, s.logid, sys, loginFlags)
if err != nil { if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err) return fmt.Errorf("NewLocalBackend: %v", err)
} }

@ -37,6 +37,7 @@ import (
_ "tailscale.com/ssh/tailssh" _ "tailscale.com/ssh/tailssh"
_ "tailscale.com/syncs" _ "tailscale.com/syncs"
_ "tailscale.com/tailcfg" _ "tailscale.com/tailcfg"
_ "tailscale.com/tsd"
_ "tailscale.com/tsweb/varz" _ "tailscale.com/tsweb/varz"
_ "tailscale.com/types/flagtype" _ "tailscale.com/types/flagtype"
_ "tailscale.com/types/key" _ "tailscale.com/types/key"

@ -37,6 +37,7 @@ import (
_ "tailscale.com/ssh/tailssh" _ "tailscale.com/ssh/tailssh"
_ "tailscale.com/syncs" _ "tailscale.com/syncs"
_ "tailscale.com/tailcfg" _ "tailscale.com/tailcfg"
_ "tailscale.com/tsd"
_ "tailscale.com/tsweb/varz" _ "tailscale.com/tsweb/varz"
_ "tailscale.com/types/flagtype" _ "tailscale.com/types/flagtype"
_ "tailscale.com/types/key" _ "tailscale.com/types/key"

@ -37,6 +37,7 @@ import (
_ "tailscale.com/ssh/tailssh" _ "tailscale.com/ssh/tailssh"
_ "tailscale.com/syncs" _ "tailscale.com/syncs"
_ "tailscale.com/tailcfg" _ "tailscale.com/tailcfg"
_ "tailscale.com/tsd"
_ "tailscale.com/tsweb/varz" _ "tailscale.com/tsweb/varz"
_ "tailscale.com/types/flagtype" _ "tailscale.com/types/flagtype"
_ "tailscale.com/types/key" _ "tailscale.com/types/key"

@ -37,6 +37,7 @@ import (
_ "tailscale.com/ssh/tailssh" _ "tailscale.com/ssh/tailssh"
_ "tailscale.com/syncs" _ "tailscale.com/syncs"
_ "tailscale.com/tailcfg" _ "tailscale.com/tailcfg"
_ "tailscale.com/tsd"
_ "tailscale.com/tsweb/varz" _ "tailscale.com/tsweb/varz"
_ "tailscale.com/types/flagtype" _ "tailscale.com/types/flagtype"
_ "tailscale.com/types/key" _ "tailscale.com/types/key"

@ -44,6 +44,7 @@ import (
_ "tailscale.com/smallzstd" _ "tailscale.com/smallzstd"
_ "tailscale.com/syncs" _ "tailscale.com/syncs"
_ "tailscale.com/tailcfg" _ "tailscale.com/tailcfg"
_ "tailscale.com/tsd"
_ "tailscale.com/tsweb/varz" _ "tailscale.com/tsweb/varz"
_ "tailscale.com/types/flagtype" _ "tailscale.com/types/flagtype"
_ "tailscale.com/types/key" _ "tailscale.com/types/key"

@ -16,6 +16,7 @@ import (
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/net/tstun" "tailscale.com/net/tstun"
"tailscale.com/tsd"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/ipproto" "tailscale.com/types/ipproto"
"tailscale.com/types/logid" "tailscale.com/types/logid"
@ -33,29 +34,26 @@ func TestInjectInboundLeak(t *testing.T) {
t.Logf(format, args...) t.Logf(format, args...)
} }
} }
sys := new(tsd.System)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Tun: tunDev, Tun: tunDev,
Dialer: dialer, Dialer: dialer,
SetSubsystem: sys.Set,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer eng.Close() defer eng.Close()
ig, ok := eng.(wgengine.InternalsGetter) sys.Set(eng)
if !ok { sys.Set(new(mem.Store))
t.Fatal("not an InternalsGetter")
}
tunWrap, magicSock, dns, ok := ig.GetInternals()
if !ok {
t.Fatal("failed to get internals")
}
lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), dialer, eng, 0) tunWrap := sys.Tun.Get()
lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ns, err := Create(logf, tunWrap, eng, magicSock, dialer, dns) ns, err := Create(logf, tunWrap, eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -89,32 +87,28 @@ func getMemStats() (ms runtime.MemStats) {
func makeNetstack(t *testing.T, config func(*Impl)) *Impl { func makeNetstack(t *testing.T, config func(*Impl)) *Impl {
tunDev := tstun.NewFake() tunDev := tstun.NewFake()
sys := &tsd.System{}
sys.Set(new(mem.Store))
dialer := new(tsdial.Dialer) dialer := new(tsdial.Dialer)
logf := tstest.WhileTestRunningLogger(t) logf := tstest.WhileTestRunningLogger(t)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Tun: tunDev, Tun: tunDev,
Dialer: dialer, Dialer: dialer,
SetSubsystem: sys.Set,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { eng.Close() }) t.Cleanup(func() { eng.Close() })
ig, ok := eng.(wgengine.InternalsGetter) sys.Set(eng)
if !ok {
t.Fatal("not an InternalsGetter")
}
tunWrap, magicSock, dns, ok := ig.GetInternals()
if !ok {
t.Fatal("failed to get internals")
}
ns, err := Create(logf, tunWrap, eng, magicSock, dialer, dns) ns, err := Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Cleanup(func() { ns.Close() }) t.Cleanup(func() { ns.Close() })
lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, new(mem.Store), dialer, eng, 0) lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatalf("NewLocalBackend: %v", err) t.Fatalf("NewLocalBackend: %v", err)
} }

@ -4,16 +4,9 @@
package netstack package netstack
import ( import (
"reflect"
"tailscale.com/wgengine"
"tailscale.com/wgengine/router" "tailscale.com/wgengine/router"
) )
func init() {
wgengine.NetstackRouterType = reflect.TypeOf(&subnetRouter{})
}
type subnetRouter struct { type subnetRouter struct {
router.Router router.Router
} }

@ -10,8 +10,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"net/netip" "net/netip"
"reflect"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@ -25,7 +25,6 @@ import (
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/dns/resolver"
"tailscale.com/net/flowtrack" "tailscale.com/net/flowtrack"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netmon" "tailscale.com/net/netmon"
@ -150,29 +149,6 @@ type userspaceEngine struct {
// Lock ordering: magicsock.Conn.mu, wgLock, then mu. // Lock ordering: magicsock.Conn.mu, wgLock, then mu.
} }
// InternalsGetter is implemented by Engines that can export their internals.
type InternalsGetter interface {
GetInternals() (_ *tstun.Wrapper, _ *magicsock.Conn, _ *dns.Manager, ok bool)
}
func (e *userspaceEngine) GetInternals() (_ *tstun.Wrapper, _ *magicsock.Conn, _ *dns.Manager, ok bool) {
return e.tundev, e.magicConn, e.dns, true
}
// ResolvingEngine is implemented by Engines that have DNS resolvers.
type ResolvingEngine interface {
GetResolver() (_ *resolver.Resolver, ok bool)
}
var (
_ ResolvingEngine = (*userspaceEngine)(nil)
_ ResolvingEngine = (*watchdogEngine)(nil)
)
func (e *userspaceEngine) GetResolver() (r *resolver.Resolver, ok bool) {
return e.dns.Resolver(), true
}
// BIRDClient handles communication with the BIRD Internet Routing Daemon. // BIRDClient handles communication with the BIRD Internet Routing Daemon.
type BIRDClient interface { type BIRDClient interface {
EnableProtocol(proto string) error EnableProtocol(proto string) error
@ -219,47 +195,37 @@ type Config struct {
// BIRDClient, if non-nil, will be used to configure BIRD whenever // BIRDClient, if non-nil, will be used to configure BIRD whenever
// this node is a primary subnet router. // this node is a primary subnet router.
BIRDClient BIRDClient BIRDClient BIRDClient
}
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
logf("Starting userspace WireGuard engine (with fake TUN device)")
return NewUserspaceEngine(logf, Config{
ListenPort: listenPort,
RespondToPing: true,
})
}
// NetstackRouterType is a gross cross-package init-time registration // SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return.
// from netstack to here, informing this package of netstack's router SetSubsystem func(any)
// type.
var NetstackRouterType reflect.Type
// IsNetstackRouter reports whether e is either fully netstack based
// (without TUN) or is at least using netstack for routing.
func IsNetstackRouter(e Engine) bool {
switch e := e.(type) {
case *userspaceEngine:
if reflect.TypeOf(e.router) == NetstackRouterType {
return true
}
case *watchdogEngine:
return IsNetstackRouter(e.wrap)
}
return IsNetstack(e)
} }
// IsNetstack reports whether e is a netstack-based TUN-free engine. // NewFakeUserspaceEngine returns a new userspace engine for testing.
func IsNetstack(e Engine) bool { //
ig, ok := e.(InternalsGetter) // The opts may contain the following types:
if !ok { //
return false // - int or uint16: to set the ListenPort.
func NewFakeUserspaceEngine(logf logger.Logf, opts ...any) (Engine, error) {
conf := Config{
RespondToPing: true,
} }
tw, _, _, ok := ig.GetInternals() for _, o := range opts {
if !ok { switch v := o.(type) {
return false case uint16:
conf.ListenPort = v
case int:
if v < 0 || v > math.MaxUint16 {
return nil, fmt.Errorf("invalid ListenPort: %d", v)
}
conf.ListenPort = uint16(v)
case func(any):
conf.SetSubsystem = v
default:
return nil, fmt.Errorf("unknown option type %T", v)
}
} }
name, err := tw.Name() logf("Starting userspace WireGuard engine (with fake TUN device)")
return err == nil && name == "FakeTUN" return NewUserspaceEngine(logf, conf)
} }
// NewUserspaceEngine creates the named tun device and returns a // NewUserspaceEngine creates the named tun device and returns a
@ -458,6 +424,15 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
e.logf("Starting network monitor...") e.logf("Starting network monitor...")
e.netMon.Start() e.netMon.Start()
if conf.SetSubsystem != nil {
conf.SetSubsystem(e.tundev)
conf.SetSubsystem(e.magicConn)
conf.SetSubsystem(e.dns)
conf.SetSubsystem(conf.Router)
conf.SetSubsystem(conf.Dialer)
conf.SetSubsystem(e.netMon)
}
e.logf("Engine created.") e.logf("Engine created.")
return e, nil return e, nil
} }
@ -1119,10 +1094,6 @@ func (e *userspaceEngine) Wait() {
<-e.waitCh <-e.waitCh
} }
func (e *userspaceEngine) GetNetMon() *netmon.Monitor {
return e.netMon
}
// LinkChange signals a network change event. It's currently // LinkChange signals a network change event. It's currently
// (2021-03-03) only called on Android. On other platforms, netMon // (2021-03-03) only called on Android. On other platforms, netMon
// generates link change events for us. // generates link change events for us.

@ -8,6 +8,7 @@ import (
"github.com/tailscale/wireguard-go/tun" "github.com/tailscale/wireguard-go/tun"
"tailscale.com/net/tstun" "tailscale.com/net/tstun"
"tailscale.com/tsd"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/wgengine" "tailscale.com/wgengine"
"tailscale.com/wgengine/netstack" "tailscale.com/wgengine/netstack"
@ -15,21 +16,23 @@ import (
) )
func TestIsNetstack(t *testing.T) { func TestIsNetstack(t *testing.T) {
e, err := wgengine.NewUserspaceEngine(t.Logf, wgengine.Config{}) sys := new(tsd.System)
e, err := wgengine.NewUserspaceEngine(t.Logf, wgengine.Config{SetSubsystem: sys.Set})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer e.Close() defer e.Close()
if !wgengine.IsNetstack(e) { if !sys.IsNetstack() {
t.Errorf("IsNetstack = false; want true") t.Errorf("IsNetstack = false; want true")
} }
} }
func TestIsNetstackRouter(t *testing.T) { func TestIsNetstackRouter(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
conf wgengine.Config conf wgengine.Config
want bool setNetstackRouter bool
want bool
}{ }{
{ {
name: "no_netstack", name: "no_netstack",
@ -50,23 +53,26 @@ func TestIsNetstackRouter(t *testing.T) {
Tun: newFakeOSTUN(), Tun: newFakeOSTUN(),
Router: netstack.NewSubnetRouterWrapper(newFakeOSRouter()), Router: netstack.NewSubnetRouterWrapper(newFakeOSRouter()),
}, },
want: true, setNetstackRouter: true,
want: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
e, err := wgengine.NewUserspaceEngine(logger.Discard, tt.conf) sys := &tsd.System{}
if tt.setNetstackRouter {
sys.NetstackRouter.Set(true)
}
conf := tt.conf
conf.SetSubsystem = sys.Set
e, err := wgengine.NewUserspaceEngine(logger.Discard, conf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer e.Close() defer e.Close()
if got := wgengine.IsNetstackRouter(e); got != tt.want { if got := sys.IsNetstackRouter(); got != tt.want {
t.Errorf("IsNetstackRouter = %v; want %v", got, tt.want) t.Errorf("IsNetstackRouter = %v; want %v", got, tt.want)
} }
if got := wgengine.IsNetstackRouter(wgengine.NewWatchdog(e)); got != tt.want {
t.Errorf("IsNetstackRouter(watchdog-wrapped) = %v; want %v", got, tt.want)
}
}) })
} }
} }

@ -17,15 +17,11 @@ import (
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/dns/resolver"
"tailscale.com/net/netmon"
"tailscale.com/net/tstun"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/wgengine/capture" "tailscale.com/wgengine/capture"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/router" "tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgcfg"
) )
@ -126,9 +122,6 @@ func (e *watchdogEngine) watchdog(name string, fn func()) {
func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *dns.Config, debug *tailcfg.Debug) error { func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *dns.Config, debug *tailcfg.Debug) error {
return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg, dnsCfg, debug) }) return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg, dnsCfg, debug) })
} }
func (e *watchdogEngine) GetNetMon() *netmon.Monitor {
return e.wrap.GetNetMon()
}
func (e *watchdogEngine) GetFilter() *filter.Filter { func (e *watchdogEngine) GetFilter() *filter.Filter {
return e.wrap.GetFilter() return e.wrap.GetFilter()
} }
@ -181,18 +174,6 @@ func (e *watchdogEngine) WhoIsIPPort(ipp netip.AddrPort) (tsIP netip.Addr, ok bo
func (e *watchdogEngine) Close() { func (e *watchdogEngine) Close() {
e.watchdog("Close", e.wrap.Close) e.watchdog("Close", e.wrap.Close)
} }
func (e *watchdogEngine) GetInternals() (tw *tstun.Wrapper, c *magicsock.Conn, d *dns.Manager, ok bool) {
if ig, ok := e.wrap.(InternalsGetter); ok {
return ig.GetInternals()
}
return
}
func (e *watchdogEngine) GetResolver() (r *resolver.Resolver, ok bool) {
if re, ok := e.wrap.(ResolvingEngine); ok {
return re.GetResolver()
}
return nil, false
}
func (e *watchdogEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) { func (e *watchdogEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) {
e.watchdog("PeerForIP", func() { ret, ok = e.wrap.PeerForIP(ip) }) e.watchdog("PeerForIP", func() { ret, ok = e.wrap.PeerForIP(ip) })
return ret, ok return ret, ok

@ -10,7 +10,6 @@ import (
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
@ -92,9 +91,6 @@ type Engine interface {
// WireGuard status changes. // WireGuard status changes.
SetStatusCallback(StatusCallback) SetStatusCallback(StatusCallback)
// GetNetMon returns the network monitor.
GetNetMon() *netmon.Monitor
// RequestStatus requests a WireGuard status update right // RequestStatus requests a WireGuard status update right
// away, sent to the callback registered via SetStatusCallback. // away, sent to the callback registered via SetStatusCallback.
RequestStatus() RequestStatus()

Loading…
Cancel
Save