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 +}