diff --git a/go.mod b/go.mod index 7ffd3fd5b..669369578 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/tools v0.1.11 golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 - golang.zx2c4.com/wireguard/windows v0.4.10 + golang.zx2c4.com/wireguard/windows v0.5.3 gvisor.dev/gvisor v0.0.0-20220801230058-850e42eb4444 honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83 inet.af/peercred v0.0.0-20210906144145-0893ea02156a @@ -266,7 +266,7 @@ require ( github.com/yeya24/promlinter v0.1.0 // indirect golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.2 // indirect diff --git a/go.sum b/go.sum index 0be2bdbc7..8db6f1994 100644 --- a/go.sum +++ b/go.sum @@ -729,8 +729,6 @@ github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -1352,7 +1350,6 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1449,7 +1446,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1508,8 +1504,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= +golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1636,11 +1633,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8= golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0= golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= -golang.zx2c4.com/wireguard/windows v0.4.10 h1:HmjzJnb+G4NCdX+sfjsQlsxGPuYaThxRbZUZFLyR0/s= -golang.zx2c4.com/wireguard/windows v0.4.10/go.mod h1:v7w/8FC48tTBm1IzScDVPEEb0/GjLta+T0ybpP9UWRg= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go index 6760a85e8..e25fa3f86 100644 --- a/net/dns/manager_windows.go +++ b/net/dns/manager_windows.go @@ -485,11 +485,7 @@ func (m windowsManager) getBasePrimaryResolver() (resolvers []netip.Addr, err er } ipLoop: - for _, stdip := range ips { - ip, ok := netip.AddrFromSlice(stdip) - if !ok { - continue - } + for _, ip := range ips { ip = ip.Unmap() // Skip IPv6 site-local resolvers. These are an ancient // and obsolete IPv6 RFC, which Windows still faithfully diff --git a/net/interfaces/interfaces_windows.go b/net/interfaces/interfaces_windows.go index 6c9412475..c3a0c5e26 100644 --- a/net/interfaces/interfaces_windows.go +++ b/net/interfaces/interfaces_windows.go @@ -6,7 +6,6 @@ package interfaces import ( "log" - "net" "net/netip" "net/url" "strings" @@ -54,22 +53,21 @@ func likelyHomeRouterIPWindows() (ret netip.Addr, ok bool) { return } - unspec := net.IPv4(0, 0, 0, 0) + v4unspec := netip.IPv4Unspecified() var best *winipcfg.MibIPforwardRow2 // best (lowest metric) found so far, or nil for i := range rs { r := &rs[i] - if r.Loopback || r.DestinationPrefix.PrefixLength != 0 || !r.DestinationPrefix.Prefix.IP().Equal(unspec) { + if r.Loopback || r.DestinationPrefix.PrefixLength != 0 || r.DestinationPrefix.Prefix().Addr().Unmap() != v4unspec { // Not a default route, so skip continue } - ip, ok := netip.AddrFromSlice(r.NextHop.IP()) - if !ok { + ip := r.NextHop.Addr().Unmap() + if !ip.IsValid() { // Not a valid gateway, so skip (won't happen though) continue } - ip = ip.Unmap() if best == nil { best = r diff --git a/wgengine/monitor/monitor_windows.go b/wgengine/monitor/monitor_windows.go index a205942ff..d21c1409a 100644 --- a/wgengine/monitor/monitor_windows.go +++ b/wgengine/monitor/monitor_windows.go @@ -7,13 +7,11 @@ package monitor import ( "context" "errors" - "net/netip" "strings" "sync" "time" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" - "tailscale.com/net/netaddr" "tailscale.com/net/tsaddr" "tailscale.com/types/logger" ) @@ -133,7 +131,7 @@ func (m *winMon) Receive() (message, error) { // unicastAddressChanged is the callback we register with Windows to call when unicast address changes. func (m *winMon) unicastAddressChanged(_ winipcfg.MibNotificationType, row *winipcfg.MibUnicastIPAddressRow) { what := "addr" - if ip, ok := netip.AddrFromSlice(row.Address.IP()); ok && tsaddr.IsTailscaleIP(ip.Unmap()) { + if ip := row.Address.Addr(); ip.IsValid() && tsaddr.IsTailscaleIP(ip.Unmap()) { what = "tsaddr" } @@ -144,8 +142,8 @@ func (m *winMon) unicastAddressChanged(_ winipcfg.MibNotificationType, row *wini // routeChanged is the callback we register with Windows to call when route changes. func (m *winMon) routeChanged(_ winipcfg.MibNotificationType, row *winipcfg.MibIPforwardRow2) { what := "route" - ipn := row.DestinationPrefix.IPNet() - if cidr, ok := netaddr.FromStdIPNet(&ipn); ok && tsaddr.IsTailscaleIP(cidr.Addr()) { + ip := row.DestinationPrefix.Prefix().Addr().Unmap() + if ip.IsValid() && tsaddr.IsTailscaleIP(ip) { what = "tsroute" } // start a goroutine to finish our work, to return to Windows out of this callback diff --git a/wgengine/router/ifconfig_windows.go b/wgengine/router/ifconfig_windows.go index aaac2f57a..887b8b13b 100644 --- a/wgengine/router/ifconfig_windows.go +++ b/wgengine/router/ifconfig_windows.go @@ -6,7 +6,6 @@ package router import ( - "bytes" "errors" "fmt" "log" @@ -18,12 +17,12 @@ import ( ole "github.com/go-ole/go-ole" "go4.org/netipx" + "golang.org/x/exp/slices" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/health" "tailscale.com/net/interfaces" - "tailscale.com/net/netaddr" "tailscale.com/net/tsaddr" "tailscale.com/util/multierr" "tailscale.com/wgengine/winnet" @@ -324,25 +323,23 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) { // ours where the nexthop is meaningless, you're supposed to use // one of the local IP addresses of the interface. Find an IPv4 // and IPv6 address we can use for this purpose. - var firstGateway4 *net.IP - var firstGateway6 *net.IP - addresses := make([]*net.IPNet, 0, len(cfg.LocalAddrs)) + var firstGateway4 netip.Addr + var firstGateway6 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 } - ipnet := netipx.PrefixIPNet(addr) - addresses = append(addresses, ipnet) - gateway := ipnet.IP - if addr.Addr().Is4() && firstGateway4 == nil { - firstGateway4 = &gateway - } else if addr.Addr().Is6() && firstGateway6 == nil { - firstGateway6 = &gateway + addresses = append(addresses, addr) + if addr.Addr().Is4() && !firstGateway4.IsValid() { + firstGateway4 = addr.Addr() + } else if addr.Addr().Is6() && !firstGateway6.IsValid() { + firstGateway6 = addr.Addr() } } - var routes []winipcfg.RouteData + var routes []*winipcfg.RouteData foundDefault4 := false foundDefault6 := false for _, route := range cfg.Routes { @@ -351,37 +348,33 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) { continue } - if route.Addr().Is6() && firstGateway6 == nil { + if route.Addr().Is6() && !firstGateway6.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. - ipnet := &net.IPNet{tsaddr.Tailscale4To6Placeholder().AsSlice(), net.CIDRMask(128, 128)} - addresses = append(addresses, ipnet) - firstGateway6 = &ipnet.IP - } else if route.Addr().Is4() && firstGateway4 == nil { + ip := tsaddr.Tailscale4To6Placeholder() + addresses = append(addresses, netip.PrefixFrom(ip, ip.BitLen())) + firstGateway6 = ip + } else if route.Addr().Is4() && !firstGateway4.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") } - ipn := netipx.PrefixIPNet(route) - var gateway net.IP + var gateway netip.Addr if route.Addr().Is4() { - gateway = *firstGateway4 + gateway = firstGateway4 } else if route.Addr().Is6() { - gateway = *firstGateway6 + gateway = firstGateway6 } - r := winipcfg.RouteData{ - Destination: net.IPNet{ - IP: ipn.IP.Mask(ipn.Mask), - Mask: ipn.Mask, - }, - NextHop: gateway, - Metric: 0, + r := &winipcfg.RouteData{ + Destination: route, + NextHop: gateway, + Metric: 0, } - if net.IP.Equal(r.Destination.IP, gateway) { + if r.Destination.Addr().Unmap() == gateway { // 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 @@ -393,12 +386,12 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) { if route.Bits() == 0 { foundDefault4 = true } - r.NextHop = *firstGateway4 + r.NextHop = firstGateway4 } else if route.Addr().Is6() { if route.Bits() == 0 { foundDefault6 = true } - r.NextHop = *firstGateway6 + r.NextHop = firstGateway6 } routes = append(routes, r) } @@ -408,18 +401,16 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) { return fmt.Errorf("syncAddresses: %w", err) } - sort.Slice(routes, func(i, j int) bool { return routeLess(&routes[i], &routes[j]) }) + slices.SortFunc(routes, routeDataLess) deduplicatedRoutes := []*winipcfg.RouteData{} for i := 0; i < len(routes); i++ { // There's only one way to get to a given IP+Mask, so delete // all matches after the first. - if i > 0 && - net.IP.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) && - bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) { + if i > 0 && routes[i].Destination == routes[i-1].Destination { continue } - deduplicatedRoutes = append(deduplicatedRoutes, &routes[i]) + deduplicatedRoutes = append(deduplicatedRoutes, routes[i]) } // Re-read interface after syncAddresses. @@ -484,28 +475,6 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) { return errAcc } -// routeLess reports whether ri should sort before rj. -// The actual sort order doesn't appear to matter. The caller just -// wants them sorted to be able to de-dup. -func routeLess(ri, rj *winipcfg.RouteData) bool { - if v := bytes.Compare(ri.Destination.IP, rj.Destination.IP); v != 0 { - return v == -1 - } - if v := bytes.Compare(ri.Destination.Mask, rj.Destination.Mask); v != 0 { - // Narrower masks first - return v == 1 - } - if ri.Metric != rj.Metric { - // Lower metrics first - return ri.Metric < rj.Metric - } - if v := bytes.Compare(ri.NextHop, rj.NextHop); v != 0 { - // No nexthop before non-empty nexthop. - return v == -1 - } - return false -} - // unwrapIP returns the shortest version of ip. func unwrapIP(ip net.IP) net.IP { if ip4 := ip.To4(); ip4 != nil { @@ -521,40 +490,40 @@ func v4Mask(m net.IPMask) net.IPMask { return m } -func netCompare(a, b net.IPNet) int { - aip, bip := unwrapIP(a.IP), unwrapIP(b.IP) - v := bytes.Compare(aip, bip) +func netCompare(a, b netip.Prefix) int { + aip, bip := a.Addr().Unmap(), b.Addr().Unmap() + v := aip.Compare(bip) if v != 0 { return v } - amask, bmask := a.Mask, b.Mask - if len(aip) == 4 { - amask = v4Mask(a.Mask) - bmask = v4Mask(b.Mask) + if a.Bits() == b.Bits() { + return 0 } - // narrower first - return -bytes.Compare(amask, bmask) + if a.Bits() > b.Bits() { + return -1 + } + return 1 } -func sortNets(a []*net.IPNet) { - sort.Slice(a, func(i, j int) bool { - return netCompare(*a[i], *a[j]) == -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 []*net.IPNet) (add, del []*net.IPNet) { - add = make([]*net.IPNet, 0, len(b)) - del = make([]*net.IPNet, 0, len(a)) +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]) { + switch netCompare(a[i], b[j]) { case -1: // a < b, delete del = append(del, a[i]) @@ -576,28 +545,21 @@ func deltaNets(a, b []*net.IPNet) (add, del []*net.IPNet) { return } -func isIPv6LinkLocal(in *net.IPNet) bool { - return len(in.IP) == 16 && in.IP.IsLinkLocalUnicast() +func isIPv6LinkLocal(a netip.Prefix) bool { + return a.Addr().Is6() && a.Addr().IsLinkLocalUnicast() } -// ipAdapterUnicastAddressToIPNet converts windows.IpAdapterUnicastAddress to net.IPNet. -func ipAdapterUnicastAddressToIPNet(u *windows.IpAdapterUnicastAddress) *net.IPNet { - ip := u.Address.IP() - w := 32 - if ip.To4() == nil { - w = 128 - } - return &net.IPNet{ - IP: ip, - Mask: net.CIDRMask(int(u.OnLinkPrefixLength), w), - } +// 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) []*net.IPNet { - nets := make([]*net.IPNet, 0) +func unicastIPNets(ifc *winipcfg.IPAdapterAddresses) []netip.Prefix { + var nets []netip.Prefix for addr := ifc.FirstUnicastAddress; addr != nil; addr = addr.Next { - nets = append(nets, ipAdapterUnicastAddressToIPNet(addr)) + nets = append(nets, ipAdapterUnicastAddressToPrefix(addr)) } return nets } @@ -612,13 +574,13 @@ func unicastIPNets(ifc *winipcfg.IPAdapterAddresses) []*net.IPNet { // 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 []*net.IPNet) error { +func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []netip.Prefix) error { var erracc error got := unicastIPNets(ifc) add, del := deltaNets(got, want) - ll := make([]*net.IPNet, 0) + ll := make([]netip.Prefix, 0) for _, a := range del { // do not delete link-local addresses, and collect them for later // applying SkipAsSource. @@ -627,29 +589,29 @@ func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []*net.IPNet) error { continue } - err := ifc.LUID.DeleteIPAddress(*a) + err := ifc.LUID.DeleteIPAddress(a) if err != nil { - erracc = fmt.Errorf("deleting IP %q: %w", *a, err) + erracc = fmt.Errorf("deleting IP %q: %w", a, err) } } for _, a := range add { - err := ifc.LUID.AddIPAddress(*a) + err := ifc.LUID.AddIPAddress(a) if err != nil { - erracc = fmt.Errorf("adding IP %q: %w", *a, err) + erracc = fmt.Errorf("adding IP %q: %w", a, err) } } for _, a := range ll { - mib, err := ifc.LUID.IPAddress(a.IP) + 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) + 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) + erracc = fmt.Errorf("setting skip-as-source on IP %q: unable to set MIB: %w", a, err) } } } @@ -657,20 +619,27 @@ func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []*net.IPNet) error { return erracc } +func routeDataLess(a, b *winipcfg.RouteData) bool { + return routeDataCompare(a, b) < 0 +} + func routeDataCompare(a, b *winipcfg.RouteData) int { - v := bytes.Compare(a.Destination.IP, b.Destination.IP) + v := a.Destination.Addr().Compare(b.Destination.Addr()) if v != 0 { return v } // Narrower masks first - v = bytes.Compare(a.Destination.Mask, b.Destination.Mask) - if v != 0 { - return -v + b1, b2 := a.Destination.Bits(), b.Destination.Bits() + if b1 != b2 { + if b1 > b2 { + return -1 + } + return 1 } // No nexthop before non-empty nexthop - v = bytes.Compare(a.NextHop, b.NextHop) + v = a.NextHop.Compare(b.NextHop) if v != 0 { return v } @@ -685,17 +654,11 @@ func routeDataCompare(a, b *winipcfg.RouteData) int { return 0 } -func sortRouteData(a []*winipcfg.RouteData) { - sort.Slice(a, func(i, j int) bool { - return routeDataCompare(a[i], a[j]) < 0 - }) -} - func deltaRouteData(a, b []*winipcfg.RouteData) (add, del []*winipcfg.RouteData) { add = make([]*winipcfg.RouteData, 0, len(b)) del = make([]*winipcfg.RouteData, 0, len(a)) - sortRouteData(a) - sortRouteData(b) + slices.SortFunc(a, routeDataLess) + slices.SortFunc(b, routeDataLess) i := 0 j := 0 @@ -751,15 +714,15 @@ func getAllInterfaceRoutes(ifc *winipcfg.IPAdapterAddresses) ([]*winipcfg.RouteD rd := make([]*winipcfg.RouteData, 0, len(routes4)+len(routes6)) for _, r := range routes4 { rd = append(rd, &winipcfg.RouteData{ - Destination: r.DestinationPrefix.IPNet(), - NextHop: r.NextHop.IP(), + Destination: r.DestinationPrefix.Prefix(), + NextHop: r.NextHop.Addr(), Metric: r.Metric, }) } for _, r := range routes6 { rd = append(rd, &winipcfg.RouteData{ - Destination: r.DestinationPrefix.IPNet(), - NextHop: r.NextHop.IP(), + Destination: r.DestinationPrefix.Prefix(), + NextHop: r.NextHop.Addr(), Metric: r.Metric, }) } @@ -777,8 +740,8 @@ func filterRoutes(routes []*winipcfg.RouteData, dontDelete []netip.Prefix) []*wi } for _, r := range routes { // We don't want to touch broadcast routes that Windows adds. - nr, ok := netaddr.FromStdIPNet(&r.Destination) - if !ok { + nr := r.Destination + if !nr.IsValid() { continue } if nr.IsSingleIP() { @@ -789,8 +752,8 @@ func filterRoutes(routes []*winipcfg.RouteData, dontDelete []netip.Prefix) []*wi } filtered := make([]*winipcfg.RouteData, 0, len(routes)) for _, r := range routes { - rr, ok := netaddr.FromStdIPNet(&r.Destination) - if ok && ddm[rr] { + rr := r.Destination + if rr.IsValid() && ddm[rr] { continue } filtered = append(filtered, r) diff --git a/wgengine/router/ifconfig_windows_test.go b/wgengine/router/ifconfig_windows_test.go index cb6c2a909..1605829af 100644 --- a/wgengine/router/ifconfig_windows_test.go +++ b/wgengine/router/ifconfig_windows_test.go @@ -7,41 +7,30 @@ package router import ( "fmt" "math/rand" - "net" "net/netip" + "reflect" "strings" "testing" - "go4.org/netipx" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) -func randIP() net.IP { +func randIP() netip.Addr { b := byte(rand.Intn(3)) - return net.IP{b, b, b, b} + return netip.AddrFrom4([4]byte{b, b, b, b}) } func randRouteData() *winipcfg.RouteData { return &winipcfg.RouteData{ - Destination: net.IPNet{ - IP: randIP(), - Mask: net.CIDRMask(rand.Intn(3)+1, 32), - }, - NextHop: randIP(), - Metric: uint32(rand.Intn(3)), + Destination: netip.PrefixFrom(randIP(), rand.Intn(30)+1), + NextHop: randIP(), + Metric: uint32(rand.Intn(3)), } } func TestRouteLess(t *testing.T) { type D = winipcfg.RouteData - ipnet := func(s string) net.IPNet { - ipp, err := netip.ParsePrefix(s) - if err != nil { - t.Fatalf("error parsing test data %q: %v", s, err) - } - return *netipx.PrefixIPNet(ipp) - } - + ipnet := netip.MustParsePrefix tests := []struct { ri, rj *winipcfg.RouteData want bool @@ -72,76 +61,51 @@ func TestRouteLess(t *testing.T) { want: true, }, { - ri: &D{Destination: ipnet("1.1.0.0/16"), Metric: 1, NextHop: net.ParseIP("3.3.3.3")}, - rj: &D{Destination: ipnet("1.1.0.0/16"), Metric: 1, NextHop: net.ParseIP("4.4.4.4")}, + ri: &D{Destination: ipnet("1.1.0.0/16"), Metric: 1, NextHop: netip.MustParseAddr("3.3.3.3")}, + rj: &D{Destination: ipnet("1.1.0.0/16"), Metric: 1, NextHop: netip.MustParseAddr("4.4.4.4")}, want: true, }, } for i, tt := range tests { - got := routeLess(tt.ri, tt.rj) + got := routeDataLess(tt.ri, tt.rj) if got != tt.want { t.Errorf("%v. less = %v; want %v", i, got, tt.want) } - back := routeLess(tt.rj, tt.ri) + back := routeDataLess(tt.rj, tt.ri) if back && got { t.Errorf("%v. less both ways", i) } } } -func TestRouteLessConsistent(t *testing.T) { +func TestRouteDataLessConsistent(t *testing.T) { for i := 0; i < 10000; i++ { ri := randRouteData() rj := randRouteData() - if routeLess(ri, rj) && routeLess(rj, ri) { + if routeDataLess(ri, rj) && routeDataLess(rj, ri) { t.Fatalf("both compare less to each other:\n\t%#v\nand\n\t%#v", ri, rj) } } } -func equalNetIPs(a, b []*net.IPNet) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if netCompare(*a[i], *b[i]) != 0 { - return false - } - } - return true -} - -func ipnet4(ip string, bits int) *net.IPNet { - return &net.IPNet{ - IP: net.ParseIP(ip), - Mask: net.CIDRMask(bits, 32), +func nets(cidrs ...string) (ret []netip.Prefix) { + for _, s := range cidrs { + ret = append(ret, netip.MustParsePrefix(s)) } + return } -// each cidr can end in "[4]" to mean To4 form. -func nets(cidrs ...string) (ret []*net.IPNet) { - for _, s := range cidrs { - to4 := strings.HasSuffix(s, "[4]") - if to4 { - s = strings.TrimSuffix(s, "[4]") - } - ip, ipNet, err := net.ParseCIDR(s) - if err != nil { - panic(fmt.Sprintf("Bogus CIDR %q in test", s)) - } - if to4 { - ip = ip.To4() - } - ipNet.IP = ip - ret = append(ret, ipNet) +func nilIfEmpty[E any](s []E) []E { + if len(s) == 0 { + return nil } - return + return s } func TestDeltaNets(t *testing.T) { tests := []struct { - a, b []*net.IPNet - wantAdd, wantDel []*net.IPNet + a, b []netip.Prefix + wantAdd, wantDel []netip.Prefix }{ { a: nets("1.2.3.4/24", "1.2.3.4/31", "1.2.3.3/32", "10.0.1.1/32", "100.0.1.1/32"), @@ -161,30 +125,16 @@ func TestDeltaNets(t *testing.T) { }, { a: nets("100.84.36.11/32", "fe80::99d0:ec2d:b2e7:536b/64"), - b: nets("100.84.36.11/32[4]"), + b: nets("100.84.36.11/32"), wantDel: nets("fe80::99d0:ec2d:b2e7:536b/64"), }, - { - a: []*net.IPNet{ - { - IP: net.ParseIP("1.2.3.4"), - Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}, - }, - }, - b: []*net.IPNet{ - { - IP: net.ParseIP("1.2.3.4"), - Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - }, - }, } for i, tt := range tests { add, del := deltaNets(tt.a, tt.b) - if !equalNetIPs(add, tt.wantAdd) { + if !reflect.DeepEqual(nilIfEmpty(add), nilIfEmpty(tt.wantAdd)) { t.Errorf("[%d] add:\n got: %v\n want: %v\n", i, add, tt.wantAdd) } - if !equalNetIPs(del, tt.wantDel) { + if !reflect.DeepEqual(nilIfEmpty(del), nilIfEmpty(tt.wantDel)) { t.Errorf("[%d] del:\n got: %v\n want: %v\n", i, del, tt.wantDel) } } @@ -210,35 +160,40 @@ func equalRouteDatas(a, b []*winipcfg.RouteData) bool { return true } +func ipnet4(ip string, bits int) netip.Prefix { + return netip.PrefixFrom(netip.MustParseAddr(ip), bits) +} + func TestFilterRoutes(t *testing.T) { - var h0 net.IP + var h0 netip.Addr + in := []*winipcfg.RouteData{ // LinkLocal and Loopback routes. - {*ipnet4("169.254.0.0", 16), h0, 1}, - {*ipnet4("169.254.255.255", 32), h0, 1}, - {*ipnet4("127.0.0.0", 8), h0, 1}, - {*ipnet4("127.255.255.255", 32), h0, 1}, + {ipnet4("169.254.0.0", 16), h0, 1}, + {ipnet4("169.254.255.255", 32), h0, 1}, + {ipnet4("127.0.0.0", 8), h0, 1}, + {ipnet4("127.255.255.255", 32), h0, 1}, // Local LAN routes. - {*ipnet4("192.168.0.0", 24), h0, 1}, - {*ipnet4("192.168.0.255", 32), h0, 1}, - {*ipnet4("192.168.1.0", 25), h0, 1}, - {*ipnet4("192.168.1.127", 32), h0, 1}, + {ipnet4("192.168.0.0", 24), h0, 1}, + {ipnet4("192.168.0.255", 32), h0, 1}, + {ipnet4("192.168.1.0", 25), h0, 1}, + {ipnet4("192.168.1.127", 32), h0, 1}, // Some random other route. - {*ipnet4("192.168.2.23", 32), h0, 1}, + {ipnet4("192.168.2.23", 32), h0, 1}, // Our own tailscale address. - {*ipnet4("100.100.100.100", 32), h0, 1}, + {ipnet4("100.100.100.100", 32), h0, 1}, // Other tailscale addresses. - {*ipnet4("100.100.100.101", 32), h0, 1}, - {*ipnet4("100.100.100.102", 32), h0, 1}, + {ipnet4("100.100.100.101", 32), h0, 1}, + {ipnet4("100.100.100.102", 32), h0, 1}, } want := []*winipcfg.RouteData{ - {*ipnet4("169.254.0.0", 16), h0, 1}, - {*ipnet4("127.0.0.0", 8), h0, 1}, - {*ipnet4("192.168.0.0", 24), h0, 1}, - {*ipnet4("192.168.1.0", 25), h0, 1}, - {*ipnet4("192.168.2.23", 32), h0, 1}, - {*ipnet4("100.100.100.101", 32), h0, 1}, - {*ipnet4("100.100.100.102", 32), h0, 1}, + {ipnet4("169.254.0.0", 16), h0, 1}, + {ipnet4("127.0.0.0", 8), h0, 1}, + {ipnet4("192.168.0.0", 24), h0, 1}, + {ipnet4("192.168.1.0", 25), h0, 1}, + {ipnet4("192.168.2.23", 32), h0, 1}, + {ipnet4("100.100.100.101", 32), h0, 1}, + {ipnet4("100.100.100.102", 32), h0, 1}, } got := filterRoutes(in, mustCIDRs("100.100.100.100/32")) @@ -248,29 +203,29 @@ func TestFilterRoutes(t *testing.T) { } func TestDeltaRouteData(t *testing.T) { - var h0 net.IP - h1 := net.ParseIP("99.99.99.99") - h2 := net.ParseIP("99.99.9.99") + var h0 netip.Addr + h1 := netip.MustParseAddr("99.99.99.99") + h2 := netip.MustParseAddr("99.99.9.99") a := []*winipcfg.RouteData{ - {*ipnet4("1.2.3.4", 32), h0, 1}, - {*ipnet4("1.2.3.4", 24), h1, 2}, - {*ipnet4("1.2.3.4", 24), h2, 1}, - {*ipnet4("1.2.3.5", 32), h0, 1}, + {ipnet4("1.2.3.4", 32), h0, 1}, + {ipnet4("1.2.3.4", 24), h1, 2}, + {ipnet4("1.2.3.4", 24), h2, 1}, + {ipnet4("1.2.3.5", 32), h0, 1}, } b := []*winipcfg.RouteData{ - {*ipnet4("1.2.3.5", 32), h0, 1}, - {*ipnet4("1.2.3.4", 24), h1, 2}, - {*ipnet4("1.2.3.4", 24), h2, 2}, + {ipnet4("1.2.3.5", 32), h0, 1}, + {ipnet4("1.2.3.4", 24), h1, 2}, + {ipnet4("1.2.3.4", 24), h2, 2}, } add, del := deltaRouteData(a, b) wantAdd := []*winipcfg.RouteData{ - {*ipnet4("1.2.3.4", 24), h2, 2}, + {ipnet4("1.2.3.4", 24), h2, 2}, } wantDel := []*winipcfg.RouteData{ - {*ipnet4("1.2.3.4", 32), h0, 1}, - {*ipnet4("1.2.3.4", 24), h2, 1}, + {ipnet4("1.2.3.4", 32), h0, 1}, + {ipnet4("1.2.3.4", 24), h2, 1}, } if !equalRouteDatas(add, wantAdd) {