From 85906b61f42abea55a565eefa5efe58658c1d19f Mon Sep 17 00:00:00 2001 From: Fran Bull Date: Mon, 23 Mar 2026 09:22:57 -0700 Subject: [PATCH] feature/conn25: call AuthReconfigAsync after address assignment When the client of a connector assigns transit IP addresses for a connector we need to let wireguard know that packets for the transit IPs should be sent to the connector node. We do this by: * keeping a map of node -> transit IPs we've assigned for it * setting a callback hook within wireguard reconfig to ask us for these extra allowed IPs. * forcing wireguard to do a reconfig after we have assigned new transit IPs. And this commit is the last part: forcing the wireguard reconfig after a new address assignment. Fixes tailscale/corp#38124 Signed-off-by: Fran Bull --- feature/conn25/conn25.go | 13 +++++++++++-- feature/conn25/conn25_test.go | 23 ++++++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go index 92d87fd19..d2cad6b49 100644 --- a/feature/conn25/conn25.go +++ b/feature/conn25/conn25.go @@ -603,13 +603,22 @@ func (e *extension) sendLoop(ctx context.Context) { case <-ctx.Done(): return case as := <-e.conn25.client.addrsCh: - if err := e.sendAddressAssignment(ctx, as); err != nil { - e.conn25.client.logf("error sending transit IP assignment (app: %s, mip: %v, src: %v): %v", as.app, as.magic, as.dst, err) + 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) } } } } +func (e *extension) handleAddressAssignment(ctx context.Context, as addrs) error { + if err := e.sendAddressAssignment(ctx, as); err != nil { + return err + } + // TODO(fran) assign the connector publickey -> transit ip addresses + e.host.AuthReconfigAsync() + return nil +} + func (c *client) enqueueAddressAssignment(addrs addrs) error { select { // TODO(fran) investigate the value of waiting for multiple addresses and sending them diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go index c8dcef817..ff3ec4c9e 100644 --- a/feature/conn25/conn25_test.go +++ b/feature/conn25/conn25_test.go @@ -798,12 +798,14 @@ func (nb *testNodeBackend) PeerAPIBase(p tailcfg.NodeView) string { type testHost struct { ipnext.Host - nb ipnext.NodeBackend - hooks ipnext.Hooks + nb ipnext.NodeBackend + hooks ipnext.Hooks + authReconfigAsync func() } func (h *testHost) NodeBackend() ipnext.NodeBackend { return h.nb } func (h *testHost) Hooks() *ipnext.Hooks { return &h.hooks } +func (h *testHost) AuthReconfigAsync() { h.authReconfigAsync() } type testSafeBackend struct { ipnext.SafeBackend @@ -812,9 +814,11 @@ type testSafeBackend struct { func (b *testSafeBackend) Sys() *tsd.System { return b.sys } -// TestEnqueueAddress tests that after enqueueAddress has been called a -// peerapi request is made to a peer. -func TestEnqueueAddress(t *testing.T) { +// TestAddressAssignmentIsHandled tests that after enqueueAddress has been called +// we handle the assignment asynchronously by: +// - making a peerapi request to a peer. +// - calling AuthReconfigAsync on the host. +func TestAddressAssignmentIsHandled(t *testing.T) { // make a fake peer to test against received := make(chan ConnectorTransitIPRequest, 1) peersAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -849,11 +853,15 @@ func TestEnqueueAddress(t *testing.T) { conn25: newConn25(logger.Discard), backend: &testSafeBackend{sys: sys}, } + authReconfigAsyncCalled := make(chan struct{}, 1) if err := ext.Init(&testHost{ nb: &testNodeBackend{ peers: []tailcfg.NodeView{connectorPeer}, peerAPIURL: peersAPI.URL, }, + authReconfigAsync: func() { + authReconfigAsyncCalled <- struct{}{} + }, }); err != nil { t.Fatal(err) } @@ -896,6 +904,11 @@ func TestEnqueueAddress(t *testing.T) { case <-time.After(5 * time.Second): t.Fatal("timed out waiting for connector to receive request") } + select { + case <-authReconfigAsyncCalled: + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for AuthReconfigAsync to be called") + } } func parseResponse(t *testing.T, buf []byte) ([]dnsmessage.Resource, []dnsmessage.Resource) {