diff --git a/client/internal/routemanager/systemops_nonandroid.go b/client/internal/routemanager/systemops_nonandroid.go index 4796a9a59..49108825b 100644 --- a/client/internal/routemanager/systemops_nonandroid.go +++ b/client/internal/routemanager/systemops_nonandroid.go @@ -6,27 +6,51 @@ 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") func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { - gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) - if err != nil && err != errRouteNotFound { - return err - } - prefixGateway, err := getExistingRIBRouteGateway(prefix) + defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) if err != nil && err != errRouteNotFound { return err } - if prefixGateway != nil && !prefixGateway.Equal(gateway) { - log.Warnf("skipping adding a new route for network %s because it already exists and is pointing to the non default gateway: %s", prefix, prefixGateway) + gatewayIP := netip.MustParseAddr(defaultGateway.String()) + if prefix.Contains(gatewayIP) { + log.Warnf("skipping adding a new route for network %s because it overlaps with the default gateway: %s", prefix, gatewayIP) return nil } + + ok, err := existsInRouteTable(prefix) + if err != nil { + return err + } + if ok { + log.Warnf("skipping adding a new route for network %s because it already exists", prefix) + return nil + } + return addToRouteTable(prefix, addr) } @@ -48,14 +72,70 @@ func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) { if err != nil { return nil, err } - _, gateway, preferredSrc, err := r.Route(prefix.Addr().AsSlice()) + _, gateway, _, err := r.Route(prefix.Addr().AsSlice()) if err != nil { log.Errorf("getting routes returned an error: %v", err) return nil, errRouteNotFound } - if gateway == nil { - return preferredSrc, nil - } 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]) + log.Debugf("checking route: %s", dst) + if err != nil { + return true, fmt.Errorf("unexpected RIB destination: %v", err) + } + + mask, err := toIPAddr(m.Addrs[2]) + log.Debugf("checking route mask: %s", mask) + if err != nil { + return true, fmt.Errorf("unexpected RIB destination: %v", err) + } + 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 59d4cb72c..28f798a2d 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -2,14 +2,20 @@ package routemanager import ( "fmt" - "github.com/netbirdio/netbird/iface" - "github.com/pion/transport/v2/stdnet" - "github.com/stretchr/testify/require" "net" "net/netip" "testing" + + "github.com/pion/transport/v2/stdnet" + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/iface" ) +// func TestAddRoute(t *testing.T) { +// addToRouteTableIfNoExists() +// } + func TestAddRemoveRoutes(t *testing.T) { testCases := []struct { name string