mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-11 03:27:20 +02:00
mapper: fix empty Peers list not clearing client peer state
When a FullUpdate produces zero visible peers (e.g., a restrictive policy isolates a node), the MapResponse has Peers: [] (empty non-nil). The Tailscale client only processes Peers as a full replacement when len(Peers) > 0 (controlclient/map.go:462), so an empty list is silently ignored and stale peers persist. This triggers when a FullUpdate() replaces a pending PolicyChange() in the batcher. The PolicyChange would have used computePeerDiff to send explicit PeersRemoved, but the FullUpdate goes through buildFromChange which sets Peers: [] that the client ignores. When a full update produces zero peers, compute the peer diff against lastSentPeers and add explicit PeersRemoved entries so the client correctly clears its stale peer state.
This commit is contained in:
committed by
Kristoffer Dalby
parent
cef5338cfe
commit
9371b4ee28
@@ -128,6 +128,30 @@ func generateMapResponse(nc nodeConnection, mapper *mapper, r change.Change) (*t
|
||||
return nil, fmt.Errorf("generating map response for nodeID %d: %w", nodeID, err)
|
||||
}
|
||||
|
||||
// When a full update (SendAllPeers=true) produces zero visible peers
|
||||
// (e.g., a restrictive policy isolates this node), the resulting
|
||||
// MapResponse has Peers: []*tailcfg.Node{} (empty non-nil slice).
|
||||
//
|
||||
// The Tailscale client only treats Peers as a full authoritative
|
||||
// replacement when len(Peers) > 0 (controlclient/map.go:462).
|
||||
// An empty Peers slice is indistinguishable from a delta response,
|
||||
// so the client silently preserves its existing peer state.
|
||||
//
|
||||
// This matters when a FullUpdate() replaces a pending PolicyChange()
|
||||
// in the batcher (addToBatch short-circuits on HasFull). The
|
||||
// PolicyChange would have computed PeersRemoved via computePeerDiff,
|
||||
// but the FullUpdate path uses WithPeers which sets Peers: [].
|
||||
//
|
||||
// Fix: when a full update results in zero peers, compute the diff
|
||||
// against lastSentPeers and add explicit PeersRemoved entries so
|
||||
// the client correctly clears its stale peer state.
|
||||
if mapResp != nil && r.SendAllPeers && len(mapResp.Peers) == 0 {
|
||||
removedPeers := nc.computePeerDiff(nil)
|
||||
if len(removedPeers) > 0 {
|
||||
mapResp.PeersRemoved = removedPeers
|
||||
}
|
||||
}
|
||||
|
||||
return mapResp, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user