[PATCH net-next 4/6] selftests: drv-net: add primary RX redirect support to NetDrvContEnv

From: Bobby Eshleman

Date: Mon Mar 16 2026 - 18:39:45 EST


From: Bobby Eshleman <bobbyeshleman@xxxxxxxx>

Add a primary_rx_redirect parameter to NetDrvContEnv that enables a BPF
tc program (nk_primary_rx_redirect.bpf.c) on the primary netkit ingress.
This program redirects traffic to the physical NIC via
bpf_redirect_neigh(), while letting ICMPv6 through for neighbor
discovery.

This will be used by the devmem netkit TX tests where the nk peer is
sending to the nk primary for redirection to the physical NIC.

Signed-off-by: Bobby Eshleman <bobbyeshleman@xxxxxxxx>
---
tools/testing/selftests/drivers/net/hw/.gitignore | 1 +
.../drivers/net/hw/nk_primary_rx_redirect.bpf.c | 41 ++++++++++++++++++++++
tools/testing/selftests/drivers/net/lib/py/env.py | 39 +++++++++++++++++++-
3 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/drivers/net/hw/.gitignore b/tools/testing/selftests/drivers/net/hw/.gitignore
index 46540468a775..3e5c51f8e1c7 100644
--- a/tools/testing/selftests/drivers/net/hw/.gitignore
+++ b/tools/testing/selftests/drivers/net/hw/.gitignore
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
iou-zcrx
ncdevmem
+nk_primary_rx_redirect
toeplitz
diff --git a/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
new file mode 100644
index 000000000000..fe3c127a4fd0
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ipv6.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define TC_ACT_OK 0
+#define ETH_P_IPV6 0x86DD
+#define IPPROTO_ICMPV6 58
+
+#define ctx_ptr(field) ((void *)(long)(field))
+
+volatile __u32 phys_ifindex;
+
+SEC("tc/ingress")
+int nk_primary_rx_redirect(struct __sk_buff *skb)
+{
+ void *data_end = ctx_ptr(skb->data_end);
+ void *data = ctx_ptr(skb->data);
+ struct ethhdr *eth;
+ struct ipv6hdr *ip6h;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (eth->h_proto != bpf_htons(ETH_P_IPV6))
+ return TC_ACT_OK;
+
+ ip6h = data + sizeof(struct ethhdr);
+ if ((void *)(ip6h + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (ip6h->nexthdr == IPPROTO_ICMPV6)
+ return TC_ACT_OK;
+
+ return bpf_redirect_neigh(phys_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 08e90448f48d..e124b9c7c95c 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -327,12 +327,13 @@ class NetDrvContEnv(NetDrvEpEnv):
+---------------+
"""

- def __init__(self, src_path, rxqueues=1, **kwargs):
+ def __init__(self, src_path, rxqueues=1, primary_rx_redirect=False, **kwargs):
self.netns = None
self._nk_host_ifname = None
self._nk_guest_ifname = None
self._tc_clsact_added = False
self._tc_attached = False
+ self._primary_rx_redirect_attached = False
self._bpf_prog_pref = None
self._bpf_prog_id = None
self._init_ns_attached = False
@@ -387,8 +388,14 @@ class NetDrvContEnv(NetDrvEpEnv):

self._setup_ns()
self._attach_bpf()
+ if primary_rx_redirect:
+ self._attach_primary_rx_redirect_bpf()

def __del__(self):
+ if self._primary_rx_redirect_attached:
+ cmd(f"tc qdisc del dev {self._nk_host_ifname} clsact", fail=False)
+ self._primary_rx_redirect_attached = False
+
if self._tc_attached:
cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
self._tc_attached = False
@@ -450,6 +457,8 @@ class NetDrvContEnv(NetDrvEpEnv):
ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)

+ ip(f"-6 route add {self.nk_guest_ipv6}/128 via {self.addr_v['6']}", host=self.remote)
+
def _tc_ensure_clsact(self):
qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
for q in qdisc:
@@ -495,3 +504,31 @@ class NetDrvContEnv(NetDrvEpEnv):
value = ipv6_bytes + ifindex_bytes
value_hex = ' '.join(f'{b:02x}' for b in value)
bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+
+ def _attach_primary_rx_redirect_bpf(self):
+ """Attach BPF redirect program on the primary netkit ingress."""
+ bpf_obj = self.test_dir / "nk_primary_rx_redirect.bpf.o"
+ if not bpf_obj.exists():
+ raise KsftSkipEx("Primary RX redirect BPF prog not found")
+
+ cmd(f"tc qdisc add dev {self._nk_host_ifname} clsact")
+ cmd(f"tc filter add dev {self._nk_host_ifname} ingress"
+ f" bpf obj {bpf_obj} sec tc/ingress direct-action")
+ self._primary_rx_redirect_attached = True
+
+ filters = json.loads(
+ cmd(f"tc -j filter show dev {self._nk_host_ifname} ingress").stdout)
+ redirect_prog_id = None
+ for bpf in filters:
+ if 'options' not in bpf:
+ continue
+ if bpf['options']['bpf_name'].startswith('nk_primary_rx_redirect'):
+ redirect_prog_id = bpf['options']['prog']['id']
+ break
+ if redirect_prog_id is None:
+ raise Exception("Failed to get primary RX redirect BPF prog ID")
+
+ bss_map_id = self._find_bss_map_id(redirect_prog_id)
+ phys_ifindex_bytes = self.ifindex.to_bytes(4, byteorder='little')
+ value_hex = ' '.join(f'{b:02x}' for b in phys_ifindex_bytes)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")

--
2.52.0