Update dependencies

This commit is contained in:
bluepython508
2024-11-01 17:33:34 +00:00
parent 033ac0b400
commit 5cdfab398d
3596 changed files with 1033483 additions and 259 deletions

91
vendor/tailscale.com/wgengine/router/callback.go generated vendored Normal file
View File

@@ -0,0 +1,91 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"sync"
"tailscale.com/net/dns"
)
// CallbackRouter is an implementation of both Router and dns.OSConfigurator.
// When either network or DNS settings are changed, SetBoth is called with both configs.
// Mainly used as a shim for OSes that want to set both network and
// DNS configuration simultaneously (Mac, iOS, Android).
type CallbackRouter struct {
SetBoth func(rcfg *Config, dcfg *dns.OSConfig) error
SplitDNS bool
// GetBaseConfigFunc optionally specifies a function to return the current DNS
// config in response to GetBaseConfig.
//
// If nil, reading the current config isn't supported and GetBaseConfig()
// will return ErrGetBaseConfigNotSupported.
GetBaseConfigFunc func() (dns.OSConfig, error)
// InitialMTU is the MTU the tun should be initialized with.
// Zero means don't change the MTU from the default. This MTU
// is applied only once, shortly after the TUN is created, and
// ignored thereafter.
InitialMTU uint32
mu sync.Mutex // protects all the following
didSetMTU bool // if we set the MTU already
rcfg *Config // last applied router config
dcfg *dns.OSConfig // last applied DNS config
}
// Up implements Router.
func (r *CallbackRouter) Up() error {
return nil // TODO: check that all callers have no need for initialization
}
// Set implements Router.
func (r *CallbackRouter) Set(rcfg *Config) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.rcfg.Equal(rcfg) {
return nil
}
if r.didSetMTU == false {
r.didSetMTU = true
rcfg.NewMTU = int(r.InitialMTU)
}
r.rcfg = rcfg
return r.SetBoth(r.rcfg, r.dcfg)
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *CallbackRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
// SetDNS implements dns.OSConfigurator.
func (r *CallbackRouter) SetDNS(dcfg dns.OSConfig) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.dcfg != nil && r.dcfg.Equal(dcfg) {
return nil
}
r.dcfg = &dcfg
return r.SetBoth(r.rcfg, r.dcfg)
}
// SupportsSplitDNS implements dns.OSConfigurator.
func (r *CallbackRouter) SupportsSplitDNS() bool {
return r.SplitDNS
}
func (r *CallbackRouter) GetBaseConfig() (dns.OSConfig, error) {
if r.GetBaseConfigFunc == nil {
return dns.OSConfig{}, dns.ErrGetBaseConfigNotSupported
}
return r.GetBaseConfigFunc()
}
func (r *CallbackRouter) Close() error {
return r.SetBoth(nil, nil) // TODO: check if makes sense
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"go4.org/netipx"
"tailscale.com/types/logger"
)
// ConsolidatingRoutes wraps a Router with logic that consolidates Routes
// whenever Set is called. It attempts to consolidate cfg.Routes into the
// smallest possible set.
func ConsolidatingRoutes(logf logger.Logf, router Router) Router {
return &consolidatingRouter{Router: router, logf: logger.WithPrefix(logf, "router: ")}
}
type consolidatingRouter struct {
Router
logf logger.Logf
}
// Set implements Router and attempts to consolidate cfg.Routes into the
// smallest possible set.
func (cr *consolidatingRouter) Set(cfg *Config) error {
return cr.Router.Set(cr.consolidateRoutes(cfg))
}
func (cr *consolidatingRouter) consolidateRoutes(cfg *Config) *Config {
if cfg == nil {
return nil
}
if len(cfg.Routes) < 2 {
return cfg
}
if len(cfg.Routes) == 2 && cfg.Routes[0].Addr().Is4() != cfg.Routes[1].Addr().Is4() {
return cfg
}
var builder netipx.IPSetBuilder
for _, route := range cfg.Routes {
builder.AddPrefix(route)
}
set, err := builder.IPSet()
if err != nil {
cr.logf("consolidateRoutes failed, keeping existing routes: %s", err)
return cfg
}
newRoutes := set.Prefixes()
oldLength := len(cfg.Routes)
newLength := len(newRoutes)
if oldLength == newLength {
// Nothing consolidated, return as-is.
return cfg
}
cr.logf("consolidated %d routes down to %d", oldLength, newLength)
newCfg := *cfg
newCfg.Routes = newRoutes
return &newCfg
}

View File

@@ -0,0 +1,834 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
*/
package router
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/util/multierr"
"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 *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 multierr.New(errs...)
}

110
vendor/tailscale.com/wgengine/router/router.go generated vendored Normal file
View File

@@ -0,0 +1,110 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package router presents an interface to manipulate the host network
// stack's state.
package router
import (
"net/netip"
"reflect"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
)
// Router is responsible for managing the system network stack.
//
// There is typically only one instance of this interface per process.
type Router interface {
// Up brings the router up.
Up() error
// Set updates the OS network stack with a new Config. It may be
// called multiple times with identical Configs, which the
// implementation should handle gracefully.
Set(*Config) error
// UpdateMagicsockPort tells the OS network stack what port magicsock
// is currently listening on, so it can be threaded through firewalls
// and such. This is distinct from Set() since magicsock may rebind
// ports independently from the Config changing.
//
// network should be either "udp4" or "udp6".
UpdateMagicsockPort(port uint16, network string) error
// Close closes the router.
Close() error
}
// New returns a new Router for the current platform, using the
// provided tun device.
//
// If netMon is nil, it's not used. It's currently (2021-07-20) only
// used on Linux in some situations.
func New(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
logf = logger.WithPrefix(logf, "router: ")
return newUserspaceRouter(logf, tundev, netMon, health)
}
// CleanUp restores the system network configuration to its original state
// in case the Tailscale daemon terminated without closing the router.
// No other state needs to be instantiated before this runs.
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, interfaceName string) {
cleanUp(logf, interfaceName)
}
// Config is the subset of Tailscale configuration that is relevant to
// the OS's network stack.
type Config struct {
// LocalAddrs are the address(es) for this node. This is
// typically one IPv4/32 (the 100.x.y.z CGNAT) and one
// IPv6/128 (Tailscale ULA).
LocalAddrs []netip.Prefix
// Routes are the routes that point into the Tailscale
// interface. These are the /32 and /128 routes to peers, as
// well as any other subnets that peers are advertising and
// this node has chosen to use.
Routes []netip.Prefix
// LocalRoutes are the routes that should not be routed through Tailscale.
// There are no priorities set in how these routes are added, normal
// routing rules apply.
LocalRoutes []netip.Prefix
// NewMTU is currently only used by the MacOS network extension
// app to set the MTU of the tun in the router configuration
// callback. If zero, the MTU is unchanged.
NewMTU int
// SubnetRoutes is the list of subnets that this node is
// advertising to other Tailscale nodes.
// As of 2023-10-11, this field is only used for network
// flow logging and is otherwise ignored.
SubnetRoutes []netip.Prefix
// Linux-only things below, ignored on other platforms.
SNATSubnetRoutes bool // SNAT traffic to local subnets
StatefulFiltering bool // Apply stateful filtering to inbound connections
NetfilterMode preftype.NetfilterMode // how much to manage netfilter rules
NetfilterKind string // what kind of netfilter to use (nftables, iptables)
}
func (a *Config) Equal(b *Config) bool {
if a == nil && b == nil {
return true
}
if (a == nil) != (b == nil) {
return false
}
return reflect.DeepEqual(a, b)
}
// shutdownConfig is a routing configuration that removes all router
// state from the OS. It's the config used when callers pass in a nil
// Config.
var shutdownConfig = Config{}

19
vendor/tailscale.com/wgengine/router/router_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
return newUserspaceBSDRouter(logf, tundev, netMon, health)
}
func cleanUp(logger.Logf, string) {
// Nothing to do.
}

24
vendor/tailscale.com/wgengine/router/router_default.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !windows && !linux && !darwin && !openbsd && !freebsd
package router
import (
"fmt"
"runtime"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
return nil, fmt.Errorf("unsupported OS %q", runtime.GOOS)
}
func cleanUp(logf logger.Logf, interfaceName string) {
// Nothing to do here.
}

38
vendor/tailscale.com/wgengine/router/router_fake.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"tailscale.com/types/logger"
)
// NewFake returns a Router that does nothing when called and always
// returns nil errors.
func NewFake(logf logger.Logf) Router {
return fakeRouter{logf: logf}
}
type fakeRouter struct {
logf logger.Logf
}
func (r fakeRouter) Up() error {
r.logf("[v1] warning: fakeRouter.Up: not implemented.")
return nil
}
func (r fakeRouter) Set(cfg *Config) error {
r.logf("[v1] warning: fakeRouter.Set: not implemented.")
return nil
}
func (r fakeRouter) UpdateMagicsockPort(_ uint16, _ string) error {
r.logf("[v1] warning: fakeRouter.UpdateMagicsockPort: not implemented.")
return nil
}
func (r fakeRouter) Close() error {
r.logf("[v1] warning: fakeRouter.Close: not implemented.")
return nil
}

31
vendor/tailscale.com/wgengine/router/router_freebsd.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
// For now this router only supports the userspace WireGuard implementations.
//
// Work is currently underway for an in-kernel FreeBSD implementation of wireguard
// https://svnweb.freebsd.org/base?view=revision&revision=357986
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
return newUserspaceBSDRouter(logf, tundev, netMon, health)
}
func cleanUp(logf logger.Logf, interfaceName string) {
// If the interface was left behind, ifconfig down will not remove it.
// In fact, this will leave a system in a tainted state where starting tailscaled
// will result in "interface tailscale0 already exists"
// until the defunct interface is ifconfig-destroyed.
ifup := []string{"ifconfig", interfaceName, "destroy"}
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
logf("ifconfig destroy: %v\n%s", err, out)
}
}

1562
vendor/tailscale.com/wgengine/router/router_linux.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

249
vendor/tailscale.com/wgengine/router/router_openbsd.go generated vendored Normal file
View File

@@ -0,0 +1,249 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"errors"
"fmt"
"log"
"net/netip"
"os/exec"
"github.com/tailscale/wireguard-go/tun"
"go4.org/netipx"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/util/set"
)
// For now this router only supports the WireGuard userspace implementation.
// There is an experimental kernel version in the works for OpenBSD:
// https://git.zx2c4.com/wireguard-openbsd.
type openbsdRouter struct {
logf logger.Logf
netMon *netmon.Monitor
tunname string
local4 netip.Prefix
local6 netip.Prefix
routes set.Set[netip.Prefix]
}
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
tunname, err := tundev.Name()
if err != nil {
return nil, err
}
return &openbsdRouter{
logf: logf,
netMon: netMon,
tunname: tunname,
}, nil
}
func cmd(args ...string) *exec.Cmd {
if len(args) == 0 {
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]", args)
}
return exec.Command(args[0], args[1:]...)
}
func (r *openbsdRouter) Up() error {
ifup := []string{"ifconfig", r.tunname, "up"}
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
r.logf("running ifconfig failed: %v\n%s", err, out)
return err
}
return nil
}
func inet(p netip.Prefix) string {
if p.Addr().Is6() {
return "inet6"
}
return "inet"
}
func (r *openbsdRouter) Set(cfg *Config) error {
if cfg == nil {
cfg = &shutdownConfig
}
// TODO: support configuring multiple local addrs on interface.
if len(cfg.LocalAddrs) == 0 {
return nil
}
numIPv4 := 0
numIPv6 := 0
localAddr4 := netip.Prefix{}
localAddr6 := netip.Prefix{}
for _, addr := range cfg.LocalAddrs {
if addr.Addr().Is4() {
numIPv4++
localAddr4 = addr
}
if addr.Addr().Is6() {
numIPv6++
localAddr6 = addr
}
}
if numIPv4 > 1 || numIPv6 > 1 {
return errors.New("openbsd doesn't support setting multiple local addrs yet")
}
var errq error
if localAddr4 != r.local4 {
if r.local4.IsValid() {
addrdel := []string{"ifconfig", r.tunname,
"inet", r.local4.String(), "-alias"}
out, err := cmd(addrdel...).CombinedOutput()
if err != nil {
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
if errq == nil {
errq = err
}
}
routedel := []string{"route", "-q", "-n",
"del", "-inet", r.local4.String(),
"-iface", r.local4.Addr().String()}
if out, err := cmd(routedel...).CombinedOutput(); err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
if errq == nil {
errq = err
}
}
}
if localAddr4.IsValid() {
addradd := []string{"ifconfig", r.tunname,
"inet", localAddr4.String(), "alias"}
out, err := cmd(addradd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
if errq == nil {
errq = err
}
}
routeadd := []string{"route", "-q", "-n",
"add", "-inet", localAddr4.String(),
"-iface", localAddr4.Addr().String()}
if out, err := cmd(routeadd...).CombinedOutput(); err != nil {
r.logf("route add failed: %v: %v\n%s", routeadd, err, out)
if errq == nil {
errq = err
}
}
}
}
if localAddr6.IsValid() {
// in https://github.com/tailscale/tailscale/issues/1307 we made
// FreeBSD use a /48 for IPv6 addresses, which is nice because we
// don't need to additionally add routing entries. Do that here too.
localAddr6 = netip.PrefixFrom(localAddr6.Addr(), 48)
}
if localAddr6 != r.local6 {
if r.local6.IsValid() {
addrdel := []string{"ifconfig", r.tunname,
"inet6", r.local6.String(), "delete"}
out, err := cmd(addrdel...).CombinedOutput()
if err != nil {
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
if errq == nil {
errq = err
}
}
}
if localAddr6.IsValid() {
addradd := []string{"ifconfig", r.tunname,
"inet6", localAddr6.String()}
out, err := cmd(addradd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
if errq == nil {
errq = err
}
}
}
}
newRoutes := set.Set[netip.Prefix]{}
for _, route := range cfg.Routes {
newRoutes.Add(route)
}
for route := range r.routes {
if _, keep := newRoutes[route]; !keep {
net := netipx.PrefixIPNet(route)
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
dst := localAddr4.Addr().String()
if route.Addr().Is6() {
dst = localAddr6.Addr().String()
}
routedel := []string{"route", "-q", "-n",
"del", "-" + inet(route), nstr,
"-iface", dst}
out, err := cmd(routedel...).CombinedOutput()
if err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
if errq == nil {
errq = err
}
}
}
}
for route := range newRoutes {
if _, exists := r.routes[route]; !exists {
net := netipx.PrefixIPNet(route)
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
dst := localAddr4.Addr().String()
if route.Addr().Is6() {
dst = localAddr6.Addr().String()
}
routeadd := []string{"route", "-q", "-n",
"add", "-" + inet(route), nstr,
"-iface", dst}
out, err := cmd(routeadd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
if errq == nil {
errq = err
}
}
}
}
r.local4 = localAddr4
r.local6 = localAddr6
r.routes = newRoutes
return errq
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *openbsdRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
func (r *openbsdRouter) Close() error {
cleanUp(r.logf, r.tunname)
return nil
}
func cleanUp(logf logger.Logf, interfaceName string) {
out, err := cmd("ifconfig", interfaceName, "down").CombinedOutput()
if err != nil {
logf("ifconfig down: %v\n%s", err, out)
}
}

View File

@@ -0,0 +1,211 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin || freebsd
package router
import (
"fmt"
"log"
"net/netip"
"os/exec"
"runtime"
"github.com/tailscale/wireguard-go/tun"
"go4.org/netipx"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
"tailscale.com/version"
)
type userspaceBSDRouter struct {
logf logger.Logf
netMon *netmon.Monitor
health *health.Tracker
tunname string
local []netip.Prefix
routes map[netip.Prefix]bool
}
func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
tunname, err := tundev.Name()
if err != nil {
return nil, err
}
return &userspaceBSDRouter{
logf: logf,
netMon: netMon,
health: health,
tunname: tunname,
}, nil
}
func (r *userspaceBSDRouter) addrsToRemove(newLocalAddrs []netip.Prefix) (remove []netip.Prefix) {
for _, cur := range r.local {
found := false
for _, v := range newLocalAddrs {
found = (v == cur)
if found {
break
}
}
if !found {
remove = append(remove, cur)
}
}
return
}
func (r *userspaceBSDRouter) addrsToAdd(newLocalAddrs []netip.Prefix) (add []netip.Prefix) {
for _, cur := range newLocalAddrs {
found := false
for _, v := range r.local {
found = (v == cur)
if found {
break
}
}
if !found {
add = append(add, cur)
}
}
return
}
func cmd(args ...string) *exec.Cmd {
if len(args) == 0 {
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]", args)
}
return exec.Command(args[0], args[1:]...)
}
func (r *userspaceBSDRouter) Up() error {
ifup := []string{"ifconfig", r.tunname, "up"}
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
r.logf("running ifconfig failed: %v\n%s", err, out)
return err
}
return nil
}
func inet(p netip.Prefix) string {
if p.Addr().Is6() {
return "inet6"
}
return "inet"
}
func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
if cfg == nil {
cfg = &shutdownConfig
}
setErr := func(err error) {
if reterr == nil {
reterr = err
}
}
addrsToRemove := r.addrsToRemove(cfg.LocalAddrs)
// If we're removing all addresses, we need to remove and re-add all
// routes.
resetRoutes := len(r.local) > 0 && len(addrsToRemove) == len(r.local)
// Update the addresses.
for _, addr := range addrsToRemove {
arg := []string{"ifconfig", r.tunname, inet(addr), addr.String(), "-alias"}
out, err := cmd(arg...).CombinedOutput()
if err != nil {
r.logf("addr del failed: %v => %v\n%s", arg, err, out)
setErr(err)
}
}
for _, addr := range r.addrsToAdd(cfg.LocalAddrs) {
var arg []string
if runtime.GOOS == "freebsd" && addr.Addr().Is6() && addr.Bits() == 128 {
// FreeBSD rejects tun addresses of the form fc00::1/128 -> fc00::1,
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=218508
// Instead add our whole /48, which works because we use a /48 route.
// Full history: https://github.com/tailscale/tailscale/issues/1307
tmp := netip.PrefixFrom(addr.Addr(), 48)
arg = []string{"ifconfig", r.tunname, inet(tmp), tmp.String()}
} else {
arg = []string{"ifconfig", r.tunname, inet(addr), addr.String(), addr.Addr().String()}
}
out, err := cmd(arg...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v => %v\n%s", arg, err, out)
setErr(err)
}
}
newRoutes := make(map[netip.Prefix]bool)
for _, route := range cfg.Routes {
if runtime.GOOS != "darwin" && route == tsaddr.TailscaleULARange() {
// Because we added the interface address as a /48 above,
// the kernel already created the Tailscale ULA route
// implicitly. We mustn't try to add/delete it ourselves.
continue
}
newRoutes[route] = true
}
// Delete any preexisting routes.
for route := range r.routes {
if resetRoutes || !newRoutes[route] {
net := netipx.PrefixIPNet(route)
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
del := "del"
if version.OS() == "macOS" {
del = "delete"
}
routedel := []string{"route", "-q", "-n",
del, "-" + inet(route), nstr,
"-iface", r.tunname}
out, err := cmd(routedel...).CombinedOutput()
if err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
setErr(err)
}
}
}
// Add the routes.
for route := range newRoutes {
if resetRoutes || !r.routes[route] {
net := netipx.PrefixIPNet(route)
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
routeadd := []string{"route", "-q", "-n",
"add", "-" + inet(route), nstr,
"-iface", r.tunname}
out, err := cmd(routeadd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
setErr(err)
}
}
}
// Store the interface and routes so we know what to change on an update.
if reterr == nil {
r.local = append([]netip.Prefix{}, cfg.LocalAddrs...)
}
r.routes = newRoutes
return reterr
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *userspaceBSDRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
func (r *userspaceBSDRouter) Close() error {
return nil
}

400
vendor/tailscale.com/wgengine/router/router_windows.go generated vendored Normal file
View File

@@ -0,0 +1,400 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/netip"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"sync"
"syscall"
"time"
"github.com/tailscale/wireguard-go/tun"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/health"
"tailscale.com/logtail/backoff"
"tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
type winRouter struct {
logf func(fmt string, args ...any)
netMon *netmon.Monitor // may be nil
health *health.Tracker
nativeTun *tun.NativeTun
routeChangeCallback *winipcfg.RouteChangeCallback
firewall *firewallTweaker
}
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
nativeTun := tundev.(*tun.NativeTun)
luid := winipcfg.LUID(nativeTun.LUID())
guid, err := luid.GUID()
if err != nil {
return nil, err
}
return &winRouter{
logf: logf,
netMon: netMon,
health: health,
nativeTun: nativeTun,
firewall: &firewallTweaker{
logf: logger.WithPrefix(logf, "firewall: "),
tunGUID: *guid,
},
}, nil
}
func (r *winRouter) Up() error {
r.firewall.clear()
var err error
t0 := time.Now()
r.routeChangeCallback, err = monitorDefaultRoutes(r.nativeTun)
d := time.Since(t0).Round(time.Millisecond)
if err != nil {
return fmt.Errorf("monitorDefaultRoutes, after %v: %v", d, err)
}
r.logf("monitorDefaultRoutes done after %v", d)
return nil
}
func (r *winRouter) Set(cfg *Config) error {
if cfg == nil {
cfg = &shutdownConfig
}
var localAddrs []string
for _, la := range cfg.LocalAddrs {
localAddrs = append(localAddrs, la.String())
}
r.firewall.set(localAddrs, cfg.Routes, cfg.LocalRoutes)
err := configureInterface(cfg, r.nativeTun, r.health)
if err != nil {
r.logf("ConfigureInterface: %v", err)
return err
}
// Flush DNS on router config change to clear cached DNS entries (solves #1430)
if err := dns.Flush(); err != nil {
r.logf("flushdns error: %v", err)
}
return nil
}
func hasDefaultRoute(routes []netip.Prefix) bool {
for _, route := range routes {
if route.Bits() == 0 {
return true
}
}
return false
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *winRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
func (r *winRouter) Close() error {
r.firewall.clear()
if r.routeChangeCallback != nil {
r.routeChangeCallback.Unregister()
}
return nil
}
func cleanUp(logf logger.Logf, interfaceName string) {
// Nothing to do here.
}
// firewallTweaker changes the Windows firewall. Normally this wouldn't be so complicated,
// but it can be REALLY SLOW to change the Windows firewall for reasons not understood.
// Like 4 minutes slow. But usually it's tens of milliseconds.
// See https://github.com/tailscale/tailscale/issues/785.
// So this tracks the desired state and runs the actual adjusting code asynchronously.
type firewallTweaker struct {
logf logger.Logf
tunGUID windows.GUID
mu sync.Mutex
didProcRule bool
running bool // doAsyncSet goroutine is running
known bool // firewall is in known state (in lastVal)
wantLocal []string // next value we want, or "" to delete the firewall rule
lastLocal []string // last set value, if known
localRoutes []netip.Prefix
lastLocalRoutes []netip.Prefix
wantKillswitch bool
lastKillswitch bool
// Only touched by doAsyncSet, so mu doesn't need to be held.
// fwProc is a subprocess that runs the wireguard-windows firewall
// killswitch code. It is only non-nil when the default route
// killswitch is active, and may go back and forth between nil and
// non-nil any number of times during the process's lifetime.
fwProc *exec.Cmd
// stop makes fwProc exit when closed.
fwProcWriter io.WriteCloser
fwProcEncoder *json.Encoder
// The path to the 'netsh.exe' binary, populated during the first call
// to runFirewall.
//
// not protected by mu; netshPath is only mutated inside netshPathOnce
netshPathOnce sync.Once
netshPath string
}
func (ft *firewallTweaker) clear() { ft.set(nil, nil, nil) }
// set takes CIDRs to allow, and the routes that point into the Tailscale tun interface.
// Empty slices remove firewall rules.
//
// set takes ownership of cidrs, but not routes.
func (ft *firewallTweaker) set(cidrs []string, routes, localRoutes []netip.Prefix) {
ft.mu.Lock()
defer ft.mu.Unlock()
if len(cidrs) == 0 {
ft.logf("marking for removal")
} else {
ft.logf("marking allowed %v", cidrs)
}
ft.wantLocal = cidrs
ft.localRoutes = localRoutes
ft.wantKillswitch = hasDefaultRoute(routes)
if ft.running {
// The doAsyncSet goroutine will check ft.wantLocal/wantKillswitch
// before returning.
return
}
ft.logf("starting netsh goroutine")
ft.running = true
go ft.doAsyncSet()
}
// getNetshPath returns the path that should be used to execute netsh.
//
// We've seen a report from a customer that we're triggering the "cannot run
// executable found relative to current directory" protection that was added to
// prevent running possibly attacker-controlled binaries. To mitigate this,
// first try looking up the path to netsh.exe in the System32 directory
// explicitly, and then fall back to the prior behaviour of passing "netsh" to
// os/exec.Command.
func (ft *firewallTweaker) getNetshPath() string {
ft.netshPathOnce.Do(func() {
// The default value is the old approach: just run "netsh" and
// let os/exec resolve that into a full path.
ft.netshPath = "netsh"
path, err := windows.KnownFolderPath(windows.FOLDERID_System, 0)
if err != nil {
ft.logf("getNetshPath: error getting FOLDERID_System: %v", err)
return
}
expath := filepath.Join(path, "netsh.exe")
if _, err := os.Stat(expath); err == nil {
ft.netshPath = expath
return
} else if !os.IsNotExist(err) {
ft.logf("getNetshPath: error checking for existence of %q: %v", expath, err)
}
// Keep default
})
return ft.netshPath
}
func (ft *firewallTweaker) runFirewall(args ...string) (time.Duration, error) {
t0 := time.Now()
args = append([]string{"advfirewall", "firewall"}, args...)
cmd := exec.Command(ft.getNetshPath(), args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS,
}
b, err := cmd.CombinedOutput()
if err != nil {
err = fmt.Errorf("%w: %v", err, string(b))
}
return time.Since(t0).Round(time.Millisecond), err
}
func (ft *firewallTweaker) doAsyncSet() {
bo := backoff.NewBackoff("win-firewall", ft.logf, time.Minute)
ctx := context.Background()
ft.mu.Lock()
for { // invariant: ft.mu must be locked when beginning this block
val := ft.wantLocal
if ft.known && slices.Equal(ft.lastLocal, val) && ft.wantKillswitch == ft.lastKillswitch && slices.Equal(ft.localRoutes, ft.lastLocalRoutes) {
ft.running = false
ft.logf("ending netsh goroutine")
ft.mu.Unlock()
return
}
wantKillswitch := ft.wantKillswitch
needClear := !ft.known || len(ft.lastLocal) > 0 || len(val) == 0
needProcRule := !ft.didProcRule
localRoutes := ft.localRoutes
ft.mu.Unlock()
err := ft.doSet(val, wantKillswitch, needClear, needProcRule, localRoutes)
if err != nil {
ft.logf("set failed: %v", err)
}
bo.BackOff(ctx, err)
ft.mu.Lock()
ft.lastLocal = val
ft.lastLocalRoutes = localRoutes
ft.lastKillswitch = wantKillswitch
ft.known = (err == nil)
}
}
// doSet creates and deletes firewall rules to make the system state
// match the values of local, killswitch, clear and procRule.
//
// local is the list of local Tailscale addresses (formatted as CIDR
// prefixes) to allow through the Windows firewall.
// killswitch, if true, enables the wireguard-windows based internet
// killswitch to prevent use of non-Tailscale default routes.
// clear, if true, removes all tailscale address firewall rules before
// adding local.
// procRule, if true, installs a firewall rule that permits the Tailscale
// process to dial out as it pleases.
//
// Must only be invoked from doAsyncSet.
func (ft *firewallTweaker) doSet(local []string, killswitch bool, clear bool, procRule bool, allowedRoutes []netip.Prefix) error {
if clear {
ft.logf("clearing Tailscale-In firewall rules...")
// We ignore the error here, because netsh returns an error for
// deleting something that doesn't match.
// TODO(bradfitz): care? That'd involve querying it before/after to see
// whether it was necessary/worked. But the output format is localized,
// so can't rely on parsing English. Maybe need to use OLE, not netsh.exe?
d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in")
ft.logf("cleared Tailscale-In firewall rules in %v", d)
}
if procRule {
ft.logf("deleting any prior Tailscale-Process rule...")
d, err := ft.runFirewall("delete", "rule", "name=Tailscale-Process", "dir=in") // best effort
if err == nil {
ft.logf("removed old Tailscale-Process rule in %v", d)
}
var exe string
exe, err = os.Executable()
if err != nil {
ft.logf("failed to find Executable for Tailscale-Process rule: %v", err)
} else {
ft.logf("adding Tailscale-Process rule to allow UDP for %q ...", exe)
d, err = ft.runFirewall("add", "rule", "name=Tailscale-Process",
"dir=in",
"action=allow",
"edge=yes",
"program="+exe,
"protocol=udp",
"profile=any",
"enable=yes",
)
if err != nil {
ft.logf("error adding Tailscale-Process rule: %v", err)
} else {
ft.mu.Lock()
ft.didProcRule = true
ft.mu.Unlock()
ft.logf("added Tailscale-Process rule in %v", d)
}
}
}
for _, cidr := range local {
ft.logf("adding Tailscale-In rule to allow %v ...", cidr)
var d time.Duration
d, err := ft.runFirewall("add", "rule", "name=Tailscale-In", "dir=in", "action=allow", "localip="+cidr, "profile=private,domain", "enable=yes")
if err != nil {
ft.logf("error adding Tailscale-In rule to allow %v: %v", cidr, err)
return err
}
ft.logf("added Tailscale-In rule to allow %v in %v", cidr, d)
}
if !killswitch {
if ft.fwProc != nil {
ft.fwProcWriter.Close()
ft.fwProcWriter = nil
ft.fwProc.Wait()
ft.fwProc = nil
ft.fwProcEncoder = nil
}
return nil
}
if ft.fwProc == nil {
exe, err := os.Executable()
if err != nil {
return err
}
proc := exec.Command(exe, "/firewall", ft.tunGUID.String())
proc.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: windows.DETACHED_PROCESS,
}
in, err := proc.StdinPipe()
if err != nil {
return err
}
out, err := proc.StdoutPipe()
if err != nil {
in.Close()
return err
}
go func(out io.ReadCloser) {
b := bufio.NewReaderSize(out, 1<<10)
for {
line, err := b.ReadString('\n')
if err != nil {
return
}
line = strings.TrimSpace(line)
if line != "" {
ft.logf("fw-child: %s", line)
}
}
}(out)
proc.Stderr = proc.Stdout
if err := proc.Start(); err != nil {
return err
}
ft.fwProcWriter = in
ft.fwProc = proc
ft.fwProcEncoder = json.NewEncoder(in)
}
// Note(maisem): when local lan access toggled, we need to inform the
// firewall to let the local routes through. The set of routes is passed
// in via stdin encoded in json.
return ft.fwProcEncoder.Encode(allowedRoutes)
}

120
vendor/tailscale.com/wgengine/router/runner.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package router
import (
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"golang.org/x/sys/unix"
)
// commandRunner abstracts helpers to run OS commands. It exists
// purely to swap out osCommandRunner (below) with a fake runner in
// tests.
type commandRunner interface {
run(...string) error
output(...string) ([]byte, error)
}
type osCommandRunner struct {
// ambientCapNetAdmin determines whether commands are executed with
// CAP_NET_ADMIN.
// CAP_NET_ADMIN is required when running as non-root and executing cmds
// like `ip rule`. Even if our process has the capability, we need to
// explicitly grant it to the new process.
// We specifically need this for Synology DSM7 where tailscaled no longer
// runs as root.
ambientCapNetAdmin bool
}
// errCode extracts and returns the process exit code from err, or
// zero if err is nil.
func errCode(err error) int {
if err == nil {
return 0
}
var e *exec.ExitError
if ok := errors.As(err, &e); ok {
return e.ExitCode()
}
s := err.Error()
if strings.HasPrefix(s, "exitcode:") {
code, err := strconv.Atoi(s[9:])
if err == nil {
return code
}
}
return -42
}
func (o osCommandRunner) run(args ...string) error {
_, err := o.output(args...)
return err
}
func (o osCommandRunner) output(args ...string) ([]byte, error) {
if len(args) == 0 {
return nil, errors.New("cmd: no argv[0]")
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = append(os.Environ(), "LC_ALL=C")
if o.ambientCapNetAdmin {
cmd.SysProcAttr = &syscall.SysProcAttr{
AmbientCaps: []uintptr{unix.CAP_NET_ADMIN},
}
}
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("running %q failed: %w\n%s", strings.Join(args, " "), err, out)
}
return out, nil
}
type runGroup struct {
OkCode []int // error codes that are acceptable, other than 0, if any
Runner commandRunner // the runner that actually runs our commands
ErrAcc error // first error encountered, if any
}
func newRunGroup(okCode []int, runner commandRunner) *runGroup {
return &runGroup{
OkCode: okCode,
Runner: runner,
}
}
func (rg *runGroup) okCode(err error) bool {
got := errCode(err)
for _, want := range rg.OkCode {
if got == want {
return true
}
}
return false
}
func (rg *runGroup) Output(args ...string) []byte {
b, err := rg.Runner.output(args...)
if rg.ErrAcc == nil && err != nil && !rg.okCode(err) {
rg.ErrAcc = err
}
return b
}
func (rg *runGroup) Run(args ...string) {
err := rg.Runner.run(args...)
if rg.ErrAcc == nil && err != nil && !rg.okCode(err) {
rg.ErrAcc = err
}
}