2020-02-05 22:16:58 +00:00
/ * SPDX - License - Identifier : MIT
*
* Copyright ( C ) 2019 WireGuard LLC . All Rights Reserved .
* /
2020-04-30 20:20:09 +00:00
package router
2020-02-05 22:16:58 +00:00
import (
"errors"
"fmt"
"log"
2022-07-25 03:08:42 +00:00
"net/netip"
2023-08-17 05:09:53 +00:00
"slices"
2020-02-05 22:16:58 +00:00
"sort"
"time"
2021-03-15 22:39:37 +00:00
"tailscale.com/health"
2024-04-28 04:18:18 +00:00
"tailscale.com/net/netmon"
2021-03-03 03:32:04 +00:00
"tailscale.com/net/tsaddr"
2022-10-12 23:35:19 +00:00
"tailscale.com/net/tstun"
2021-11-02 21:30:48 +00:00
"tailscale.com/util/multierr"
2020-02-05 22:16:58 +00:00
"tailscale.com/wgengine/winnet"
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
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"
2020-02-05 22:16:58 +00:00
)
2020-09-10 18:40:02 +00:00
// 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 ) {
2020-12-04 13:56:34 +00:00
ourLuid := winipcfg . LUID ( tun . LUID ( ) )
2020-02-05 22:16:58 +00:00
lastMtu := uint32 ( 0 )
doIt := func ( ) error {
2020-09-10 18:40:02 +00:00
mtu , err := getDefaultRouteMTU ( )
2020-02-05 22:16:58 +00:00
if err != nil {
2020-09-26 02:11:05 +00:00
return fmt . Errorf ( "error getting default route MTU: %w" , err )
2020-02-05 22:16:58 +00:00
}
2020-09-10 18:40:02 +00:00
2020-02-05 22:16:58 +00:00
if mtu > 0 && ( lastMtu == 0 || lastMtu != mtu ) {
2020-09-26 02:11:05 +00:00
iface , err := ourLuid . IPInterface ( windows . AF_INET )
2020-02-05 22:16:58 +00:00
if err != nil {
2021-03-03 04:26:36 +00:00
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 ) )
2020-02-05 22:16:58 +00:00
}
2020-09-26 02:11:05 +00:00
iface , err = ourLuid . IPInterface ( windows . AF_INET6 )
2020-02-05 22:16:58 +00:00
if err != nil {
2020-09-26 02:11:05 +00:00
if ! errors . Is ( err , windows . ERROR_NOT_FOUND ) {
return fmt . Errorf ( "error getting v6 interface: %w" , err )
2020-09-11 18:59:48 +00:00
}
} else {
2020-09-26 02:11:05 +00:00
iface . NLMTU = mtu - 80
if iface . NLMTU < 1280 {
iface . NLMTU = 1280
2020-09-11 18:59:48 +00:00
}
err = iface . Set ( )
if err != nil {
2020-09-26 02:11:05 +00:00
return fmt . Errorf ( "error setting v6 MTU: %w" , err )
2020-09-11 18:59:48 +00:00
}
2020-02-05 22:16:58 +00:00
}
lastMtu = mtu
}
return nil
}
2020-12-04 13:56:34 +00:00
err := doIt ( )
2020-02-05 22:16:58 +00:00
if err != nil {
return nil , err
}
2020-09-26 02:11:05 +00:00
cb , err := winipcfg . RegisterRouteChangeCallback ( func ( notificationType winipcfg . MibNotificationType , route * winipcfg . MibIPforwardRow2 ) {
2020-02-05 22:16:58 +00:00
//fmt.Printf("MonitorDefaultRoutes: changed: %v\n", route.DestinationPrefix)
if route . DestinationPrefix . PrefixLength == 0 {
_ = doIt ( )
}
} )
if err != nil {
return nil , err
}
return cb , nil
}
2020-09-10 18:40:02 +00:00
func getDefaultRouteMTU ( ) ( uint32 , error ) {
2024-04-28 04:18:18 +00:00
mtus , err := netmon . NonTailscaleMTUs ( )
2020-09-10 18:40:02 +00:00
if err != nil {
return 0 , err
}
2020-09-26 02:11:05 +00:00
routes , err := winipcfg . GetIPForwardTable2 ( windows . AF_INET )
2020-09-10 18:40:02 +00:00
if err != nil {
return 0 , err
}
best := ^ uint32 ( 0 )
mtu := uint32 ( 0 )
for _ , route := range routes {
if route . DestinationPrefix . PrefixLength != 0 {
continue
}
2020-09-26 02:11:05 +00:00
routeMTU := mtus [ route . InterfaceLUID ]
2020-09-10 18:40:02 +00:00
if routeMTU == 0 {
continue
}
if route . Metric < best {
best = route . Metric
mtu = routeMTU
}
}
2020-09-26 02:11:05 +00:00
routes , err = winipcfg . GetIPForwardTable2 ( windows . AF_INET6 )
2020-09-10 18:40:02 +00:00
if err != nil {
return 0 , err
}
best = ^ uint32 ( 0 )
for _ , route := range routes {
if route . DestinationPrefix . PrefixLength != 0 {
continue
}
2020-09-26 02:11:05 +00:00
routeMTU := mtus [ route . InterfaceLUID ]
2020-09-10 18:40:02 +00:00
if routeMTU == 0 {
continue
}
if route . Metric < best {
best = route . Metric
if routeMTU < mtu {
mtu = routeMTU
}
}
}
return mtu , nil
}
2020-09-16 21:41:28 +00:00
// setPrivateNetwork marks the provided network adapter's category to private.
// It returns (false, nil) if the adapter was not found.
2020-12-04 13:56:34 +00:00
func setPrivateNetwork ( ifcLUID winipcfg . LUID ) ( bool , error ) {
2020-09-16 21:41:28 +00:00
// NLM_NETWORK_CATEGORY values.
const (
categoryPublic = 0
categoryPrivate = 1
categoryDomain = 2
)
2020-11-16 17:55:44 +00:00
2020-12-04 13:56:34 +00:00
ifcGUID , err := ifcLUID . GUID ( )
if err != nil {
return false , fmt . Errorf ( "ifcLUID.GUID: %v" , err )
}
2022-11-14 23:51:09 +00:00
// aaron: DO NOT call Initialize() or Uninitialize() on c!
// We've already handled that process-wide.
2020-09-16 21:41:28 +00:00
var c ole . Connection
2020-02-05 22:16:58 +00:00
m , err := winnet . NewNetworkListManager ( & c )
if err != nil {
2020-02-21 20:22:29 +00:00
return false , fmt . Errorf ( "winnet.NewNetworkListManager: %v" , err )
2020-02-05 22:16:58 +00:00
}
defer m . Release ( )
cl , err := m . GetNetworkConnections ( )
if err != nil {
2020-02-21 20:22:29 +00:00
return false , fmt . Errorf ( "m.GetNetworkConnections: %v" , err )
2020-02-05 22:16:58 +00:00
}
defer cl . Release ( )
for _ , nco := range cl {
aid , err := nco . GetAdapterId ( )
if err != nil {
2020-02-21 20:22:29 +00:00
return false , fmt . Errorf ( "nco.GetAdapterId: %v" , err )
2020-02-05 22:16:58 +00:00
}
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 )
}
2024-04-19 19:36:20 +00:00
if cat != categoryPrivate && cat != categoryDomain {
2020-09-16 21:41:28 +00:00
if err := n . SetCategory ( categoryPrivate ) ; err != nil {
2020-02-05 22:16:58 +00:00
return false , fmt . Errorf ( "SetCategory: %v" , err )
}
}
return true , nil
}
return false , nil
}
2020-12-10 23:55:37 +00:00
// interfaceFromLUID returns IPAdapterAddresses with specified LUID.
2020-12-04 13:56:34 +00:00
func interfaceFromLUID ( luid winipcfg . LUID , flags winipcfg . GAAFlags ) ( * winipcfg . IPAdapterAddresses , error ) {
2020-09-26 02:11:05 +00:00
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
}
}
2020-12-04 13:56:34 +00:00
return nil , fmt . Errorf ( "interfaceFromLUID: interface with LUID %v not found" , luid )
2020-09-26 02:11:05 +00:00
}
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
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" ,
} )
2022-11-13 15:32:37 +00:00
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
func configureInterface ( cfg * Config , tun * tun . NativeTun , ht * health . Tracker ) ( retErr error ) {
2023-09-22 15:49:09 +00:00
var mtu = tstun . DefaultTUNMTU ( )
2020-12-04 13:56:34 +00:00
luid := winipcfg . LUID ( tun . LUID ( ) )
iface , err := interfaceFromLUID ( luid ,
2020-09-21 21:52:52 +00:00
// 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:
2020-09-26 02:11:05 +00:00
winipcfg . GAAFlagIncludeAllInterfaces ,
)
2020-02-05 22:16:58 +00:00
if err != nil {
2021-03-03 04:26:36 +00:00
return fmt . Errorf ( "getting interface: %w" , err )
2020-02-05 22:16:58 +00:00
}
2021-10-13 00:28:44 +00:00
// Send non-nil return errors to retErrc, to interrupt our background
2021-01-25 21:20:19 +00:00
// setPrivateNetwork goroutine.
retErrc := make ( chan error , 1 )
defer func ( ) {
if retErr != nil {
retErrc <- retErr
}
} ( )
2020-02-05 22:16:58 +00:00
go func ( ) {
// It takes a weirdly long time for Windows to notice the
// new interface has come up. Poll periodically until it
// does.
2020-09-16 21:41:28 +00:00
const tries = 20
2024-04-16 20:15:13 +00:00
for i := range tries {
2020-12-04 13:56:34 +00:00
found , err := setPrivateNetwork ( luid )
2020-02-05 22:16:58 +00:00
if err != nil {
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
ht . SetUnhealthy ( networkCategoryWarnable , health . Args { health . ArgError : err . Error ( ) } )
2020-09-16 21:41:28 +00:00
log . Printf ( "setPrivateNetwork(try=%d): %v" , i , err )
} else {
health: begin work to use structured health warnings instead of strings, pipe changes into ipn.Notify (#12406)
Updates tailscale/tailscale#4136
This PR is the first round of work to move from encoding health warnings as strings and use structured data instead. The current health package revolves around the idea of Subsystems. Each subsystem can have (or not have) a Go error associated with it. The overall health of the backend is given by the concatenation of all these errors.
This PR polishes the concept of Warnable introduced by @bradfitz a few weeks ago. Each Warnable is a component of the backend (for instance, things like 'dns' or 'magicsock' are Warnables). Each Warnable has a unique identifying code. A Warnable is an entity we can warn the user about, by setting (or unsetting) a WarningState for it. Warnables have:
- an identifying Code, so that the GUI can track them as their WarningStates come and go
- a Title, which the GUIs can use to tell the user what component of the backend is broken
- a Text, which is a function that is called with a set of Args to generate a more detailed error message to explain the unhappy state
Additionally, this PR also begins to send Warnables and their WarningStates through LocalAPI to the clients, using ipn.Notify messages. An ipn.Notify is only issued when a warning is added or removed from the Tracker.
In a next PR, we'll get rid of subsystems entirely, and we'll start using structured warnings for all errors affecting the backend functionality.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
2024-06-14 18:53:56 +00:00
ht . SetHealthy ( networkCategoryWarnable )
2020-09-16 21:41:28 +00:00
if found {
2021-01-25 21:20:19 +00:00
if i > 0 {
log . Printf ( "setPrivateNetwork(try=%d): success" , i )
}
2020-09-16 21:41:28 +00:00
return
}
log . Printf ( "setPrivateNetwork(try=%d): not found" , i )
2020-02-05 22:16:58 +00:00
}
2021-01-25 21:20:19 +00:00
select {
case <- time . After ( time . Second ) :
case <- retErrc :
return
}
2020-02-05 22:16:58 +00:00
}
2020-12-04 13:56:34 +00:00
log . Printf ( "setPrivateNetwork: adapter LUID %v not found after %d tries, giving up" , luid , tries )
2020-02-05 22:16:58 +00:00
} ( )
2021-03-03 04:26:36 +00:00
// 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
}
2024-07-17 19:00:03 +00:00
// 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
2022-08-11 17:23:35 +00:00
addresses := make ( [ ] netip . Prefix , 0 , len ( cfg . LocalAddrs ) )
2021-03-03 04:26:36 +00:00
for _ , addr := range cfg . LocalAddrs {
2022-07-25 03:08:42 +00:00
if ( addr . Addr ( ) . Is4 ( ) && ipif4 == nil ) || ( addr . Addr ( ) . Is6 ( ) && ipif6 == nil ) {
2021-03-03 04:26:36 +00:00
// Can't program addresses for disabled protocol.
continue
}
2022-08-11 17:23:35 +00:00
addresses = append ( addresses , addr )
2024-07-17 19:00:03 +00:00
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 ( )
2020-02-05 22:16:58 +00:00
}
}
2024-02-13 17:32:48 +00:00
var routes [ ] * routeData
2020-02-05 22:16:58 +00:00
foundDefault4 := false
foundDefault6 := false
2020-05-12 07:08:52 +00:00
for _ , route := range cfg . Routes {
2022-07-25 03:08:42 +00:00
if ( route . Addr ( ) . Is4 ( ) && ipif4 == nil ) || ( route . Addr ( ) . Is6 ( ) && ipif6 == nil ) {
2021-03-03 04:26:36 +00:00
// Can't program routes for disabled protocol.
continue
}
2024-07-17 19:00:03 +00:00
if route . Addr ( ) . Is6 ( ) && ! gatewayAddr6 . IsValid ( ) {
2021-03-03 03:32:04 +00:00
// 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.
2022-08-11 17:23:35 +00:00
ip := tsaddr . Tailscale4To6Placeholder ( )
addresses = append ( addresses , netip . PrefixFrom ( ip , ip . BitLen ( ) ) )
2024-07-17 19:00:03 +00:00
gatewayAddr6 = ip
} else if route . Addr ( ) . Is4 ( ) && ! gatewayAddr4 . IsValid ( ) {
2021-03-03 04:26:36 +00:00
// TODO: do same dummy behavior as v6?
2021-05-03 20:00:32 +00:00
return errors . New ( "due to a Windows limitation, one cannot have interface routes without an interface address" )
2020-05-08 01:07:13 +00:00
}
2020-02-05 22:16:58 +00:00
2024-07-17 19:00:03 +00:00
var gateway , localAddr netip . Addr
2022-07-25 03:08:42 +00:00
if route . Addr ( ) . Is4 ( ) {
2024-07-17 19:00:03 +00:00
localAddr = localAddr4
gateway = gatewayAddr4
2022-07-25 03:08:42 +00:00
} else if route . Addr ( ) . Is6 ( ) {
2024-07-17 19:00:03 +00:00
localAddr = localAddr6
gateway = gatewayAddr6
2020-05-08 01:07:13 +00:00
}
2024-07-17 19:00:03 +00:00
switch destAddr := route . Addr ( ) . Unmap ( ) ; {
case destAddr == localAddr :
2020-05-08 01:07:13 +00:00
// 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
2024-07-17 19:00:03 +00:00
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
2020-05-08 01:07:13 +00:00
}
2024-07-17 19:00:03 +00:00
r := & routeData {
RouteData : winipcfg . RouteData {
Destination : route ,
NextHop : gateway ,
Metric : 0 ,
} ,
}
2022-07-25 03:08:42 +00:00
if route . Addr ( ) . Is4 ( ) {
2021-05-15 01:07:28 +00:00
if route . Bits ( ) == 0 {
2020-05-08 01:07:13 +00:00
foundDefault4 = true
2020-02-05 22:16:58 +00:00
}
2022-07-25 03:08:42 +00:00
} else if route . Addr ( ) . Is6 ( ) {
2021-05-15 01:07:28 +00:00
if route . Bits ( ) == 0 {
2020-05-08 01:07:13 +00:00
foundDefault6 = true
2020-02-05 22:16:58 +00:00
}
}
2020-05-08 01:07:13 +00:00
routes = append ( routes , r )
2020-02-05 22:16:58 +00:00
}
2020-09-22 16:13:45 +00:00
err = syncAddresses ( iface , addresses )
2020-02-05 22:16:58 +00:00
if err != nil {
2021-03-03 04:26:36 +00:00
return fmt . Errorf ( "syncAddresses: %w" , err )
2020-02-05 22:16:58 +00:00
}
2024-02-13 17:32:48 +00:00
slices . SortFunc ( routes , ( * routeData ) . Compare )
2020-02-05 22:16:58 +00:00
2024-02-13 17:32:48 +00:00
deduplicatedRoutes := [ ] * routeData { }
2024-04-16 20:15:13 +00:00
for i := range len ( routes ) {
2020-02-05 22:16:58 +00:00
// There's only one way to get to a given IP+Mask, so delete
// all matches after the first.
2022-08-11 17:23:35 +00:00
if i > 0 && routes [ i ] . Destination == routes [ i - 1 ] . Destination {
2020-02-05 22:16:58 +00:00
continue
}
2022-08-11 17:23:35 +00:00
deduplicatedRoutes = append ( deduplicatedRoutes , routes [ i ] )
2020-02-05 22:16:58 +00:00
}
2020-09-26 02:11:05 +00:00
// Re-read interface after syncAddresses.
2020-12-04 13:56:34 +00:00
iface , err = interfaceFromLUID ( luid ,
2020-09-26 02:11:05 +00:00
// 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 {
2021-03-03 04:26:36 +00:00
return fmt . Errorf ( "getting interface: %w" , err )
2020-09-26 02:11:05 +00:00
}
2020-02-05 22:16:58 +00:00
var errAcc error
2021-03-12 03:24:45 +00:00
err = syncRoutes ( iface , deduplicatedRoutes , cfg . LocalAddrs )
2020-02-05 22:16:58 +00:00
if err != nil && errAcc == nil {
2020-09-01 20:27:42 +00:00
log . Printf ( "setroutes: %v" , err )
2020-02-05 22:16:58 +00:00
errAcc = err
}
2021-03-03 04:26:36 +00:00
if ipif4 != nil {
ipif4 , err = iface . LUID . IPInterface ( windows . AF_INET )
if err != nil {
return fmt . Errorf ( "getting AF_INET interface: %w" , err )
2020-09-11 18:59:48 +00:00
}
2021-03-03 04:26:36 +00:00
if foundDefault4 {
ipif4 . UseAutomaticMetric = false
ipif4 . Metric = 0
2020-09-11 18:59:48 +00:00
}
if mtu > 0 {
2021-03-03 04:26:36 +00:00
ipif4 . NLMTU = uint32 ( mtu )
tun . ForceMTU ( int ( ipif4 . NLMTU ) )
2020-09-11 18:59:48 +00:00
}
2021-03-03 04:26:36 +00:00
err = ipif4 . Set ( )
2020-09-11 18:59:48 +00:00
if err != nil && errAcc == nil {
errAcc = err
}
2020-02-05 22:16:58 +00:00
}
2021-03-03 04:26:36 +00:00
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
}
}
}
2020-02-05 22:16:58 +00:00
return errAcc
}
2020-09-01 20:24:58 +00:00
2022-08-11 17:23:35 +00:00
func netCompare ( a , b netip . Prefix ) int {
aip , bip := a . Addr ( ) . Unmap ( ) , b . Addr ( ) . Unmap ( )
v := aip . Compare ( bip )
2020-09-22 16:13:45 +00:00
if v != 0 {
return v
}
2022-08-11 17:23:35 +00:00
if a . Bits ( ) == b . Bits ( ) {
return 0
2020-09-22 16:13:45 +00:00
}
// narrower first
2022-08-11 17:23:35 +00:00
if a . Bits ( ) > b . Bits ( ) {
return - 1
}
return 1
2020-09-22 16:13:45 +00:00
}
2022-08-11 17:23:35 +00:00
func sortNets ( s [ ] netip . Prefix ) {
sort . Slice ( s , func ( i , j int ) bool {
return netCompare ( s [ i ] , s [ j ] ) == - 1
2020-09-22 16:13:45 +00:00
} )
}
// deltaNets returns the changes to turn a into b.
2022-08-11 17:23:35 +00:00
func deltaNets ( a , b [ ] netip . Prefix ) ( add , del [ ] netip . Prefix ) {
add = make ( [ ] netip . Prefix , 0 , len ( b ) )
del = make ( [ ] netip . Prefix , 0 , len ( a ) )
2020-09-22 16:13:45 +00:00
sortNets ( a )
sortNets ( b )
i := 0
j := 0
for i < len ( a ) && j < len ( b ) {
2022-08-11 17:23:35 +00:00
switch netCompare ( a [ i ] , b [ j ] ) {
2020-09-22 16:13:45 +00:00
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
}
2022-08-11 17:23:35 +00:00
func isIPv6LinkLocal ( a netip . Prefix ) bool {
return a . Addr ( ) . Is6 ( ) && a . Addr ( ) . IsLinkLocalUnicast ( )
2020-09-22 16:13:45 +00:00
}
2022-08-11 17:23:35 +00:00
// 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 ) )
2020-09-26 02:11:05 +00:00
}
// unicastIPNets returns all unicast net.IPNet for ifc interface.
2022-08-11 17:23:35 +00:00
func unicastIPNets ( ifc * winipcfg . IPAdapterAddresses ) [ ] netip . Prefix {
var nets [ ] netip . Prefix
2020-09-26 02:11:05 +00:00
for addr := ifc . FirstUnicastAddress ; addr != nil ; addr = addr . Next {
2022-08-11 17:23:35 +00:00
nets = append ( nets , ipAdapterUnicastAddressToPrefix ( addr ) )
2020-09-26 02:11:05 +00:00
}
return nets
}
2020-09-22 16:13:45 +00:00
// syncAddresses incrementally sets the interface's unicast IP addresses,
// doing the minimum number of AddAddresses & DeleteAddress calls.
// This avoids the full FlushAddresses.
//
2022-06-14 18:16:01 +00:00
// Any IPv6 link-local addresses are not deleted out of caution as some
// configurations may repeatedly re-add them. Link-local addresses are adjusted
2022-09-25 18:29:55 +00:00
// to set SkipAsSource. SkipAsSource prevents the addresses from being added to
2022-06-14 18:16:01 +00:00
// 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/
2022-08-11 17:23:35 +00:00
func syncAddresses ( ifc * winipcfg . IPAdapterAddresses , want [ ] netip . Prefix ) error {
2020-09-22 16:13:45 +00:00
var erracc error
2020-09-26 02:11:05 +00:00
got := unicastIPNets ( ifc )
2020-09-22 16:13:45 +00:00
add , del := deltaNets ( got , want )
2022-06-14 18:16:01 +00:00
2022-08-11 17:23:35 +00:00
ll := make ( [ ] netip . Prefix , 0 )
2020-09-22 16:13:45 +00:00
for _ , a := range del {
2022-06-14 18:16:01 +00:00
// do not delete link-local addresses, and collect them for later
// applying SkipAsSource.
if isIPv6LinkLocal ( a ) {
ll = append ( ll , a )
continue
}
2022-08-11 17:23:35 +00:00
err := ifc . LUID . DeleteIPAddress ( a )
2020-09-22 16:13:45 +00:00
if err != nil {
2022-08-11 17:23:35 +00:00
erracc = fmt . Errorf ( "deleting IP %q: %w" , a , err )
2020-09-22 16:13:45 +00:00
}
}
2020-09-26 02:11:05 +00:00
for _ , a := range add {
2022-08-11 17:23:35 +00:00
err := ifc . LUID . AddIPAddress ( a )
2020-09-26 02:11:05 +00:00
if err != nil {
2022-08-11 17:23:35 +00:00
erracc = fmt . Errorf ( "adding IP %q: %w" , a , err )
2020-09-26 02:11:05 +00:00
}
2020-09-22 16:13:45 +00:00
}
2022-06-14 18:16:01 +00:00
for _ , a := range ll {
2022-08-11 17:23:35 +00:00
mib , err := ifc . LUID . IPAddress ( a . Addr ( ) )
2022-06-14 18:16:01 +00:00
if err != nil {
2022-08-11 17:23:35 +00:00
erracc = fmt . Errorf ( "setting skip-as-source on IP %q: unable to retrieve MIB: %w" , a , err )
2022-06-14 18:16:01 +00:00
continue
}
if ! mib . SkipAsSource {
mib . SkipAsSource = true
if err := mib . Set ( ) ; err != nil {
2022-08-11 17:23:35 +00:00
erracc = fmt . Errorf ( "setting skip-as-source on IP %q: unable to set MIB: %w" , a , err )
2022-06-14 18:16:01 +00:00
}
}
}
2020-09-22 16:13:45 +00:00
return erracc
}
2024-02-13 17:32:48 +00:00
// 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
2022-08-11 17:23:35 +00:00
}
2024-02-13 17:32:48 +00:00
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 ( ) )
2020-09-22 16:13:45 +00:00
if v != 0 {
return v
}
// Narrower masks first
2024-02-13 17:32:48 +00:00
b1 , b2 := rd . Destination . Bits ( ) , other . Destination . Bits ( )
2022-08-11 17:23:35 +00:00
if b1 != b2 {
if b1 > b2 {
return - 1
}
return 1
2020-09-22 16:13:45 +00:00
}
// No nexthop before non-empty nexthop
2024-02-13 17:32:48 +00:00
v = rd . NextHop . Compare ( other . NextHop )
2020-09-22 16:13:45 +00:00
if v != 0 {
return v
}
// Lower metrics first
2024-02-13 17:32:48 +00:00
if rd . Metric < other . Metric {
2020-09-22 16:13:45 +00:00
return - 1
2024-02-13 17:32:48 +00:00
} else if rd . Metric > other . Metric {
2020-09-22 16:13:45 +00:00
return 1
}
return 0
}
2024-02-13 17:32:48 +00:00
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 )
2020-09-22 16:13:45 +00:00
i := 0
j := 0
for i < len ( a ) && j < len ( b ) {
2024-02-13 17:32:48 +00:00
switch a [ i ] . Compare ( b [ j ] ) {
2020-09-22 16:13:45 +00:00
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
}
2020-09-26 02:11:05 +00:00
// getInterfaceRoutes returns all the interface's routes.
// Corresponds to GetIpForwardTable2 function, but filtered by interface.
2020-10-29 21:38:59 +00:00
func getInterfaceRoutes ( ifc * winipcfg . IPAdapterAddresses , family winipcfg . AddressFamily ) ( matches [ ] * winipcfg . MibIPforwardRow2 , err error ) {
2020-09-26 02:11:05 +00:00
routes , err := winipcfg . GetIPForwardTable2 ( family )
if err != nil {
return nil , err
}
for i := range routes {
if routes [ i ] . InterfaceLUID == ifc . LUID {
2020-10-29 21:38:59 +00:00
matches = append ( matches , & routes [ i ] )
2020-09-26 02:11:05 +00:00
}
}
2020-10-29 21:38:59 +00:00
return
2020-09-26 02:11:05 +00:00
}
2024-02-13 17:32:48 +00:00
func getAllInterfaceRoutes ( ifc * winipcfg . IPAdapterAddresses ) ( [ ] * routeData , error ) {
2021-02-25 04:06:49 +00:00
routes4 , err := getInterfaceRoutes ( ifc , windows . AF_INET )
2020-09-22 16:13:45 +00:00
if err != nil {
2021-04-24 04:23:59 +00:00
return nil , err
2020-09-22 16:13:45 +00:00
}
2021-02-25 04:06:49 +00:00
routes6 , err := getInterfaceRoutes ( ifc , windows . AF_INET6 )
if err != nil {
// TODO: what if v6 unavailable?
2021-04-24 04:23:59 +00:00
return nil , err
2021-02-25 04:06:49 +00:00
}
2024-02-13 17:32:48 +00:00
rd := make ( [ ] * routeData , 0 , len ( routes4 ) + len ( routes6 ) )
2021-02-25 04:06:49 +00:00
for _ , r := range routes4 {
2024-02-13 17:32:48 +00:00
rd = append ( rd , & routeData {
RouteData : winipcfg . RouteData {
Destination : r . DestinationPrefix . Prefix ( ) ,
NextHop : r . NextHop . Addr ( ) ,
Metric : r . Metric ,
} ,
Row : r ,
2021-02-25 04:06:49 +00:00
} )
}
2024-02-13 17:32:48 +00:00
2021-02-25 04:06:49 +00:00
for _ , r := range routes6 {
2024-02-13 17:32:48 +00:00
rd = append ( rd , & routeData {
RouteData : winipcfg . RouteData {
Destination : r . DestinationPrefix . Prefix ( ) ,
NextHop : r . NextHop . Addr ( ) ,
Metric : r . Metric ,
} ,
Row : r ,
2020-09-26 02:11:05 +00:00
} )
2020-09-22 16:13:45 +00:00
}
2021-04-24 04:23:59 +00:00
return rd , nil
}
// filterRoutes removes routes that have been added by Windows and should not
// be managed by us.
2024-02-13 17:32:48 +00:00
func filterRoutes ( routes [ ] * routeData , dontDelete [ ] netip . Prefix ) [ ] * routeData {
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
ddm := make ( map [ netip . Prefix ] bool )
2021-04-24 04:23:59 +00:00
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.
2022-08-11 17:23:35 +00:00
nr := r . Destination
if ! nr . IsValid ( ) {
2021-04-24 04:23:59 +00:00
continue
}
if nr . IsSingleIP ( ) {
continue
}
2022-07-25 03:08:42 +00:00
lastIP := netipx . RangeOfPrefix ( nr ) . To ( )
ddm [ netip . PrefixFrom ( lastIP , lastIP . BitLen ( ) ) ] = true
2021-04-24 04:23:59 +00:00
}
2024-02-13 17:32:48 +00:00
filtered := make ( [ ] * routeData , 0 , len ( routes ) )
2021-04-24 04:23:59 +00:00
for _ , r := range routes {
2022-08-11 17:23:35 +00:00
rr := r . Destination
if rr . IsValid ( ) && ddm [ rr ] {
2021-04-24 04:23:59 +00:00
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.
2024-02-13 17:32:48 +00:00
func syncRoutes ( ifc * winipcfg . IPAdapterAddresses , want [ ] * routeData , dontDelete [ ] netip . Prefix ) error {
2021-04-24 04:23:59 +00:00
existingRoutes , err := getAllInterfaceRoutes ( ifc )
if err != nil {
return err
}
got := filterRoutes ( existingRoutes , dontDelete )
2020-09-22 16:13:45 +00:00
add , del := deltaRouteData ( got , want )
var errs [ ] error
for _ , a := range del {
2024-02-13 17:32:48 +00:00
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 ( )
}
2020-09-22 16:13:45 +00:00
if err != nil {
2020-09-23 21:01:00 +00:00
dstStr := a . Destination . String ( )
if dstStr == "169.254.255.255/32" {
// Issue 785. Ignore these routes
// failing to delete. Harmless.
2021-04-24 04:23:59 +00:00
// TODO(maisem): do we still need this?
2020-09-23 21:01:00 +00:00
continue
}
errs = append ( errs , fmt . Errorf ( "deleting route %v: %w" , dstStr , err ) )
2020-09-22 16:13:45 +00:00
}
}
for _ , a := range add {
2020-09-26 02:11:05 +00:00
err := ifc . LUID . AddRoute ( a . Destination , a . NextHop , a . Metric )
2020-09-22 16:13:45 +00:00
if err != nil {
2020-09-23 21:01:00 +00:00
errs = append ( errs , fmt . Errorf ( "adding route %v: %w" , & a . Destination , err ) )
2020-09-22 16:13:45 +00:00
}
}
2021-11-02 21:30:48 +00:00
return multierr . New ( errs ... )
2020-09-22 16:13:45 +00:00
}