Skip to content
This repository was archived by the owner on Dec 9, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion pkg/driver/netnamespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,34 @@ func applyRoutingConfig(containerNsPAth string, ifName string, routeConfig []api
if route.Source != "" {
r.Src = net.ParseIP(route.Source)
}

// List existing routes to check for duplicates.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when is the case a namespace has already a duplicate route?

in addition, the netlink library also handles this, as you can see it has ip route add and ip route replace

existingRoutes, err := nhNs.RouteList(nsLink, netlink.FAMILY_ALL)
if err != nil {
errorList = append(errorList, fmt.Errorf("failed to list routes for interface %s: %w", ifName, err))
continue
}

// Check if the route already exists.
alreadyExists := false
for _, existingRoute := range existingRoutes {
if existingRoute.Dst != nil && r.Dst != nil &&
existingRoute.Dst.String() == r.Dst.String() &&
existingRoute.Gw.Equal(r.Gw) &&
existingRoute.Src.Equal(r.Src) &&
existingRoute.Scope == r.Scope {
alreadyExists = true
break
}
}

if alreadyExists {
continue
}

if err := nhNs.RouteAdd(&r); err != nil && !errors.Is(err, syscall.EEXIST) {
errorList = append(errorList, fmt.Errorf("fail to add route %s for interface %s on namespace %s: %w", r.String(), ifName, containerNsPAth, err))
}

}
return errors.Join(errorList...)
}
Expand Down
144 changes: 141 additions & 3 deletions pkg/driver/netnamespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,147 @@ limitations under the License.
package driver

import (
"fmt"
"runtime"
"testing"

"github.com/google/dranet/internal/nlwrap"
"github.com/google/dranet/pkg/apis"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)

func Test_applyRoutingConfig(t *testing.T) {
// TODO: see hostdevice_test.go and ethtool_test.go
}
func TestApplyRoutingConfig(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("Skipping test on non-linux platform")
}

tests := []struct {
name string
preAddrs []string
routeConfig []apis.RouteConfig
family int
expectedDest string
expectedCount int
expectErr bool
}{
{
name: "IPv6 route already exists",
preAddrs: []string{"fd36::3:0:e:0:0/96"},
routeConfig: []apis.RouteConfig{
{Destination: "fd36::3:0:e:0:0/96"},
},
family: netlink.FAMILY_V6,
expectedDest: "fd36::3:0:e:0:0/96",
expectedCount: 1,
},
{
name: "IPv6 route does not exist",
preAddrs: []string{},
routeConfig: []apis.RouteConfig{
{Destination: "fd36:3:0:f::/64"},
},
family: netlink.FAMILY_V6,
expectedDest: "fd36:3:0:f::/64",
expectedCount: 1,
},
{
name: "IPv4 route already exists",
preAddrs: []string{"192.168.1.1/24"},
routeConfig: []apis.RouteConfig{
{Destination: "192.168.1.0/24"},
},
family: netlink.FAMILY_V4,
expectedDest: "192.168.1.0/24",
expectedCount: 1,
},
{
name: "IPv4 route does not exist",
preAddrs: []string{},
routeConfig: []apis.RouteConfig{
{Destination: "10.0.0.0/8"},
},
family: netlink.FAMILY_V4,
expectedDest: "10.0.0.0/8",
expectedCount: 1,
},
{
name: "Empty route config",
preAddrs: []string{"192.168.1.1/24"},
routeConfig: []apis.RouteConfig{},
family: netlink.FAMILY_V4,
expectedDest: "192.168.1.0/24",
expectedCount: 1,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ns, err := netns.New()
if err != nil {
t.Fatalf("failed to create new netns: %v", err)
}
defer ns.Close()

ifName := "test-eth0"
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifName,
Namespace: netlink.NsFd(ns),
},
}
if err := netlink.LinkAdd(dummy); err != nil {
t.Fatalf("failed to add dummy link: %v", err)
}

nhNs, err := nlwrap.NewHandleAt(ns)
if err != nil {
t.Fatalf("failed to get netlink handle in namespace: %v", err)
}
defer nhNs.Close()

nsLink, err := nhNs.LinkByName(ifName)
if err != nil {
t.Fatalf("failed to find link %q in namespace: %v", ifName, err)
}

if err := nhNs.LinkSetUp(nsLink); err != nil {
t.Fatalf("failed to set link up: %v", err)
}

for _, addrStr := range tc.preAddrs {
addr, err := netlink.ParseAddr(addrStr)
if err != nil {
t.Fatalf("failed to parse address %q: %v", addrStr, err)
}
if err := nhNs.AddrAdd(nsLink, addr); err != nil {
t.Fatalf("failed to add address to link: %v", err)
}
}

err = applyRoutingConfig(fmt.Sprintf("/proc/self/fd/%d", ns), ifName, tc.routeConfig)
if (err != nil) != tc.expectErr {
t.Fatalf("applyRoutingConfig() error = %v, wantErr %v", err, tc.expectErr)
}
if err != nil {
return
}

routes, err := nhNs.RouteList(nsLink, tc.family)
if err != nil {
t.Fatalf("failed to list routes: %v", err)
}

routeCount := 0
for _, r := range routes {
if r.Dst != nil && r.Dst.String() == tc.expectedDest {
routeCount++
}
}

if routeCount != tc.expectedCount {
t.Errorf("found %d routes for dest %s, expected %d", routeCount, tc.expectedDest, tc.expectedCount)
}
})
}
}
Loading