diff --git a/network/route.go b/network/route.go index 844e3c3..57fe12d 100644 --- a/network/route.go +++ b/network/route.go @@ -23,6 +23,15 @@ import ( // routing table at add-time. const VPNRouteMetric = 9999 +// PreferLocalRoutes controls whether routes added by AddRoutes are given the +// explicit high VPNRouteMetric priority, so that an overlapping local/ +// connected route always takes precedence over the VPN route to the same +// destination. Defaults to false (routes are added with the OS default +// metric/priority, matching behavior prior to the introduction of +// VPNRouteMetric); callers that want local routes to win opt in by setting +// this to true (e.g. from a config value) before routes are added. +var PreferLocalRoutes = false + // DarwinAddRoute adds a route via the BSD routing table. Unlike Linux/Windows, // BSD's routing table has no per-route metric - preference between an // overlapping local route and this VPN route is instead resolved by @@ -83,12 +92,15 @@ func LinuxAddRoute(destination string, gateway string, interfaceName string) err return fmt.Errorf("invalid destination address: %v", err) } - // Create route. Priority is set explicitly (rather than left at the - // default of 0) so that this route never outranks a local/connected - // route to the same destination - see VPNRouteMetric. + // Create route. When PreferLocalRoutes is enabled, Priority is set + // explicitly (rather than left at the default of 0) so that this route + // never outranks a local/connected route to the same destination - see + // VPNRouteMetric. route := &netlink.Route{ - Dst: ipNet, - Priority: VPNRouteMetric, + Dst: ipNet, + } + if PreferLocalRoutes { + route.Priority = VPNRouteMetric } if gateway != "" { @@ -135,8 +147,10 @@ func LinuxRemoveRoute(destination string, interfaceName string) error { // a local/native route to the same destination on a different // interface (or with a different metric) must never be touched. route := &netlink.Route{ - Dst: ipNet, - Priority: VPNRouteMetric, + Dst: ipNet, + } + if PreferLocalRoutes { + route.Priority = VPNRouteMetric } if interfaceName != "" { diff --git a/network/route_windows.go b/network/route_windows.go index 5bca2d1..a5a3eea 100644 --- a/network/route_windows.go +++ b/network/route_windows.go @@ -84,11 +84,15 @@ func WindowsAddRoute(destination string, gateway string, interfaceName string) e return fmt.Errorf("either gateway or interface must be specified") } - // Add the route using winipcfg. Metric is set explicitly (rather than a - // low value like 1, which would nearly always outrank local routes) so - // that an overlapping local/connected route is preferred over this VPN - // route - see VPNRouteMetric. - err = luid.AddRoute(prefix, nextHop, VPNRouteMetric) + // Add the route using winipcfg. When PreferLocalRoutes is enabled, + // metric is set explicitly (rather than a low value like 1, which would + // nearly always outrank local routes) so that an overlapping local/ + // connected route is preferred over this VPN route - see VPNRouteMetric. + var metric uint32 + if PreferLocalRoutes { + metric = VPNRouteMetric + } + err = luid.AddRoute(prefix, nextHop, metric) if err != nil { return fmt.Errorf("failed to add route: %v", err) } @@ -152,15 +156,19 @@ func WindowsRemoveRoute(destination string, interfaceName string) error { } // Find and delete matching route. When we know which interface we added - // the route on, only delete the entry on that interface with our - // VPNRouteMetric so we never remove an unrelated local/native route to - // the same destination. + // the route on, only delete the entry on that interface with the metric + // we added it with (see PreferLocalRoutes) so we never remove an + // unrelated local/native route to the same destination. + var wantMetric uint32 + if PreferLocalRoutes { + wantMetric = VPNRouteMetric + } for _, route := range routes { routePrefix := route.DestinationPrefix.Prefix() if routePrefix != prefix { continue } - if haveLuid && (route.InterfaceLUID != luid || route.Metric != VPNRouteMetric) { + if haveLuid && (route.InterfaceLUID != luid || route.Metric != wantMetric) { continue } logger.Info("Removing route to %s on interface %s", destination, interfaceName)