state: return a node's primary routes in stable order

They came from map iteration, so an unchanged router rendered its self
node differently on every call.

Updates #3502
This commit is contained in:
Kristoffer Dalby
2026-10-02 10:51:16 +00:00
committed by Kristoffer Dalby
parent 31582dd2c2
commit ba413fca3d
2 changed files with 25 additions and 1 deletions
+3 -1
View File
@@ -986,7 +986,7 @@ func (s *NodeStore) PrimaryRouteFor(prefix netip.Prefix) (types.NodeID, bool) {
}
// PrimaryRoutesForNode returns the prefixes for which id is the current
// primary advertiser.
// primary advertiser, sorted so an unchanged node renders identically.
func (s *NodeStore) PrimaryRoutesForNode(id types.NodeID) []netip.Prefix {
snap := s.data.Load()
if !snap.isPrimaryRoute[id] {
@@ -1001,6 +1001,8 @@ func (s *NodeStore) PrimaryRoutesForNode(id types.NodeID) []netip.Prefix {
}
}
slices.SortFunc(out, netip.Prefix.Compare)
return out
}
+22
View File
@@ -1496,6 +1496,28 @@ func TestHealthOnlyWriteReusesPeerMap(t *testing.T) {
peersCalls.Load())
}
// TestPrimaryRoutesForNodeSorted checks a router's primaries come back in
// a stable order: they render into its self node, which the mapper resends
// whenever it differs from what the node holds (issue #3502).
func TestPrimaryRoutesForNodeSorted(t *testing.T) {
node := createTestNode(1, 1, "user1", "router")
prefixes := make([]netip.Prefix, 0, 8)
for i := range 8 {
prefixes = append(prefixes, netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i), 0, 0}), 16))
}
node.Hostinfo = &tailcfg.Hostinfo{Hostname: "router", RoutableIPs: prefixes}
node.ApprovedRoutes = prefixes
node.IsOnline = new(true)
store := NewNodeStore(types.Nodes{&node}, allowAllPeersFunc, TestBatchSize, TestBatchTimeout)
for range 20 {
require.Equal(t, prefixes, store.PrimaryRoutesForNode(1))
}
}
func BenchmarkSnapshotPayloadDense(b *testing.B) {
const nodeCount = 500