diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index e5db9619b..81b64a135 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -72,9 +72,10 @@ func normalizeDNSName(name string) (dnsname.FQDN, error) { func init() { feature.Register(featureName) ipnext.RegisterExtension(featureName, func(logf logger.Logf, sb ipnext.SafeBackend) (ipnext.Extension, error) { + conn25 := newConn25(logger.WithPrefix(logf, "conn25: ")) + conn25.backend = sb return &extension{ - conn25: newConn25(logger.WithPrefix(logf, "conn25: ")), - backend: sb, + conn25: conn25, }, nil }) ipnlocal.RegisterPeerAPIHandler("/v0/connector/transit-ip", handleConnectorTransitIP) @@ -97,10 +98,7 @@ func handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.ResponseWriter, // extension is an [ipnext.Extension] managing the connector on platforms // that import this package. type extension struct { - conn25 *Conn25 // safe for concurrent access and only set at creation - backend ipnext.SafeBackend // safe for concurrent access and only set at creation - - host ipnext.Host // set in Init, read-only after + conn25 *Conn25 // safe for concurrent access and only set at creation ctxCancel context.CancelCauseFunc // cancels sendLoop goroutine } @@ -119,7 +117,7 @@ func (e *extension) Init(host ipnext.Host) error { if e.ctxCancel != nil { return nil } - e.host = host + e.conn25.host = host dph := newDatapathHandler(e.conn25, e.conn25.client.logf) if err := e.installHooks(dph); err != nil { @@ -129,17 +127,17 @@ func (e *extension) Init(host ipnext.Host) error { ctx, cancel := context.WithCancelCause(context.Background()) e.ctxCancel = cancel - go e.sendLoop(ctx) + go e.conn25.sendLoop(ctx) return nil } func (e *extension) installHooks(dph *datapathHandler) error { // Make sure we can access the DNS manager and the system tun. - dnsManager, ok := e.backend.Sys().DNSManager.GetOK() + dnsManager, ok := e.conn25.backend.Sys().DNSManager.GetOK() if !ok { return errors.New("could not access system dns manager") } - tun, ok := e.backend.Sys().Tun.GetOK() + tun, ok := e.conn25.backend.Sys().Tun.GetOK() if !ok { return errors.New("could not access system tun") } @@ -170,15 +168,15 @@ func (e *extension) installHooks(dph *datapathHandler) error { // Manage how we react to changes to the current node, // including property changes (e.g. HostInfo, Capabilities, CapMap). - e.host.Hooks().OnSelfChange.Add(e.onSelfChange) + e.conn25.host.Hooks().OnSelfChange.Add(e.onSelfChange) // Manage how we react profile state changes, which include // prefs changes. - e.host.Hooks().ProfileStateChange.Add(e.profileStateChange) + e.conn25.host.Hooks().ProfileStateChange.Add(e.profileStateChange) // Allow the client to send packets with Transit IP destinations // in the link-local space. - e.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) { + e.conn25.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) { if !e.conn25.isConfigured() { return false, "" } @@ -187,7 +185,7 @@ func (e *extension) installHooks(dph *datapathHandler) error { // Allow the connector to receive packets with Transit IP destinations // in the link-local space. - e.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) { + e.conn25.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) { if !e.conn25.isConfigured() { return false, "" } @@ -196,7 +194,7 @@ func (e *extension) installHooks(dph *datapathHandler) error { // Allow the connector to receive packets with Transit IP destinations // that are not "local" to it, and that it does not advertise. - e.host.Hooks().Filter.IngressAllowHooks.Add(func(p packet.Parsed) (bool, string) { + e.conn25.host.Hooks().Filter.IngressAllowHooks.Add(func(p packet.Parsed) (bool, string) { if !e.conn25.isConfigured() { return false, "" } @@ -204,7 +202,7 @@ func (e *extension) installHooks(dph *datapathHandler) error { }) // Give the client the Magic IP range to install on the OS. - e.host.Hooks().ExtraRouterConfigRoutes.Set(func() views.Slice[netip.Prefix] { + e.conn25.host.Hooks().ExtraRouterConfigRoutes.Set(func() views.Slice[netip.Prefix] { if !e.conn25.isConfigured() { return views.Slice[netip.Prefix]{} } @@ -212,7 +210,7 @@ func (e *extension) installHooks(dph *datapathHandler) error { }) // Tell WireGuard what Transit IPs belong to which connector peers. - e.host.Hooks().ExtraWireGuardAllowedIPs.Set(func(k key.NodePublic) views.Slice[netip.Prefix] { + e.conn25.host.Hooks().ExtraWireGuardAllowedIPs.Set(func(k key.NodePublic) views.Slice[netip.Prefix] { if !e.conn25.isConfigured() { return views.Slice[netip.Prefix]{} } @@ -227,7 +225,7 @@ func (e *extension) installHooks(dph *datapathHandler) error { // for Conn25 to be fully configured and ready to use. func (e *extension) seedPrefsConfig() { var cfg config - cfg.prefs = configFromPrefs(e.host.Profiles().CurrentPrefs()) + cfg.prefs = configFromPrefs(e.conn25.host.Profiles().CurrentPrefs()) e.conn25.reconfig(cfg) } @@ -315,7 +313,10 @@ type appAddr struct { // Conn25 holds state for routing traffic for a domain via a connector. type Conn25 struct { - mu sync.Mutex // mu protects reconfiguration of client and connector + mu sync.Mutex // mu protects reconfiguration of client and connector + host ipnext.Host + backend ipnext.SafeBackend + client *client connector *connector } @@ -778,30 +779,30 @@ func (c *client) addTransitIPForConnector(tip netip.Addr, conn tailcfg.NodeView) return c.assignments.insertTransitConnMapping(tip, conn.Key()) } -func (e *extension) sendLoop(ctx context.Context) { +func (c *Conn25) sendLoop(ctx context.Context) { for { select { case <-ctx.Done(): return - case as := <-e.conn25.client.addrsCh: - if err := e.handleAddressAssignment(ctx, as); err != nil { - e.conn25.client.logf("error handling transit IP assignment (app: %s, mip: %v, src: %v): %v", as.app, as.magic, as.dst, err) + case as := <-c.client.addrsCh: + if err := c.handleAddressAssignment(ctx, as); err != nil { + c.client.logf("error handling transit IP assignment (app: %s, mip: %v, src: %v): %v", as.app, as.magic, as.dst, err) } } } } -func (e *extension) handleAddressAssignment(ctx context.Context, as addrs) error { - conn, err := e.sendAddressAssignment(ctx, as) +func (c *Conn25) handleAddressAssignment(ctx context.Context, as addrs) error { + conn, err := c.sendAddressAssignment(ctx, as) if err != nil { return err } - err = e.conn25.client.addTransitIPForConnector(as.transit, conn) + err = c.client.addTransitIPForConnector(as.transit, conn) if err != nil { return err } - e.host.AuthReconfigAsync() + c.host.AuthReconfigAsync() return nil } @@ -868,14 +869,14 @@ func makePeerAPIReq(ctx context.Context, httpClient *http.Client, urlBase string return nil } -func (e *extension) sendAddressAssignment(ctx context.Context, as addrs) (tailcfg.NodeView, error) { - app, ok := e.conn25.client.getConfig().nv.appsByName[as.app] +func (c *Conn25) sendAddressAssignment(ctx context.Context, as addrs) (tailcfg.NodeView, error) { + app, ok := c.client.getConfig().nv.appsByName[as.app] if !ok { - e.conn25.client.logf("App not found for app: %s (domain: %s)", as.app, as.domain) + c.client.logf("App not found for app: %s (domain: %s)", as.app, as.domain) return tailcfg.NodeView{}, errors.New("app not found") } - nb := e.host.NodeBackend() + nb := c.host.NodeBackend() peers := appc.PickConnector(nb, app) var urlBase string var conn tailcfg.NodeView @@ -889,7 +890,7 @@ func (e *extension) sendAddressAssignment(ctx context.Context, as addrs) (tailcf if urlBase == "" { return tailcfg.NodeView{}, errors.New("no connector peer found to handle address assignment") } - client := e.backend.Sys().Dialer.Get().PeerAPIHTTPClient() + client := c.backend.Sys().Dialer.Get().PeerAPIHTTPClient() return conn, makePeerAPIReq(ctx, client, urlBase, as) } diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go index 8e829724b..658fd1519 100644 --- a/feature/conn25/conn25_test.go +++ b/feature/conn25/conn25_test.go @@ -1095,12 +1095,10 @@ func TestAddressAssignmentIsHandled(t *testing.T) { Key: key.NodePublicFromRaw32(mem.B([]byte{0: 0xff, 1: 0xff, 31: 0x01})), }).View() - ext := &extension{ - conn25: newConn25(logger.Discard), - backend: newTestSafeBackend(), - } + conn25 := newConn25(logger.Discard) + backend := newTestSafeBackend() authReconfigAsyncCalled := make(chan struct{}, 1) - if err := ext.Init(&testHost{ + host := &testHost{ nb: &testNodeBackend{ peers: []tailcfg.NodeView{connectorPeer}, peerAPIURL: peersAPI.URL, @@ -1109,10 +1107,10 @@ func TestAddressAssignmentIsHandled(t *testing.T) { authReconfigAsync: func() { authReconfigAsyncCalled <- struct{}{} }, - }); err != nil { - t.Fatal(err) } - defer ext.Shutdown() + conn25.backend = backend + conn25.host = host + go conn25.sendLoop(t.Context()) sn := makeSelfNode(t, []appctype.Conn25Attr{{ Name: "app1", @@ -1121,7 +1119,7 @@ func TestAddressAssignmentIsHandled(t *testing.T) { }}, []string{}) cfg := mustConfig(t, sn, testPrefsNotConnector) - ext.conn25.reconfig(cfg) + conn25.reconfig(cfg) as := addrs{ dst: netip.MustParseAddr("1.2.3.4"), @@ -1130,10 +1128,10 @@ func TestAddressAssignmentIsHandled(t *testing.T) { domain: "example.com.", app: "app1", } - if err := ext.conn25.client.assignments.insert(as); err != nil { + if err := conn25.client.assignments.insert(as); err != nil { t.Fatalf("error inserting address assignments: %v", err) } - ext.conn25.client.enqueueAddressAssignment(as) + conn25.client.enqueueAddressAssignment(as) select { case got := <-received: @@ -1521,12 +1519,10 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) { }).View(), } - ext := &extension{ - conn25: newConn25(logger.Discard), - backend: newTestSafeBackend(), - } + conn25 := newConn25(logger.Discard) + backend := newTestSafeBackend() authReconfigAsyncCalled := make(chan struct{}, 1) - if err := ext.Init(&testHost{ + host := &testHost{ nb: &testNodeBackend{ peers: connectorPeers, peerAPIURL: peersAPI.URL, @@ -1535,10 +1531,10 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) { authReconfigAsync: func() { authReconfigAsyncCalled <- struct{}{} }, - }); err != nil { - t.Fatal(err) } - defer ext.Shutdown() + conn25.backend = backend + conn25.host = host + go conn25.sendLoop(t.Context()) sn := makeSelfNode(t, []appctype.Conn25Attr{ { @@ -1554,7 +1550,7 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) { }, []string{}) cfg := mustConfig(t, sn, testPrefsNotConnector) - ext.conn25.reconfig(cfg) + conn25.reconfig(cfg) type lookup struct { connKey key.NodePublic @@ -1651,10 +1647,10 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Add and enqueue the addrs, and then wait for the send to complete // (as indicated by authReconfig being called). - if err := ext.conn25.client.assignments.insert(tt.as); err != nil { + if err := conn25.client.assignments.insert(tt.as); err != nil { t.Fatalf("error inserting address assignment: %v", err) } - if err := ext.conn25.client.enqueueAddressAssignment(tt.as); err != nil { + if err := conn25.client.enqueueAddressAssignment(tt.as); err != nil { t.Fatalf("error enqueuing address assignment: %v", err) } select { @@ -1665,7 +1661,7 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) { // Check that each of the lookups behaves as expected for i, lu := range tt.lookups { - got, ok := ext.conn25.client.assignments.lookupTransitIPsByConnKey(lu.connKey) + got, ok := conn25.client.assignments.lookupTransitIPsByConnKey(lu.connKey) if ok != lu.expectedOk { t.Fatalf("unexpected ok result at index %d wanted %v, got %v", i, lu.expectedOk, ok) }