From e9a47f3c04133cc62ff3dd8d8c6ca7e287373856 Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Tue, 9 Dec 2025 14:00:24 +0000 Subject: [PATCH] ipn,ipn/local: always accept routes for Tailscale Services (cgnat range) Updates #18198 Co-authored-by: James Tucker Signed-off-by: chaosinthecrd --- ipn/ipnlocal/local.go | 48 +++++++++++++++--- ipn/ipnlocal/local_test.go | 99 ++++++++++++++++++++++++++++++++++++++ types/netmap/netmap.go | 8 ++- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index cebb96130..83cd09c25 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -5051,7 +5051,6 @@ func (b *LocalBackend) authReconfig() { // // b.mu must be held. func (b *LocalBackend) authReconfigLocked() { - if b.shutdownCalled { b.logf("[v1] authReconfig: skipping because in shutdown") return @@ -5083,9 +5082,9 @@ func (b *LocalBackend) authReconfigLocked() { } var flags netmap.WGConfigFlags - if prefs.RouteAll() { - flags |= netmap.AllowSubnetRoutes - } + // NOTE: we want to always allow subnet routes for the sake of enabling services + flags |= netmap.AllowSubnetRoutes + if hasPAC && disableSubnetsIfPAC { if flags&netmap.AllowSubnetRoutes != 0 { b.logf("authReconfig: have PAC; disabling subnet routes") @@ -5429,6 +5428,34 @@ func peerRoutes(logf logger.Logf, peers []wgcfg.Peer, cgnatThreshold int) (route return routes } +func (b *LocalBackend) servicesRoutes(routes []netip.Prefix) []netip.Prefix { + var servicesRangeBuilder netipx.IPSetBuilder + servicesRangeBuilder.AddPrefix(tsaddr.CGNATRange()) + servicesRangeBuilder.AddPrefix(tsaddr.TailscaleULARange()) + + services, err := servicesRangeBuilder.IPSet() + if err != nil { + b.logf("accept routes filter: failed to build filtered set, all routes will be accepted: %v (check accept-routes flag)", err) + return routes + } + + var builder netipx.IPSetBuilder + for _, r := range routes { + builder.AddPrefix(r) + } + + builder.Intersect(services) + + set, err := builder.IPSet() + if err != nil { + b.logf("accept routes filter: failed to build filtered set, all routes will be accepted: %v (check accept-routes flag)", err) + return routes + } + + b.logf("accept routes filter: accepting routes: %v", set.Ranges()) + return set.Prefixes() +} + // routerConfig produces a router.Config from a wireguard config and IPN prefs. // // b.mu must be held. @@ -5455,13 +5482,20 @@ func (b *LocalBackend) routerConfigLocked(cfg *wgcfg.Config, prefs ipn.PrefsView doStatefulFiltering = true } + var routes []netip.Prefix + if prefs.RouteAll() { + routes = peerRoutes(b.logf, cfg.Peers, singleRouteThreshold) + } else { + routes = b.servicesRoutes(peerRoutes(b.logf, cfg.Peers, singleRouteThreshold)) + } + rs := &router.Config{ LocalAddrs: unmapIPPrefixes(cfg.Addresses), SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes().AsSlice()), SNATSubnetRoutes: !prefs.NoSNAT(), StatefulFiltering: doStatefulFiltering, NetfilterMode: prefs.NetfilterMode(), - Routes: peerRoutes(b.logf, cfg.Peers, singleRouteThreshold), + Routes: routes, NetfilterKind: netfilterKind, } @@ -7774,9 +7808,7 @@ func maybeUsernameOf(actor ipnauth.Actor) string { return username } -var ( - metricCurrentWatchIPNBus = clientmetric.NewGauge("localbackend_current_watch_ipn_bus") -) +var metricCurrentWatchIPNBus = clientmetric.NewGauge("localbackend_current_watch_ipn_bus") func (b *LocalBackend) stateEncrypted() opt.Bool { switch runtime.GOOS { diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 02997a0e1..12a3fb685 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -7295,3 +7295,102 @@ func TestStripKeysFromPrefs(t *testing.T) { }) } } + +func TestRouteAllDisabled(t *testing.T) { + pp := netip.MustParsePrefix + + tests := []struct { + name string + peers []wgcfg.Peer + wantEndpoints []netip.Prefix + routeAll bool + }{ + { + name: "route_all_disabled", + routeAll: false, + peers: []wgcfg.Peer{ + { + AllowedIPs: []netip.Prefix{ + // if one ip in the Tailscale ULA range is added, the entire range is added to the router config + pp("fd7a:115c:a1e0::2501:9b83/128"), + pp("100.80.207.38/32"), + pp("100.80.207.56/32"), + pp("100.80.207.40/32"), + pp("100.94.122.93/32"), + pp("100.79.141.115/32"), + + // ips outside the tailscale cgnat/ula range are not added to the router config + pp("192.168.0.45/32"), + pp("fd7a:115c:b1e0::2501:9b83/128"), + pp("fdf8:f966:e27c:0:5:0:0:10/128"), + }, + }, + }, + wantEndpoints: []netip.Prefix{ + pp("100.80.207.38/32"), + pp("100.80.207.56/32"), + pp("100.80.207.40/32"), + pp("100.94.122.93/32"), + pp("100.79.141.115/32"), + pp("fd7a:115c:a1e0::/48"), + }, + }, + { + name: "route_all_enabled", + routeAll: true, + peers: []wgcfg.Peer{ + { + AllowedIPs: []netip.Prefix{ + // if one ip in the Tailscale ULA range is added, the entire range is added to the router config + pp("fd7a:115c:a1e0::2501:9b83/128"), + pp("100.80.207.38/32"), + pp("100.80.207.56/32"), + pp("100.80.207.40/32"), + pp("100.94.122.93/32"), + pp("100.79.141.115/32"), + + // ips outside the tailscale cgnat/ula range are not added to the router config + pp("192.168.0.45/32"), + pp("fd7a:115c:b1e0::2501:9b83/128"), + pp("fdf8:f966:e27c:0:5:0:0:10/128"), + }, + }, + }, + wantEndpoints: []netip.Prefix{ + pp("100.80.207.38/32"), + pp("100.80.207.56/32"), + pp("100.80.207.40/32"), + pp("100.94.122.93/32"), + pp("100.79.141.115/32"), + pp("192.168.0.45/32"), + pp("fd7a:115c:a1e0::/48"), + pp("fd7a:115c:b1e0::2501:9b83/128"), + pp("fdf8:f966:e27c:0:5:0:0:10/128"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + prefs := ipn.Prefs{RouteAll: tt.routeAll} + lb := newTestLocalBackend(t) + cfg := &wgcfg.Config{ + Peers: tt.peers, + } + + rcfg := lb.routerConfigLocked(cfg, prefs.View(), false) + for _, p := range rcfg.Routes { + found := false + for _, r := range tt.wantEndpoints { + if p.Addr() == r.Addr() { + found = true + break + } + } + if !found { + t.Errorf("unexpected prefix %q in router config", p.String()) + } + } + }) + } +} diff --git a/types/netmap/netmap.go b/types/netmap/netmap.go index c54562f4d..60dc38d48 100644 --- a/types/netmap/netmap.go +++ b/types/netmap/netmap.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/tka" "tailscale.com/types/key" @@ -154,8 +155,11 @@ func (nm *NetworkMap) SelfNodeOrZero() tailcfg.NodeView { // AnyPeersAdvertiseRoutes reports whether any peer is advertising non-exit node routes. func (nm *NetworkMap) AnyPeersAdvertiseRoutes() bool { for _, p := range nm.Peers { - if p.PrimaryRoutes().Len() > 0 { - return true + // NOTE: (ChaosInTheCRD) if the peer being advertised is a tailscale ip, we ignore it in this check + for _, r := range p.PrimaryRoutes().AsSlice() { + if !tsaddr.IsTailscaleIP(r.Addr()) { + return true + } } } return false