From ec649e707f2f111fd01ca649f3e0b5fe867fde99 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Wed, 29 Jun 2022 13:25:45 -0700 Subject: [PATCH] ipn/ipnlocal: prefer to use one CGNAT route on the Mac Together with 06aa1416325424ab2881a9e2442697f9707f363b this minimizes the number of NEPacketTunnelNetworkSettings updates that we have to do, and thus avoids Chrome interrupting outstanding requests due to (perceived) network changes. Updates #3102 Signed-off-by: Mihai Parparita --- ipn/ipnlocal/local.go | 34 +++++++++++++++++++++++++++++++++- net/interfaces/interfaces.go | 22 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index e9431a39a..cabda6f47 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -2175,7 +2175,6 @@ func (b *LocalBackend) authReconfig() { nm := b.netMap hasPAC := b.prevIfState.HasPAC() disableSubnetsIfPAC := nm != nil && nm.Debug != nil && nm.Debug.DisableSubnetsIfPAC.EqualBool(true) - oneCGNATRoute := nm != nil && nm.Debug != nil && nm.Debug.OneCGNATRoute.EqualBool(true) b.mu.Unlock() if blocked { @@ -2220,6 +2219,7 @@ func (b *LocalBackend) authReconfig() { return } + oneCGNATRoute := shouldUseOneCGNATRoute(nm, b.logf, version.OS()) rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute) dcfg := dnsConfigForNetmap(nm, prefs, b.logf, version.OS()) @@ -2232,6 +2232,38 @@ func (b *LocalBackend) authReconfig() { b.initPeerAPIListener() } +// shouldUseOneCGNATRoute reports whether we should prefer to make one big +// CGNAT /10 route rather than a /32 per peer. +// +// The versionOS is a Tailscale-style version ("iOS", "macOS") and not +// a runtime.GOOS. +func shouldUseOneCGNATRoute(nm *netmap.NetworkMap, logf logger.Logf, versionOS string) bool { + // Explicit enabling or disabling always take precedence. + if nm.Debug != nil { + if v, ok := nm.Debug.OneCGNATRoute.Get(); ok { + logf("[v1] shouldUseOneCGNATRoute: explicit=%v", v) + return v + } + } + // Also prefer to do this on the Mac, so that we don't need to constantly + // update the network extension configuration (which is disruptive to + // Chrome, see https://github.com/tailscale/tailscale/issues/3102). Only + // use fine-grained routes if another interfaces is also using the CGNAT + // IP range. + if versionOS == "macOS" { + hasCGNATInterface, err := interfaces.HasCGNATInterface() + if err != nil { + logf("shouldUseOneCGNATRoute: Could not determine if any interfaces use CGNAT: %v", err) + return false + } + logf("[v1] shouldUseOneCGNATRoute: macOS automatic=%v", !hasCGNATInterface) + if !hasCGNATInterface { + return true + } + } + return false +} + // dnsConfigForNetmap returns a *dns.Config for the given netmap, // prefs, client OS version, and cloud hosting environment. // diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index 4f6c6e680..d724af602 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -720,3 +720,25 @@ func DefaultRouteInterface() (string, error) { func DefaultRoute() (DefaultRouteDetails, error) { return defaultRoute() } + +// HasCGNATInterface reports whether there are any non-Tailscale interfaces that +// use a CGNAT IP range. +func HasCGNATInterface() (bool, error) { + hasCGNATInterface := false + cgnatRange := tsaddr.CGNATRange() + err := ForeachInterface(func(i Interface, pfxs []netaddr.IPPrefix) { + if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) { + return + } + for _, pfx := range pfxs { + if cgnatRange.Overlaps(pfx) { + hasCGNATInterface = true + break + } + } + }) + if err != nil { + return false, err + } + return hasCGNATInterface, nil +}