ipn/ipnlocal: prefer to use one CGNAT route on the Mac

Together with 06aa141632 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 <mihai@tailscale.com>
This commit is contained in:
Mihai Parparita 2022-06-29 13:25:45 -07:00 committed by Mihai Parparita
parent 3f4fd64311
commit ec649e707f
2 changed files with 55 additions and 1 deletions

View File

@ -2175,7 +2175,6 @@ func (b *LocalBackend) authReconfig() {
nm := b.netMap nm := b.netMap
hasPAC := b.prevIfState.HasPAC() hasPAC := b.prevIfState.HasPAC()
disableSubnetsIfPAC := nm != nil && nm.Debug != nil && nm.Debug.DisableSubnetsIfPAC.EqualBool(true) 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() b.mu.Unlock()
if blocked { if blocked {
@ -2220,6 +2219,7 @@ func (b *LocalBackend) authReconfig() {
return return
} }
oneCGNATRoute := shouldUseOneCGNATRoute(nm, b.logf, version.OS())
rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute) rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute)
dcfg := dnsConfigForNetmap(nm, prefs, b.logf, version.OS()) dcfg := dnsConfigForNetmap(nm, prefs, b.logf, version.OS())
@ -2232,6 +2232,38 @@ func (b *LocalBackend) authReconfig() {
b.initPeerAPIListener() 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, // dnsConfigForNetmap returns a *dns.Config for the given netmap,
// prefs, client OS version, and cloud hosting environment. // prefs, client OS version, and cloud hosting environment.
// //

View File

@ -720,3 +720,25 @@ func DefaultRouteInterface() (string, error) {
func DefaultRoute() (DefaultRouteDetails, error) { func DefaultRoute() (DefaultRouteDetails, error) {
return defaultRoute() 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
}