mirror of
https://github.com/netbirdio/netbird.git
synced 2025-07-21 08:22:20 +02:00
405 lines
12 KiB
Go
405 lines
12 KiB
Go
package networkmonitor
|
|
|
|
import (
|
|
"net"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
|
)
|
|
|
|
func TestRouteChanged(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
route systemops.RouteUpdate
|
|
nexthopv4 systemops.Nexthop
|
|
nexthopv6 systemops.Nexthop
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "soft interface should be ignored",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Name: "ISATAP-Interface", // isSoftInterface checks name
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.2"),
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "modified route with different v4 nexthop IP should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.2"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "modified route with same v4 nexthop (IP and Intf Index) should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "added route with different v6 nexthop IP should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteAdded,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::2"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "added route with same v6 nexthop (IP and Intf Index) should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteAdded,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "deleted route matching tracked v4 nexthop (IP and Intf Index) should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteDeleted,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "deleted route not matching tracked v4 nexthop (different IP) should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteDeleted,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.3"), // Different IP
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{
|
|
Index: 1, Name: "eth0",
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "modified v4 route with same IP, different Intf Index should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "modified v4 route with same IP, one Intf nil, other non-nil should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: nil, // Intf is nil
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"}, // Tracked Intf is not nil
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "added v4 route with same IP, different Intf Index should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteAdded,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "deleted v4 route with same IP, different Intf Index should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteDeleted,
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{ // This is the route being deleted
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{ // This is our tracked nexthop
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
|
|
},
|
|
expected: false, // Because nexthopv4.Equal(route.NextHop) will be false
|
|
},
|
|
{
|
|
name: "modified v6 route with different IP, same Intf Index should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::3"), // Different IP
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "modified v6 route with same IP, different Intf Index should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "modified v6 route with same IP, same Intf Index should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteModified,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "deleted v6 route matching tracked nexthop (IP and Intf Index) should return true",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteDeleted,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "deleted v6 route not matching tracked nexthop (different IP) should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteDeleted,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::3"), // Different IP
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "deleted v6 route not matching tracked nexthop (same IP, different Intf Index) should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteDeleted,
|
|
Destination: netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{ // This is the route being deleted
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv6: systemops.Nexthop{ // This is our tracked nexthop
|
|
IP: netip.MustParseAddr("2001:db8::1"),
|
|
Intf: &net.Interface{Index: 2, Name: "eth1"}, // Different Intf Index
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "unknown route type should return false",
|
|
route: systemops.RouteUpdate{
|
|
Type: systemops.RouteUpdateType(99), // Unknown type
|
|
Destination: netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
|
NextHop: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.1"),
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
},
|
|
nexthopv4: systemops.Nexthop{
|
|
IP: netip.MustParseAddr("192.168.1.2"), // Different from route.NextHop
|
|
Intf: &net.Interface{Index: 1, Name: "eth0"},
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := routeChanged(tt.route, tt.nexthopv4, tt.nexthopv6)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsSoftInterface(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ifname string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "ISATAP interface should be detected",
|
|
ifname: "ISATAP tunnel adapter",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "lowercase soft interface should be detected",
|
|
ifname: "isatap.{14A5CF17-CA72-43EC-B4EA-B4B093641B7D}",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "Teredo interface should be detected",
|
|
ifname: "Teredo Tunneling Pseudo-Interface",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "regular interface should not be detected as soft",
|
|
ifname: "eth0",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "another regular interface should not be detected as soft",
|
|
ifname: "wlan0",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isSoftInterface(tt.ifname)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|