From 6e26d03fb8c35927cbea71742e49e5c3507c9332 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Fri, 9 Jun 2023 18:27:09 +0200 Subject: [PATCH] split systemops for operating systems and add linux --- client/internal/routemanager/systemops_bsd.go | 82 ++++++++++++++++ .../internal/routemanager/systemops_linux.go | 39 ++++++++ .../routemanager/systemops_nonandroid.go | 71 -------------- .../routemanager/systemops_nonandroid_test.go | 88 ----------------- .../internal/routemanager/systemops_test.go | 97 +++++++++++++++++++ .../routemanager/systemops_windows.go | 4 + 6 files changed, 222 insertions(+), 159 deletions(-) create mode 100644 client/internal/routemanager/systemops_bsd.go create mode 100644 client/internal/routemanager/systemops_test.go create mode 100644 client/internal/routemanager/systemops_windows.go diff --git a/client/internal/routemanager/systemops_bsd.go b/client/internal/routemanager/systemops_bsd.go new file mode 100644 index 000000000..e777ec8ec --- /dev/null +++ b/client/internal/routemanager/systemops_bsd.go @@ -0,0 +1,82 @@ +//go:build darwin || dragonfly || freebsd || netbsd || openbsd +// +build darwin dragonfly freebsd netbsd openbsd + +package routemanager + +import ( + "fmt" + "net" + "net/netip" + "syscall" + + "golang.org/x/net/route" +) + +// selected BSD Route flags. +const ( + RTF_UP = 0x1 + RTF_GATEWAY = 0x2 + RTF_HOST = 0x4 + RTF_REJECT = 0x8 + RTF_DYNAMIC = 0x10 + RTF_MODIFIED = 0x20 + RTF_STATIC = 0x800 + RTF_BLACKHOLE = 0x1000 + RTF_LOCAL = 0x200000 + RTF_BROADCAST = 0x400000 + RTF_MULTICAST = 0x800000 +) + +func existsInRouteTable(prefix netip.Prefix) (bool, error) { + tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0) + if err != nil { + return false, err + } + msgs, err := route.ParseRIB(route.RIBTypeRoute, tab) + if err != nil { + return false, err + } + + for _, msg := range msgs { + m := msg.(*route.RouteMessage) + + if m.Version < 3 || m.Version > 5 { + return false, fmt.Errorf("unexpected RIB message version: %d", m.Version) + } + if m.Type != 4 /* RTM_GET */ { + return true, fmt.Errorf("unexpected RIB message type: %d", m.Type) + } + + if m.Flags&RTF_UP == 0 || + m.Flags&(RTF_REJECT|RTF_BLACKHOLE) != 0 { + continue + } + + dst, err := toIPAddr(m.Addrs[0]) + if err != nil { + return true, fmt.Errorf("unexpected RIB destination: %v", err) + } + + mask, _ := toIPAddr(m.Addrs[2]) + cidr, _ := net.IPMask(mask.To4()).Size() + if dst.String() == prefix.Addr().String() && cidr == prefix.Bits() { + return true, nil + } + } + + return false, nil +} + +func toIPAddr(a route.Addr) (net.IP, error) { + switch t := a.(type) { + case *route.Inet4Addr: + ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3]) + return ip, nil + case *route.Inet6Addr: + ip := make(net.IP, net.IPv6len) + copy(ip, t.IP[:]) + return ip, nil + default: + return net.IP{}, fmt.Errorf("unknown family: %v", t) + } +} diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index 67c59be2d..368cb9075 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -61,6 +61,45 @@ func removeFromRouteTable(prefix netip.Prefix) error { return nil } +func existsInRouteTable(prefix netip.Prefix) (bool, error) { + tab, err := syscall.NetlinkRIB(syscall.RTM_GETROUTE, syscall.AF_UNSPEC) + if err != nil { + return nil, err + } + msgs, err := syscall.ParseNetlinkMessage(tab) + if err != nil { + return nil, err + } +loop: + for _, m := range msgs { + switch m.Header.Type { + case syscall.NLMSG_DONE: + break loop + case syscall.RTM_NEWROUTE: + rt := (*routeInfoInMemory)(unsafe.Pointer(&m.Data[0])) + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, err + } + if rt.Family != syscall.AF_INET { + continue loop + } + + for _, attr := range attrs { + if attr.Attr.Type == syscall.RTA_DST { + ip := net.IP(attr.Value) + mask := net.CIDRMask(int(rt.DstLen), len(attr.Value)*8) + cidr, _ := mask.Size() + if ip.String() == prefix.Addr().String() && cidr == prefix.Bits() { + return true, nil + } + } + } + } + } + return false, nil +} + func enableIPForwarding() error { bytes, err := os.ReadFile(ipv4ForwardingPath) if err != nil { diff --git a/client/internal/routemanager/systemops_nonandroid.go b/client/internal/routemanager/systemops_nonandroid.go index c9efd7882..3958e0e05 100644 --- a/client/internal/routemanager/systemops_nonandroid.go +++ b/client/internal/routemanager/systemops_nonandroid.go @@ -6,26 +6,9 @@ import ( "fmt" "net" "net/netip" - "syscall" "github.com/libp2p/go-netroute" log "github.com/sirupsen/logrus" - "golang.org/x/net/route" -) - -// selected BSD Route flags. -const ( - RTF_UP = 0x1 - RTF_GATEWAY = 0x2 - RTF_HOST = 0x4 - RTF_REJECT = 0x8 - RTF_DYNAMIC = 0x10 - RTF_MODIFIED = 0x20 - RTF_STATIC = 0x800 - RTF_BLACKHOLE = 0x1000 - RTF_LOCAL = 0x200000 - RTF_BROADCAST = 0x400000 - RTF_MULTICAST = 0x800000 ) var errRouteNotFound = fmt.Errorf("route not found") @@ -80,57 +63,3 @@ func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) { return gateway, nil } - -func existsInRouteTable(prefix netip.Prefix) (bool, error) { - tab, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0) - if err != nil { - return false, err - } - msgs, err := route.ParseRIB(route.RIBTypeRoute, tab) - if err != nil { - return false, err - } - - for _, msg := range msgs { - m := msg.(*route.RouteMessage) - - if m.Version < 3 || m.Version > 5 { - return false, fmt.Errorf("unexpected RIB message version: %d", m.Version) - } - if m.Type != 4 /* RTM_GET */ { - return true, fmt.Errorf("unexpected RIB message type: %d", m.Type) - } - - if m.Flags&RTF_UP == 0 || - m.Flags&(RTF_REJECT|RTF_BLACKHOLE) != 0 { - continue - } - - dst, err := toIPAddr(m.Addrs[0]) - if err != nil { - return true, fmt.Errorf("unexpected RIB destination: %v", err) - } - - mask, _ := toIPAddr(m.Addrs[2]) - cidr, _ := net.IPMask(mask.To4()).Size() - if dst.String() == prefix.Addr().String() && cidr == prefix.Bits() { - return true, nil - } - } - - return false, nil -} - -func toIPAddr(a route.Addr) (net.IP, error) { - switch t := a.(type) { - case *route.Inet4Addr: - ip := net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3]) - return ip, nil - case *route.Inet6Addr: - ip := make(net.IP, net.IPv6len) - copy(ip, t.IP[:]) - return ip, nil - default: - return net.IP{}, fmt.Errorf("unknown family: %v", t) - } -} diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 2020aa8d4..48e262adb 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -1,16 +1,12 @@ package routemanager import ( - "bytes" "fmt" "net" "net/netip" - "os" - "strings" "testing" "github.com/pion/transport/v2/stdnet" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/iface" @@ -79,90 +75,6 @@ func TestAddRemoveRoutes(t *testing.T) { } } -func TestAddExistAndRemoveRoute(t *testing.T) { - defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) - fmt.Println("defaultGateway: ", defaultGateway) - if err != nil { - t.Fatal("shouldn't return error when fetching the gateway: ", err) - } - testCases := []struct { - name string - prefix netip.Prefix - preExistingPrefix netip.Prefix - shouldAddRoute bool - }{ - { - name: "Should Add And Remove random Route", - prefix: netip.MustParsePrefix("99.99.99.99/32"), - shouldAddRoute: true, - }, - { - name: "Should Not Add Route if overlaps with default gateway", - prefix: netip.MustParsePrefix(defaultGateway.String() + "/31"), - shouldAddRoute: false, - }, - { - name: "Should Add Route if bigger network exists", - prefix: netip.MustParsePrefix("100.100.100.0/24"), - preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"), - shouldAddRoute: true, - }, - { - name: "Should Add Route if smaller network exists", - prefix: netip.MustParsePrefix("100.100.0.0/16"), - preExistingPrefix: netip.MustParsePrefix("100.100.100.0/24"), - shouldAddRoute: true, - }, - { - name: "Should Not Add Route if same network exists", - prefix: netip.MustParsePrefix("100.100.0.0/16"), - preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"), - shouldAddRoute: false, - }, - } - - MOCK_ADDR := "127.0.0.1" - - for _, testCase := range testCases { - var buf bytes.Buffer - log.SetOutput(&buf) - defer func() { - log.SetOutput(os.Stderr) - }() - t.Run(testCase.name, func(t *testing.T) { - // Prepare the environment - if testCase.preExistingPrefix.IsValid() { - err := addToRouteTableIfNoExists(testCase.preExistingPrefix, MOCK_ADDR) - require.NoError(t, err, "should not return err when adding pre-existing route") - } - - // Add the route - err = addToRouteTableIfNoExists(testCase.prefix, MOCK_ADDR) - require.NoError(t, err, "should not return err when adding pre-existing route") - - if testCase.shouldAddRoute { - // test if route exists after adding - ok, err := existsInRouteTable(testCase.prefix) - require.NoError(t, err, "should not return err") - require.True(t, ok, "route should exist") - - // remove route again if added - err = removeFromRouteTableIfNonSystem(testCase.prefix, MOCK_ADDR) - require.NoError(t, err, "should not return err") - } - - // route should either not have been added or should have been removed - // In case of already existing route, it should not have been added (but still exist) - ok, err := existsInRouteTable(testCase.prefix) - fmt.Println("Buffer string: ", buf.String()) - require.NoError(t, err, "should not return err") - if !strings.Contains(buf.String(), "because it already exists") { - require.False(t, ok, "route should not exist") - } - }) - } -} - func TestGetExistingRIBRouteGateway(t *testing.T) { gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) if err != nil { diff --git a/client/internal/routemanager/systemops_test.go b/client/internal/routemanager/systemops_test.go new file mode 100644 index 000000000..d02b470f8 --- /dev/null +++ b/client/internal/routemanager/systemops_test.go @@ -0,0 +1,97 @@ +package routemanager + +import ( + "bytes" + "fmt" + "net/netip" + "os" + "strings" + "testing" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" +) + +func TestAddExistAndRemoveRoute(t *testing.T) { + defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) + fmt.Println("defaultGateway: ", defaultGateway) + if err != nil { + t.Fatal("shouldn't return error when fetching the gateway: ", err) + } + testCases := []struct { + name string + prefix netip.Prefix + preExistingPrefix netip.Prefix + shouldAddRoute bool + }{ + { + name: "Should Add And Remove random Route", + prefix: netip.MustParsePrefix("99.99.99.99/32"), + shouldAddRoute: true, + }, + { + name: "Should Not Add Route if overlaps with default gateway", + prefix: netip.MustParsePrefix(defaultGateway.String() + "/31"), + shouldAddRoute: false, + }, + { + name: "Should Add Route if bigger network exists", + prefix: netip.MustParsePrefix("100.100.100.0/24"), + preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"), + shouldAddRoute: true, + }, + { + name: "Should Add Route if smaller network exists", + prefix: netip.MustParsePrefix("100.100.0.0/16"), + preExistingPrefix: netip.MustParsePrefix("100.100.100.0/24"), + shouldAddRoute: true, + }, + { + name: "Should Not Add Route if same network exists", + prefix: netip.MustParsePrefix("100.100.0.0/16"), + preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"), + shouldAddRoute: false, + }, + } + + MOCK_ADDR := "127.0.0.1" + + for _, testCase := range testCases { + var buf bytes.Buffer + log.SetOutput(&buf) + defer func() { + log.SetOutput(os.Stderr) + }() + t.Run(testCase.name, func(t *testing.T) { + // Prepare the environment + if testCase.preExistingPrefix.IsValid() { + err := addToRouteTableIfNoExists(testCase.preExistingPrefix, MOCK_ADDR) + require.NoError(t, err, "should not return err when adding pre-existing route") + } + + // Add the route + err = addToRouteTableIfNoExists(testCase.prefix, MOCK_ADDR) + require.NoError(t, err, "should not return err when adding pre-existing route") + + if testCase.shouldAddRoute { + // test if route exists after adding + ok, err := existsInRouteTable(testCase.prefix) + require.NoError(t, err, "should not return err") + require.True(t, ok, "route should exist") + + // remove route again if added + err = removeFromRouteTableIfNonSystem(testCase.prefix, MOCK_ADDR) + require.NoError(t, err, "should not return err") + } + + // route should either not have been added or should have been removed + // In case of already existing route, it should not have been added (but still exist) + ok, err := existsInRouteTable(testCase.prefix) + fmt.Println("Buffer string: ", buf.String()) + require.NoError(t, err, "should not return err") + if !strings.Contains(buf.String(), "because it already exists") { + require.False(t, ok, "route should not exist") + } + }) + } +} diff --git a/client/internal/routemanager/systemops_windows.go b/client/internal/routemanager/systemops_windows.go new file mode 100644 index 000000000..751b04846 --- /dev/null +++ b/client/internal/routemanager/systemops_windows.go @@ -0,0 +1,4 @@ +//go:build windows +// +build windows + +package routemanager