package systemops import ( "net/netip" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/client/iface/wgaddr" "github.com/netbirdio/netbird/client/internal/routemanager/notifier" "github.com/netbirdio/netbird/client/internal/routemanager/vars" ) type mockWGIface struct { address wgaddr.Address name string } func (m *mockWGIface) Address() wgaddr.Address { return m.address } func (m *mockWGIface) Name() string { return m.name } func TestSysOps_validateRoute(t *testing.T) { wgNetwork := netip.MustParsePrefix("10.0.0.0/24") mockWG := &mockWGIface{ address: wgaddr.Address{ IP: wgNetwork.Addr(), Network: wgNetwork, }, name: "wg0", } sysOps := &SysOps{ wgInterface: mockWG, notifier: ¬ifier.Notifier{}, } tests := []struct { name string prefix string expectError bool }{ // Valid routes { name: "valid IPv4 route", prefix: "192.168.1.0/24", expectError: false, }, { name: "valid IPv6 route", prefix: "2001:db8::/32", expectError: false, }, { name: "valid single IPv4 host", prefix: "8.8.8.8/32", expectError: false, }, { name: "valid single IPv6 host", prefix: "2001:4860:4860::8888/128", expectError: false, }, // Invalid routes - loopback { name: "IPv4 loopback", prefix: "127.0.0.1/32", expectError: true, }, { name: "IPv6 loopback", prefix: "::1/128", expectError: true, }, // Invalid routes - link-local unicast { name: "IPv4 link-local unicast", prefix: "169.254.1.1/32", expectError: true, }, { name: "IPv6 link-local unicast", prefix: "fe80::1/128", expectError: true, }, // Invalid routes - multicast { name: "IPv4 multicast", prefix: "224.0.0.1/32", expectError: true, }, { name: "IPv6 multicast", prefix: "ff02::1/128", expectError: true, }, // Invalid routes - link-local multicast { name: "IPv4 link-local multicast", prefix: "224.0.0.0/24", expectError: true, }, { name: "IPv6 link-local multicast", prefix: "ff02::/16", expectError: true, }, // Invalid routes - interface-local multicast (IPv6 only) { name: "IPv6 interface-local multicast", prefix: "ff01::1/128", expectError: true, }, // Invalid routes - overlaps with WG interface network { name: "overlaps with WG network - exact match", prefix: "10.0.0.0/24", expectError: true, }, { name: "overlaps with WG network - subset", prefix: "10.0.0.1/32", expectError: true, }, { name: "overlaps with WG network - host in range", prefix: "10.0.0.100/32", expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { prefix, err := netip.ParsePrefix(tt.prefix) require.NoError(t, err, "Failed to parse test prefix %s", tt.prefix) err = sysOps.validateRoute(prefix) if tt.expectError { require.Error(t, err, "validateRoute() expected error for %s", tt.prefix) assert.Equal(t, vars.ErrRouteNotAllowed, err, "validateRoute() expected ErrRouteNotAllowed for %s", tt.prefix) } else { assert.NoError(t, err, "validateRoute() expected no error for %s", tt.prefix) } }) } } func TestSysOps_validateRoute_SubnetOverlap(t *testing.T) { wgNetwork := netip.MustParsePrefix("192.168.100.0/24") mockWG := &mockWGIface{ address: wgaddr.Address{ IP: wgNetwork.Addr(), Network: wgNetwork, }, name: "wg0", } sysOps := &SysOps{ wgInterface: mockWG, notifier: ¬ifier.Notifier{}, } tests := []struct { name string prefix string expectError bool description string }{ { name: "identical subnet", prefix: "192.168.100.0/24", expectError: true, description: "exact same network as WG interface", }, { name: "broader subnet containing WG network", prefix: "192.168.0.0/16", expectError: false, description: "broader network that contains WG network should be allowed", }, { name: "host within WG network", prefix: "192.168.100.50/32", expectError: true, description: "specific host within WG network", }, { name: "subnet within WG network", prefix: "192.168.100.128/25", expectError: true, description: "smaller subnet within WG network", }, { name: "adjacent subnet - same /23", prefix: "192.168.101.0/24", expectError: false, description: "adjacent subnet, no overlap", }, { name: "adjacent subnet - different /16", prefix: "192.167.100.0/24", expectError: false, description: "different network, no overlap", }, { name: "WG network broadcast address", prefix: "192.168.100.255/32", expectError: true, description: "broadcast address of WG network", }, { name: "WG network first usable", prefix: "192.168.100.1/32", expectError: true, description: "first usable address in WG network", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { prefix, err := netip.ParsePrefix(tt.prefix) require.NoError(t, err, "Failed to parse test prefix %s", tt.prefix) err = sysOps.validateRoute(prefix) if tt.expectError { require.Error(t, err, "validateRoute() expected error for %s (%s)", tt.prefix, tt.description) assert.Equal(t, vars.ErrRouteNotAllowed, err, "validateRoute() expected ErrRouteNotAllowed for %s (%s)", tt.prefix, tt.description) } else { assert.NoError(t, err, "validateRoute() expected no error for %s (%s)", tt.prefix, tt.description) } }) } } func TestSysOps_validateRoute_InvalidPrefix(t *testing.T) { wgNetwork := netip.MustParsePrefix("10.0.0.0/24") mockWG := &mockWGIface{ address: wgaddr.Address{ IP: wgNetwork.Addr(), Network: wgNetwork, }, name: "wt0", } sysOps := &SysOps{ wgInterface: mockWG, notifier: ¬ifier.Notifier{}, } var invalidPrefix netip.Prefix err := sysOps.validateRoute(invalidPrefix) require.Error(t, err, "validateRoute() expected error for invalid prefix") assert.Equal(t, vars.ErrRouteNotAllowed, err, "validateRoute() expected ErrRouteNotAllowed for invalid prefix") }