ipn/ipnlocal: update netmap cache after peer deltas are applied (#20111)

Add an UpdatePeers method to the cache. This allows us to support netmap peer deltas,
by allowing just the peers to be updated in an existing cache. As a safety check, reject
an update if there was no base netmap data to apply a change to.

Then, when processing peer mutations in the backend, capture any changes that should
be applied to the cache and update it, if one is enabled.

Updates #12542

Change-Id: I2f8790a8fdc5e85fce6700ba4821a8cb10dddffa
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
This commit is contained in:
M. J. Fromberger
2026-06-12 09:41:00 -07:00
committed by GitHub
parent b23089a5ef
commit 9cb071666c
4 changed files with 135 additions and 0 deletions
+33
View File
@@ -2422,6 +2422,12 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
needsAuthReconfig := netmapDeltaNeedsAuthReconfig(cn, muts)
cn.UpdateNetmapDelta(muts)
// In order that we can update the cache, keep track of which nodes are
// updated and removed. The nodeBackend has already applied any deltas, so
// we just need to know which nodes need updating.
updateIDs := set.Of[tailcfg.NodeID]()
removeIDs := set.Of[tailcfg.NodeID]()
// Dispatch Upsert/Remove per-peer to magicsock, and any per-field
// patches via the existing UpdateNetmapDelta path. The per-peer
// methods take c.mu themselves, so we can't call them from inside
@@ -2434,12 +2440,15 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
ms.UpsertPeer(m.Node)
peersUpsertedOrRemoved = true
metricNetmapDeltaPeerUpserted.Add(1)
updateIDs.Add(m.Node.ID())
case netmap.NodeMutationRemove:
ms.RemovePeer(m.NodeIDBeingMutated())
peersUpsertedOrRemoved = true
metricNetmapDeltaPeerRemoved.Add(1)
removeIDs.Add(m.NodeIDBeingMutated())
default:
metricNetmapDeltaPeerPatched.Add(1)
updateIDs.Add(m.NodeIDBeingMutated())
}
}
ms.UpdateNetmapDelta(muts)
@@ -2476,6 +2485,30 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
return true
}
// Reaching here, apply any peer changes to the netmap cache (if relevant).
// Note we do this AFTER the updates are applied in the nodeBackend, so that
// we can get its updated views to put back into the cache.
if buildfeatures.HasCacheNetMap &&
cn.SelfHasCap(tailcfg.NodeAttrCacheNetworkMaps) &&
envknob.BoolDefaultTrue("TS_USE_CACHED_NETMAP") {
var peersToUpdate []tailcfg.NodeView
for id := range updateIDs {
if n, ok := cn.NodeByID(id); ok {
peersToUpdate = append(peersToUpdate, n)
}
}
var peersToRemove []tailcfg.StableNodeID
for id := range removeIDs {
if n, ok := cn.NodeByID(id); ok {
peersToRemove = append(peersToRemove, n.StableID())
}
}
if err := b.writePeerDeltaToDiskLocked(peersToUpdate, peersToRemove); err != nil {
b.logf("update netmap cache for peer deltas: %v", err)
}
}
// A single MapResponse can carry upserts/removes (full Nodes) AND
// per-field patches in the same delta. Build one Notify that
// reflects all of them; per-session stripping in [sendToLocked]