diff --git a/hscontrol/mapper/mapper_test.go b/hscontrol/mapper/mapper_test.go index d6d6088b..9c736ccb 100644 --- a/hscontrol/mapper/mapper_test.go +++ b/hscontrol/mapper/mapper_test.go @@ -484,12 +484,12 @@ func TestBuildFromChangeVisibilityMatchesFullMap(t *testing.T) { return false } - // wantFull pins the actual peer-visibility semantics so the invariant below - // cannot pass vacuously (e.g. if every path broke to zero identically). - // Note deny_all: an empty ACL set compiles to zero matchers, which headscale - // treats as "no visibility restriction" — all peers are visible on every - // path (the packet filter denies traffic separately). user_isolation and - // autogroup_self are the discriminating cases that prove filtering works. + // wantFull pins the actual peer-visibility semantics so the cross-path + // check below cannot pass vacuously (e.g. if every path broke to zero + // identically). + // Note deny_all: an empty ACL set yields no peer adjacency, so nothing is + // visible on any path. user_isolation and autogroup_self remain the + // discriminating cases that prove filtering works. tests := []struct { name string policy string @@ -504,7 +504,7 @@ func TestBuildFromChangeVisibilityMatchesFullMap(t *testing.T) { ]}`, 1, }, - {"deny_all", `{"acls":[]}`, 2}, + {"deny_all", `{"acls":[]}`, 0}, { "autogroup_self", `{"acls":[{"action":"accept","src":["autogroup:member"],"dst":["autogroup:self:*"]}]}`, diff --git a/hscontrol/policy/policy_test.go b/hscontrol/policy/policy_test.go index 45f0d629..41312f1e 100644 --- a/hscontrol/policy/policy_test.go +++ b/hscontrol/policy/policy_test.go @@ -284,11 +284,7 @@ func TestBuildPeerMapFromPolicy(t *testing.T) { want = append(want, n.ID) } - var got []types.NodeID - for _, n := range pm.BuildPeerMap(tt.nodes.ViewSlice())[tt.node.ID] { - got = append(got, n.ID()) - } - + got := pm.BuildPeerMap(tt.nodes.ViewSlice())[tt.node.ID] if !assert.ElementsMatch(t, want, got) { t.Log("Matchers: ") diff --git a/hscontrol/state/state.go b/hscontrol/state/state.go index fb0f6263..c0d355d7 100644 --- a/hscontrol/state/state.go +++ b/hscontrol/state/state.go @@ -871,14 +871,10 @@ func (s *State) ListPeers(nodeID types.NodeID, peerIDs ...types.NodeID) views.Sl return s.nodeStore.ListPeers(nodeID) } - // For specific peerIDs, filter from all nodes. - // This path is used for incremental updates (NodeAdded, NodeChanged) - // where the caller already knows which peer IDs are involved. - // Peer visibility filtering happens in the mapper against the live - // policy (buildTailPeers and the shared visiblePeerIDs filter), because - // the snapshot peer map is not rebuilt on policy changes. - allNodes := s.nodeStore.ListNodes() - + // Incremental updates (NodeAdded, NodeChanged) name the peers involved. + // Resolve them through the recipient's adjacency so a changed node the + // policy hides from this recipient is never delivered; the mapper still + // applies the live matchers on top. nodeIDSet := make(map[types.NodeID]struct{}, len(peerIDs)) for _, id := range peerIDs { nodeIDSet[id] = struct{}{} @@ -886,20 +882,11 @@ func (s *State) ListPeers(nodeID types.NodeID, peerIDs ...types.NodeID) views.Sl var filteredNodes []types.NodeView - for _, node := range allNodes.All() { - // A node is never its own peer. [db.ListPeers] enforces this with - // `id <> nodeID`; the caller may name the recipient in peerIDs - // (a change batch that includes it), and the mapper's only other - // self filter is [policy.ReduceNodes], which is skipped when the - // node has no matchers. Self would then reach the client in - // [tailcfg.MapResponse.PeersChanged], where it is merged into the - // peer map and listed alongside the self node. - if node.ID() == nodeID { - continue - } - - if _, exists := nodeIDSet[node.ID()]; exists { - filteredNodes = append(filteredNodes, node) + // Adjacency is built from node pairs, so it never contains the + // recipient: a change batch naming it cannot return it as its own peer. + for _, peer := range s.nodeStore.ListPeers(nodeID).All() { + if _, exists := nodeIDSet[peer.ID()]; exists { + filteredNodes = append(filteredNodes, peer) } }