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()