net/interfaces: fix default route lookup on Windows

It wasn't using the right metric. Apparently you're supposed to sum the route
metric and interface metric. Whoops.

While here, optimize a few little things too, not that this code
should be too hot.

Fixes #2707 (at least; probably dups but I'm failing to find)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-08-26 12:00:18 -07:00 committed by Brad Fitzpatrick
parent 4aab083cae
commit 3606e68721

View File

@ -112,22 +112,36 @@ func NonTailscaleMTUs() (map[winipcfg.LUID]uint32, error) {
return mtus, err return mtus, err
} }
func notTailscaleInterface(iface *winipcfg.IPAdapterAddresses) bool {
// TODO(bradfitz): do this without the Description method's
// utf16-to-string allocation. But at least we only do it for
// the virtual interfaces, for which there won't be many.
return !(iface.IfType == winipcfg.IfTypePropVirtual &&
iface.Description() == tsconst.WintunInterfaceDesc)
}
// NonTailscaleInterfaces returns a map of interface LUID to interface // NonTailscaleInterfaces returns a map of interface LUID to interface
// for all interfaces except Tailscale tunnels. // for all interfaces except Tailscale tunnels.
func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) { func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
ifs, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces) return getInterfaces(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces, notTailscaleInterface)
}
// getInterfaces returns a map of interfaces keyed by their LUID for
// all interfaces matching the provided match predicate.
//
// The family (AF_UNSPEC, AF_INET, or AF_INET6) and flags are passed
// to winipcfg.GetAdaptersAddresses.
func getInterfaces(family winipcfg.AddressFamily, flags winipcfg.GAAFlags, match func(*winipcfg.IPAdapterAddresses) bool) (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
ifs, err := winipcfg.GetAdaptersAddresses(family, flags)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret := map[winipcfg.LUID]*winipcfg.IPAdapterAddresses{} ret := map[winipcfg.LUID]*winipcfg.IPAdapterAddresses{}
for _, iface := range ifs { for _, iface := range ifs {
if iface.Description() == tsconst.WintunInterfaceDesc { if match(iface) {
continue ret[iface.LUID] = iface
} }
ret[iface.LUID] = iface
} }
return ret, nil return ret, nil
} }
@ -135,8 +149,26 @@ func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, e
// default route for the given address family. // default route for the given address family.
// //
// It returns (nil, nil) if no interface is found. // It returns (nil, nil) if no interface is found.
//
// The family must be one of AF_INET or AF_INET6.
func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddresses, error) { func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddresses, error) {
ifs, err := NonTailscaleInterfaces() ifs, err := getInterfaces(family, winipcfg.GAAFlagIncludeAllInterfaces, func(iface *winipcfg.IPAdapterAddresses) bool {
switch iface.IfType {
case winipcfg.IfTypeSoftwareLoopback:
return false
}
switch family {
case windows.AF_INET:
if iface.Flags&winipcfg.IPAAFlagIpv4Enabled == 0 {
return false
}
case windows.AF_INET6:
if iface.Flags&winipcfg.IPAAFlagIpv6Enabled == 0 {
return false
}
}
return iface.OperStatus == winipcfg.IfOperStatusUp && notTailscaleInterface(iface)
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -149,12 +181,31 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
bestMetric := ^uint32(0) bestMetric := ^uint32(0)
var bestIface *winipcfg.IPAdapterAddresses var bestIface *winipcfg.IPAdapterAddresses
for _, route := range routes { for _, route := range routes {
iface := ifs[route.InterfaceLUID] if route.DestinationPrefix.PrefixLength != 0 {
if route.DestinationPrefix.PrefixLength != 0 || iface == nil { // Not a default route.
continue continue
} }
if iface.OperStatus == winipcfg.IfOperStatusUp && route.Metric < bestMetric { iface := ifs[route.InterfaceLUID]
bestMetric = route.Metric if iface == nil {
continue
}
// Microsoft docs say:
//
// "The actual route metric used to compute the route
// preferences for IPv4 is the summation of the route
// metric offset specified in the Metric member of the
// MIB_IPFORWARD_ROW2 structure and the interface
// metric specified in this member for IPv4"
metric := route.Metric
switch family {
case windows.AF_INET:
metric += iface.Ipv4Metric
case windows.AF_INET6:
metric += iface.Ipv6Metric
}
if metric < bestMetric {
bestMetric = metric
bestIface = iface bestIface = iface
} }
} }
@ -163,6 +214,9 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
} }
func DefaultRouteInterface() (string, error) { func DefaultRouteInterface() (string, error) {
// We always return the IPv4 default route.
// TODO(bradfitz): adjust API if/when anything cares. They could in theory differ, though,
// in which case we might send traffic to the wrong interface.
iface, err := GetWindowsDefault(windows.AF_INET) iface, err := GetWindowsDefault(windows.AF_INET)
if err != nil { if err != nil {
return "", err return "", err