From 5cf3b2955d4c910a4770ce1e7e602850fde75c8c Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Mon, 20 May 2024 01:14:37 +0900 Subject: [PATCH 1/8] Refactor sysops to use struct --- client/internal/routemanager/manager.go | 14 +- .../routemanager/sysctl/sysctl_linux.go | 103 +++++ .../routemanager/systemops/systemops.go | 168 +------ .../systemops/systemops_android.go | 33 -- .../systemops/systemops_darwin_test.go | 6 +- .../systemops/systemops_generic.go | 412 ++++++++++++++++++ ...mops_test.go => systemops_generic_test.go} | 52 ++- .../routemanager/systemops/systemops_ios.go | 33 -- .../routemanager/systemops/systemops_linux.go | 125 +----- .../systemops/systemops_mobile.go | 34 ++ .../systemops/systemops_nonlinux.go | 14 +- .../routemanager/systemops/systemops_unix.go | 25 +- .../systemops/systemops_windows.go | 71 ++- 13 files changed, 674 insertions(+), 416 deletions(-) create mode 100644 client/internal/routemanager/sysctl/sysctl_linux.go delete mode 100644 client/internal/routemanager/systemops/systemops_android.go create mode 100644 client/internal/routemanager/systemops/systemops_generic.go rename client/internal/routemanager/systemops/{systemops_test.go => systemops_generic_test.go} (89%) delete mode 100644 client/internal/routemanager/systemops/systemops_ios.go create mode 100644 client/internal/routemanager/systemops/systemops_mobile.go diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index 4293d5e85b8..5d448a34591 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -46,6 +46,7 @@ type DefaultManager struct { clientNetworks map[route.HAUniqueID]*clientNetwork routeSelector *routeselector.RouteSelector serverRouter serverRouter + routingManager *systemops.RoutingManager statusRecorder *peer.Status wgInterface *iface.WGIface pubKey string @@ -64,12 +65,15 @@ func NewManager( initialRoutes []*route.Route, ) *DefaultManager { mCTX, cancel := context.WithCancel(ctx) + routingManager := systemops.NewRoutingManager(wgInterface) + dm := &DefaultManager{ ctx: mCTX, stop: cancel, dnsRouteInterval: dnsRouteInterval, clientNetworks: make(map[route.HAUniqueID]*clientNetwork), routeSelector: routeselector.NewRouteSelector(), + routingManager: routingManager, statusRecorder: statusRecorder, wgInterface: wgInterface, pubKey: pubKey, @@ -78,10 +82,10 @@ func NewManager( dm.routeRefCounter = refcounter.New( func(prefix netip.Prefix, _ any) (any, error) { - return nil, systemops.AddVPNRoute(prefix, wgInterface.ToInterface()) + return nil, routingManager.AddVPNRoute(prefix, wgInterface.ToInterface()) }, func(prefix netip.Prefix, _ any) error { - return systemops.RemoveVPNRoute(prefix, wgInterface.ToInterface()) + return routingManager.RemoveVPNRoute(prefix, wgInterface.ToInterface()) }, ) @@ -114,7 +118,7 @@ func (m *DefaultManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePee return nil, nil, nil } - if err := systemops.CleanupRouting(); err != nil { + if err := m.routingManager.CleanupRouting(); err != nil { log.Warnf("Failed cleaning up routing: %v", err) } @@ -122,7 +126,7 @@ func (m *DefaultManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePee signalAddress := m.statusRecorder.GetSignalState().URL ips := resolveURLsToIPs([]string{mgmtAddress, signalAddress}) - beforePeerHook, afterPeerHook, err := systemops.SetupRouting(ips, m.wgInterface) + beforePeerHook, afterPeerHook, err := m.routingManager.SetupRouting(ips) if err != nil { return nil, nil, fmt.Errorf("setup routing: %w", err) } @@ -158,7 +162,7 @@ func (m *DefaultManager) Stop() { } if !nbnet.CustomRoutingDisabled() { - if err := systemops.CleanupRouting(); err != nil { + if err := m.routingManager.CleanupRouting(); err != nil { log.Errorf("Error cleaning up routing: %v", err) } else { log.Info("Routing cleanup complete") diff --git a/client/internal/routemanager/sysctl/sysctl_linux.go b/client/internal/routemanager/sysctl/sysctl_linux.go new file mode 100644 index 00000000000..3f2937c896b --- /dev/null +++ b/client/internal/routemanager/sysctl/sysctl_linux.go @@ -0,0 +1,103 @@ +// go:build !android +package sysctl + +import ( + "fmt" + "net" + "os" + "strconv" + "strings" + + "github.com/hashicorp/go-multierror" + log "github.com/sirupsen/logrus" + + nberrors "github.com/netbirdio/netbird/client/errors" + "github.com/netbirdio/netbird/iface" +) + +const ( + rpFilterPath = "net.ipv4.conf.all.rp_filter" + rpFilterInterfacePath = "net.ipv4.conf.%s.rp_filter" + srcValidMarkPath = "net.ipv4.conf.all.src_valid_mark" +) + +// Setup configures sysctl settings for RP filtering and source validation. +func Setup(wgIface *iface.WGIface) (map[string]int, error) { + keys := map[string]int{} + var result *multierror.Error + + oldVal, err := Set(srcValidMarkPath, 1, false) + if err != nil { + result = multierror.Append(result, err) + } else { + keys[srcValidMarkPath] = oldVal + } + + oldVal, err = Set(rpFilterPath, 2, true) + if err != nil { + result = multierror.Append(result, err) + } else { + keys[rpFilterPath] = oldVal + } + + interfaces, err := net.Interfaces() + if err != nil { + result = multierror.Append(result, fmt.Errorf("list interfaces: %w", err)) + } + + for _, intf := range interfaces { + if intf.Name == "lo" || wgIface != nil && intf.Name == wgIface.Name() { + continue + } + + i := fmt.Sprintf(rpFilterInterfacePath, intf.Name) + oldVal, err := Set(i, 2, true) + if err != nil { + result = multierror.Append(result, err) + } else { + keys[i] = oldVal + } + } + + return keys, nberrors.FormatErrorOrNil(result) +} + +// Set sets a sysctl configuration, if onlyIfOne is true it will only set the new value if it's set to 1 +func Set(key string, desiredValue int, onlyIfOne bool) (int, error) { + path := fmt.Sprintf("/proc/sys/%s", strings.ReplaceAll(key, ".", "/")) + currentValue, err := os.ReadFile(path) + if err != nil { + return -1, fmt.Errorf("read sysctl %s: %w", key, err) + } + + currentV, err := strconv.Atoi(strings.TrimSpace(string(currentValue))) + if err != nil && len(currentValue) > 0 { + return -1, fmt.Errorf("convert current desiredValue to int: %w", err) + } + + if currentV == desiredValue || onlyIfOne && currentV != 1 { + return currentV, nil + } + + //nolint:gosec + if err := os.WriteFile(path, []byte(strconv.Itoa(desiredValue)), 0644); err != nil { + return currentV, fmt.Errorf("write sysctl %s: %w", key, err) + } + log.Debugf("Set sysctl %s from %d to %d", key, currentV, desiredValue) + + return currentV, nil +} + +// Cleanup resets sysctl settings to their original values. +func Cleanup(originalSettings map[string]int) error { + var result *multierror.Error + + for key, value := range originalSettings { + _, err := Set(key, value, false) + if err != nil { + result = multierror.Append(result, err) + } + } + + return nberrors.FormatErrorOrNil(result) +} diff --git a/client/internal/routemanager/systemops/systemops.go b/client/internal/routemanager/systemops/systemops.go index 2baccb64b6e..eac82610547 100644 --- a/client/internal/routemanager/systemops/systemops.go +++ b/client/internal/routemanager/systemops/systemops.go @@ -1,27 +1,11 @@ -//go:build !android && !ios - package systemops import ( - "context" - "errors" - "fmt" "net" "net/netip" - "runtime" - "strconv" - - "github.com/hashicorp/go-multierror" - "github.com/libp2p/go-netroute" - log "github.com/sirupsen/logrus" - nberrors "github.com/netbirdio/netbird/client/errors" - "github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/internal/routemanager/refcounter" - "github.com/netbirdio/netbird/client/internal/routemanager/util" - "github.com/netbirdio/netbird/client/internal/routemanager/vars" "github.com/netbirdio/netbird/iface" - nbnet "github.com/netbirdio/netbird/util/net" ) type Nexthop struct { @@ -36,6 +20,17 @@ var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1) var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1) var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1) +type RoutingManager struct { + refCounter *ExclusionCounter + wgInterface *iface.WGIface +} + +func NewRoutingManager(wgInterface *iface.WGIface) *RoutingManager { + return &RoutingManager{ + wgInterface: wgInterface, + } +} + // TODO: fix: for default our wg address now appears as the default gw func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error { addr := netip.IPv4Unspecified() @@ -278,145 +273,4 @@ func addNonExistingRoute(prefix netip.Prefix, intf *net.Interface) error { log.Warnf("Unable to add route for current default gateway route. Will proceed without it. error: %s", err) } } - - return addToRouteTable(prefix, Nexthop{netip.Addr{}, intf}) -} - -// genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given, -// it will remove the split /1 prefixes -func genericRemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { - nextHop := Nexthop{netip.Addr{}, intf} - - if prefix == vars.Defaultv4 { - var result *multierror.Error - if err := removeFromRouteTable(splitDefaultv4_1, nextHop); err != nil { - result = multierror.Append(result, err) - } - if err := removeFromRouteTable(splitDefaultv4_2, nextHop); err != nil { - result = multierror.Append(result, err) - } - - // TODO: remove once IPv6 is supported on the interface - if err := removeFromRouteTable(splitDefaultv6_1, nextHop); err != nil { - result = multierror.Append(result, err) - } - if err := removeFromRouteTable(splitDefaultv6_2, nextHop); err != nil { - result = multierror.Append(result, err) - } - - return result.ErrorOrNil() - } else if prefix == vars.Defaultv6 { - var result *multierror.Error - if err := removeFromRouteTable(splitDefaultv6_1, nextHop); err != nil { - result = multierror.Append(result, err) - } - if err := removeFromRouteTable(splitDefaultv6_2, nextHop); err != nil { - result = multierror.Append(result, err) - } - - return nberrors.FormatErrorOrNil(result) - } - - return removeFromRouteTable(prefix, nextHop) -} - -func setupRoutingWithRefCounter(refCounter **ExclusionCounter, initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { - initialNextHopV4, err := GetNextHop(netip.IPv4Unspecified()) - if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { - log.Errorf("Unable to get initial v4 default next hop: %v", err) - } - initialNextHopV6, err := GetNextHop(netip.IPv6Unspecified()) - if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { - log.Errorf("Unable to get initial v6 default next hop: %v", err) - } - - *refCounter = refcounter.New( - func(prefix netip.Prefix, _ any) (Nexthop, error) { - initialNexthop := initialNextHopV4 - if prefix.Addr().Is6() { - initialNexthop = initialNextHopV6 - } - - nexthop, err := addRouteToNonVPNIntf(prefix, wgIface, initialNexthop) - if errors.Is(err, vars.ErrRouteNotAllowed) || errors.Is(err, vars.ErrRouteNotFound) { - log.Tracef("Adding for prefix %s: %v", prefix, err) - // These errors are not critical but also we should not track and try to remove the routes either. - return nexthop, refcounter.ErrIgnore - } - return nexthop, err - }, - removeFromRouteTable, - ) - - return setupHooks(*refCounter, initAddresses) -} - -func cleanupRoutingWithRefCounter(routeManager *ExclusionCounter) error { - if routeManager == nil { - return nil - } - - // TODO: Remove hooks selectively - nbnet.RemoveDialerHooks() - nbnet.RemoveListenerHooks() - - if err := routeManager.Flush(); err != nil { - return fmt.Errorf("flush route manager: %w", err) - } - - return nil -} - -func setupHooks(routeManager *ExclusionCounter, initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { - beforeHook := func(connID nbnet.ConnectionID, ip net.IP) error { - prefix, err := util.GetPrefixFromIP(ip) - if err != nil { - return fmt.Errorf("convert ip to prefix: %w", err) - } - - if _, err := routeManager.IncrementWithID(string(connID), prefix, nil); err != nil { - return fmt.Errorf("adding route reference: %v", err) - } - - return nil - } - afterHook := func(connID nbnet.ConnectionID) error { - if err := routeManager.DecrementWithID(string(connID)); err != nil { - return fmt.Errorf("remove route reference: %w", err) - } - - return nil - } - - for _, ip := range initAddresses { - if err := beforeHook("init", ip); err != nil { - log.Errorf("Failed to add route reference: %v", err) - } - } - - nbnet.AddDialerHook(func(ctx context.Context, connID nbnet.ConnectionID, resolvedIPs []net.IPAddr) error { - if ctx.Err() != nil { - return ctx.Err() - } - - var result *multierror.Error - for _, ip := range resolvedIPs { - result = multierror.Append(result, beforeHook(connID, ip.IP)) - } - return nberrors.FormatErrorOrNil(result) - }) - - nbnet.AddDialerCloseHook(func(connID nbnet.ConnectionID, conn *net.Conn) error { - return afterHook(connID) - }) - - nbnet.AddListenerWriteHook(func(connID nbnet.ConnectionID, ip *net.IPAddr, data []byte) error { - return beforeHook(connID, ip.IP) - }) - - nbnet.AddListenerCloseHook(func(connID nbnet.ConnectionID, conn net.PacketConn) error { - return afterHook(connID) - }) - - return beforeHook, afterHook, nil } diff --git a/client/internal/routemanager/systemops/systemops_android.go b/client/internal/routemanager/systemops/systemops_android.go deleted file mode 100644 index d312e5e4938..00000000000 --- a/client/internal/routemanager/systemops/systemops_android.go +++ /dev/null @@ -1,33 +0,0 @@ -package systemops - -import ( - "net" - "net/netip" - "runtime" - - log "github.com/sirupsen/logrus" - - "github.com/netbirdio/netbird/client/internal/peer" - "github.com/netbirdio/netbird/iface" -) - -func SetupRouting([]net.IP, *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { - return nil, nil, nil -} - -func CleanupRouting() error { - return nil -} - -func EnableIPForwarding() error { - log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) - return nil -} - -func AddVPNRoute(netip.Prefix, *net.Interface) error { - return nil -} - -func RemoveVPNRoute(netip.Prefix, *net.Interface) error { - return nil -} diff --git a/client/internal/routemanager/systemops/systemops_darwin_test.go b/client/internal/routemanager/systemops/systemops_darwin_test.go index 59fd9b74a5b..5c72cba6c45 100644 --- a/client/internal/routemanager/systemops/systemops_darwin_test.go +++ b/client/internal/routemanager/systemops/systemops_darwin_test.go @@ -35,13 +35,15 @@ func TestConcurrentRoutes(t *testing.T) { baseIP := netip.MustParseAddr("192.0.2.0") intf := &net.Interface{Name: "lo0"} + r := NewRoutingManager(nil) + var wg sync.WaitGroup for i := 0; i < 1024; i++ { wg.Add(1) go func(ip netip.Addr) { defer wg.Done() prefix := netip.PrefixFrom(ip, 32) - if err := addToRouteTable(prefix, Nexthop{netip.Addr{}, intf}); err != nil { + if err := r.addToRouteTable(prefix, Nexthop{netip.Addr{}, intf}); err != nil { t.Errorf("Failed to add route for %s: %v", prefix, err) } }(baseIP) @@ -57,7 +59,7 @@ func TestConcurrentRoutes(t *testing.T) { go func(ip netip.Addr) { defer wg.Done() prefix := netip.PrefixFrom(ip, 32) - if err := removeFromRouteTable(prefix, Nexthop{netip.Addr{}, intf}); err != nil { + if err := r.removeFromRouteTable(prefix, Nexthop{netip.Addr{}, intf}); err != nil { t.Errorf("Failed to remove route for %s: %v", prefix, err) } }(baseIP) diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go new file mode 100644 index 00000000000..0c23f09c1eb --- /dev/null +++ b/client/internal/routemanager/systemops/systemops_generic.go @@ -0,0 +1,412 @@ +//go:build !android && !ios + +package systemops + +import ( + "context" + "errors" + "fmt" + "net" + "net/netip" + "runtime" + "strconv" + + "github.com/hashicorp/go-multierror" + "github.com/libp2p/go-netroute" + log "github.com/sirupsen/logrus" + + nberrors "github.com/netbirdio/netbird/client/errors" + "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/client/internal/routemanager/refcounter" + "github.com/netbirdio/netbird/client/internal/routemanager/util" + "github.com/netbirdio/netbird/client/internal/routemanager/vars" + "github.com/netbirdio/netbird/iface" + nbnet "github.com/netbirdio/netbird/util/net" +) + +var splitDefaultv4_1 = netip.PrefixFrom(netip.IPv4Unspecified(), 1) +var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1) +var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1) +var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1) + +func (r *RoutingManager) setupRefCounter(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { + initialNextHopV4, err := GetNextHop(netip.IPv4Unspecified()) + if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { + log.Errorf("Unable to get initial v4 default next hop: %v", err) + } + initialNextHopV6, err := GetNextHop(netip.IPv6Unspecified()) + if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { + log.Errorf("Unable to get initial v6 default next hop: %v", err) + } + + refCounter := refcounter.New( + func(prefix netip.Prefix, _ any) (Nexthop, error) { + initialNexthop := initialNextHopV4 + if prefix.Addr().Is6() { + initialNexthop = initialNextHopV6 + } + + nexthop, err := r.addRouteToNonVPNIntf(prefix, r.wgInterface, initialNexthop) + if errors.Is(err, vars.ErrRouteNotAllowed) || errors.Is(err, vars.ErrRouteNotFound) { + log.Tracef("Adding for prefix %s: %v", prefix, err) + // These errors are not critical but also we should not track and try to remove the routes either. + return nexthop, refcounter.ErrIgnore + } + return nexthop, err + }, + r.removeFromRouteTable, + ) + + r.refCounter = refCounter + + return r.setupHooks(initAddresses) +} + +func (r *RoutingManager) cleanupRefCounter() error { + if r.refCounter == nil { + return nil + } + + // TODO: Remove hooks selectively + nbnet.RemoveDialerHooks() + nbnet.RemoveListenerHooks() + + if err := r.refCounter.Flush(); err != nil { + return fmt.Errorf("flush route manager: %w", err) + } + + return nil +} + +// TODO: fix: for default our wg address now appears as the default gw +func (r *RoutingManager) addRouteForCurrentDefaultGateway(prefix netip.Prefix) error { + addr := netip.IPv4Unspecified() + if prefix.Addr().Is6() { + addr = netip.IPv6Unspecified() + } + + nexthop, err := GetNextHop(addr) + if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { + return fmt.Errorf("get existing route gateway: %s", err) + } + + if !prefix.Contains(nexthop.IP) { + log.Debugf("Skipping adding a new route for gateway %s because it is not in the network %s", nexthop.IP, prefix) + return nil + } + + gatewayPrefix := netip.PrefixFrom(nexthop.IP, 32) + if nexthop.IP.Is6() { + gatewayPrefix = netip.PrefixFrom(nexthop.IP, 128) + } + + ok, err := existsInRouteTable(gatewayPrefix) + if err != nil { + return fmt.Errorf("unable to check if there is an existing route for gateway %s. error: %s", gatewayPrefix, err) + } + + if ok { + log.Debugf("Skipping adding a new route for gateway %s because it already exists", gatewayPrefix) + return nil + } + + nexthop, err = GetNextHop(nexthop.IP) + if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { + return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err) + } + + log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, nexthop.IP) + return r.addToRouteTable(gatewayPrefix, nexthop) +} + +// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface. +// If the next hop or interface is pointing to the VPN interface, it will return the initial values. +func (r *RoutingManager) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIface, initialNextHop Nexthop) (Nexthop, error) { + addr := prefix.Addr() + switch { + case addr.IsLoopback(), + addr.IsLinkLocalUnicast(), + addr.IsLinkLocalMulticast(), + addr.IsInterfaceLocalMulticast(), + addr.IsUnspecified(), + addr.IsMulticast(): + + return Nexthop{}, vars.ErrRouteNotAllowed + } + + // Determine the exit interface and next hop for the prefix, so we can add a specific route + nexthop, err := GetNextHop(addr) + if err != nil { + return Nexthop{}, fmt.Errorf("get next hop: %w", err) + } + + log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop.IP, prefix, nexthop.IP) + exitNextHop := Nexthop{ + IP: nexthop.IP, + Intf: nexthop.Intf, + } + + vpnAddr, ok := netip.AddrFromSlice(vpnIntf.Address().IP) + if !ok { + return Nexthop{}, fmt.Errorf("failed to convert vpn address to netip.Addr") + } + + // if next hop is the VPN address or the interface is the VPN interface, we should use the initial values + if exitNextHop.IP == vpnAddr || exitNextHop.Intf != nil && exitNextHop.Intf.Name == vpnIntf.Name() { + log.Debugf("Route for prefix %s is pointing to the VPN interface", prefix) + exitNextHop = initialNextHop + } + + log.Debugf("Adding a new route for prefix %s with next hop %s", prefix, exitNextHop.IP) + if err := r.addToRouteTable(prefix, exitNextHop); err != nil { + return Nexthop{}, fmt.Errorf("add route to table: %w", err) + } + + return exitNextHop, nil +} + +// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix +// in two /1 prefixes to avoid replacing the existing default route +func (r *RoutingManager) genericAddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { + nextHop := Nexthop{netip.Addr{}, intf} + + if prefix == vars.Defaultv4 { + if err := r.addToRouteTable(splitDefaultv4_1, nextHop); err != nil { + return err + } + if err := r.addToRouteTable(splitDefaultv4_2, nextHop); err != nil { + if err2 := r.removeFromRouteTable(splitDefaultv4_1, nextHop); err2 != nil { + log.Warnf("Failed to rollback route addition: %s", err2) + } + return err + } + + // TODO: remove once IPv6 is supported on the interface + if err := r.addToRouteTable(splitDefaultv6_1, nextHop); err != nil { + return fmt.Errorf("add unreachable route split 1: %w", err) + } + if err := r.addToRouteTable(splitDefaultv6_2, nextHop); err != nil { + if err2 := r.removeFromRouteTable(splitDefaultv6_1, nextHop); err2 != nil { + log.Warnf("Failed to rollback route addition: %s", err2) + } + return fmt.Errorf("add unreachable route split 2: %w", err) + } + + return nil + } else if prefix == vars.Defaultv6 { + if err := r.addToRouteTable(splitDefaultv6_1, nextHop); err != nil { + return fmt.Errorf("add unreachable route split 1: %w", err) + } + if err := r.addToRouteTable(splitDefaultv6_2, nextHop); err != nil { + if err2 := r.removeFromRouteTable(splitDefaultv6_1, nextHop); err2 != nil { + log.Warnf("Failed to rollback route addition: %s", err2) + } + return fmt.Errorf("add unreachable route split 2: %w", err) + } + + return nil + } + + return r.addNonExistingRoute(prefix, intf) +} + +// addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table +func (r *RoutingManager) addNonExistingRoute(prefix netip.Prefix, intf *net.Interface) error { + ok, err := existsInRouteTable(prefix) + if err != nil { + return fmt.Errorf("exists in route table: %w", err) + } + if ok { + log.Warnf("Skipping adding a new route for network %s because it already exists", prefix) + return nil + } + + ok, err = isSubRange(prefix) + if err != nil { + return fmt.Errorf("sub range: %w", err) + } + + if ok { + if err := r.addRouteForCurrentDefaultGateway(prefix); err != nil { + log.Warnf("Unable to add route for current default gateway route. Will proceed without it. error: %s", err) + } + } + + return r.addToRouteTable(prefix, Nexthop{netip.Addr{}, intf}) +} + +// genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given, +// it will remove the split /1 prefixes +func (r *RoutingManager) genericRemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { + nextHop := Nexthop{netip.Addr{}, intf} + + if prefix == vars.Defaultv4 { + var result *multierror.Error + if err := r.removeFromRouteTable(splitDefaultv4_1, nextHop); err != nil { + result = multierror.Append(result, err) + } + if err := r.removeFromRouteTable(splitDefaultv4_2, nextHop); err != nil { + result = multierror.Append(result, err) + } + + // TODO: remove once IPv6 is supported on the interface + if err := r.removeFromRouteTable(splitDefaultv6_1, nextHop); err != nil { + result = multierror.Append(result, err) + } + if err := r.removeFromRouteTable(splitDefaultv6_2, nextHop); err != nil { + result = multierror.Append(result, err) + } + + return nberrors.FormatErrorOrNil(result) + } else if prefix == vars.Defaultv6 { + var result *multierror.Error + if err := r.removeFromRouteTable(splitDefaultv6_1, nextHop); err != nil { + result = multierror.Append(result, err) + } + if err := r.removeFromRouteTable(splitDefaultv6_2, nextHop); err != nil { + result = multierror.Append(result, err) + } + + return nberrors.FormatErrorOrNil(result) + } + + return r.removeFromRouteTable(prefix, nextHop) +} + +func (r *RoutingManager) setupHooks(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { + beforeHook := func(connID nbnet.ConnectionID, ip net.IP) error { + prefix, err := util.GetPrefixFromIP(ip) + if err != nil { + return fmt.Errorf("convert ip to prefix: %w", err) + } + + if _, err := r.refCounter.IncrementWithID(string(connID), prefix, nil); err != nil { + return fmt.Errorf("adding route reference: %v", err) + } + + return nil + } + afterHook := func(connID nbnet.ConnectionID) error { + if err := r.refCounter.DecrementWithID(string(connID)); err != nil { + return fmt.Errorf("remove route reference: %w", err) + } + + return nil + } + + for _, ip := range initAddresses { + if err := beforeHook("init", ip); err != nil { + log.Errorf("Failed to add route reference: %v", err) + } + } + + nbnet.AddDialerHook(func(ctx context.Context, connID nbnet.ConnectionID, resolvedIPs []net.IPAddr) error { + if ctx.Err() != nil { + return ctx.Err() + } + + var result *multierror.Error + for _, ip := range resolvedIPs { + result = multierror.Append(result, beforeHook(connID, ip.IP)) + } + return nberrors.FormatErrorOrNil(result) + }) + + nbnet.AddDialerCloseHook(func(connID nbnet.ConnectionID, conn *net.Conn) error { + return afterHook(connID) + }) + + nbnet.AddListenerWriteHook(func(connID nbnet.ConnectionID, ip *net.IPAddr, data []byte) error { + return beforeHook(connID, ip.IP) + }) + + nbnet.AddListenerCloseHook(func(connID nbnet.ConnectionID, conn net.PacketConn) error { + return afterHook(connID) + }) + + return beforeHook, afterHook, nil +} + +func GetNextHop(ip netip.Addr) (Nexthop, error) { + r, err := netroute.New() + if err != nil { + return Nexthop{}, fmt.Errorf("new netroute: %w", err) + } + intf, gateway, preferredSrc, err := r.Route(ip.AsSlice()) + if err != nil { + log.Debugf("Failed to get route for %s: %v", ip, err) + return Nexthop{}, vars.ErrRouteNotFound + } + + log.Debugf("Route for %s: interface %v nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc) + if gateway == nil { + if preferredSrc == nil { + return Nexthop{}, vars.ErrRouteNotFound + } + log.Debugf("No next hop found for IP %s, using preferred source %s", ip, preferredSrc) + + addr, err := ipToAddr(preferredSrc, intf) + if err != nil { + return Nexthop{}, fmt.Errorf("convert preferred source to address: %w", err) + } + return Nexthop{ + IP: addr.Unmap(), + Intf: intf, + }, nil + } + + addr, err := ipToAddr(gateway, intf) + if err != nil { + return Nexthop{}, fmt.Errorf("convert gateway to address: %w", err) + } + + return Nexthop{ + IP: addr, + Intf: intf, + }, nil +} + +// converts a net.IP to a netip.Addr including the zone based on the passed interface +func ipToAddr(ip net.IP, intf *net.Interface) (netip.Addr, error) { + addr, ok := netip.AddrFromSlice(ip) + if !ok { + return netip.Addr{}, fmt.Errorf("failed to convert IP address to netip.Addr: %s", ip) + } + + if intf != nil && (addr.IsLinkLocalMulticast() || addr.IsLinkLocalUnicast()) { + log.Tracef("Adding zone %s to address %s", intf.Name, addr) + if runtime.GOOS == "windows" { + addr = addr.WithZone(strconv.Itoa(intf.Index)) + } else { + addr = addr.WithZone(intf.Name) + } + } + + return addr.Unmap(), nil +} + +func existsInRouteTable(prefix netip.Prefix) (bool, error) { + routes, err := getRoutesFromTable() + if err != nil { + return false, fmt.Errorf("get routes from table: %w", err) + } + for _, tableRoute := range routes { + if tableRoute == prefix { + return true, nil + } + } + return false, nil +} + +func isSubRange(prefix netip.Prefix) (bool, error) { + routes, err := getRoutesFromTable() + if err != nil { + return false, fmt.Errorf("get routes from table: %w", err) + } + for _, tableRoute := range routes { + if tableRoute.Bits() > vars.MinRangeBits && tableRoute.Contains(prefix.Addr()) && tableRoute.Bits() < prefix.Bits() { + return true, nil + } + } + return false, nil +} diff --git a/client/internal/routemanager/systemops/systemops_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go similarity index 89% rename from client/internal/routemanager/systemops/systemops_test.go rename to client/internal/routemanager/systemops/systemops_generic_test.go index ce113402e64..02c9b29384c 100644 --- a/client/internal/routemanager/systemops/systemops_test.go +++ b/client/internal/routemanager/systemops/systemops_generic_test.go @@ -63,17 +63,20 @@ func TestAddRemoveRoutes(t *testing.T) { err = wgInterface.Create() require.NoError(t, err, "should create testing wireguard interface") - _, _, err = SetupRouting(nil, wgInterface) + + r := NewRoutingManager(wgInterface) + + _, _, err = r.SetupRouting(nil, wgInterface) require.NoError(t, err) t.Cleanup(func() { - assert.NoError(t, CleanupRouting()) + assert.NoError(t, r.CleanupRouting()) }) index, err := net.InterfaceByName(wgInterface.Name()) require.NoError(t, err, "InterfaceByName should not return err") intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()} - err = AddVPNRoute(testCase.prefix, intf) + err = r.AddVPNRoute(testCase.prefix, intf) require.NoError(t, err, "genericAddVPNRoute should not return err") if testCase.shouldRouteToWireguard { @@ -84,7 +87,7 @@ func TestAddRemoveRoutes(t *testing.T) { exists, err := existsInRouteTable(testCase.prefix) require.NoError(t, err, "existsInRouteTable should not return err") if exists && testCase.shouldRouteToWireguard { - err = RemoveVPNRoute(testCase.prefix, intf) + err = r.RemoveVPNRoute(testCase.prefix, intf) require.NoError(t, err, "genericRemoveVPNRoute should not return err") prefixNexthop, err := GetNextHop(testCase.prefix.Addr()) @@ -214,14 +217,16 @@ func TestAddExistAndRemoveRoute(t *testing.T) { require.NoError(t, err, "InterfaceByName should not return err") intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()} + r := NewRoutingManager(wgInterface) + // Prepare the environment if testCase.preExistingPrefix.IsValid() { - err := AddVPNRoute(testCase.preExistingPrefix, intf) + err := r.AddVPNRoute(testCase.preExistingPrefix, intf) require.NoError(t, err, "should not return err when adding pre-existing route") } // Add the route - err = AddVPNRoute(testCase.prefix, intf) + err = r.AddVPNRoute(testCase.prefix, intf) require.NoError(t, err, "should not return err when adding route") if testCase.shouldAddRoute { @@ -231,7 +236,7 @@ func TestAddExistAndRemoveRoute(t *testing.T) { require.True(t, ok, "route should exist") // remove route again if added - err = RemoveVPNRoute(testCase.prefix, intf) + err = r.RemoveVPNRoute(testCase.prefix, intf) require.NoError(t, err, "should not return err") } @@ -348,58 +353,59 @@ func setupTestEnv(t *testing.T) { setupDummyInterfacesAndRoutes(t) - wgIface := createWGInterface(t, expectedVPNint, "100.64.0.1/24", 51820) + wgInterface := createWGInterface(t, expectedVPNint, "100.64.0.1/24", 51820) t.Cleanup(func() { - assert.NoError(t, wgIface.Close()) + assert.NoError(t, wgInterface.Close()) }) - _, _, err := SetupRouting(nil, wgIface) + r := NewRoutingManager(wgInterface) + _, _, err := r.SetupRouting(nil, wgInterface) require.NoError(t, err, "setupRouting should not return err") t.Cleanup(func() { - assert.NoError(t, CleanupRouting()) + assert.NoError(t, r.CleanupRouting()) }) - index, err := net.InterfaceByName(wgIface.Name()) + index, err := net.InterfaceByName(wgInterface.Name()) require.NoError(t, err, "InterfaceByName should not return err") - intf := &net.Interface{Index: index.Index, Name: wgIface.Name()} + intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()} // default route exists in main table and vpn table - err = AddVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf) + err = r.AddVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf) require.NoError(t, err, "addVPNRoute should not return err") t.Cleanup(func() { - err = RemoveVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf) + err = r.RemoveVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf) assert.NoError(t, err, "removeVPNRoute should not return err") }) // 10.0.0.0/8 route exists in main table and vpn table - err = AddVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf) + err = r.AddVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf) require.NoError(t, err, "addVPNRoute should not return err") t.Cleanup(func() { - err = RemoveVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf) + err = r.RemoveVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf) assert.NoError(t, err, "removeVPNRoute should not return err") }) // 10.10.0.0/24 more specific route exists in vpn table - err = AddVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf) + err = r.AddVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf) require.NoError(t, err, "addVPNRoute should not return err") t.Cleanup(func() { - err = RemoveVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf) + err = r.RemoveVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf) assert.NoError(t, err, "removeVPNRoute should not return err") }) // 127.0.10.0/24 more specific route exists in vpn table - err = AddVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf) + err = r.AddVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf) require.NoError(t, err, "addVPNRoute should not return err") t.Cleanup(func() { - err = RemoveVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf) + err = r.RemoveVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf) assert.NoError(t, err, "removeVPNRoute should not return err") }) // unique route in vpn table - err = AddVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf) + err = r.AddVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf) require.NoError(t, err, "addVPNRoute should not return err") t.Cleanup(func() { - err = RemoveVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf) + err = r.RemoveVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf) assert.NoError(t, err, "removeVPNRoute should not return err") }) } diff --git a/client/internal/routemanager/systemops/systemops_ios.go b/client/internal/routemanager/systemops/systemops_ios.go deleted file mode 100644 index d312e5e4938..00000000000 --- a/client/internal/routemanager/systemops/systemops_ios.go +++ /dev/null @@ -1,33 +0,0 @@ -package systemops - -import ( - "net" - "net/netip" - "runtime" - - log "github.com/sirupsen/logrus" - - "github.com/netbirdio/netbird/client/internal/peer" - "github.com/netbirdio/netbird/iface" -) - -func SetupRouting([]net.IP, *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { - return nil, nil, nil -} - -func CleanupRouting() error { - return nil -} - -func EnableIPForwarding() error { - log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) - return nil -} - -func AddVPNRoute(netip.Prefix, *net.Interface) error { - return nil -} - -func RemoveVPNRoute(netip.Prefix, *net.Interface) error { - return nil -} diff --git a/client/internal/routemanager/systemops/systemops_linux.go b/client/internal/routemanager/systemops/systemops_linux.go index 3ef99d35736..d76d3ac631d 100644 --- a/client/internal/routemanager/systemops/systemops_linux.go +++ b/client/internal/routemanager/systemops/systemops_linux.go @@ -9,17 +9,16 @@ import ( "net" "net/netip" "os" - "strconv" - "strings" "syscall" "github.com/hashicorp/go-multierror" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" + nberrors "github.com/netbirdio/netbird/client/errors" "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/client/internal/routemanager/sysctl" "github.com/netbirdio/netbird/client/internal/routemanager/vars" - "github.com/netbirdio/netbird/iface" nbnet "github.com/netbirdio/netbird/util/net" ) @@ -34,16 +33,10 @@ const ( // ipv4ForwardingPath is the path to the file containing the IP forwarding setting. ipv4ForwardingPath = "net.ipv4.ip_forward" - - rpFilterPath = "net.ipv4.conf.all.rp_filter" - rpFilterInterfacePath = "net.ipv4.conf.%s.rp_filter" - srcValidMarkPath = "net.ipv4.conf.all.src_valid_mark" ) var ErrTableIDExists = errors.New("ID exists with different name") -var refCounter *ExclusionCounter - // originalSysctl stores the original sysctl values before they are modified var originalSysctl map[string]int @@ -93,17 +86,17 @@ func getSetupRules() []ruleParams { // Rule 2 (VPN Traffic Routing): Directs all remaining traffic to the 'NetbirdVPNTableID' custom routing table. // This table is where a default route or other specific routes received from the management server are configured, // enabling VPN connectivity. -func SetupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) { +func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) { if isLegacy() { log.Infof("Using legacy routing setup") - return setupRoutingWithRefCounter(&refCounter, initAddresses, wgIface) + return r.setupRefCounter(initAddresses) } if err = addRoutingTableName(); err != nil { log.Errorf("Error adding routing table name: %v", err) } - originalValues, err := setupSysctl(wgIface) + originalValues, err := sysctl.Setup(r.wgInterface) if err != nil { log.Errorf("Error setting up sysctl: %v", err) sysctlFailed = true @@ -112,7 +105,7 @@ func SetupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.Before defer func() { if err != nil { - if cleanErr := CleanupRouting(); cleanErr != nil { + if cleanErr := r.CleanupRouting(); cleanErr != nil { log.Errorf("Error cleaning up routing: %v", cleanErr) } } @@ -124,7 +117,7 @@ func SetupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.Before if errors.Is(err, syscall.EOPNOTSUPP) { log.Warnf("Rule operations are not supported, falling back to the legacy routing setup") setIsLegacy(true) - return setupRoutingWithRefCounter(&refCounter, initAddresses, wgIface) + return r.setupRefCounter(initAddresses) } return nil, nil, fmt.Errorf("%s: %w", rule.description, err) } @@ -136,9 +129,9 @@ func SetupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (_ peer.Before // CleanupRouting performs a thorough cleanup of the routing configuration established by 'setupRouting'. // It systematically removes the three rules and any associated routing table entries to ensure a clean state. // The function uses error aggregation to report any errors encountered during the cleanup process. -func CleanupRouting() error { +func (r *RoutingManager) CleanupRouting() error { if isLegacy() { - return cleanupRoutingWithRefCounter(refCounter) + return r.cleanupRefCounter() } var result *multierror.Error @@ -157,26 +150,26 @@ func CleanupRouting() error { } } - if err := cleanupSysctl(originalSysctl); err != nil { + if err := sysctl.Cleanup(originalSysctl); err != nil { result = multierror.Append(result, fmt.Errorf("cleanup sysctl: %w", err)) } originalSysctl = nil sysctlFailed = false - return result.ErrorOrNil() + return nberrors.FormatErrorOrNil(result) } -func addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { return addRoute(prefix, nexthop, syscall.RT_TABLE_MAIN) } -func removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *RoutingManager) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { return removeRoute(prefix, nexthop, syscall.RT_TABLE_MAIN) } -func AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *RoutingManager) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { if isLegacy() { - return genericAddVPNRoute(prefix, intf) + return r.genericAddVPNRoute(prefix, intf) } if sysctlFailed && (prefix == vars.Defaultv4 || prefix == vars.Defaultv6) { @@ -197,9 +190,9 @@ func AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { return nil } -func RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *RoutingManager) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { if isLegacy() { - return genericRemoveVPNRoute(prefix, intf) + return r.genericRemoveVPNRoute(prefix, intf) } // TODO remove this once we have ipv6 support @@ -374,11 +367,11 @@ func flushRoutes(tableID, family int) error { } } - return result.ErrorOrNil() + return nberrors.FormatErrorOrNil(result) } func EnableIPForwarding() error { - _, err := setSysctl(ipv4ForwardingPath, 1, false) + _, err := sysctl.Set(ipv4ForwardingPath, 1, false) return err } @@ -509,83 +502,3 @@ func getAddressFamily(prefix netip.Prefix) int { } return netlink.FAMILY_V6 } - -// setupSysctl configures sysctl settings for RP filtering and source validation. -func setupSysctl(wgIface *iface.WGIface) (map[string]int, error) { - keys := map[string]int{} - var result *multierror.Error - - oldVal, err := setSysctl(srcValidMarkPath, 1, false) - if err != nil { - result = multierror.Append(result, err) - } else { - keys[srcValidMarkPath] = oldVal - } - - oldVal, err = setSysctl(rpFilterPath, 2, true) - if err != nil { - result = multierror.Append(result, err) - } else { - keys[rpFilterPath] = oldVal - } - - interfaces, err := net.Interfaces() - if err != nil { - result = multierror.Append(result, fmt.Errorf("list interfaces: %w", err)) - } - - for _, intf := range interfaces { - if intf.Name == "lo" || wgIface != nil && intf.Name == wgIface.Name() { - continue - } - - i := fmt.Sprintf(rpFilterInterfacePath, intf.Name) - oldVal, err := setSysctl(i, 2, true) - if err != nil { - result = multierror.Append(result, err) - } else { - keys[i] = oldVal - } - } - - return keys, result.ErrorOrNil() -} - -// setSysctl sets a sysctl configuration, if onlyIfOne is true it will only set the new value if it's set to 1 -func setSysctl(key string, desiredValue int, onlyIfOne bool) (int, error) { - path := fmt.Sprintf("/proc/sys/%s", strings.ReplaceAll(key, ".", "/")) - currentValue, err := os.ReadFile(path) - if err != nil { - return -1, fmt.Errorf("read sysctl %s: %w", key, err) - } - - currentV, err := strconv.Atoi(strings.TrimSpace(string(currentValue))) - if err != nil && len(currentValue) > 0 { - return -1, fmt.Errorf("convert current desiredValue to int: %w", err) - } - - if currentV == desiredValue || onlyIfOne && currentV != 1 { - return currentV, nil - } - - //nolint:gosec - if err := os.WriteFile(path, []byte(strconv.Itoa(desiredValue)), 0644); err != nil { - return currentV, fmt.Errorf("write sysctl %s: %w", key, err) - } - log.Debugf("Set sysctl %s from %d to %d", key, currentV, desiredValue) - - return currentV, nil -} - -func cleanupSysctl(originalSettings map[string]int) error { - var result *multierror.Error - - for key, value := range originalSettings { - _, err := setSysctl(key, value, false) - if err != nil { - result = multierror.Append(result, err) - } - } - - return result.ErrorOrNil() -} diff --git a/client/internal/routemanager/systemops/systemops_mobile.go b/client/internal/routemanager/systemops/systemops_mobile.go new file mode 100644 index 00000000000..f3367a47685 --- /dev/null +++ b/client/internal/routemanager/systemops/systemops_mobile.go @@ -0,0 +1,34 @@ +//go:build ios || android + +package systemops + +import ( + "net" + "net/netip" + "runtime" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal/peer" +) + +func (r *RoutingManager) SetupRouting([]net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { + return nil, nil, nil +} + +func (r *RoutingManager) CleanupRouting() error { + return nil +} + +func (r *RoutingManager) AddVPNRoute(netip.Prefix, *net.Interface) error { + return nil +} + +func (r *RoutingManager) RemoveVPNRoute(netip.Prefix, *net.Interface) error { + return nil +} + +func EnableIPForwarding() error { + log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) + return nil +} diff --git a/client/internal/routemanager/systemops/systemops_nonlinux.go b/client/internal/routemanager/systemops/systemops_nonlinux.go index 735238abc81..9b606980e46 100644 --- a/client/internal/routemanager/systemops/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops/systemops_nonlinux.go @@ -10,15 +10,15 @@ import ( log "github.com/sirupsen/logrus" ) -func EnableIPForwarding() error { - log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) - return nil +func (r *RoutingManager) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { + return r.genericAddVPNRoute(prefix, intf) } -func AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { - return genericAddVPNRoute(prefix, intf) +func (r *RoutingManager) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { + return r.genericRemoveVPNRoute(prefix, intf) } -func RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { - return genericRemoveVPNRoute(prefix, intf) +func EnableIPForwarding() error { + log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) + return nil } diff --git a/client/internal/routemanager/systemops/systemops_unix.go b/client/internal/routemanager/systemops/systemops_unix.go index 7b6b60a0ccb..b450b364109 100644 --- a/client/internal/routemanager/systemops/systemops_unix.go +++ b/client/internal/routemanager/systemops/systemops_unix.go @@ -14,38 +14,37 @@ import ( log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/client/internal/peer" - "github.com/netbirdio/netbird/iface" ) -var refCounter *ExclusionCounter - -func SetupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { - return setupRoutingWithRefCounter(&refCounter, initAddresses, wgIface) +func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { + return r.setupRefCounter(initAddresses) } -func CleanupRouting() error { - return cleanupRoutingWithRefCounter(refCounter) +func (r *RoutingManager) CleanupRouting() error { + return r.cleanupRefCounter() } -func addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { - return routeCmd("add", prefix, nexthop) +func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { + return r.routeCmd("add", prefix, nexthop) } -func removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { - return routeCmd("delete", prefix, nexthop) +func (r *RoutingManager) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { + return r.routeCmd("delete", prefix, nexthop) } -func routeCmd(action string, prefix netip.Prefix, nexthop Nexthop) error { +func (r *RoutingManager) routeCmd(action string, prefix netip.Prefix, nexthop Nexthop) error { inet := "-inet" network := prefix.String() if prefix.IsSingleIP() { network = prefix.Addr().String() } + + args := []string{"-n", action} if prefix.Addr().Is6() { inet = "-inet6" } - args := []string{"-n", action, inet, network} + args = append(args, inet, network) if nexthop.IP.IsValid() { args = append(args, nexthop.IP.Unmap().String()) } else if nexthop.Intf != nil { diff --git a/client/internal/routemanager/systemops/systemops_windows.go b/client/internal/routemanager/systemops/systemops_windows.go index 7ca29a74504..c84a1690068 100644 --- a/client/internal/routemanager/systemops/systemops_windows.go +++ b/client/internal/routemanager/systemops/systemops_windows.go @@ -18,7 +18,6 @@ import ( "github.com/netbirdio/netbird/client/firewall/uspfilter" "github.com/netbirdio/netbird/client/internal/peer" - "github.com/netbirdio/netbird/iface" ) type MSFT_NetRoute struct { @@ -57,14 +56,43 @@ var prefixList []netip.Prefix var lastUpdate time.Time var mux = sync.Mutex{} -var refCounter *ExclusionCounter +func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { + return r.setupRefCounter(initAddresses) +} -func SetupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { - return setupRoutingWithRefCounter(&refCounter, initAddresses, wgIface) +func (r *RoutingManager) CleanupRouting() error { + return r.cleanupRefCounter() } -func CleanupRouting() error { - return cleanupRoutingWithRefCounter(refCounter) +func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { + if nexthop.IP.Zone() != "" && nexthop.Intf == nil { + zone, err := strconv.Atoi(nexthop.IP.Zone()) + if err != nil { + return fmt.Errorf("invalid zone: %w", err) + } + nexthop.Intf = &net.Interface{Index: zone} + nexthop.IP.WithZone("") + } + + return addRouteCmd(prefix, nexthop) +} + +func (r *RoutingManager) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { + args := []string{"delete", prefix.String()} + if nexthop.IP.IsValid() { + nexthop.IP.WithZone("") + args = append(args, nexthop.IP.Unmap().String()) + } + + routeCmd := uspfilter.GetSystem32Command("route") + + out, err := exec.Command(routeCmd, args...).CombinedOutput() + log.Tracef("route %s: %s", strings.Join(args, " "), out) + + if err != nil { + return fmt.Errorf("remove route: %w", err) + } + return nil } func getRoutesFromTable() ([]netip.Prefix, error) { @@ -185,37 +213,6 @@ func addRouteCmd(prefix netip.Prefix, nexthop Nexthop) error { return nil } -func addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { - if nexthop.IP.Zone() != "" && nexthop.Intf == nil { - zone, err := strconv.Atoi(nexthop.IP.Zone()) - if err != nil { - return fmt.Errorf("invalid zone: %w", err) - } - nexthop.Intf = &net.Interface{Index: zone} - nexthop.IP.WithZone("") - } - - return addRouteCmd(prefix, nexthop) -} - -func removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { - args := []string{"delete", prefix.String()} - if nexthop.IP.IsValid() { - nexthop.IP.WithZone("") - args = append(args, nexthop.IP.Unmap().String()) - } - - routeCmd := uspfilter.GetSystem32Command("route") - - out, err := exec.Command(routeCmd, args...).CombinedOutput() - log.Tracef("route %s: %s", strings.Join(args, " "), out) - - if err != nil { - return fmt.Errorf("remove route: %w", err) - } - return nil -} - func isCacheDisabled() bool { return os.Getenv("NB_DISABLE_ROUTE_CACHE") == "true" } From c56f0acc8d8e7947e0ca048fdd66bbae817c5dd6 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Mon, 20 May 2024 01:16:29 +0900 Subject: [PATCH 2/8] Blackhole IPv6 routes on macOS --- client/internal/routemanager/systemops/systemops_unix.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/internal/routemanager/systemops/systemops_unix.go b/client/internal/routemanager/systemops/systemops_unix.go index b450b364109..33d35c04389 100644 --- a/client/internal/routemanager/systemops/systemops_unix.go +++ b/client/internal/routemanager/systemops/systemops_unix.go @@ -42,6 +42,13 @@ func (r *RoutingManager) routeCmd(action string, prefix netip.Prefix, nexthop Ne args := []string{"-n", action} if prefix.Addr().Is6() { inet = "-inet6" + // TODO: Remove once we have IPv6 support on the interface + // Point the route to lo0 if the nexthop is the WireGuard interface, otherwise the operation fails + // without IPv6 support on the interface. + if nexthop.Intf != nil && nexthop.Intf.Name == r.wgInterface.Name() { + args = append(args, "-blackhole") + nexthop.Intf = &net.Interface{Name: "lo0"} + } } args = append(args, inet, network) From 15812866f1c950d1539d0d840336107407fcf4e1 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Mon, 20 May 2024 01:26:10 +0900 Subject: [PATCH 3/8] Fix linter --- .../internal/routemanager/systemops/systemops_generic_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go index 02c9b29384c..5712dac079c 100644 --- a/client/internal/routemanager/systemops/systemops_generic_test.go +++ b/client/internal/routemanager/systemops/systemops_generic_test.go @@ -359,7 +359,7 @@ func setupTestEnv(t *testing.T) { }) r := NewRoutingManager(wgInterface) - _, _, err := r.SetupRouting(nil, wgInterface) + _, _, err := r.SetupRouting(nil) require.NoError(t, err, "setupRouting should not return err") t.Cleanup(func() { assert.NoError(t, r.CleanupRouting()) From 653be23726e77b5a6f8c1885a62caae84dd55e61 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Mon, 20 May 2024 01:33:25 +0900 Subject: [PATCH 4/8] Fix test --- .../internal/routemanager/systemops/systemops_generic_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go index 5712dac079c..16ab56bb141 100644 --- a/client/internal/routemanager/systemops/systemops_generic_test.go +++ b/client/internal/routemanager/systemops/systemops_generic_test.go @@ -66,7 +66,7 @@ func TestAddRemoveRoutes(t *testing.T) { r := NewRoutingManager(wgInterface) - _, _, err = r.SetupRouting(nil, wgInterface) + _, _, err = r.SetupRouting(nil) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, r.CleanupRouting()) From 4d0acebcacbf7e8dbdfc7a2949c14b63bf46b448 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Tue, 21 May 2024 23:55:33 +0900 Subject: [PATCH 5/8] Rename to SysOps --- client/internal/routemanager/manager.go | 4 ++-- .../internal/routemanager/systemops/systemops.go | 6 +++--- .../systemops/systemops_darwin_test.go | 2 +- .../routemanager/systemops/systemops_generic.go | 16 ++++++++-------- .../systemops/systemops_generic_test.go | 6 +++--- .../routemanager/systemops/systemops_linux.go | 12 ++++++------ .../routemanager/systemops/systemops_mobile.go | 8 ++++---- .../routemanager/systemops/systemops_nonlinux.go | 4 ++-- .../routemanager/systemops/systemops_unix.go | 10 +++++----- .../routemanager/systemops/systemops_windows.go | 8 ++++---- 10 files changed, 38 insertions(+), 38 deletions(-) diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index 5d448a34591..a482148fc29 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -46,7 +46,7 @@ type DefaultManager struct { clientNetworks map[route.HAUniqueID]*clientNetwork routeSelector *routeselector.RouteSelector serverRouter serverRouter - routingManager *systemops.RoutingManager + routingManager *systemops.SysOps statusRecorder *peer.Status wgInterface *iface.WGIface pubKey string @@ -65,7 +65,7 @@ func NewManager( initialRoutes []*route.Route, ) *DefaultManager { mCTX, cancel := context.WithCancel(ctx) - routingManager := systemops.NewRoutingManager(wgInterface) + routingManager := systemops.NewSysOps(wgInterface) dm := &DefaultManager{ ctx: mCTX, diff --git a/client/internal/routemanager/systemops/systemops.go b/client/internal/routemanager/systemops/systemops.go index eac82610547..3a9ebc2b35e 100644 --- a/client/internal/routemanager/systemops/systemops.go +++ b/client/internal/routemanager/systemops/systemops.go @@ -20,13 +20,13 @@ var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1) var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1) var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1) -type RoutingManager struct { +type SysOps struct { refCounter *ExclusionCounter wgInterface *iface.WGIface } -func NewRoutingManager(wgInterface *iface.WGIface) *RoutingManager { - return &RoutingManager{ +func NewSysOps(wgInterface *iface.WGIface) *SysOps { + return &SysOps{ wgInterface: wgInterface, } } diff --git a/client/internal/routemanager/systemops/systemops_darwin_test.go b/client/internal/routemanager/systemops/systemops_darwin_test.go index 5c72cba6c45..e42bd343d73 100644 --- a/client/internal/routemanager/systemops/systemops_darwin_test.go +++ b/client/internal/routemanager/systemops/systemops_darwin_test.go @@ -35,7 +35,7 @@ func TestConcurrentRoutes(t *testing.T) { baseIP := netip.MustParseAddr("192.0.2.0") intf := &net.Interface{Name: "lo0"} - r := NewRoutingManager(nil) + r := NewSysOps(nil) var wg sync.WaitGroup for i := 0; i < 1024; i++ { diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go index 0c23f09c1eb..c13d180c978 100644 --- a/client/internal/routemanager/systemops/systemops_generic.go +++ b/client/internal/routemanager/systemops/systemops_generic.go @@ -29,7 +29,7 @@ var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1) var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1) var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1) -func (r *RoutingManager) setupRefCounter(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { +func (r *SysOps) setupRefCounter(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { initialNextHopV4, err := GetNextHop(netip.IPv4Unspecified()) if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { log.Errorf("Unable to get initial v4 default next hop: %v", err) @@ -62,7 +62,7 @@ func (r *RoutingManager) setupRefCounter(initAddresses []net.IP) (peer.BeforeAdd return r.setupHooks(initAddresses) } -func (r *RoutingManager) cleanupRefCounter() error { +func (r *SysOps) cleanupRefCounter() error { if r.refCounter == nil { return nil } @@ -79,7 +79,7 @@ func (r *RoutingManager) cleanupRefCounter() error { } // TODO: fix: for default our wg address now appears as the default gw -func (r *RoutingManager) addRouteForCurrentDefaultGateway(prefix netip.Prefix) error { +func (r *SysOps) addRouteForCurrentDefaultGateway(prefix netip.Prefix) error { addr := netip.IPv4Unspecified() if prefix.Addr().Is6() { addr = netip.IPv6Unspecified() @@ -121,7 +121,7 @@ func (r *RoutingManager) addRouteForCurrentDefaultGateway(prefix netip.Prefix) e // addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface. // If the next hop or interface is pointing to the VPN interface, it will return the initial values. -func (r *RoutingManager) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIface, initialNextHop Nexthop) (Nexthop, error) { +func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIface, initialNextHop Nexthop) (Nexthop, error) { addr := prefix.Addr() switch { case addr.IsLoopback(), @@ -167,7 +167,7 @@ func (r *RoutingManager) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *ifac // genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix // in two /1 prefixes to avoid replacing the existing default route -func (r *RoutingManager) genericAddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) genericAddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { nextHop := Nexthop{netip.Addr{}, intf} if prefix == vars.Defaultv4 { @@ -211,7 +211,7 @@ func (r *RoutingManager) genericAddVPNRoute(prefix netip.Prefix, intf *net.Inter } // addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table -func (r *RoutingManager) addNonExistingRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) addNonExistingRoute(prefix netip.Prefix, intf *net.Interface) error { ok, err := existsInRouteTable(prefix) if err != nil { return fmt.Errorf("exists in route table: %w", err) @@ -237,7 +237,7 @@ func (r *RoutingManager) addNonExistingRoute(prefix netip.Prefix, intf *net.Inte // genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given, // it will remove the split /1 prefixes -func (r *RoutingManager) genericRemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) genericRemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { nextHop := Nexthop{netip.Addr{}, intf} if prefix == vars.Defaultv4 { @@ -273,7 +273,7 @@ func (r *RoutingManager) genericRemoveVPNRoute(prefix netip.Prefix, intf *net.In return r.removeFromRouteTable(prefix, nextHop) } -func (r *RoutingManager) setupHooks(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { +func (r *SysOps) setupHooks(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { beforeHook := func(connID nbnet.ConnectionID, ip net.IP) error { prefix, err := util.GetPrefixFromIP(ip) if err != nil { diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go index 16ab56bb141..601518ceabf 100644 --- a/client/internal/routemanager/systemops/systemops_generic_test.go +++ b/client/internal/routemanager/systemops/systemops_generic_test.go @@ -64,7 +64,7 @@ func TestAddRemoveRoutes(t *testing.T) { err = wgInterface.Create() require.NoError(t, err, "should create testing wireguard interface") - r := NewRoutingManager(wgInterface) + r := NewSysOps(wgInterface) _, _, err = r.SetupRouting(nil) require.NoError(t, err) @@ -217,7 +217,7 @@ func TestAddExistAndRemoveRoute(t *testing.T) { require.NoError(t, err, "InterfaceByName should not return err") intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()} - r := NewRoutingManager(wgInterface) + r := NewSysOps(wgInterface) // Prepare the environment if testCase.preExistingPrefix.IsValid() { @@ -358,7 +358,7 @@ func setupTestEnv(t *testing.T) { assert.NoError(t, wgInterface.Close()) }) - r := NewRoutingManager(wgInterface) + r := NewSysOps(wgInterface) _, _, err := r.SetupRouting(nil) require.NoError(t, err, "setupRouting should not return err") t.Cleanup(func() { diff --git a/client/internal/routemanager/systemops/systemops_linux.go b/client/internal/routemanager/systemops/systemops_linux.go index d76d3ac631d..bca47d1f975 100644 --- a/client/internal/routemanager/systemops/systemops_linux.go +++ b/client/internal/routemanager/systemops/systemops_linux.go @@ -86,7 +86,7 @@ func getSetupRules() []ruleParams { // Rule 2 (VPN Traffic Routing): Directs all remaining traffic to the 'NetbirdVPNTableID' custom routing table. // This table is where a default route or other specific routes received from the management server are configured, // enabling VPN connectivity. -func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) { +func (r *SysOps) SetupRouting(initAddresses []net.IP) (_ peer.BeforeAddPeerHookFunc, _ peer.AfterRemovePeerHookFunc, err error) { if isLegacy() { log.Infof("Using legacy routing setup") return r.setupRefCounter(initAddresses) @@ -129,7 +129,7 @@ func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (_ peer.BeforeAddP // CleanupRouting performs a thorough cleanup of the routing configuration established by 'setupRouting'. // It systematically removes the three rules and any associated routing table entries to ensure a clean state. // The function uses error aggregation to report any errors encountered during the cleanup process. -func (r *RoutingManager) CleanupRouting() error { +func (r *SysOps) CleanupRouting() error { if isLegacy() { return r.cleanupRefCounter() } @@ -159,15 +159,15 @@ func (r *RoutingManager) CleanupRouting() error { return nberrors.FormatErrorOrNil(result) } -func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { return addRoute(prefix, nexthop, syscall.RT_TABLE_MAIN) } -func (r *RoutingManager) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { return removeRoute(prefix, nexthop, syscall.RT_TABLE_MAIN) } -func (r *RoutingManager) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { if isLegacy() { return r.genericAddVPNRoute(prefix, intf) } @@ -190,7 +190,7 @@ func (r *RoutingManager) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) e return nil } -func (r *RoutingManager) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { if isLegacy() { return r.genericRemoveVPNRoute(prefix, intf) } diff --git a/client/internal/routemanager/systemops/systemops_mobile.go b/client/internal/routemanager/systemops/systemops_mobile.go index f3367a47685..1517cf94933 100644 --- a/client/internal/routemanager/systemops/systemops_mobile.go +++ b/client/internal/routemanager/systemops/systemops_mobile.go @@ -12,19 +12,19 @@ import ( "github.com/netbirdio/netbird/client/internal/peer" ) -func (r *RoutingManager) SetupRouting([]net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { +func (r *SysOps) SetupRouting([]net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { return nil, nil, nil } -func (r *RoutingManager) CleanupRouting() error { +func (r *SysOps) CleanupRouting() error { return nil } -func (r *RoutingManager) AddVPNRoute(netip.Prefix, *net.Interface) error { +func (r *SysOps) AddVPNRoute(netip.Prefix, *net.Interface) error { return nil } -func (r *RoutingManager) RemoveVPNRoute(netip.Prefix, *net.Interface) error { +func (r *SysOps) RemoveVPNRoute(netip.Prefix, *net.Interface) error { return nil } diff --git a/client/internal/routemanager/systemops/systemops_nonlinux.go b/client/internal/routemanager/systemops/systemops_nonlinux.go index 9b606980e46..0f70cd78d89 100644 --- a/client/internal/routemanager/systemops/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops/systemops_nonlinux.go @@ -10,11 +10,11 @@ import ( log "github.com/sirupsen/logrus" ) -func (r *RoutingManager) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) AddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { return r.genericAddVPNRoute(prefix, intf) } -func (r *RoutingManager) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { +func (r *SysOps) RemoveVPNRoute(prefix netip.Prefix, intf *net.Interface) error { return r.genericRemoveVPNRoute(prefix, intf) } diff --git a/client/internal/routemanager/systemops/systemops_unix.go b/client/internal/routemanager/systemops/systemops_unix.go index 33d35c04389..ddf23d98bc4 100644 --- a/client/internal/routemanager/systemops/systemops_unix.go +++ b/client/internal/routemanager/systemops/systemops_unix.go @@ -16,23 +16,23 @@ import ( "github.com/netbirdio/netbird/client/internal/peer" ) -func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { +func (r *SysOps) SetupRouting(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { return r.setupRefCounter(initAddresses) } -func (r *RoutingManager) CleanupRouting() error { +func (r *SysOps) CleanupRouting() error { return r.cleanupRefCounter() } -func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { return r.routeCmd("add", prefix, nexthop) } -func (r *RoutingManager) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { return r.routeCmd("delete", prefix, nexthop) } -func (r *RoutingManager) routeCmd(action string, prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) routeCmd(action string, prefix netip.Prefix, nexthop Nexthop) error { inet := "-inet" network := prefix.String() if prefix.IsSingleIP() { diff --git a/client/internal/routemanager/systemops/systemops_windows.go b/client/internal/routemanager/systemops/systemops_windows.go index c84a1690068..b869bc85bf5 100644 --- a/client/internal/routemanager/systemops/systemops_windows.go +++ b/client/internal/routemanager/systemops/systemops_windows.go @@ -56,15 +56,15 @@ var prefixList []netip.Prefix var lastUpdate time.Time var mux = sync.Mutex{} -func (r *RoutingManager) SetupRouting(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { +func (r *SysOps) SetupRouting(initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) { return r.setupRefCounter(initAddresses) } -func (r *RoutingManager) CleanupRouting() error { +func (r *SysOps) CleanupRouting() error { return r.cleanupRefCounter() } -func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) error { if nexthop.IP.Zone() != "" && nexthop.Intf == nil { zone, err := strconv.Atoi(nexthop.IP.Zone()) if err != nil { @@ -77,7 +77,7 @@ func (r *RoutingManager) addToRouteTable(prefix netip.Prefix, nexthop Nexthop) e return addRouteCmd(prefix, nexthop) } -func (r *RoutingManager) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { +func (r *SysOps) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) error { args := []string{"delete", prefix.String()} if nexthop.IP.IsValid() { nexthop.IP.WithZone("") From ef52f5ec508317073843c77933bdfb45e449f76e Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Fri, 31 May 2024 23:33:46 +0900 Subject: [PATCH 6/8] Rename remaining symbols --- client/internal/routemanager/manager.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index a482148fc29..53943055c29 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -46,7 +46,7 @@ type DefaultManager struct { clientNetworks map[route.HAUniqueID]*clientNetwork routeSelector *routeselector.RouteSelector serverRouter serverRouter - routingManager *systemops.SysOps + sysOps *systemops.SysOps statusRecorder *peer.Status wgInterface *iface.WGIface pubKey string @@ -65,7 +65,7 @@ func NewManager( initialRoutes []*route.Route, ) *DefaultManager { mCTX, cancel := context.WithCancel(ctx) - routingManager := systemops.NewSysOps(wgInterface) + sysOps := systemops.NewSysOps(wgInterface) dm := &DefaultManager{ ctx: mCTX, @@ -73,7 +73,7 @@ func NewManager( dnsRouteInterval: dnsRouteInterval, clientNetworks: make(map[route.HAUniqueID]*clientNetwork), routeSelector: routeselector.NewRouteSelector(), - routingManager: routingManager, + sysOps: sysOps, statusRecorder: statusRecorder, wgInterface: wgInterface, pubKey: pubKey, @@ -82,10 +82,10 @@ func NewManager( dm.routeRefCounter = refcounter.New( func(prefix netip.Prefix, _ any) (any, error) { - return nil, routingManager.AddVPNRoute(prefix, wgInterface.ToInterface()) + return nil, sysOps.AddVPNRoute(prefix, wgInterface.ToInterface()) }, func(prefix netip.Prefix, _ any) error { - return routingManager.RemoveVPNRoute(prefix, wgInterface.ToInterface()) + return sysOps.RemoveVPNRoute(prefix, wgInterface.ToInterface()) }, ) @@ -118,7 +118,7 @@ func (m *DefaultManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePee return nil, nil, nil } - if err := m.routingManager.CleanupRouting(); err != nil { + if err := m.sysOps.CleanupRouting(); err != nil { log.Warnf("Failed cleaning up routing: %v", err) } @@ -126,7 +126,7 @@ func (m *DefaultManager) Init() (peer.BeforeAddPeerHookFunc, peer.AfterRemovePee signalAddress := m.statusRecorder.GetSignalState().URL ips := resolveURLsToIPs([]string{mgmtAddress, signalAddress}) - beforePeerHook, afterPeerHook, err := m.routingManager.SetupRouting(ips) + beforePeerHook, afterPeerHook, err := m.sysOps.SetupRouting(ips) if err != nil { return nil, nil, fmt.Errorf("setup routing: %w", err) } @@ -162,7 +162,7 @@ func (m *DefaultManager) Stop() { } if !nbnet.CustomRoutingDisabled() { - if err := m.routingManager.CleanupRouting(); err != nil { + if err := m.sysOps.CleanupRouting(); err != nil { log.Errorf("Error cleaning up routing: %v", err) } else { log.Info("Routing cleanup complete") From 4f7b226296d7d4cbbd4584de53610c37922dd936 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Tue, 4 Jun 2024 21:05:27 +0900 Subject: [PATCH 7/8] Fix rebasing --- .../routemanager/systemops/systemops.go | 249 ------------------ .../systemops/systemops_generic.go | 4 + .../routemanager/systemops/systemops_unix.go | 18 +- 3 files changed, 9 insertions(+), 262 deletions(-) diff --git a/client/internal/routemanager/systemops/systemops.go b/client/internal/routemanager/systemops/systemops.go index 3a9ebc2b35e..9ee51538b5a 100644 --- a/client/internal/routemanager/systemops/systemops.go +++ b/client/internal/routemanager/systemops/systemops.go @@ -15,11 +15,6 @@ type Nexthop struct { type ExclusionCounter = refcounter.Counter[any, Nexthop] -var splitDefaultv4_1 = netip.PrefixFrom(netip.IPv4Unspecified(), 1) -var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1) -var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1) -var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1) - type SysOps struct { refCounter *ExclusionCounter wgInterface *iface.WGIface @@ -30,247 +25,3 @@ func NewSysOps(wgInterface *iface.WGIface) *SysOps { wgInterface: wgInterface, } } - -// TODO: fix: for default our wg address now appears as the default gw -func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error { - addr := netip.IPv4Unspecified() - if prefix.Addr().Is6() { - addr = netip.IPv6Unspecified() - } - - nexthop, err := GetNextHop(addr) - if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { - return fmt.Errorf("get existing route gateway: %s", err) - } - - if !prefix.Contains(nexthop.IP) { - log.Debugf("Skipping adding a new route for gateway %s because it is not in the network %s", nexthop.IP, prefix) - return nil - } - - gatewayPrefix := netip.PrefixFrom(nexthop.IP, 32) - if nexthop.IP.Is6() { - gatewayPrefix = netip.PrefixFrom(nexthop.IP, 128) - } - - ok, err := existsInRouteTable(gatewayPrefix) - if err != nil { - return fmt.Errorf("unable to check if there is an existing route for gateway %s. error: %s", gatewayPrefix, err) - } - - if ok { - log.Debugf("Skipping adding a new route for gateway %s because it already exists", gatewayPrefix) - return nil - } - - nexthop, err = GetNextHop(nexthop.IP) - if err != nil && !errors.Is(err, vars.ErrRouteNotFound) { - return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err) - } - - log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, nexthop.IP) - return addToRouteTable(gatewayPrefix, nexthop) -} - -func GetNextHop(ip netip.Addr) (Nexthop, error) { - r, err := netroute.New() - if err != nil { - return Nexthop{}, fmt.Errorf("new netroute: %w", err) - } - intf, gateway, preferredSrc, err := r.Route(ip.AsSlice()) - if err != nil { - log.Debugf("Failed to get route for %s: %v", ip, err) - return Nexthop{}, vars.ErrRouteNotFound - } - - log.Debugf("Route for %s: interface %v nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc) - if gateway == nil { - if runtime.GOOS == "freebsd" { - return Nexthop{Intf: intf}, nil - } - - if preferredSrc == nil { - return Nexthop{}, vars.ErrRouteNotFound - } - log.Debugf("No next hop found for IP %s, using preferred source %s", ip, preferredSrc) - - addr, err := ipToAddr(preferredSrc, intf) - if err != nil { - return Nexthop{}, fmt.Errorf("convert preferred source to address: %w", err) - } - return Nexthop{ - IP: addr.Unmap(), - Intf: intf, - }, nil - } - - addr, err := ipToAddr(gateway, intf) - if err != nil { - return Nexthop{}, fmt.Errorf("convert gateway to address: %w", err) - } - - return Nexthop{ - IP: addr, - Intf: intf, - }, nil -} - -// converts a net.IP to a netip.Addr including the zone based on the passed interface -func ipToAddr(ip net.IP, intf *net.Interface) (netip.Addr, error) { - addr, ok := netip.AddrFromSlice(ip) - if !ok { - return netip.Addr{}, fmt.Errorf("failed to convert IP address to netip.Addr: %s", ip) - } - - if intf != nil && (addr.IsLinkLocalMulticast() || addr.IsLinkLocalUnicast()) { - log.Tracef("Adding zone %s to address %s", intf.Name, addr) - if runtime.GOOS == "windows" { - addr = addr.WithZone(strconv.Itoa(intf.Index)) - } else { - addr = addr.WithZone(intf.Name) - } - } - - return addr.Unmap(), nil -} - -func existsInRouteTable(prefix netip.Prefix) (bool, error) { - routes, err := getRoutesFromTable() - if err != nil { - return false, fmt.Errorf("get routes from table: %w", err) - } - for _, tableRoute := range routes { - if tableRoute == prefix { - return true, nil - } - } - return false, nil -} - -func isSubRange(prefix netip.Prefix) (bool, error) { - routes, err := getRoutesFromTable() - if err != nil { - return false, fmt.Errorf("get routes from table: %w", err) - } - for _, tableRoute := range routes { - if tableRoute.Bits() > vars.MinRangeBits && tableRoute.Contains(prefix.Addr()) && tableRoute.Bits() < prefix.Bits() { - return true, nil - } - } - return false, nil -} - -// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface. -// If the next hop or interface is pointing to the VPN interface, it will return the initial values. -func addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIface, initialNextHop Nexthop) (Nexthop, error) { - addr := prefix.Addr() - switch { - case addr.IsLoopback(), - addr.IsLinkLocalUnicast(), - addr.IsLinkLocalMulticast(), - addr.IsInterfaceLocalMulticast(), - addr.IsUnspecified(), - addr.IsMulticast(): - - return Nexthop{}, vars.ErrRouteNotAllowed - } - - // Determine the exit interface and next hop for the prefix, so we can add a specific route - nexthop, err := GetNextHop(addr) - if err != nil { - return Nexthop{}, fmt.Errorf("get next hop: %w", err) - } - - log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop.IP, prefix, nexthop.IP) - exitNextHop := Nexthop{ - IP: nexthop.IP, - Intf: nexthop.Intf, - } - - vpnAddr, ok := netip.AddrFromSlice(vpnIntf.Address().IP) - if !ok { - return Nexthop{}, fmt.Errorf("failed to convert vpn address to netip.Addr") - } - - // if next hop is the VPN address or the interface is the VPN interface, we should use the initial values - if exitNextHop.IP == vpnAddr || exitNextHop.Intf != nil && exitNextHop.Intf.Name == vpnIntf.Name() { - log.Debugf("Route for prefix %s is pointing to the VPN interface", prefix) - exitNextHop = initialNextHop - } - - log.Debugf("Adding a new route for prefix %s with next hop %s", prefix, exitNextHop.IP) - if err := addToRouteTable(prefix, exitNextHop); err != nil { - return Nexthop{}, fmt.Errorf("add route to table: %w", err) - } - - return exitNextHop, nil -} - -// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix -// in two /1 prefixes to avoid replacing the existing default route -func genericAddVPNRoute(prefix netip.Prefix, intf *net.Interface) error { - nextHop := Nexthop{netip.Addr{}, intf} - - if prefix == vars.Defaultv4 { - if err := addToRouteTable(splitDefaultv4_1, nextHop); err != nil { - return err - } - if err := addToRouteTable(splitDefaultv4_2, nextHop); err != nil { - if err2 := removeFromRouteTable(splitDefaultv4_1, nextHop); err2 != nil { - log.Warnf("Failed to rollback route addition: %s", err2) - } - return err - } - - // TODO: remove once IPv6 is supported on the interface - if err := addToRouteTable(splitDefaultv6_1, nextHop); err != nil { - return fmt.Errorf("add unreachable route split 1: %w", err) - } - if err := addToRouteTable(splitDefaultv6_2, nextHop); err != nil { - if err2 := removeFromRouteTable(splitDefaultv6_1, nextHop); err2 != nil { - log.Warnf("Failed to rollback route addition: %s", err2) - } - return fmt.Errorf("add unreachable route split 2: %w", err) - } - - return nil - } else if prefix == vars.Defaultv6 { - if err := addToRouteTable(splitDefaultv6_1, nextHop); err != nil { - return fmt.Errorf("add unreachable route split 1: %w", err) - } - if err := addToRouteTable(splitDefaultv6_2, nextHop); err != nil { - if err2 := removeFromRouteTable(splitDefaultv6_1, nextHop); err2 != nil { - log.Warnf("Failed to rollback route addition: %s", err2) - } - return fmt.Errorf("add unreachable route split 2: %w", err) - } - - return nil - } - - return addNonExistingRoute(prefix, intf) -} - -// addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table -func addNonExistingRoute(prefix netip.Prefix, intf *net.Interface) error { - ok, err := existsInRouteTable(prefix) - if err != nil { - return fmt.Errorf("exists in route table: %w", err) - } - if ok { - log.Warnf("Skipping adding a new route for network %s because it already exists", prefix) - return nil - } - - ok, err = isSubRange(prefix) - if err != nil { - return fmt.Errorf("sub range: %w", err) - } - - if ok { - err := addRouteForCurrentDefaultGateway(prefix) - if err != nil { - log.Warnf("Unable to add route for current default gateway route. Will proceed without it. error: %s", err) - } - } -} diff --git a/client/internal/routemanager/systemops/systemops_generic.go b/client/internal/routemanager/systemops/systemops_generic.go index c13d180c978..01b9ebda6cf 100644 --- a/client/internal/routemanager/systemops/systemops_generic.go +++ b/client/internal/routemanager/systemops/systemops_generic.go @@ -340,6 +340,10 @@ func GetNextHop(ip netip.Addr) (Nexthop, error) { log.Debugf("Route for %s: interface %v nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc) if gateway == nil { + if runtime.GOOS == "freebsd" { + return Nexthop{Intf: intf}, nil + } + if preferredSrc == nil { return Nexthop{}, vars.ErrRouteNotFound } diff --git a/client/internal/routemanager/systemops/systemops_unix.go b/client/internal/routemanager/systemops/systemops_unix.go index ddf23d98bc4..d4d2a31ed18 100644 --- a/client/internal/routemanager/systemops/systemops_unix.go +++ b/client/internal/routemanager/systemops/systemops_unix.go @@ -34,24 +34,16 @@ func (r *SysOps) removeFromRouteTable(prefix netip.Prefix, nexthop Nexthop) erro func (r *SysOps) routeCmd(action string, prefix netip.Prefix, nexthop Nexthop) error { inet := "-inet" + if prefix.Addr().Is6() { + inet = "-inet6" + } + network := prefix.String() if prefix.IsSingleIP() { network = prefix.Addr().String() } - args := []string{"-n", action} - if prefix.Addr().Is6() { - inet = "-inet6" - // TODO: Remove once we have IPv6 support on the interface - // Point the route to lo0 if the nexthop is the WireGuard interface, otherwise the operation fails - // without IPv6 support on the interface. - if nexthop.Intf != nil && nexthop.Intf.Name == r.wgInterface.Name() { - args = append(args, "-blackhole") - nexthop.Intf = &net.Interface{Name: "lo0"} - } - } - - args = append(args, inet, network) + args := []string{"-n", action, inet, network} if nexthop.IP.IsValid() { args = append(args, nexthop.IP.Unmap().String()) } else if nexthop.Intf != nil { From 4f7efbe42751b514d91d4bbbb4658a4674f96132 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Wed, 5 Jun 2024 20:01:52 +0900 Subject: [PATCH 8/8] Remove repetitions --- .../systemops/systemops_generic_test.go | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go index 601518ceabf..f7cb174770f 100644 --- a/client/internal/routemanager/systemops/systemops_generic_test.go +++ b/client/internal/routemanager/systemops/systemops_generic_test.go @@ -348,6 +348,17 @@ func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listen return wgInterface } +func setupRouteAndCleanup(t *testing.T, r *SysOps, prefix netip.Prefix, intf *net.Interface) { + t.Helper() + + err := r.AddVPNRoute(prefix, intf) + require.NoError(t, err, "addVPNRoute should not return err") + t.Cleanup(func() { + err = r.RemoveVPNRoute(prefix, intf) + assert.NoError(t, err, "removeVPNRoute should not return err") + }) +} + func setupTestEnv(t *testing.T) { t.Helper() @@ -370,44 +381,19 @@ func setupTestEnv(t *testing.T) { intf := &net.Interface{Index: index.Index, Name: wgInterface.Name()} // default route exists in main table and vpn table - err = r.AddVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf) - require.NoError(t, err, "addVPNRoute should not return err") - t.Cleanup(func() { - err = r.RemoveVPNRoute(netip.MustParsePrefix("0.0.0.0/0"), intf) - assert.NoError(t, err, "removeVPNRoute should not return err") - }) + setupRouteAndCleanup(t, r, netip.MustParsePrefix("0.0.0.0/0"), intf) // 10.0.0.0/8 route exists in main table and vpn table - err = r.AddVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf) - require.NoError(t, err, "addVPNRoute should not return err") - t.Cleanup(func() { - err = r.RemoveVPNRoute(netip.MustParsePrefix("10.0.0.0/8"), intf) - assert.NoError(t, err, "removeVPNRoute should not return err") - }) + setupRouteAndCleanup(t, r, netip.MustParsePrefix("10.0.0.0/8"), intf) // 10.10.0.0/24 more specific route exists in vpn table - err = r.AddVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf) - require.NoError(t, err, "addVPNRoute should not return err") - t.Cleanup(func() { - err = r.RemoveVPNRoute(netip.MustParsePrefix("10.10.0.0/24"), intf) - assert.NoError(t, err, "removeVPNRoute should not return err") - }) + setupRouteAndCleanup(t, r, netip.MustParsePrefix("10.10.0.0/24"), intf) // 127.0.10.0/24 more specific route exists in vpn table - err = r.AddVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf) - require.NoError(t, err, "addVPNRoute should not return err") - t.Cleanup(func() { - err = r.RemoveVPNRoute(netip.MustParsePrefix("127.0.10.0/24"), intf) - assert.NoError(t, err, "removeVPNRoute should not return err") - }) + setupRouteAndCleanup(t, r, netip.MustParsePrefix("127.0.10.0/24"), intf) // unique route in vpn table - err = r.AddVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf) - require.NoError(t, err, "addVPNRoute should not return err") - t.Cleanup(func() { - err = r.RemoveVPNRoute(netip.MustParsePrefix("172.16.0.0/12"), intf) - assert.NoError(t, err, "removeVPNRoute should not return err") - }) + setupRouteAndCleanup(t, r, netip.MustParsePrefix("172.16.0.0/12"), intf) } func assertWGOutInterface(t *testing.T, prefix netip.Prefix, wgIface *iface.WGIface, invert bool) {