Files
tailscale/control/controlclient
14449d9e36 [release-branch] fix various netmap delta bugs (#21121)
* ipn/ipnlocal: fix cache update for peers deleted by netmap deltas (#20851)

After a netmap delta is applied, we scan the mutations for affected peers and
update the cache (if enabled) for those peers. For removals in particular, we
were relying on the node backend to resolve node IDs (provided by the delta
mutation) to stable IDs.

Prior to 65fd320a this happened to work because the node backend would hold on
to all the peers mentioned by the previous full netmap, even after applying
deltas. But that was essentially accidental, and once we fixed it not to do
that, these lookups no longer worked. We need the stable ID, since that is how
the cache is keyed, and now that they're no longer pinned, we were not properly
evicting removed peers from the cache.

To fix this, capture removed peer stable IDs while applying mutations to the
node backend, instead of trying to look them up afterward.

Updates #20796

Change-Id: I14ded78eaf9657645f0869a52460fd3cd86edba6
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
(cherry picked from commit f3552c29c0)

* ipn/ipnlocal: don't evict another node's index entries on netmap deltas

When applying netmap deltas, nodeBackend evicted its index entries
(nodeByAddr, nodeByKey, nodeByWGString, nodeByStableID, nodeByName)
derived from a node's last-known value without checking that the entry
still pointed at that node. Control can reassign a churning ephemeral
peer's Tailscale IP (or MagicDNS name) to a newer peer and deliver the
new peer's upsert before the old peer's removal, either in an earlier
MapResponse or reordered within one batch by the NodeID sort in
netmap.MutationsFromMapResponse. The removal then wiped the new
owner's entry.

The peers map itself stayed correct in every ordering, so WireGuard
kept the peer and handshakes succeeded, but WhoIs lookups by IP failed
until the next full netmap rebuilt the indexes. On App Connectors that
surfaced as "peerapi: unknown peer" and refused DNS connections from
affected clients, with a toggle of Tailscale (forcing a full netmap)
as the only recovery.

Make every index eviction conditional on the entry still mapping to
the node being removed or replaced, and add a regression test covering
the cross-batch, intra-batch, and upsert-eviction orderings.

Also add an end-to-end test in tstest/integration showing that a
MapResponse reusing an address is handled incrementally rather than as
a full netmap, and that LocalBackend.WhoIs still resolves the reused
address afterwards, which is the lookup PeerAPI makes before it
accepts a connection.

Updates tailscale/corp#47435

Co-authored-by: Brendan Creane <bcreane@gmail.com>
Signed-off-by: Brendan Creane <bcreane@gmail.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3f8c2a9d41e07b6a5cd2e94f78b013c6ad2f5e91
(cherry picked from commit 2ae2808b64)

* ipn/ipnlocal: preserve peer deltas on expiry

Refresh the expiry timer netmap from the live peer state before
reinstalling it, preventing delta updates from being rolled back.

Updates tailscale/corp#47686

Change-Id: Idc738acea82bab5a8ba772084a41e55b38a06bcc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 0640312e51)

* control/controlclient: replay user profiles on delta peer upserts

A full netmap carries only the profiles of users with a currently
visible peer (netmapForResponse), and nodeBackend replaces its live
profile set wholesale on every full netmap install. A full netmap that
arrives while a user has no visible peer therefore drops that user's
profile downstream.

When a peer of that user later returns as an incremental upsert,
control does not resend the profile, because MapResponse.UserProfiles
has carried only new or updated profiles since mapver 5. The upsert
indexes the node by address and key, so WireGuard admits its traffic,
but WhoIs then fails one step later at the user profile lookup,
surfacing as "peerapi: unknown peer" until the next full netmap. It is
a second, independent cause of the symptom fixed by the recent index
eviction change.

mapSession.lastUserProfile holds the profile the whole time, so when
handling a response incrementally, also deliver the profiles of
upserted peers' users (and sharers) from that store, before the
mutations that reference them.

The backport adapts the new tests to this branch's extra viaTSMP
parameter on handleNonKeepAliveMapResponse.

Updates tailscale/corp#47435

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9c4b2f6a8e0d47d3b1a5c2e7f4098d61b23a7c50
(cherry picked from commit 5201273aec)

---------

Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
Signed-off-by: Brendan Creane <bcreane@gmail.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Co-authored-by: M. J. Fromberger <fromberger@tailscale.com>
Co-authored-by: Brendan Creane <bcreane@gmail.com>
2026-09-08 15:08:10 -07:00
..