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)