From 6e967446e412a5f54d9a5888d6514520ff4e40ae Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 3 May 2023 13:57:17 -0700 Subject: [PATCH] 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 --- cmd/tailscaled/depaware.txt | 1 + cmd/tailscaled/tailscaled.go | 97 +++++++------ cmd/tailscaled/tailscaled_windows.go | 9 +- cmd/tsconnect/wasm/wasm_js.go | 13 +- ipn/ipnlocal/local.go | 67 ++++----- ipn/ipnlocal/local_test.go | 15 +- ipn/ipnlocal/loglines_test.go | 8 +- ipn/ipnlocal/peerapi.go | 12 +- ipn/ipnlocal/serve.go | 2 +- ipn/ipnlocal/state_test.go | 23 ++- ipn/ipnserver/server.go | 7 +- net/tstun/fake.go | 5 +- ssh/tailssh/tailssh_test.go | 11 +- tsd/tsd.go | 135 ++++++++++++++++++ tsnet/tsnet.go | 20 +-- .../tailscaled_deps_test_darwin.go | 1 + .../tailscaled_deps_test_freebsd.go | 1 + .../integration/tailscaled_deps_test_linux.go | 1 + .../tailscaled_deps_test_openbsd.go | 1 + .../tailscaled_deps_test_windows.go | 1 + wgengine/netstack/netstack_test.go | 42 +++--- wgengine/netstack/subnet_router_wrapper.go | 7 - wgengine/userspace.go | 101 +++++-------- wgengine/userspace_ext_test.go | 30 ++-- wgengine/watchdog.go | 19 --- wgengine/wgengine.go | 4 - 26 files changed, 373 insertions(+), 260 deletions(-) create mode 100644 tsd/tsd.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 58f61e60e..7e51976fc 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -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 tailscale.com/tka from tailscale.com/ipn/ipnlocal+ 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/mono from tailscale.com/net/tstun+ tailscale.com/tstime/rate from tailscale.com/wgengine/filter+ diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index d2c28de99..0d46cdb0f 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -50,6 +50,7 @@ import ( "tailscale.com/safesocket" "tailscale.com/smallzstd" "tailscale.com/syncs" + "tailscale.com/tsd" "tailscale.com/tsweb/varz" "tailscale.com/types/flagtype" "tailscale.com/types/logger" @@ -330,12 +331,16 @@ var debugMux *http.ServeMux func run() error { var logf logger.Logf = log.Printf + + sys := new(tsd.System) + netMon, err := netmon.New(func(format string, args ...any) { logf(format, args...) }) if err != nil { return fmt.Errorf("netmon.New: %w", err) } + sys.Set(netMon) pol := logpolicy.New(logtail.CollectionNode, netMon) pol.SetVerbosityLevel(args.verbose) @@ -386,10 +391,10 @@ func run() error { 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) if err != nil { 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 { debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus) } @@ -433,7 +438,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, return } } - lb, err := getLocalBackend(ctx, logf, logID, netMon) + lb, err := getLocalBackend(ctx, logf, logID, sys) if err == nil { logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond)) srv.SetLocalBackend(lb) @@ -457,31 +462,28 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, 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 { - logPol.Logtail.SetNetMon(netMon) + logPol.Logtail.SetNetMon(sys.NetMon.Get()) } socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr) 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 { 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 ig, ok := e.(wgengine.InternalsGetter); ok { - if _, mc, _, ok := ig.GetInternals(); ok { - debugMux.HandleFunc("/debug/magicsock", mc.ServeHTTPDebug) - } + if ms, ok := sys.MagicSock.GetOK(); ok { + debugMux.HandleFunc("/debug/magicsock", ms.ServeHTTPDebug) } go runDebugServer(debugMux, args.debug) } - ns, err := newNetstack(logf, dialer, e) + ns, err := newNetstack(logf, sys) if err != nil { 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() if onlyNetstack { + e := sys.Engine.Get() dialer.UseNetstackForIP = func(ip netip.Addr) bool { _, ok := e.PeerForIP(ip) return ok @@ -519,16 +522,15 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID tshttpproxy.SetSelfProxy(addrs...) } - e = wgengine.NewWatchdog(e) - opts := ipnServerOpts() store, err := store.New(logf, statePathOrDefault()) if err != nil { 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 { 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 // 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 == "" { - return nil, false, errors.New("no --tun value specified") + return false, errors.New("no --tun value specified") } var errs []error for _, name := range strings.Split(args.tunname, ",") { logf("wgengine.NewUserspaceEngine(tun %q) ...", name) - e, onlyNetstack, err = tryEngine(logf, netMon, dialer, name) + onlyNetstack, err = tryEngine(logf, sys, name) if err == nil { - return e, onlyNetstack, nil + return onlyNetstack, nil } logf("wgengine.NewUserspaceEngine(tun %q) error: %v", name, err) errs = append(errs, err) } - return nil, false, multierr.New(errs...) + return false, multierr.New(errs...) } // handleSubnetsInNetstack reports whether netstack should handle subnet routers @@ -593,21 +595,23 @@ func handleSubnetsInNetstack() bool { 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{ - ListenPort: args.port, - NetMon: netMon, - Dialer: dialer, + ListenPort: args.port, + NetMon: sys.NetMon.Get(), + Dialer: sys.Dialer.Get(), + SetSubsystem: sys.Set, } onlyNetstack = name == "userspace-networking" + netstackSubnetRouter := onlyNetstack // but mutated later on some platforms netns.SetEnabled(!onlyNetstack) if args.birdSocketPath != "" && createBIRDClient != nil { log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath) conf.BIRDClient, err = createBIRDClient(args.birdSocketPath) if err != nil { - return nil, false, fmt.Errorf("createBIRDClient: %w", err) + return false, fmt.Errorf("createBIRDClient: %w", err) } } 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. conf.DNS, err = dns.NewOSConfigurator(logf, "") // empty interface name if err != nil { - return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err) + return false, fmt.Errorf("dns.NewOSConfigurator: %w", err) } } } else { dev, devName, err := tstunNew(logf, name) if err != nil { 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 if strings.HasPrefix(name, "tap:") { conf.IsTAP = true 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 { 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) if err != nil { dev.Close() r.Close() - return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err) + return false, fmt.Errorf("dns.NewOSConfigurator: %w", err) } conf.DNS = d conf.Router = r if handleSubnetsInNetstack() { conf.Router = netstack.NewSubnetRouterWrapper(conf.Router) + netstackSubnetRouter = true } } - e, err = wgengine.NewUserspaceEngine(logf, conf) + e, err := wgengine.NewUserspaceEngine(logf, conf) 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 { @@ -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) { - tunDev, magicConn, dns, ok := e.(wgengine.InternalsGetter).GetInternals() - if !ok { - return nil, fmt.Errorf("%T is not a wgengine.InternalsGetter", e) - } - return netstack.Create(logf, tunDev, e, magicConn, dialer, dns) +func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) { + return netstack.Create(logf, sys.Tun.Get(), sys.Engine.Get(), sys.MagicSock.Get(), sys.Dialer.Get(), sys.DNSManager.Get()) } // mustStartProxyListeners creates listeners for local SOCKS and HTTP diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 077d3f99f..ae538bed6 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -47,6 +47,7 @@ import ( "tailscale.com/net/dns" "tailscale.com/net/netmon" "tailscale.com/net/tstun" + "tailscale.com/tsd" "tailscale.com/types/logger" "tailscale.com/types/logid" "tailscale.com/util/winutil" @@ -292,13 +293,15 @@ func beWindowsSubprocess() bool { } }() + sys := new(tsd.System) netMon, err := netmon.New(log.Printf) if err != nil { - log.Printf("Could not create netMon: %v", err) - netMon = nil + log.Fatalf("Could not create netMon: %v", err) } + sys.Set(netMon) + publicLogID, _ := logid.ParsePublicID(logID) - err = startIPNServer(ctx, log.Printf, publicLogID, netMon) + err = startIPNServer(ctx, log.Printf, publicLogID, sys) if err != nil { log.Fatalf("ipnserver: %v", err) } diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index a1b0fd95e..153941b29 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -37,6 +37,7 @@ import ( "tailscale.com/safesocket" "tailscale.com/smallzstd" "tailscale.com/tailcfg" + "tailscale.com/tsd" "tailscale.com/wgengine" "tailscale.com/wgengine/netstack" "tailscale.com/words" @@ -96,6 +97,8 @@ func newIPN(jsConfig js.Value) map[string]any { logtail := logtail.NewLogger(c, log.Printf) logf := logtail.Logf + sys := new(tsd.System) + sys.Set(store) dialer := &tsdial.Dialer{Logf: logf} eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ Dialer: dialer, @@ -103,12 +106,9 @@ func newIPN(jsConfig js.Value) map[string]any { if err != nil { log.Fatal(err) } + sys.Set(eng) - tunDev, magicConn, dnsManager, ok := eng.(wgengine.InternalsGetter).GetInternals() - if !ok { - log.Fatalf("%T is not a wgengine.InternalsGetter", eng) - } - ns, err := netstack.Create(logf, tunDev, eng, magicConn, dialer, dnsManager) + ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get()) if err != nil { 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) { return ns.DialContextTCP(ctx, dst) } + sys.NetstackRouter.Set(true) logid := lpc.PublicID 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 { log.Fatalf("ipnlocal.NewLocalBackend: %v", err) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index e2894e663..15202b0fd 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -60,6 +60,7 @@ import ( "tailscale.com/syncs" "tailscale.com/tailcfg" "tailscale.com/tka" + "tailscale.com/tsd" "tailscale.com/types/dnstype" "tailscale.com/types/empty" "tailscale.com/types/key" @@ -137,10 +138,11 @@ type LocalBackend struct { logf logger.Logf // general logging keyLogf logger.Logf // for printing list of peers 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 - store ipn.StateStore - dialer *tsdial.Dialer // non-nil + store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys + dialer *tsdial.Dialer // non-nil; TODO(bradfitz): remove; use sys backendLogID logid.PublicID unregisterNetMon func() unregisterHealthWatch func() @@ -267,10 +269,10 @@ type clientGen func(controlclient.Options) (controlclient.Client, error) // but is not actually running. // // 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) { - if e == nil { - panic("ipn.NewLocalBackend: engine must not be nil") - } +func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, loginFlags controlclient.LoginFlags) (*LocalBackend, error) { + e := sys.Engine.Get() + store := sys.StateStore.Get() + dialer := sys.Dialer.Get() pm, err := newProfileManager(store, logf) if err != nil { @@ -301,10 +303,11 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor logf: logf, keyLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), + sys: sys, e: e, - pm: pm, - store: store, dialer: dialer, + store: store, + pm: pm, backendLogID: logID, state: ipn.NoState, portpoll: portpoll, @@ -313,7 +316,8 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor 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 { 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.e.SetStatusCallback(b.setWgengineStatus) - netMon := e.GetNetMon() b.prevIfState = netMon.InterfaceState() // Call our linkChange code once with the current state, and // 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) - wiredPeerAPIPort := false - if ig, ok := e.(wgengine.InternalsGetter); ok { - if tunWrap, _, _, ok := ig.GetInternals(); ok { - tunWrap.PeerAPIPort = b.GetPeerAPIPort - wiredPeerAPIPort = true - } - } - if !wiredPeerAPIPort { + if tunWrap, ok := b.sys.Tun.GetOK(); ok { + tunWrap.PeerAPIPort = b.GetPeerAPIPort + } else { 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. +// It is always non-nil. func (b *LocalBackend) Dialer() *tsdial.Dialer { return b.dialer } @@ -644,7 +643,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func defer b.mu.Unlock() sb.MutateStatus(func(s *ipnstate.Status) { s.Version = version.Long() - s.TUN = !wgengine.IsNetstack(b.e) + s.TUN = !b.sys.IsNetstack() s.BackendState = b.state.String() s.AuthURL = b.authURLSticky if err := health.OverallError(); err != nil { @@ -1315,8 +1314,8 @@ func (b *LocalBackend) Start(opts ipn.Options) error { hostinfo := hostinfo.New() hostinfo.BackendLogID = b.backendLogID.String() hostinfo.FrontendLogID = opts.FrontendLogID - hostinfo.Userspace.Set(wgengine.IsNetstack(b.e)) - hostinfo.UserspaceRouter.Set(wgengine.IsNetstackRouter(b.e)) + hostinfo.Userspace.Set(b.sys.IsNetstack()) + hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter()) if b.cc != nil { // TODO(apenwarr): avoid the need to reinit controlclient. @@ -1401,7 +1400,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { var err error - isNetstack := wgengine.IsNetstackRouter(b.e) + isNetstack := b.sys.IsNetstackRouter() debugFlags := controlDebugFlags if isNetstack { debugFlags = append([]string{"netstack"}, debugFlags...) @@ -1423,7 +1422,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { HTTPTestClient: httpTestClient, DiscoPublicKey: discoPublic, DebugFlags: debugFlags, - NetMon: b.e.GetNetMon(), + NetMon: b.sys.NetMon.Get(), Pinger: b, PopBrowserURL: b.tellClientToBrowseToURL, OnClientVersion: b.onClientVersion, @@ -3317,14 +3316,12 @@ func (b *LocalBackend) initPeerAPIListener() { directFileMode: b.directFileRoot != "", directFileDoFinalRename: b.directFileDoFinalRename, } - if re, ok := b.e.(wgengine.ResolvingEngine); ok { - if r, ok := re.GetResolver(); ok { - ps.resolver = r - } + if dm, ok := b.sys.DNSManager.GetOK(); ok { + ps.resolver = dm.Resolver() } b.peerAPIServer = ps - isNetstack := wgengine.IsNetstack(b.e) + isNetstack := b.sys.IsNetstack() for i, a := range b.netMap.Addresses { var ln net.Listener var err error @@ -4040,7 +4037,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn. b.setServeProxyHandlersLocked() // don't listen on netmap addresses if we're in userspace mode - if !wgengine.IsNetstack(b.e) { + if !b.sys.IsNetstack() { b.updateServeTCPPortNetMapAddrListenersLocked(servePorts) } } @@ -4391,7 +4388,7 @@ func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr { } func (b *LocalBackend) CheckIPForwarding() error { - if wgengine.IsNetstackRouter(b.e) { + if b.sys.IsNetstackRouter() { return nil } @@ -4537,13 +4534,9 @@ func (b *LocalBackend) DebugReSTUN() error { } func (b *LocalBackend) magicConn() (*magicsock.Conn, error) { - ig, ok := b.e.(wgengine.InternalsGetter) - if !ok { - return nil, errors.New("engine isn't InternalsGetter") - } - _, mc, _, ok := ig.GetInternals() + mc, ok := b.sys.MagicSock.GetOK() if !ok { - return nil, errors.New("failed to get internals") + return nil, errors.New("failed to get magicsock from sys") } return mc, nil } diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 2a474e46d..9fd44def8 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -20,6 +20,7 @@ import ( "tailscale.com/net/interfaces" "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" + "tailscale.com/tsd" "tailscale.com/tstest" "tailscale.com/types/key" "tailscale.com/types/logger" @@ -501,13 +502,16 @@ func TestLazyMachineKeyGeneration(t *testing.T) { tstest.Replace(t, &panicOnMachineKeyGeneration, func() bool { return true }) var logf logger.Logf = logger.Discard + sys := new(tsd.System) store := new(mem.Store) - eng, err := wgengine.NewFakeUserspaceEngine(logf, 0) + sys.Set(store) + eng, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set) if err != nil { t.Fatalf("NewFakeUserspaceEngine: %v", err) } 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 { t.Fatalf("NewLocalBackend: %v", err) } @@ -765,13 +769,16 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) { func TestStatusWithoutPeers(t *testing.T) { logf := tstest.WhileTestRunningLogger(t) 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 { t.Fatalf("NewFakeUserspaceEngine: %v", err) } + sys.Set(e) 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 { t.Fatalf("NewLocalBackend: %v", err) } diff --git a/ipn/ipnlocal/loglines_test.go b/ipn/ipnlocal/loglines_test.go index cde3f9198..361791858 100644 --- a/ipn/ipnlocal/loglines_test.go +++ b/ipn/ipnlocal/loglines_test.go @@ -12,6 +12,7 @@ import ( "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/store/mem" "tailscale.com/tailcfg" + "tailscale.com/tsd" "tailscale.com/tstest" "tailscale.com/types/key" "tailscale.com/types/logger" @@ -47,14 +48,17 @@ func TestLocalLogLines(t *testing.T) { idA := logid(0xaa) // set up a LocalBackend, super bare bones. No functional data. + sys := new(tsd.System) store := new(mem.Store) - e, err := wgengine.NewFakeUserspaceEngine(logf, 0) + sys.Set(store) + e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set) if err != nil { t.Fatal(err) } 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 { t.Fatal(err) } diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index e8e06b6ee..e4e69727f 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -50,7 +50,6 @@ import ( "tailscale.com/util/clientmetric" "tailscale.com/util/multierr" "tailscale.com/version/distro" - "tailscale.com/wgengine" "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 = "" } @@ -1239,12 +1238,9 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req http.Error(w, "denied; no debug access", http.StatusForbidden) return } - eng := h.ps.b.e - if ig, ok := eng.(wgengine.InternalsGetter); ok { - if _, mc, _, ok := ig.GetInternals(); ok { - mc.ServeHTTPDebug(w, r) - return - } + if mc, ok := h.ps.b.sys.MagicSock.GetOK(); ok { + mc.ServeHTTPDebug(w, r) + return } http.Error(w, "miswired", 500) } diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index e6d1d0104..7bf4604dd 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -143,7 +143,7 @@ func (s *serveListener) Run() { } 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 // assigned). No need to warn. Notably, WSL2 (Issue 6303). return false diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 7a2c7132e..22e4ada82 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -17,6 +17,7 @@ import ( "tailscale.com/ipn" "tailscale.com/ipn/store/mem" "tailscale.com/tailcfg" + "tailscale.com/tsd" "tailscale.com/tstest" "tailscale.com/types/empty" "tailscale.com/types/key" @@ -297,14 +298,17 @@ func TestStateMachine(t *testing.T) { c := qt.New(t) logf := tstest.WhileTestRunningLogger(t) + sys := new(tsd.System) store := new(testStateStorage) - e, err := wgengine.NewFakeUserspaceEngine(logf, 0) + sys.Set(store) + e, err := wgengine.NewFakeUserspaceEngine(logf, sys.Set) if err != nil { t.Fatalf("NewFakeUserspaceEngine: %v", err) } 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 { t.Fatalf("NewLocalBackend: %v", err) } @@ -941,13 +945,16 @@ func TestStateMachine(t *testing.T) { func TestEditPrefsHasNoKeys(t *testing.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 { t.Fatalf("NewFakeUserspaceEngine: %v", err) } 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 { t.Fatalf("NewLocalBackend: %v", err) } @@ -1023,10 +1030,14 @@ func TestWGEngineStatusRace(t *testing.T) { t.Skip("test fails") c := qt.New(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) 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) var cc *mockControl diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 3abfa9364..705c01016 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -37,7 +37,7 @@ import ( type Server struct { lb atomic.Pointer[ipnlocal.LocalBackend] 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 // resetOnZero is whether to call bs.Reset on transition from // 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. -// The netMon parameter is optional; if non-nil it's used to do faster interface -// lookups. // // To start it, use the Server.Run method. // // At some point, either before or after Run, the Server's SetLocalBackend // method must also be called before Server can do anything useful. func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server { + if netMon == nil { + panic("nil netMon") + } return &Server{ backendLogID: logID, logf: logf, diff --git a/net/tstun/fake.go b/net/tstun/fake.go index 4bf29d093..d1ae467fa 100644 --- a/net/tstun/fake.go +++ b/net/tstun/fake.go @@ -47,8 +47,11 @@ func (t *fakeTUN) Write(b [][]byte, n int) (int, error) { 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) 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) BatchSize() int { return 1 } diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index c0935d24b..607b4eeaa 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -38,6 +38,7 @@ import ( "tailscale.com/net/tsdial" "tailscale.com/tailcfg" "tailscale.com/tempfork/gliderlabs/ssh" + "tailscale.com/tsd" "tailscale.com/tstest" "tailscale.com/types/logger" "tailscale.com/types/logid" @@ -815,14 +816,14 @@ func TestSSHAuthFlow(t *testing.T) { func TestSSH(t *testing.T) { 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 { t.Fatal(err) } - lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, - new(mem.Store), - new(tsdial.Dialer), - eng, 0) + sys.Set(eng) + sys.Set(new(mem.Store)) + lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0) if err != nil { t.Fatal(err) } diff --git a/tsd/tsd.go b/tsd/tsd.go new file mode 100644 index 000000000..ffa245bc9 --- /dev/null +++ b/tsd/tsd.go @@ -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 +} diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 2bea9026b..1b0ae7bb6 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -47,6 +47,7 @@ import ( "tailscale.com/net/socks5" "tailscale.com/net/tsdial" "tailscale.com/smallzstd" + "tailscale.com/tsd" "tailscale.com/types/logger" "tailscale.com/types/logid" "tailscale.com/types/nettype" @@ -482,23 +483,21 @@ func (s *Server) start() (reterr error) { } closePool.add(s.netMon) + sys := new(tsd.System) s.dialer = &tsdial.Dialer{Logf: logf} // mutated below (before used) eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - ListenPort: 0, - NetMon: s.netMon, - Dialer: s.dialer, + ListenPort: 0, + NetMon: s.netMon, + Dialer: s.dialer, + SetSubsystem: sys.Set, }) if err != nil { return err } closePool.add(s.dialer) + sys.Set(eng) - tunDev, magicConn, dns, ok := eng.(wgengine.InternalsGetter).GetInternals() - if !ok { - return fmt.Errorf("%T is not a wgengine.InternalsGetter", eng) - } - - ns, err := netstack.Create(logf, tunDev, eng, magicConn, s.dialer, dns) + ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get()) if err != nil { return fmt.Errorf("netstack.Create: %w", err) } @@ -522,12 +521,13 @@ func (s *Server) start() (reterr error) { return err } } + sys.Set(s.Store) loginFlags := controlclient.LoginDefault if s.Ephemeral { 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 { return fmt.Errorf("NewLocalBackend: %v", err) } diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index ed2e6e5ff..75ef83af0 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -37,6 +37,7 @@ import ( _ "tailscale.com/ssh/tailssh" _ "tailscale.com/syncs" _ "tailscale.com/tailcfg" + _ "tailscale.com/tsd" _ "tailscale.com/tsweb/varz" _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index ed2e6e5ff..75ef83af0 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -37,6 +37,7 @@ import ( _ "tailscale.com/ssh/tailssh" _ "tailscale.com/syncs" _ "tailscale.com/tailcfg" + _ "tailscale.com/tsd" _ "tailscale.com/tsweb/varz" _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index ed2e6e5ff..75ef83af0 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -37,6 +37,7 @@ import ( _ "tailscale.com/ssh/tailssh" _ "tailscale.com/syncs" _ "tailscale.com/tailcfg" + _ "tailscale.com/tsd" _ "tailscale.com/tsweb/varz" _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index ed2e6e5ff..75ef83af0 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -37,6 +37,7 @@ import ( _ "tailscale.com/ssh/tailssh" _ "tailscale.com/syncs" _ "tailscale.com/tailcfg" + _ "tailscale.com/tsd" _ "tailscale.com/tsweb/varz" _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index 33620c9d9..6eb00eff4 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -44,6 +44,7 @@ import ( _ "tailscale.com/smallzstd" _ "tailscale.com/syncs" _ "tailscale.com/tailcfg" + _ "tailscale.com/tsd" _ "tailscale.com/tsweb/varz" _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index 7a30fd84a..f08308e2d 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -16,6 +16,7 @@ import ( "tailscale.com/net/tsaddr" "tailscale.com/net/tsdial" "tailscale.com/net/tstun" + "tailscale.com/tsd" "tailscale.com/tstest" "tailscale.com/types/ipproto" "tailscale.com/types/logid" @@ -33,29 +34,26 @@ func TestInjectInboundLeak(t *testing.T) { t.Logf(format, args...) } } + sys := new(tsd.System) eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - Tun: tunDev, - Dialer: dialer, + Tun: tunDev, + Dialer: dialer, + SetSubsystem: sys.Set, }) if err != nil { t.Fatal(err) } defer eng.Close() - ig, ok := eng.(wgengine.InternalsGetter) - if !ok { - t.Fatal("not an InternalsGetter") - } - tunWrap, magicSock, dns, ok := ig.GetInternals() - if !ok { - t.Fatal("failed to get internals") - } + sys.Set(eng) + sys.Set(new(mem.Store)) - 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 { 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 { t.Fatal(err) } @@ -89,32 +87,28 @@ func getMemStats() (ms runtime.MemStats) { func makeNetstack(t *testing.T, config func(*Impl)) *Impl { tunDev := tstun.NewFake() + sys := &tsd.System{} + sys.Set(new(mem.Store)) dialer := new(tsdial.Dialer) logf := tstest.WhileTestRunningLogger(t) eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - Tun: tunDev, - Dialer: dialer, + Tun: tunDev, + Dialer: dialer, + SetSubsystem: sys.Set, }) if err != nil { t.Fatal(err) } t.Cleanup(func() { eng.Close() }) - ig, ok := eng.(wgengine.InternalsGetter) - if !ok { - t.Fatal("not an InternalsGetter") - } - tunWrap, magicSock, dns, ok := ig.GetInternals() - if !ok { - t.Fatal("failed to get internals") - } + sys.Set(eng) - 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 { t.Fatal(err) } 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 { t.Fatalf("NewLocalBackend: %v", err) } diff --git a/wgengine/netstack/subnet_router_wrapper.go b/wgengine/netstack/subnet_router_wrapper.go index dd9c96ec6..97eb6406f 100644 --- a/wgengine/netstack/subnet_router_wrapper.go +++ b/wgengine/netstack/subnet_router_wrapper.go @@ -4,16 +4,9 @@ package netstack import ( - "reflect" - - "tailscale.com/wgengine" "tailscale.com/wgengine/router" ) -func init() { - wgengine.NetstackRouterType = reflect.TypeOf(&subnetRouter{}) -} - type subnetRouter struct { router.Router } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 1356b36df..e29e20dae 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -10,8 +10,8 @@ import ( "errors" "fmt" "io" + "math" "net/netip" - "reflect" "runtime" "strings" "sync" @@ -25,7 +25,6 @@ import ( "tailscale.com/health" "tailscale.com/ipn/ipnstate" "tailscale.com/net/dns" - "tailscale.com/net/dns/resolver" "tailscale.com/net/flowtrack" "tailscale.com/net/interfaces" "tailscale.com/net/netmon" @@ -150,29 +149,6 @@ type userspaceEngine struct { // 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. type BIRDClient interface { EnableProtocol(proto string) error @@ -219,47 +195,37 @@ type Config struct { // BIRDClient, if non-nil, will be used to configure BIRD whenever // this node is a primary subnet router. 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 -// from netstack to here, informing this package of netstack's router -// 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) + // SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return. + SetSubsystem func(any) } -// IsNetstack reports whether e is a netstack-based TUN-free engine. -func IsNetstack(e Engine) bool { - ig, ok := e.(InternalsGetter) - if !ok { - return false +// NewFakeUserspaceEngine returns a new userspace engine for testing. +// +// The opts may contain the following types: +// +// - int or uint16: to set the ListenPort. +func NewFakeUserspaceEngine(logf logger.Logf, opts ...any) (Engine, error) { + conf := Config{ + RespondToPing: true, } - tw, _, _, ok := ig.GetInternals() - if !ok { - return false + for _, o := range opts { + switch v := o.(type) { + 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() - return err == nil && name == "FakeTUN" + logf("Starting userspace WireGuard engine (with fake TUN device)") + return NewUserspaceEngine(logf, conf) } // 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.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.") return e, nil } @@ -1119,10 +1094,6 @@ func (e *userspaceEngine) Wait() { <-e.waitCh } -func (e *userspaceEngine) GetNetMon() *netmon.Monitor { - return e.netMon -} - // LinkChange signals a network change event. It's currently // (2021-03-03) only called on Android. On other platforms, netMon // generates link change events for us. diff --git a/wgengine/userspace_ext_test.go b/wgengine/userspace_ext_test.go index b2235c9c9..67b337051 100644 --- a/wgengine/userspace_ext_test.go +++ b/wgengine/userspace_ext_test.go @@ -8,6 +8,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "tailscale.com/net/tstun" + "tailscale.com/tsd" "tailscale.com/types/logger" "tailscale.com/wgengine" "tailscale.com/wgengine/netstack" @@ -15,21 +16,23 @@ import ( ) 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 { t.Fatal(err) } defer e.Close() - if !wgengine.IsNetstack(e) { + if !sys.IsNetstack() { t.Errorf("IsNetstack = false; want true") } } func TestIsNetstackRouter(t *testing.T) { tests := []struct { - name string - conf wgengine.Config - want bool + name string + conf wgengine.Config + setNetstackRouter bool + want bool }{ { name: "no_netstack", @@ -50,23 +53,26 @@ func TestIsNetstackRouter(t *testing.T) { Tun: newFakeOSTUN(), Router: netstack.NewSubnetRouterWrapper(newFakeOSRouter()), }, - want: true, + setNetstackRouter: true, + want: true, }, } for _, tt := range tests { 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 { t.Fatal(err) } 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) } - - if got := wgengine.IsNetstackRouter(wgengine.NewWatchdog(e)); got != tt.want { - t.Errorf("IsNetstackRouter(watchdog-wrapped) = %v; want %v", got, tt.want) - } }) } } diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 76abfa8cc..19505be89 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -17,15 +17,11 @@ import ( "tailscale.com/envknob" "tailscale.com/ipn/ipnstate" "tailscale.com/net/dns" - "tailscale.com/net/dns/resolver" - "tailscale.com/net/netmon" - "tailscale.com/net/tstun" "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/netmap" "tailscale.com/wgengine/capture" "tailscale.com/wgengine/filter" - "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/router" "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 { 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 { return e.wrap.GetFilter() } @@ -181,18 +174,6 @@ func (e *watchdogEngine) WhoIsIPPort(ipp netip.AddrPort) (tsIP netip.Addr, ok bo func (e *watchdogEngine) 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) { e.watchdog("PeerForIP", func() { ret, ok = e.wrap.PeerForIP(ip) }) return ret, ok diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 4d4e240c1..df591c9e0 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -10,7 +10,6 @@ import ( "tailscale.com/ipn/ipnstate" "tailscale.com/net/dns" - "tailscale.com/net/netmon" "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/netmap" @@ -92,9 +91,6 @@ type Engine interface { // WireGuard status changes. SetStatusCallback(StatusCallback) - // GetNetMon returns the network monitor. - GetNetMon() *netmon.Monitor - // RequestStatus requests a WireGuard status update right // away, sent to the callback registered via SetStatusCallback. RequestStatus()