From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-yx1-f54.google.com (mail-yx1-f54.google.com [74.125.224.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7A3F63A4524 for ; Tue, 28 Apr 2026 22:42:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.54 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777416149; cv=none; b=cZ5T3ScsmEdhjFE6TUPtKlfzP0hhc4rtpynNGWlKgqAxxdYYEc6xylPmJ5toC2VSztg1AHTRA5Zp6hPOPae7ZIWqOvFFhzJ6795gCxPs2le26U7i452pS+OJw8kgR1gUax8rVGuXiZ3DvLyg2eBSrBB4DsGnQR2RA3Pra1FY2JE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777416149; c=relaxed/simple; bh=vvD7ZVWeDph/PmXrgLs3xxE6chJh2m9PrzPft/75fXI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=irdvQatibWD1OKqwexBrHyScTyFVU2jbd4OkjrqZLJ1WxlAWHgmvqSCFs4Vs96d6uI9tywRpikiH6jNDzsGNuJPIQtIY0M8UemXCpaqKdIZbbNL7PcFLvbqhjLJ+566/bFRfQ8S7KVTbUB7v+bk6jpHtx+YJwg+6GFpIT3SZVmA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=KvfjK8Dz; arc=none smtp.client-ip=74.125.224.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="KvfjK8Dz" Received: by mail-yx1-f54.google.com with SMTP id 956f58d0204a3-651d692e833so12601669d50.3 for ; Tue, 28 Apr 2026 15:42:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777416145; x=1778020945; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=inxNSTovjyLa8xw5MJR4WDKrVFR+ca5kQUy/85838Ic=; b=KvfjK8DzIfz8LKykKdKroR4ObY9Dy9Ejx+tPX4Y3wTsa5GqusCYy/qtt719Q6GDWBL 1VbZ2xUhQa/yqL/nsjPTDM9leXgfrYnVUOHNKtzOKWtXI449vFzvWREMOzzm3THekhfp GX7guIvPfzTcyUV3BvbgB7NBr+pJf2wftDn9W3jntmu3i31puj6VvGyNls5uzYZdtFib KjkykqHeZ/4oC0zu3AVjHiS0hVToY0t4JCguv4P/aBSKFflqJKnsXxfH7cZ8gQYjJKrP fVnsYn5ViBOw46SaVyl71rHmwZMC+DzVfLdswuvLJomBsTUNHWd47zdKVan2o+YvO7rS ndxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777416145; x=1778020945; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=inxNSTovjyLa8xw5MJR4WDKrVFR+ca5kQUy/85838Ic=; b=hbXnFcFBbOaW4lTiBHnSfH6fJbvP5hqkWh0LGNmn3qcDC8o4iGpekz9wTX771X1aAt xa3slb9r8c6+39nBZDrAUBu6bhNlrLOfBwoISzdAhu8ft39QDwTB3IyBzg8v6b3BuSCJ k8tSZUt+IP9hAZXsosko1gSvf3FYTWsavNou2QRGTBuKSLksY1h9iYaFW05ymPkwcZRF hoiXperCfeV6ESMxnC++xCBGQTjW/IJsXy27efyM6gZJk7oC7qrXCKRR4C62FHOzZtFF PyDwDHDkV0SDAekHUuquRwXHHtk5o3oAMhHZPgbHQuKBDNXyhCh+UUTpGhaHHwobOB4e zHnQ== X-Forwarded-Encrypted: i=1; AFNElJ83QnDmLz0NHAymjWTSenPdk2kYVLvtWka3o2IEgLsIAke4p3ZYC5W+QIhAj752q3rXOJdqRvrCp+Wv@vger.kernel.org X-Gm-Message-State: AOJu0YynxQeIJfBzuV3yN/tyl+yN1NknyHRCutmbCTXWO6j/daLh416u f3xXao6t5TCNel3gTKQMhXhW5xL9YOAgfMyvrzqoohSMG+TljIZ9+wK2 X-Gm-Gg: AeBDietAKGGLpWczrDDYNWPVi9IEztT2YW+FiS+yDflsTd+l5nyrIFtvzhTYtlZRiE5 jpGDjE+XN8zPEL+zpH9wjgEQ4g6WsUgupdU4sW3bcmlr7ID4xP6tUPwel9fydtcuMcP130bP6XC ynM9kKDjpaovY7XwBD4GyJKDr6E0Osvy0+Qnb4rwO/NyqAkqpXByAKwOJ7flY/i+hl/5Yny/qX6 METwFr2DCCcqYHXmtbh59rnCvny3lnbODwJr9Y1hc65y2Bnphd7AVfyp0YKQ9y6vAOPAYRvFrcA Z1cEWntKs2h5o4/jMGf+ZhmKEcirj4HbGAHQo0Ibcvm50qJcXKexzNGl8114YUIuXFVgMlEWN01 A5ZcgRUmqQG11p1MIbF/xIhGI5EODxNAVqLnXeKZL4M78nARQMLAYIFA+Klb6c0uD7gXeurRMcN MdQBV5xW1qjso9yfkCiyIyAcHr6cf4wnHLKW/qAWx0314= X-Received: by 2002:a05:690e:d5c:b0:654:1261:8b50 with SMTP id 956f58d0204a3-65beed80fecmr4987018d50.19.1777416145443; Tue, 28 Apr 2026 15:42:25 -0700 (PDT) Received: from localhost ([2a03:2880:f806:27::]) by smtp.gmail.com with ESMTPSA id 956f58d0204a3-65bff4fe796sm326550d50.7.2026.04.28.15.42.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 28 Apr 2026 15:42:25 -0700 (PDT) From: Bobby Eshleman Date: Tue, 28 Apr 2026 15:42:07 -0700 Subject: [PATCH net-next 10/11] selftests: drv-net: add primary_rx_redirect support to NetDrvContEnv Precedence: bulk X-Mailing-List: linux-rdma@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260428-tcp-dm-netkit-v1-10-719280eba4d2@meta.com> References: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com> In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Jonathan Corbet , Shuah Khan , Alex Shi , Yanteng Si , Dongliang Mu , Michael Chan , Pavan Chebbi , Joshua Washington , Harshitha Ramamurthy , Saeed Mahameed , Tariq Toukan , Mark Bloch , Leon Romanovsky , Alexander Duyck , kernel-team@meta.com, Daniel Borkmann , Nikolay Aleksandrov , Shuah Khan Cc: netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-rdma@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Stanislav Fomichev , Mina Almasry , Bobby Eshleman X-Mailer: b4 0.14.3 From: Bobby Eshleman When sending from a namespace that has access to a netkit device with a leased queue, the nk primary in the host namespace needs to redirect its RX to the physical device. This patch adds that redirection bpf program and teaches the harness to install it. Add primary_rx_redirect=False parameter to NetDrvContEnv.__init__(). When enabled, _attach_primary_rx_redirect_bpf() attaches a new BPF TC program (nk_primary_rx_redirect.bpf.c) to the primary (host-side) netkit interface. The program redirects non-ICMPv6 IPv6 packets to the physical NIC via bpf_redirect_neigh(), with the physical ifindex configured via the .bss map. Extract _find_bss_map_id() from _attach_bpf() into a reusable helper so other BPF attachment methods can use it. Also add an IPv6 host route on the remote endpoint for the netkit guest IP via the physical NIC address, so the remote can send packets that traverse the redirect path to the guest. Assisted-by: Claude Code:claude-sonnet-4-6 Signed-off-by: Bobby Eshleman --- .../drivers/net/hw/nk_primary_rx_redirect.bpf.c | 41 +++++++++++++ tools/testing/selftests/drivers/net/lib/py/env.py | 67 ++++++++++++++++++---- 2 files changed, 96 insertions(+), 12 deletions(-) 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 +#include +#include +#include +#include + +#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 24ce122abd9c..d569d01ef791 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -336,15 +336,17 @@ 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 + self._remote_route_added = False self._old_fwd = None self._old_accept_ra = None @@ -396,8 +398,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 @@ -406,6 +414,11 @@ class NetDrvContEnv(NetDrvEpEnv): cmd(f"tc qdisc del dev {self.ifname} clsact") self._tc_clsact_added = False + if self._remote_route_added: + cmd(f"ip -6 route del {self.nk_guest_ipv6}/128", + host=self.remote, fail=False) + self._remote_route_added = False + if self._nk_host_ifname: cmd(f"ip link del dev {self._nk_host_ifname}") self._nk_host_ifname = None @@ -459,6 +472,9 @@ 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) + self._remote_route_added = True + def _tc_ensure_clsact(self): qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout) for q in qdisc: @@ -476,6 +492,15 @@ class NetDrvContEnv(NetDrvEpEnv): return (bpf['pref'], bpf['options']['prog']['id']) raise Exception("Failed to get BPF prog ID") + def _find_bss_map_id(self, prog_id): + """Find the .bss map ID for a loaded BPF program.""" + prog_info = bpftool(f"prog show id {prog_id}", json=True) + for map_id in prog_info.get("map_ids", []): + map_info = bpftool(f"map show id {map_id}", json=True) + if map_info.get("name", "").endswith("bss"): + return map_id + raise Exception(f"Failed to find .bss map for prog {prog_id}") + def _attach_bpf(self): bpf_obj = self.test_dir / "nk_forward.bpf.o" if not bpf_obj.exists(): @@ -487,17 +512,7 @@ class NetDrvContEnv(NetDrvEpEnv): self._tc_attached = True (self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids() - prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True) - map_ids = prog_info.get("map_ids", []) - - bss_map_id = None - for map_id in map_ids: - map_info = bpftool(f"map show id {map_id}", json=True) - if map_info.get("name").endswith("bss"): - bss_map_id = map_id - - if bss_map_id is None: - raise Exception("Failed to find .bss map") + bss_map_id = self._find_bss_map_id(self._bpf_prog_id) ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix) ipv6_bytes = ipv6_addr.packed @@ -505,3 +520,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