Skip to content
This repository was archived by the owner on Dec 9, 2025. It is now read-only.

Commit bed70f4

Browse files
feat: Copy permanent neighbor (ARP/NDP) entries from host
1 parent f39e684 commit bed70f4

File tree

5 files changed

+117
-0
lines changed

5 files changed

+117
-0
lines changed

pkg/apis/types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ type NetworkConfig struct {
2626
// Routes defines static routes to be configured for this interface.
2727
Routes []RouteConfig `json:"routes,omitempty"`
2828

29+
// Neighbors defines neighbor (ARP/NDP) entries to be configured for this interface.
30+
Neighbors []NeighborConfig `json:"neighbors,omitempty"`
31+
2932
// Ethtool defines hardware offload features and other settings managed by `ethtool`.
3033
Ethtool *EthtoolConfig `json:"ethtool,omitempty"`
3134
}
@@ -85,6 +88,19 @@ type RouteConfig struct {
8588
Scope uint8 `json:"scope,omitempty"`
8689
}
8790

91+
// NeighborConfig represents a neighbor (ARP/NDP) entry.
92+
type NeighborConfig struct {
93+
// Destination is the target IP address.
94+
Destination string `json:"destination,omitempty"`
95+
// HardwareAddr is the MAC address of the neighbor.
96+
HardwareAddr string `json:"hardwareAddr,omitempty"`
97+
// State is the state of the neighbor entry (e.g., permanent, reachable).
98+
// Refers to Linux neighbor states (e.g., 0x02 for NUD_REACHABLE, 0x80 for NUD_PERMANENT).
99+
State int `json:"state,omitempty"`
100+
// Family is the address family of the neighbor entry (e.g., AF_INET, AF_INET6).
101+
Family int `json:"family,omitempty"`
102+
}
103+
88104
// EthtoolConfig defines ethtool-based optimizations for a network interface.
89105
// These settings correspond to features typically toggled using `ethtool -K <dev> <feature> on|off`.
90106
type EthtoolConfig struct {

pkg/driver/dra_hooks.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,27 @@ func (np *NetworkDriver) prepareResourceClaim(ctx context.Context, claim *resour
315315
podCfg.NetworkInterfaceConfigInPod.Routes = append(podCfg.NetworkInterfaceConfigInPod.Routes, routeCfg)
316316
}
317317

318+
// Obtain the neighbors associated to the interface
319+
neighs, err := nlHandle.NeighList(link.Attrs().Index, netlink.FAMILY_ALL)
320+
if err != nil {
321+
klog.Infof("failed to get neighbors for interface %s: %v", ifName, err)
322+
}
323+
for _, neigh := range neighs {
324+
if neigh.HardwareAddr == nil {
325+
continue
326+
}
327+
if neigh.State != netlink.NUD_PERMANENT {
328+
continue
329+
}
330+
neighCfg := apis.NeighborConfig{
331+
Destination: neigh.IP.String(),
332+
HardwareAddr: neigh.HardwareAddr.String(),
333+
State: neigh.State,
334+
Family: neigh.Family,
335+
}
336+
podCfg.NetworkInterfaceConfigInPod.Neighbors = append(podCfg.NetworkInterfaceConfigInPod.Neighbors, neighCfg)
337+
}
338+
318339
// Get RDMA configuration: link and char devices
319340
if rdmaDev, _ := rdmamap.GetRdmaDeviceForNetdevice(ifName); rdmaDev != "" {
320341
klog.V(2).Infof("RunPodSandbox processing RDMA device: %s", rdmaDev)

pkg/driver/netnamespace.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,47 @@ func applyRoutingConfig(containerNsPAth string, ifName string, routeConfig []api
8787
}
8888
return errors.Join(errorList...)
8989
}
90+
91+
func applyNeighborConfig(containerNsPAth string, ifName string, neighConfig []apis.NeighborConfig) error {
92+
containerNs, err := netns.GetFromPath(containerNsPAth)
93+
if err != nil {
94+
return fmt.Errorf("could not get network namespace from path %s: %w", containerNsPAth, err)
95+
}
96+
defer containerNs.Close()
97+
98+
nhNs, err := netlink.NewHandleAt(containerNs)
99+
if err != nil {
100+
return fmt.Errorf("can not get netlink handle: %v", err)
101+
}
102+
defer nhNs.Close()
103+
104+
nsLink, err := nhNs.LinkByName(ifName)
105+
if err != nil && !errors.Is(err, netlink.ErrDumpInterrupted) {
106+
return fmt.Errorf("link not found for interface %s on namespace %s: %w", ifName, containerNsPAth, err)
107+
}
108+
109+
var errorList []error
110+
for _, neigh := range neighConfig {
111+
ip := net.ParseIP(neigh.Destination)
112+
if ip == nil {
113+
errorList = append(errorList, fmt.Errorf("invalid ip address: %s", neigh.Destination))
114+
continue
115+
}
116+
mac, err := net.ParseMAC(neigh.HardwareAddr)
117+
if err != nil {
118+
errorList = append(errorList, fmt.Errorf("invalid mac address: %s", neigh.HardwareAddr))
119+
continue
120+
}
121+
n := netlink.Neigh{
122+
LinkIndex: nsLink.Attrs().Index,
123+
State: neigh.State,
124+
Family: neigh.Family,
125+
IP: ip,
126+
HardwareAddr: mac,
127+
}
128+
if err := nhNs.NeighAdd(&n); err != nil && !errors.Is(err, syscall.EEXIST) {
129+
errorList = append(errorList, fmt.Errorf("failed to add permanent neighbor entry %s (%s) for interface %s: %w", neigh.Destination, neigh.HardwareAddr, ifName, err))
130+
}
131+
}
132+
return errors.Join(errorList...)
133+
}

pkg/driver/nri_hooks.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ func (np *NetworkDriver) runPodSandbox(ctx context.Context, pod *api.PodSandbox)
202202
klog.Infof("RunPodSandbox error configuring device %s namespace %s routing: %v", deviceName, ns, err)
203203
return fmt.Errorf("error configuring device %s routes on namespace %s: %v", deviceName, ns, err)
204204
}
205+
206+
// Configure neighbors
207+
err = applyNeighborConfig(ns, ifNameInNs, config.NetworkInterfaceConfigInPod.Neighbors)
208+
if err != nil {
209+
klog.Infof("RunPodSandbox for pod %s/%s (UID %s) failed to apply neighbor configuration for interface %s in namespace %s: %v", pod.Namespace, pod.Name, pod.Uid, ifNameInNs, ns, err)
210+
return fmt.Errorf("failed to apply neighbor configuration for interface %s in namespace %s: %w", ifNameInNs, ns, err)
211+
}
212+
205213
resourceClaimStatusDevice.WithConditions(
206214
metav1apply.Condition().
207215
WithType("NetworkReady").

tests/e2e.bats

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,31 @@ EOF
402402
)
403403
kubectl rollout status ds/dranet --namespace=kube-system
404404
}
405+
406+
@test "permanent neighbor entry is copied to pod namespace" {
407+
local NODE_NAME="$CLUSTER_NAME"-worker
408+
local DUMMY_IFACE="dummy-neigh"
409+
local NEIGH_IP="192.168.1.1"
410+
local NEIGH_MAC="00:11:22:33:44:55"
411+
412+
# Create a dummy interface on the worker node
413+
docker exec "$NODE_NAME" bash -c "ip link add $DUMMY_IFACE type dummy"
414+
docker exec "$NODE_NAME" bash -c "ip link set up dev $DUMMY_IFACE"
415+
docker exec "$NODE_NAME" bash -c "ip addr add 169.254.169.15/32 dev $DUMMY_IFACE"
416+
417+
# Add a permanent neighbor entry on the worker node
418+
docker exec "$NODE_NAME" bash -c "ip neigh add $NEIGH_IP lladdr $NEIGH_MAC dev $DUMMY_IFACE nud permanent"
419+
420+
kubectl apply -f "$BATS_TEST_DIRNAME"/../tests/manifests/deviceclass.yaml
421+
kubectl apply -f "$BATS_TEST_DIRNAME"/../tests/manifests/resourceclaim.yaml
422+
kubectl wait --timeout=30s --for=condition=ready pods -l app=pod
423+
424+
# Get the pod name
425+
POD_NAME=$(kubectl get pods -l app=pod -o name)
426+
427+
# Verify the neighbor entry inside the pod's network namespace
428+
run kubectl exec "$POD_NAME" -- ip neigh show
429+
assert_success
430+
assert_output --partial "$NEIGH_IP dev eth99 lladdr $NEIGH_MAC PERM"
431+
}
432+

0 commit comments

Comments
 (0)