mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 16:22:03 +01:00 
			
		
		
		
	Updates #7123 Change-Id: Ie9be6814831f661ad5636afcd51d063a0d7a907d Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			835 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			835 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /* SPDX-License-Identifier: MIT
 | |
|  *
 | |
|  * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
 | |
|  */
 | |
| 
 | |
| package osrouter
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"net/netip"
 | |
| 	"slices"
 | |
| 	"sort"
 | |
| 	"time"
 | |
| 
 | |
| 	"tailscale.com/health"
 | |
| 	"tailscale.com/net/netmon"
 | |
| 	"tailscale.com/net/tsaddr"
 | |
| 	"tailscale.com/net/tstun"
 | |
| 	"tailscale.com/wgengine/router"
 | |
| 	"tailscale.com/wgengine/winnet"
 | |
| 
 | |
| 	ole "github.com/go-ole/go-ole"
 | |
| 	"github.com/tailscale/wireguard-go/tun"
 | |
| 	"go4.org/netipx"
 | |
| 	"golang.org/x/sys/windows"
 | |
| 	"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
 | |
| )
 | |
| 
 | |
| // monitorDefaultRoutes subscribes to route change events and updates
 | |
| // the Tailscale tunnel interface's MTU to match that of the
 | |
| // underlying default route.
 | |
| //
 | |
| // This is an attempt at making the MTU mostly correct, but in
 | |
| // practice this entire piece of code ends up just using the 1280
 | |
| // value passed in at device construction time. This code might make
 | |
| // the MTU go lower due to very low-MTU IPv4 interfaces.
 | |
| //
 | |
| // TODO: this code is insufficient to control the MTU correctly. The
 | |
| // correct way to do it is per-peer PMTU discovery, and synthesizing
 | |
| // ICMP fragmentation-needed messages within tailscaled. This code may
 | |
| // address a few rare corner cases, but is unlikely to significantly
 | |
| // help with MTU issues compared to a static 1280B implementation.
 | |
| func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, error) {
 | |
| 	ourLuid := winipcfg.LUID(tun.LUID())
 | |
| 	lastMtu := uint32(0)
 | |
| 	doIt := func() error {
 | |
| 		mtu, err := getDefaultRouteMTU()
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error getting default route MTU: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if mtu > 0 && (lastMtu == 0 || lastMtu != mtu) {
 | |
| 			iface, err := ourLuid.IPInterface(windows.AF_INET)
 | |
| 			if err != nil {
 | |
| 				if !errors.Is(err, windows.ERROR_NOT_FOUND) {
 | |
| 					return fmt.Errorf("getting v4 interface: %w", err)
 | |
| 				}
 | |
| 			} else {
 | |
| 				iface.NLMTU = mtu - 80
 | |
| 				// If the TUN device was created with a smaller MTU,
 | |
| 				// though, such as 1280, we don't want to go bigger
 | |
| 				// than configured. (See the comment on minimalMTU in
 | |
| 				// the wgengine package.)
 | |
| 				if min, err := tun.MTU(); err == nil && min < int(iface.NLMTU) {
 | |
| 					iface.NLMTU = uint32(min)
 | |
| 				}
 | |
| 				if iface.NLMTU < 576 {
 | |
| 					iface.NLMTU = 576
 | |
| 				}
 | |
| 				err = iface.Set()
 | |
| 				if err != nil {
 | |
| 					return fmt.Errorf("error setting v4 MTU: %w", err)
 | |
| 				}
 | |
| 				tun.ForceMTU(int(iface.NLMTU))
 | |
| 			}
 | |
| 			iface, err = ourLuid.IPInterface(windows.AF_INET6)
 | |
| 			if err != nil {
 | |
| 				if !errors.Is(err, windows.ERROR_NOT_FOUND) {
 | |
| 					return fmt.Errorf("error getting v6 interface: %w", err)
 | |
| 				}
 | |
| 			} else {
 | |
| 				iface.NLMTU = mtu - 80
 | |
| 				if iface.NLMTU < 1280 {
 | |
| 					iface.NLMTU = 1280
 | |
| 				}
 | |
| 				err = iface.Set()
 | |
| 				if err != nil {
 | |
| 					return fmt.Errorf("error setting v6 MTU: %w", err)
 | |
| 				}
 | |
| 			}
 | |
| 			lastMtu = mtu
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	err := doIt()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	cb, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
 | |
| 		//fmt.Printf("MonitorDefaultRoutes: changed: %v\n", route.DestinationPrefix)
 | |
| 		if route.DestinationPrefix.PrefixLength == 0 {
 | |
| 			_ = doIt()
 | |
| 		}
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return cb, nil
 | |
| }
 | |
| 
 | |
| func getDefaultRouteMTU() (uint32, error) {
 | |
| 	mtus, err := netmon.NonTailscaleMTUs()
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 
 | |
| 	routes, err := winipcfg.GetIPForwardTable2(windows.AF_INET)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	best := ^uint32(0)
 | |
| 	mtu := uint32(0)
 | |
| 	for _, route := range routes {
 | |
| 		if route.DestinationPrefix.PrefixLength != 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		routeMTU := mtus[route.InterfaceLUID]
 | |
| 		if routeMTU == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if route.Metric < best {
 | |
| 			best = route.Metric
 | |
| 			mtu = routeMTU
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	routes, err = winipcfg.GetIPForwardTable2(windows.AF_INET6)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	best = ^uint32(0)
 | |
| 	for _, route := range routes {
 | |
| 		if route.DestinationPrefix.PrefixLength != 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		routeMTU := mtus[route.InterfaceLUID]
 | |
| 		if routeMTU == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if route.Metric < best {
 | |
| 			best = route.Metric
 | |
| 			if routeMTU < mtu {
 | |
| 				mtu = routeMTU
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return mtu, nil
 | |
| }
 | |
| 
 | |
| // setPrivateNetwork marks the provided network adapter's category to private.
 | |
| // It returns (false, nil) if the adapter was not found.
 | |
| func setPrivateNetwork(ifcLUID winipcfg.LUID) (bool, error) {
 | |
| 	// NLM_NETWORK_CATEGORY values.
 | |
| 	const (
 | |
| 		categoryPublic  = 0
 | |
| 		categoryPrivate = 1
 | |
| 		categoryDomain  = 2
 | |
| 	)
 | |
| 
 | |
| 	ifcGUID, err := ifcLUID.GUID()
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("ifcLUID.GUID: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// aaron: DO NOT call Initialize() or Uninitialize() on c!
 | |
| 	// We've already handled that process-wide.
 | |
| 	var c ole.Connection
 | |
| 
 | |
| 	m, err := winnet.NewNetworkListManager(&c)
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("winnet.NewNetworkListManager: %v", err)
 | |
| 	}
 | |
| 	defer m.Release()
 | |
| 
 | |
| 	cl, err := m.GetNetworkConnections()
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("m.GetNetworkConnections: %v", err)
 | |
| 	}
 | |
| 	defer cl.Release()
 | |
| 
 | |
| 	for _, nco := range cl {
 | |
| 		aid, err := nco.GetAdapterId()
 | |
| 		if err != nil {
 | |
| 			return false, fmt.Errorf("nco.GetAdapterId: %v", err)
 | |
| 		}
 | |
| 		if aid != ifcGUID.String() {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		n, err := nco.GetNetwork()
 | |
| 		if err != nil {
 | |
| 			return false, fmt.Errorf("GetNetwork: %v", err)
 | |
| 		}
 | |
| 		defer n.Release()
 | |
| 
 | |
| 		cat, err := n.GetCategory()
 | |
| 		if err != nil {
 | |
| 			return false, fmt.Errorf("GetCategory: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		if cat != categoryPrivate && cat != categoryDomain {
 | |
| 			if err := n.SetCategory(categoryPrivate); err != nil {
 | |
| 				return false, fmt.Errorf("SetCategory: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 		return true, nil
 | |
| 	}
 | |
| 
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| // interfaceFromLUID returns IPAdapterAddresses with specified LUID.
 | |
| func interfaceFromLUID(luid winipcfg.LUID, flags winipcfg.GAAFlags) (*winipcfg.IPAdapterAddresses, error) {
 | |
| 	addresses, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, flags)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	for _, addr := range addresses {
 | |
| 		if addr.LUID == luid {
 | |
| 			return addr, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, fmt.Errorf("interfaceFromLUID: interface with LUID %v not found", luid)
 | |
| }
 | |
| 
 | |
| var networkCategoryWarnable = health.Register(&health.Warnable{
 | |
| 	Code:     "set-network-category-failed",
 | |
| 	Severity: health.SeverityMedium,
 | |
| 	Title:    "Windows network configuration failed",
 | |
| 	Text: func(args health.Args) string {
 | |
| 		return fmt.Sprintf("Failed to set the network category to private on the Tailscale adapter. This may prevent Tailscale from working correctly. Error: %s", args[health.ArgError])
 | |
| 	},
 | |
| 	MapDebugFlag: "warn-network-category-unhealthy",
 | |
| })
 | |
| 
 | |
| func configureInterface(cfg *router.Config, tun *tun.NativeTun, ht *health.Tracker) (retErr error) {
 | |
| 	var mtu = tstun.DefaultTUNMTU()
 | |
| 	luid := winipcfg.LUID(tun.LUID())
 | |
| 	iface, err := interfaceFromLUID(luid,
 | |
| 		// Issue 474: on early boot, when the network is still
 | |
| 		// coming up, if the Tailscale service comes up first,
 | |
| 		// the Tailscale adapter it finds might not have the
 | |
| 		// IPv4 service available yet? Try this flag:
 | |
| 		winipcfg.GAAFlagIncludeAllInterfaces,
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("getting interface: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Send non-nil return errors to retErrc, to interrupt our background
 | |
| 	// setPrivateNetwork goroutine.
 | |
| 	retErrc := make(chan error, 1)
 | |
| 	defer func() {
 | |
| 		if retErr != nil {
 | |
| 			retErrc <- retErr
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		// It takes a weirdly long time for Windows to notice the
 | |
| 		// new interface has come up. Poll periodically until it
 | |
| 		// does.
 | |
| 		const tries = 20
 | |
| 		for i := range tries {
 | |
| 			found, err := setPrivateNetwork(luid)
 | |
| 			if err != nil {
 | |
| 				ht.SetUnhealthy(networkCategoryWarnable, health.Args{health.ArgError: err.Error()})
 | |
| 				log.Printf("setPrivateNetwork(try=%d): %v", i, err)
 | |
| 			} else {
 | |
| 				ht.SetHealthy(networkCategoryWarnable)
 | |
| 				if found {
 | |
| 					if i > 0 {
 | |
| 						log.Printf("setPrivateNetwork(try=%d): success", i)
 | |
| 					}
 | |
| 					return
 | |
| 				}
 | |
| 				log.Printf("setPrivateNetwork(try=%d): not found", i)
 | |
| 			}
 | |
| 			select {
 | |
| 			case <-time.After(time.Second):
 | |
| 			case <-retErrc:
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 		log.Printf("setPrivateNetwork: adapter LUID %v not found after %d tries, giving up", luid, tries)
 | |
| 	}()
 | |
| 
 | |
| 	// Figure out which of IPv4 and IPv6 are available. Both protocols
 | |
| 	// can be disabled on a per-interface basis by the user, as well
 | |
| 	// as globally via a registry policy. We skip programming anything
 | |
| 	// related to the disabled protocols, since by definition they're
 | |
| 	// unusable.
 | |
| 	ipif4, err := iface.LUID.IPInterface(windows.AF_INET)
 | |
| 	if err != nil {
 | |
| 		if !errors.Is(err, windows.ERROR_NOT_FOUND) {
 | |
| 			return fmt.Errorf("getting AF_INET interface: %w", err)
 | |
| 		}
 | |
| 		log.Printf("AF_INET interface not found on Tailscale adapter, skipping IPv4 programming")
 | |
| 		ipif4 = nil
 | |
| 	}
 | |
| 	ipif6, err := iface.LUID.IPInterface(windows.AF_INET6)
 | |
| 	if err != nil {
 | |
| 		if !errors.Is(err, windows.ERROR_NOT_FOUND) {
 | |
| 			return fmt.Errorf("getting AF_INET6 interface: %w", err)
 | |
| 		}
 | |
| 		log.Printf("AF_INET6 interface not found on Tailscale adapter, skipping IPv6 programming")
 | |
| 		ipif6 = nil
 | |
| 	}
 | |
| 
 | |
| 	// Windows requires routes to have a nexthop. Routes created using
 | |
| 	// the interface's local IP address or an unspecified IP address
 | |
| 	// ("0.0.0.0" or "::") as the nexthop are considered on-link routes.
 | |
| 	//
 | |
| 	// Notably, Windows treats on-link subnet routes differently, reserving the last
 | |
| 	// IP in the range as the broadcast IP and therefore prohibiting TCP connections
 | |
| 	// to it, resulting in WSA error 10049: "The requested address is not valid in its context."
 | |
| 	// This does not happen with single-host routes, such as routes to Tailscale IP addresses,
 | |
| 	// but becomes a problem with advertised subnets when all IPs in the range should be reachable.
 | |
| 	// See https://github.com/tailscale/support-escalations/issues/57 for details.
 | |
| 	//
 | |
| 	// For routes such as ours where the nexthop is meaningless, we can use an
 | |
| 	// arbitrary nexthop address, such as TailscaleServiceIP, to prevent the
 | |
| 	// routes from being marked as on-link. We can still create on-link routes
 | |
| 	// for single-host Tailscale routes, but we shouldn't attempt to create a
 | |
| 	// route for the interface's own IP.
 | |
| 	var localAddr4, localAddr6 netip.Addr
 | |
| 	var gatewayAddr4, gatewayAddr6 netip.Addr
 | |
| 	addresses := make([]netip.Prefix, 0, len(cfg.LocalAddrs))
 | |
| 	for _, addr := range cfg.LocalAddrs {
 | |
| 		if (addr.Addr().Is4() && ipif4 == nil) || (addr.Addr().Is6() && ipif6 == nil) {
 | |
| 			// Can't program addresses for disabled protocol.
 | |
| 			continue
 | |
| 		}
 | |
| 		addresses = append(addresses, addr)
 | |
| 		if addr.Addr().Is4() && !gatewayAddr4.IsValid() {
 | |
| 			localAddr4 = addr.Addr()
 | |
| 			gatewayAddr4 = tsaddr.TailscaleServiceIP()
 | |
| 		} else if addr.Addr().Is6() && !gatewayAddr6.IsValid() {
 | |
| 			localAddr6 = addr.Addr()
 | |
| 			gatewayAddr6 = tsaddr.TailscaleServiceIPv6()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var routes []*routeData
 | |
| 	foundDefault4 := false
 | |
| 	foundDefault6 := false
 | |
| 	for _, route := range cfg.Routes {
 | |
| 		if (route.Addr().Is4() && ipif4 == nil) || (route.Addr().Is6() && ipif6 == nil) {
 | |
| 			// Can't program routes for disabled protocol.
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if route.Addr().Is6() && !gatewayAddr6.IsValid() {
 | |
| 			// Windows won't let us set IPv6 routes without having an
 | |
| 			// IPv6 local address set. However, when we've configured
 | |
| 			// a default route, we want to forcibly grab IPv6 traffic
 | |
| 			// even if the v6 overlay network isn't configured. To do
 | |
| 			// that, we add a dummy local IPv6 address to serve as a
 | |
| 			// route source.
 | |
| 			ip := tsaddr.Tailscale4To6Placeholder()
 | |
| 			addresses = append(addresses, netip.PrefixFrom(ip, ip.BitLen()))
 | |
| 			gatewayAddr6 = ip
 | |
| 		} else if route.Addr().Is4() && !gatewayAddr4.IsValid() {
 | |
| 			// TODO: do same dummy behavior as v6?
 | |
| 			return errors.New("due to a Windows limitation, one cannot have interface routes without an interface address")
 | |
| 		}
 | |
| 
 | |
| 		var gateway, localAddr netip.Addr
 | |
| 		if route.Addr().Is4() {
 | |
| 			localAddr = localAddr4
 | |
| 			gateway = gatewayAddr4
 | |
| 		} else if route.Addr().Is6() {
 | |
| 			localAddr = localAddr6
 | |
| 			gateway = gatewayAddr6
 | |
| 		}
 | |
| 
 | |
| 		switch destAddr := route.Addr().Unmap(); {
 | |
| 		case destAddr == localAddr:
 | |
| 			// no need to add a route for the interface's
 | |
| 			// own IP. The kernel does that for us.
 | |
| 			// If we try to replace it, we'll fail to
 | |
| 			// add the route unless NextHop is set, but
 | |
| 			// then the interface's IP won't be pingable.
 | |
| 			continue
 | |
| 		case route.IsSingleIP() && (destAddr == gateway || tsaddr.IsTailscaleIP(destAddr)):
 | |
| 			// add an on-link route if the destination
 | |
| 			// is the nexthop itself or a single Tailscale IP.
 | |
| 			gateway = localAddr
 | |
| 		}
 | |
| 
 | |
| 		r := &routeData{
 | |
| 			RouteData: winipcfg.RouteData{
 | |
| 				Destination: route,
 | |
| 				NextHop:     gateway,
 | |
| 				Metric:      0,
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		if route.Addr().Is4() {
 | |
| 			if route.Bits() == 0 {
 | |
| 				foundDefault4 = true
 | |
| 			}
 | |
| 		} else if route.Addr().Is6() {
 | |
| 			if route.Bits() == 0 {
 | |
| 				foundDefault6 = true
 | |
| 			}
 | |
| 		}
 | |
| 		routes = append(routes, r)
 | |
| 	}
 | |
| 
 | |
| 	err = syncAddresses(iface, addresses)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("syncAddresses: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	slices.SortFunc(routes, (*routeData).Compare)
 | |
| 
 | |
| 	deduplicatedRoutes := []*routeData{}
 | |
| 	for i := range len(routes) {
 | |
| 		// There's only one way to get to a given IP+Mask, so delete
 | |
| 		// all matches after the first.
 | |
| 		if i > 0 && routes[i].Destination == routes[i-1].Destination {
 | |
| 			continue
 | |
| 		}
 | |
| 		deduplicatedRoutes = append(deduplicatedRoutes, routes[i])
 | |
| 	}
 | |
| 
 | |
| 	// Re-read interface after syncAddresses.
 | |
| 	iface, err = interfaceFromLUID(luid,
 | |
| 		// Issue 474: on early boot, when the network is still
 | |
| 		// coming up, if the Tailscale service comes up first,
 | |
| 		// the Tailscale adapter it finds might not have the
 | |
| 		// IPv4 service available yet? Try this flag:
 | |
| 		winipcfg.GAAFlagIncludeAllInterfaces,
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("getting interface: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var errAcc error
 | |
| 	err = syncRoutes(iface, deduplicatedRoutes, cfg.LocalAddrs)
 | |
| 	if err != nil && errAcc == nil {
 | |
| 		log.Printf("setroutes: %v", err)
 | |
| 		errAcc = err
 | |
| 	}
 | |
| 
 | |
| 	if ipif4 != nil {
 | |
| 		ipif4, err = iface.LUID.IPInterface(windows.AF_INET)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("getting AF_INET interface: %w", err)
 | |
| 		}
 | |
| 		if foundDefault4 {
 | |
| 			ipif4.UseAutomaticMetric = false
 | |
| 			ipif4.Metric = 0
 | |
| 		}
 | |
| 		if mtu > 0 {
 | |
| 			ipif4.NLMTU = uint32(mtu)
 | |
| 			tun.ForceMTU(int(ipif4.NLMTU))
 | |
| 		}
 | |
| 		err = ipif4.Set()
 | |
| 		if err != nil && errAcc == nil {
 | |
| 			errAcc = err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ipif6 != nil {
 | |
| 		ipif6, err = iface.LUID.IPInterface(windows.AF_INET6)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("getting AF_INET6 interface: %w", err)
 | |
| 		} else {
 | |
| 			if foundDefault6 {
 | |
| 				ipif6.UseAutomaticMetric = false
 | |
| 				ipif6.Metric = 0
 | |
| 			}
 | |
| 			if mtu > 0 {
 | |
| 				ipif6.NLMTU = uint32(mtu)
 | |
| 			}
 | |
| 			ipif6.DadTransmits = 0
 | |
| 			ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
 | |
| 			err = ipif6.Set()
 | |
| 			if err != nil && errAcc == nil {
 | |
| 				errAcc = err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return errAcc
 | |
| }
 | |
| 
 | |
| func netCompare(a, b netip.Prefix) int {
 | |
| 	aip, bip := a.Addr().Unmap(), b.Addr().Unmap()
 | |
| 	v := aip.Compare(bip)
 | |
| 	if v != 0 {
 | |
| 		return v
 | |
| 	}
 | |
| 
 | |
| 	if a.Bits() == b.Bits() {
 | |
| 		return 0
 | |
| 	}
 | |
| 	// narrower first
 | |
| 	if a.Bits() > b.Bits() {
 | |
| 		return -1
 | |
| 	}
 | |
| 	return 1
 | |
| }
 | |
| 
 | |
| func sortNets(s []netip.Prefix) {
 | |
| 	sort.Slice(s, func(i, j int) bool {
 | |
| 		return netCompare(s[i], s[j]) == -1
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // deltaNets returns the changes to turn a into b.
 | |
| func deltaNets(a, b []netip.Prefix) (add, del []netip.Prefix) {
 | |
| 	add = make([]netip.Prefix, 0, len(b))
 | |
| 	del = make([]netip.Prefix, 0, len(a))
 | |
| 	sortNets(a)
 | |
| 	sortNets(b)
 | |
| 
 | |
| 	i := 0
 | |
| 	j := 0
 | |
| 	for i < len(a) && j < len(b) {
 | |
| 		switch netCompare(a[i], b[j]) {
 | |
| 		case -1:
 | |
| 			// a < b, delete
 | |
| 			del = append(del, a[i])
 | |
| 			i++
 | |
| 		case 0:
 | |
| 			// a == b, no diff
 | |
| 			i++
 | |
| 			j++
 | |
| 		case 1:
 | |
| 			// a > b, add missing entry
 | |
| 			add = append(add, b[j])
 | |
| 			j++
 | |
| 		default:
 | |
| 			panic("unexpected compare result")
 | |
| 		}
 | |
| 	}
 | |
| 	del = append(del, a[i:]...)
 | |
| 	add = append(add, b[j:]...)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func isIPv6LinkLocal(a netip.Prefix) bool {
 | |
| 	return a.Addr().Is6() && a.Addr().IsLinkLocalUnicast()
 | |
| }
 | |
| 
 | |
| // ipAdapterUnicastAddressToPrefix converts windows.IpAdapterUnicastAddress to netip.Prefix
 | |
| func ipAdapterUnicastAddressToPrefix(u *windows.IpAdapterUnicastAddress) netip.Prefix {
 | |
| 	ip, _ := netip.AddrFromSlice(u.Address.IP())
 | |
| 	return netip.PrefixFrom(ip.Unmap(), int(u.OnLinkPrefixLength))
 | |
| }
 | |
| 
 | |
| // unicastIPNets returns all unicast net.IPNet for ifc interface.
 | |
| func unicastIPNets(ifc *winipcfg.IPAdapterAddresses) []netip.Prefix {
 | |
| 	var nets []netip.Prefix
 | |
| 	for addr := ifc.FirstUnicastAddress; addr != nil; addr = addr.Next {
 | |
| 		nets = append(nets, ipAdapterUnicastAddressToPrefix(addr))
 | |
| 	}
 | |
| 	return nets
 | |
| }
 | |
| 
 | |
| // syncAddresses incrementally sets the interface's unicast IP addresses,
 | |
| // doing the minimum number of AddAddresses & DeleteAddress calls.
 | |
| // This avoids the full FlushAddresses.
 | |
| //
 | |
| // Any IPv6 link-local addresses are not deleted out of caution as some
 | |
| // configurations may repeatedly re-add them. Link-local addresses are adjusted
 | |
| // to set SkipAsSource. SkipAsSource prevents the addresses from being added to
 | |
| // DNS locally or remotely and from being picked as a source address for
 | |
| // outgoing packets with unspecified sources. See #4647 and
 | |
| // https://web.archive.org/web/20200912120956/https://devblogs.microsoft.com/scripting/use-powershell-to-change-ip-behavior-with-skipassource/
 | |
| func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []netip.Prefix) error {
 | |
| 	var erracc error
 | |
| 
 | |
| 	got := unicastIPNets(ifc)
 | |
| 	add, del := deltaNets(got, want)
 | |
| 
 | |
| 	ll := make([]netip.Prefix, 0)
 | |
| 	for _, a := range del {
 | |
| 		// do not delete link-local addresses, and collect them for later
 | |
| 		// applying SkipAsSource.
 | |
| 		if isIPv6LinkLocal(a) {
 | |
| 			ll = append(ll, a)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		err := ifc.LUID.DeleteIPAddress(a)
 | |
| 		if err != nil {
 | |
| 			erracc = fmt.Errorf("deleting IP %q: %w", a, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, a := range add {
 | |
| 		err := ifc.LUID.AddIPAddress(a)
 | |
| 		if err != nil {
 | |
| 			erracc = fmt.Errorf("adding IP %q: %w", a, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, a := range ll {
 | |
| 		mib, err := ifc.LUID.IPAddress(a.Addr())
 | |
| 		if err != nil {
 | |
| 			erracc = fmt.Errorf("setting skip-as-source on IP %q: unable to retrieve MIB: %w", a, err)
 | |
| 			continue
 | |
| 		}
 | |
| 		if !mib.SkipAsSource {
 | |
| 			mib.SkipAsSource = true
 | |
| 			if err := mib.Set(); err != nil {
 | |
| 				erracc = fmt.Errorf("setting skip-as-source on IP %q: unable to set MIB: %w", a, err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return erracc
 | |
| }
 | |
| 
 | |
| // routeData wraps winipcfg.RouteData with an additional field that permits
 | |
| // caching of the associated MibIPForwardRow2; by keeping it around, we can
 | |
| // avoid unnecessary (and slow) lookups of information that we already have.
 | |
| type routeData struct {
 | |
| 	winipcfg.RouteData
 | |
| 	Row *winipcfg.MibIPforwardRow2
 | |
| }
 | |
| 
 | |
| func (rd *routeData) Less(other *routeData) bool {
 | |
| 	return rd.Compare(other) < 0
 | |
| }
 | |
| 
 | |
| func (rd *routeData) Compare(other *routeData) int {
 | |
| 	v := rd.Destination.Addr().Compare(other.Destination.Addr())
 | |
| 	if v != 0 {
 | |
| 		return v
 | |
| 	}
 | |
| 
 | |
| 	// Narrower masks first
 | |
| 	b1, b2 := rd.Destination.Bits(), other.Destination.Bits()
 | |
| 	if b1 != b2 {
 | |
| 		if b1 > b2 {
 | |
| 			return -1
 | |
| 		}
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	// No nexthop before non-empty nexthop
 | |
| 	v = rd.NextHop.Compare(other.NextHop)
 | |
| 	if v != 0 {
 | |
| 		return v
 | |
| 	}
 | |
| 
 | |
| 	// Lower metrics first
 | |
| 	if rd.Metric < other.Metric {
 | |
| 		return -1
 | |
| 	} else if rd.Metric > other.Metric {
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| func deltaRouteData(a, b []*routeData) (add, del []*routeData) {
 | |
| 	add = make([]*routeData, 0, len(b))
 | |
| 	del = make([]*routeData, 0, len(a))
 | |
| 	slices.SortFunc(a, (*routeData).Compare)
 | |
| 	slices.SortFunc(b, (*routeData).Compare)
 | |
| 
 | |
| 	i := 0
 | |
| 	j := 0
 | |
| 	for i < len(a) && j < len(b) {
 | |
| 		switch a[i].Compare(b[j]) {
 | |
| 		case -1:
 | |
| 			// a < b, delete
 | |
| 			del = append(del, a[i])
 | |
| 			i++
 | |
| 		case 0:
 | |
| 			// a == b, no diff
 | |
| 			i++
 | |
| 			j++
 | |
| 		case 1:
 | |
| 			// a > b, add missing entry
 | |
| 			add = append(add, b[j])
 | |
| 			j++
 | |
| 		default:
 | |
| 			panic("unexpected compare result")
 | |
| 		}
 | |
| 	}
 | |
| 	del = append(del, a[i:]...)
 | |
| 	add = append(add, b[j:]...)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // getInterfaceRoutes returns all the interface's routes.
 | |
| // Corresponds to GetIpForwardTable2 function, but filtered by interface.
 | |
| func getInterfaceRoutes(ifc *winipcfg.IPAdapterAddresses, family winipcfg.AddressFamily) (matches []*winipcfg.MibIPforwardRow2, err error) {
 | |
| 	routes, err := winipcfg.GetIPForwardTable2(family)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	for i := range routes {
 | |
| 		if routes[i].InterfaceLUID == ifc.LUID {
 | |
| 			matches = append(matches, &routes[i])
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func getAllInterfaceRoutes(ifc *winipcfg.IPAdapterAddresses) ([]*routeData, error) {
 | |
| 	routes4, err := getInterfaceRoutes(ifc, windows.AF_INET)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	routes6, err := getInterfaceRoutes(ifc, windows.AF_INET6)
 | |
| 	if err != nil {
 | |
| 		// TODO: what if v6 unavailable?
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	rd := make([]*routeData, 0, len(routes4)+len(routes6))
 | |
| 	for _, r := range routes4 {
 | |
| 		rd = append(rd, &routeData{
 | |
| 			RouteData: winipcfg.RouteData{
 | |
| 				Destination: r.DestinationPrefix.Prefix(),
 | |
| 				NextHop:     r.NextHop.Addr(),
 | |
| 				Metric:      r.Metric,
 | |
| 			},
 | |
| 			Row: r,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	for _, r := range routes6 {
 | |
| 		rd = append(rd, &routeData{
 | |
| 			RouteData: winipcfg.RouteData{
 | |
| 				Destination: r.DestinationPrefix.Prefix(),
 | |
| 				NextHop:     r.NextHop.Addr(),
 | |
| 				Metric:      r.Metric,
 | |
| 			},
 | |
| 			Row: r,
 | |
| 		})
 | |
| 	}
 | |
| 	return rd, nil
 | |
| }
 | |
| 
 | |
| // filterRoutes removes routes that have been added by Windows and should not
 | |
| // be managed by us.
 | |
| func filterRoutes(routes []*routeData, dontDelete []netip.Prefix) []*routeData {
 | |
| 	ddm := make(map[netip.Prefix]bool)
 | |
| 	for _, dd := range dontDelete {
 | |
| 		// See issue 1448: we don't want to touch the routes added
 | |
| 		// by Windows for our interface addresses.
 | |
| 		ddm[dd] = true
 | |
| 	}
 | |
| 	for _, r := range routes {
 | |
| 		// We don't want to touch broadcast routes that Windows adds.
 | |
| 		nr := r.Destination
 | |
| 		if !nr.IsValid() {
 | |
| 			continue
 | |
| 		}
 | |
| 		if nr.IsSingleIP() {
 | |
| 			continue
 | |
| 		}
 | |
| 		lastIP := netipx.RangeOfPrefix(nr).To()
 | |
| 		ddm[netip.PrefixFrom(lastIP, lastIP.BitLen())] = true
 | |
| 	}
 | |
| 	filtered := make([]*routeData, 0, len(routes))
 | |
| 	for _, r := range routes {
 | |
| 		rr := r.Destination
 | |
| 		if rr.IsValid() && ddm[rr] {
 | |
| 			continue
 | |
| 		}
 | |
| 		filtered = append(filtered, r)
 | |
| 	}
 | |
| 	return filtered
 | |
| }
 | |
| 
 | |
| // syncRoutes incrementally sets multiples routes on an interface.
 | |
| // This avoids a full ifc.FlushRoutes call.
 | |
| // dontDelete is a list of interface address routes that the
 | |
| // synchronization logic should never delete.
 | |
| func syncRoutes(ifc *winipcfg.IPAdapterAddresses, want []*routeData, dontDelete []netip.Prefix) error {
 | |
| 	existingRoutes, err := getAllInterfaceRoutes(ifc)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	got := filterRoutes(existingRoutes, dontDelete)
 | |
| 
 | |
| 	add, del := deltaRouteData(got, want)
 | |
| 
 | |
| 	var errs []error
 | |
| 	for _, a := range del {
 | |
| 		var err error
 | |
| 		if a.Row == nil {
 | |
| 			// DeleteRoute requires a routing table lookup, so only do that if
 | |
| 			// a does not already have the row.
 | |
| 			err = ifc.LUID.DeleteRoute(a.Destination, a.NextHop)
 | |
| 		} else {
 | |
| 			// Otherwise, delete the row directly.
 | |
| 			err = a.Row.Delete()
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			dstStr := a.Destination.String()
 | |
| 			if dstStr == "169.254.255.255/32" {
 | |
| 				// Issue 785. Ignore these routes
 | |
| 				// failing to delete. Harmless.
 | |
| 				// TODO(maisem): do we still need this?
 | |
| 				continue
 | |
| 			}
 | |
| 			errs = append(errs, fmt.Errorf("deleting route %v: %w", dstStr, err))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, a := range add {
 | |
| 		err := ifc.LUID.AddRoute(a.Destination, a.NextHop, a.Metric)
 | |
| 		if err != nil {
 | |
| 			errs = append(errs, fmt.Errorf("adding route %v: %w", &a.Destination, err))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return errors.Join(errs...)
 | |
| }
 |