From c66430c67049ad46ba66d5412051327f760d1350 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 1 Nov 2025 22:43:43 +0100 Subject: [PATCH] types: add order-independent endpoint comparison Add EndpointsChanged function that compares endpoint slices without regard to ordering. Uses netip.AddrPort.Compare with slices.SortFunc to enable accurate change detection. Update PeerChangeFromMapRequest to only include endpoints in PeerChange when they actually changed, avoiding unnecessary updates to peers. --- hscontrol/types/node.go | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index 8cf40ced..0481f621 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -509,8 +509,10 @@ func (node *Node) PeerChangeFromMapRequest(req tailcfg.MapRequest) tailcfg.PeerC } } - // TODO(kradalby): Find a good way to compare updates - ret.Endpoints = req.Endpoints + // Compare endpoints using order-independent comparison + if EndpointsChanged(node.Endpoints, req.Endpoints) { + ret.Endpoints = req.Endpoints + } now := time.Now() ret.LastSeen = &now @@ -518,6 +520,32 @@ func (node *Node) PeerChangeFromMapRequest(req tailcfg.MapRequest) tailcfg.PeerC return ret } +// EndpointsChanged compares two endpoint slices and returns true if they differ. +// The comparison is order-independent - endpoints are sorted before comparison. +func EndpointsChanged(oldEndpoints, newEndpoints []netip.AddrPort) bool { + if len(oldEndpoints) != len(newEndpoints) { + return true + } + + if len(oldEndpoints) == 0 { + return false + } + + // Make copies to avoid modifying the original slices + oldCopy := slices.Clone(oldEndpoints) + newCopy := slices.Clone(newEndpoints) + + // Sort both slices to enable order-independent comparison + slices.SortFunc(oldCopy, func(a, b netip.AddrPort) int { + return a.Compare(b) + }) + slices.SortFunc(newCopy, func(a, b netip.AddrPort) int { + return a.Compare(b) + }) + + return !slices.Equal(oldCopy, newCopy) +} + func (node *Node) RegisterMethodToV1Enum() v1.RegisterMethod { switch node.RegisterMethod { case "authkey":