diff --git a/hscontrol/mapper/tail_test.go b/hscontrol/mapper/tail_test.go index 83e97935f..0a702c81b 100644 --- a/hscontrol/mapper/tail_test.go +++ b/hscontrol/mapper/tail_test.go @@ -137,8 +137,8 @@ func TestTailNode(t *testing.T) { ), Addresses: []netip.Prefix{netip.MustParsePrefix("100.64.0.1/32")}, AllowedIPs: []netip.Prefix{ - tsaddr.AllIPv4(), netip.MustParsePrefix("100.64.0.1/32"), + tsaddr.AllIPv4(), netip.MustParsePrefix("192.168.0.0/24"), tsaddr.AllIPv6(), }, @@ -399,11 +399,9 @@ func TestTailNodeDisableIPv4(t *testing.T) { name: "cap_keeps_advertised_subnet_route", hasCap: true, approved: []netip.Prefix{subnet}, - // AllowedIPs is sorted by netip.Prefix.Compare so IPv4 - // sorts before IPv6. wantAllowed: []netip.Prefix{ - subnet, netip.MustParsePrefix("fd7a:115c:a1e0::1/128"), + subnet, }, wantPrimary: []netip.Prefix{subnet}, wantAddrs: []netip.Prefix{netip.MustParsePrefix("fd7a:115c:a1e0::1/128")}, diff --git a/hscontrol/servertest/via_compat_test.go b/hscontrol/servertest/via_compat_test.go index ae9264916..d9bfdc656 100644 --- a/hscontrol/servertest/via_compat_test.go +++ b/hscontrol/servertest/via_compat_test.go @@ -601,9 +601,11 @@ func selfRoutes(nm *netmap.NetworkMap) string { var allowed, primary []string - for _, pfx := range self.AllowedIPs().All() { + for i, pfx := range self.AllowedIPs().All() { if !views.SliceContains(self.Addresses(), pfx) { allowed = append(allowed, pfx.String()) + } else if i >= self.Addresses().Len() { + allowed = append(allowed, "own address after a route: "+pfx.String()) } } @@ -828,6 +830,10 @@ func comparePeers( prefix := peer.AllowedIPs().At(i) if isTailscaleIP(prefix) { tsIPs = append(tsIPs, prefix) + + // As SaaS: own addresses lead, and whois reads routes by position. + assert.Lessf(check["peer-addresses"], i, peer.Addresses().Len(), + "peer %s: own address %s comes after a route", name, prefix) } else { routePrefixes = append(routePrefixes, prefix.String()) } diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index 84ba4dbc5..31bfe569f 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -1411,8 +1411,10 @@ func (nv NodeView) TailNode( // routeFunc returns ALL routes (subnet + exit) for this node. allRoutes := primaryRouteFunc(nv.ID()) - allowedIPs := slices.Concat(addresses, allRoutes) - slices.SortFunc(allowedIPs, netip.Prefix.Compare) + + // Own addresses lead, as on SaaS: `tailscale whois` prints AllowedIPs[2:] + // as the peer's routes. + allowedIPs := slices.Concat(addresses, slices.SortedFunc(slices.Values(allRoutes), netip.Prefix.Compare)) // PrimaryRoutes only includes non-exit subnet routes for HA tracking. var primaryRoutes []netip.Prefix