From 82edf94df72a52bcdc95fe37b20217e6003d92c0 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 4 Mar 2021 12:04:31 -0800 Subject: [PATCH] ipn/ipnlocal: make IPv6 OS routes be a single /48 for our ULA space And if we have over 10,000 CGNAT routes, just route the entire CGNAT range. (for the hello test server) Fixes #1450 Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 46 +++++++++++++++++-- ipn/ipnlocal/local_test.go | 94 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 5 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index e3a60515a..1d1502605 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1362,6 +1362,41 @@ func magicDNSRootDomains(nm *netmap.NetworkMap) []string { ipv6Default = netaddr.MustParseIPPrefix("::/0") ) +// peerRoutes returns the routerConfig.Routes to access peers. +// If there are over cgnatThreshold CGNAT routes, one big CGNAT route +// is used instead. +func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPrefix) { + tsULA := tsaddr.TailscaleULARange() + cgNAT := tsaddr.CGNATRange() + var didULA bool + var cgNATIPs []netaddr.IPPrefix + for _, peer := range peers { + for _, aip := range peer.AllowedIPs { + aip = unmapIPPrefix(aip) + // Only add the Tailscale IPv6 ULA once, if we see anybody using part of it. + if aip.IP.Is6() && aip.IsSingleIP() && tsULA.Contains(aip.IP) { + if !didULA { + didULA = true + routes = append(routes, tsULA) + } + continue + } + if aip.IsSingleIP() && cgNAT.Contains(aip.IP) { + cgNATIPs = append(cgNATIPs, aip) + } else { + routes = append(routes, aip) + } + } + } + if len(cgNATIPs) > cgnatThreshold { + // Probably the hello server. Just append one big route. + routes = append(routes, cgNAT) + } else { + routes = append(routes, cgNATIPs...) + } + return routes +} + // routerConfig produces a router.Config from a wireguard config and IPN prefs. func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { rs := &router.Config{ @@ -1369,10 +1404,7 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes), SNATSubnetRoutes: !prefs.NoSNAT, NetfilterMode: prefs.NetfilterMode, - } - - for _, peer := range cfg.Peers { - rs.Routes = append(rs.Routes, unmapIPPrefixes(peer.AllowedIPs)...) + Routes: peerRoutes(cfg.Peers, 10_000), } // Sanity check: we expect the control server to program both a v4 @@ -1409,10 +1441,14 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { return rs } +func unmapIPPrefix(ipp netaddr.IPPrefix) netaddr.IPPrefix { + return netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits} +} + func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) { for _, ipps := range ippsList { for _, ipp := range ipps { - ret = append(ret, netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits}) + ret = append(ret, unmapIPPrefix(ipp)) } } return ret diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index f8c1d88df..667cc2287 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -5,12 +5,14 @@ package ipnlocal import ( + "reflect" "testing" "inet.af/netaddr" "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/netmap" + "tailscale.com/wgengine/wgcfg" ) func TestNetworkMapCompare(t *testing.T) { @@ -171,3 +173,95 @@ func TestShrinkDefaultRoute(t *testing.T) { } } } + +func TestPeerRoutes(t *testing.T) { + pp := netaddr.MustParseIPPrefix + tests := []struct { + name string + peers []wgcfg.Peer + want []netaddr.IPPrefix + }{ + { + name: "small_v4", + peers: []wgcfg.Peer{ + { + AllowedIPs: []netaddr.IPPrefix{ + pp("100.101.102.103/32"), + }, + }, + }, + want: []netaddr.IPPrefix{ + pp("100.101.102.103/32"), + }, + }, + { + name: "big_v4", + peers: []wgcfg.Peer{ + { + AllowedIPs: []netaddr.IPPrefix{ + pp("100.101.102.103/32"), + pp("100.101.102.104/32"), + pp("100.101.102.105/32"), + }, + }, + }, + want: []netaddr.IPPrefix{ + pp("100.64.0.0/10"), + }, + }, + { + name: "has_1_v6", + peers: []wgcfg.Peer{ + { + AllowedIPs: []netaddr.IPPrefix{ + pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"), + }, + }, + }, + want: []netaddr.IPPrefix{ + pp("fd7a:115c:a1e0::/48"), + }, + }, + { + name: "has_2_v6", + peers: []wgcfg.Peer{ + { + AllowedIPs: []netaddr.IPPrefix{ + pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"), + pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b241/128"), + }, + }, + }, + want: []netaddr.IPPrefix{ + pp("fd7a:115c:a1e0::/48"), + }, + }, + { + name: "big_v4_big_v6", + peers: []wgcfg.Peer{ + { + AllowedIPs: []netaddr.IPPrefix{ + pp("100.101.102.103/32"), + pp("100.101.102.104/32"), + pp("100.101.102.105/32"), + pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"), + pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b241/128"), + }, + }, + }, + want: []netaddr.IPPrefix{ + pp("fd7a:115c:a1e0::/48"), + pp("100.64.0.0/10"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := peerRoutes(tt.peers, 2) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("got = %v; want %v", got, tt.want) + } + }) + } + +}