//go:build linux package network import ( "errors" "fmt" "strings" "testing" "github.com/vishvananda/netlink" ) // mockNetworkOps allows us to control the behavior of netlink calls. type mockNetworkOps struct { linkByNameFunc func(name string) (netlink.Link, error) linkSetMTUFunc func(link netlink.Link, mtu int) error linkSetUpFunc func(link netlink.Link) error addrAddFunc func(link netlink.Link, addr *netlink.Addr) error routeAddFunc func(route *netlink.Route) error routeReplaceFunc func(route *netlink.Route) error } func (m *mockNetworkOps) LinkList() ([]netlink.Link, error) { return nil, nil } func (m *mockNetworkOps) LinkByName(name string) (netlink.Link, error) { if m.linkByNameFunc != nil { return m.linkByNameFunc(name) } return nil, fmt.Errorf("not implemented") } func (m *mockNetworkOps) LinkSetMTU(link netlink.Link, mtu int) error { if m.linkSetMTUFunc != nil { return m.linkSetMTUFunc(link, mtu) } return nil } func (m *mockNetworkOps) LinkSetUp(link netlink.Link) error { if m.linkSetUpFunc != nil { return m.linkSetUpFunc(link) } return nil } func (m *mockNetworkOps) AddrAdd(link netlink.Link, addr *netlink.Addr) error { if m.addrAddFunc != nil { return m.addrAddFunc(link, addr) } return nil } func (m *mockNetworkOps) RouteAdd(route *netlink.Route) error { if m.routeAddFunc != nil { return m.routeAddFunc(route) } return nil } func (m *mockNetworkOps) RouteReplace(route *netlink.Route) error { if m.routeReplaceFunc != nil { return m.routeReplaceFunc(route) } return nil } // mockLink implements netlink.Link. type mockLink struct { name string idx int } func (m *mockLink) Type() string { return "mock" } func (m *mockLink) Attrs() *netlink.LinkAttrs { return &netlink.LinkAttrs{Name: m.name, Index: m.idx} } func TestConfigureInterface_Success(t *testing.T) { t.Parallel() mock := &mockNetworkOps{ linkByNameFunc: func(name string) (netlink.Link, error) { return &mockLink{name: name, idx: 1}, nil }, } nm := &NetworkManager{Ops: mock} err := nm.ConfigureInterface("tun0", "10.0.0.1/24", 1420) if err != nil { t.Errorf("expected success, got %v", err) } } func TestConfigureInterface_RouteFallback(t *testing.T) { t.Parallel() routeAddCalled := false routeReplaceCalled := false mock := &mockNetworkOps{ linkByNameFunc: func(name string) (netlink.Link, error) { return &mockLink{name: name, idx: 1}, nil }, routeAddFunc: func(route *netlink.Route) error { routeAddCalled = true return errors.New("file exists") // Simulate EEXIST }, routeReplaceFunc: func(route *netlink.Route) error { routeReplaceCalled = true return nil }, } nm := &NetworkManager{Ops: mock} err := nm.ConfigureInterface("tun0", "10.0.0.1/24", 1420) if err != nil { t.Errorf("expected success after fallback, got %v", err) } if !routeAddCalled { t.Error("expected RouteAdd to be called first") } if !routeReplaceCalled { t.Error("expected RouteReplace to be called after RouteAdd fails with 'file exists'") } } func TestConfigureInterface_RouteFailure(t *testing.T) { t.Parallel() mock := &mockNetworkOps{ linkByNameFunc: func(name string) (netlink.Link, error) { return &mockLink{name: name, idx: 1}, nil }, routeAddFunc: func(route *netlink.Route) error { return errors.New("critical network failure") }, routeReplaceFunc: func(route *netlink.Route) error { return errors.New("critical network failure") }, } nm := &NetworkManager{Ops: mock} err := nm.ConfigureInterface("tun0", "10.0.0.1/24", 1420) if err == nil { t.Error("expected error when both RouteAdd and RouteReplace fail, got nil") } if !strings.Contains(err.Error(), "failed to configure default route") { t.Errorf("expected route error, got: %v", err) } } func TestConfigureInterface_LinkNotFound(t *testing.T) { t.Parallel() mock := &mockNetworkOps{ linkByNameFunc: func(name string) (netlink.Link, error) { return nil, errors.New("no such device") }, } nm := &NetworkManager{Ops: mock} err := nm.ConfigureInterface("nonexistent", "10.0.0.1/24", 1420) if err == nil { t.Error("expected error when link is not found, got nil") } if !strings.Contains(err.Error(), "failed to find link") { t.Errorf("expected link not found error, got: %v", err) } }