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

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

Updates #8036

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2023-05-03 13:57:17 -07:00
committed by Brad Fitzpatrick
parent 0d7303b798
commit 6e967446e4
26 changed files with 373 additions and 260 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
// SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return.
SetSubsystem func(any)
}
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
logf("Starting userspace WireGuard engine (with fake TUN device)")
return NewUserspaceEngine(logf, Config{
ListenPort: listenPort,
// 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,
})
}
// 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
}
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)
}
case *watchdogEngine:
return IsNetstackRouter(e.wrap)
}
return IsNetstack(e)
}
// IsNetstack reports whether e is a netstack-based TUN-free engine.
func IsNetstack(e Engine) bool {
ig, ok := e.(InternalsGetter)
if !ok {
return false
}
tw, _, _, ok := ig.GetInternals()
if !ok {
return false
}
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.

View File

@@ -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)
}
})
}
}

View File

@@ -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

View File

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