From d045eb237f5f68c5416dbcb1a90706529667d5e5 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 1 Nov 2025 22:46:02 +0100 Subject: [PATCH] mapper: handle NodeEndpoint and NodeDERP changes Add batcher cases for NodeEndpoint and NodeDERP that query NodeStore for current state and send PeerChangedPatch with both endpoints and DERP region, avoiding full map responses. Update batcher_test to pass NodeView to change constructors. --- hscontrol/mapper/batcher.go | 24 ++++++++++++++++++++++++ hscontrol/mapper/batcher_test.go | 11 +++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/hscontrol/mapper/batcher.go b/hscontrol/mapper/batcher.go index b56bca08..f1bec009 100644 --- a/hscontrol/mapper/batcher.go +++ b/hscontrol/mapper/batcher.go @@ -128,6 +128,30 @@ func generateMapResponse(nodeID types.NodeID, version tailcfg.CapabilityVersion, }) } + case change.NodeEndpoint, change.NodeDERP: + // Endpoint or DERP changes can be sent as lightweight patches. + // Query the NodeStore for the current peer state to construct the PeerChange. + // Even if only endpoint or only DERP changed, we include both in the patch + // since they're often updated together and it's minimal overhead. + responseType = "patch" + peer, found := mapper.state.GetNodeByID(c.NodeID) + if !found { + return nil, fmt.Errorf("node not found in NodeStore: %d", c.NodeID) + } + + peerChange := &tailcfg.PeerChange{ + NodeID: c.NodeID.NodeID(), + Endpoints: peer.Endpoints().AsSlice(), + DERPRegion: 0, // Will be set below if available + } + + // Extract DERP region from Hostinfo if available + if hi := peer.AsStruct().Hostinfo; hi != nil && hi.NetInfo != nil { + peerChange.DERPRegion = hi.NetInfo.PreferredDERP + } + + mapResp, err = mapper.peerChangedPatchResponse(nodeID, []*tailcfg.PeerChange{peerChange}) + default: // The following will always hit this: // change.Full, change.Policy diff --git a/hscontrol/mapper/batcher_test.go b/hscontrol/mapper/batcher_test.go index 30e75f48..a327a8f9 100644 --- a/hscontrol/mapper/batcher_test.go +++ b/hscontrol/mapper/batcher_test.go @@ -50,7 +50,11 @@ func (t *testBatcherWrapper) AddNode(id types.NodeID, c chan<- *tailcfg.MapRespo // Send the online notification that poll.go would normally send // This ensures other nodes get notified about this node coming online - t.AddWork(change.NodeOnline(id)) + node, ok := t.state.GetNodeByID(id) + if !ok { + return fmt.Errorf("node not found after adding to batcher: %d", id) + } + t.AddWork(change.NodeOnline(node)) return nil } @@ -65,7 +69,10 @@ func (t *testBatcherWrapper) RemoveNode(id types.NodeID, c chan<- *tailcfg.MapRe // Send the offline notification that poll.go would normally send // Do this BEFORE removing from batcher so the change can be processed - t.AddWork(change.NodeOffline(id)) + node, ok := t.state.GetNodeByID(id) + if ok { + t.AddWork(change.NodeOffline(node)) + } // Finally remove from the real batcher removed := t.Batcher.RemoveNode(id, c)