From: Mahe Tardy <mahe.tardy@gmail.com>
To: bpf@vger.kernel.org
Cc: andrew+netdev@lunn.ch, andrii@kernel.org, ast@kernel.org,
daniel@iogearbox.net, davem@davemloft.net, eddyz87@gmail.com,
edumazet@google.com, john.fastabend@gmail.com, kuba@kernel.org,
martin.lau@linux.dev, pabeni@redhat.com, song@kernel.org,
liamwisehart@meta.com, Mahe Tardy <mahe.tardy@gmail.com>
Subject: [PATCH v1 2/4] selftests/bpf: Add netpoll kfunc sanity test
Date: Mon, 11 May 2026 08:53:42 +0000 [thread overview]
Message-ID: <20260511085344.3302-3-mahe.tardy@gmail.com> (raw)
In-Reply-To: <20260511085344.3302-1-mahe.tardy@gmail.com>
From: Song Liu <song@kernel.org>
Add a selftest that exercises the bpf_netpoll kfuncs end-to-end:
1. A SYSCALL BPF program creates a netpoll context on a dummy
network device and stores it in a kptr map.
2. A fentry program is attached to dummy_xmit, the driver function
that will receive the netpoll sent packet.
3. An LSM program attached to security_file_open retrieves the
context from the map and calls bpf_netpoll_send_udp().
The userspace harness creates a network namespace with a dummy device,
runs the setup program, attach the fentry hook then triggers the LSM
hook by opening /dev/null. The dummy device accepts all packets, so
send_status == 0 confirms success and the packet content is verified by
reading the skb from the dummy driver xmit function.
The code was partially generated by AI and each line was manually
reviewed.
Signed-off-by: Song Liu <song@kernel.org>
Co-developed-by: Mahe Tardy <mahe.tardy@gmail.com>
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
tools/testing/selftests/bpf/config | 1 +
.../selftests/bpf/prog_tests/netpoll.c | 92 ++++++++++++++
.../selftests/bpf/progs/netpoll_common.h | 64 ++++++++++
.../selftests/bpf/progs/netpoll_sanity.c | 118 ++++++++++++++++++
4 files changed, 275 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/netpoll.c
create mode 100644 tools/testing/selftests/bpf/progs/netpoll_common.h
create mode 100644 tools/testing/selftests/bpf/progs/netpoll_sanity.c
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index 24855381290d..bd6d0a4e5239 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -7,6 +7,7 @@ CONFIG_BPF_JIT=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_BPF_LIRC_MODE2=y
CONFIG_BPF_LSM=y
+CONFIG_BPF_NETPOLL=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_BPF_SYSCALL=y
# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set
diff --git a/tools/testing/selftests/bpf/prog_tests/netpoll.c b/tools/testing/selftests/bpf/prog_tests/netpoll.c
new file mode 100644
index 000000000000..1cfac4b13e7d
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/netpoll.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "netpoll_sanity.skel.h"
+
+#define NS_TEST "netpoll_sanity_ns"
+#define DUMMY_DEV "dummy0"
+#define DUMMY_IP "10.0.0.1"
+#define REMOTE_IP "10.0.0.2"
+
+void test_netpoll_sanity(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct nstoken *nstoken = NULL;
+ struct netpoll_sanity *skel;
+ int err, pfd, fd;
+
+ skel = netpoll_sanity__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel open_and_load"))
+ return;
+
+ /* Create a network namespace with a dummy device */
+ SYS(fail, "ip netns add %s", NS_TEST);
+ SYS(fail, "ip -net %s link add %s type dummy", NS_TEST, DUMMY_DEV);
+ SYS(fail, "ip -net %s addr add %s/24 dev %s", NS_TEST, DUMMY_IP, DUMMY_DEV);
+ SYS(fail, "ip -net %s link set %s up", NS_TEST, DUMMY_DEV);
+
+ nstoken = open_netns(NS_TEST);
+ if (!ASSERT_OK_PTR(nstoken, "open_netns"))
+ goto fail;
+
+ /* Configure the BPF program globals */
+ snprintf(skel->bss->dev_name, sizeof(skel->bss->dev_name), "%s", DUMMY_DEV);
+ skel->bss->remote_ip = inet_addr(REMOTE_IP);
+ skel->bss->local_port = 5555;
+ skel->bss->remote_port = 6666;
+ skel->bss->remote_mac[0] = 0xaa;
+ skel->bss->remote_mac[1] = 0xbb;
+ skel->bss->remote_mac[2] = 0xcc;
+ skel->bss->remote_mac[3] = 0xdd;
+ skel->bss->remote_mac[4] = 0xee;
+ skel->bss->remote_mac[5] = 0xff;
+
+ /* Step 1: Run the setup SYSCALL prog */
+ pfd = bpf_program__fd(skel->progs.netpoll_setup_test);
+ if (!ASSERT_GT(pfd, 0, "netpoll_setup_test fd"))
+ goto fail;
+
+ err = bpf_prog_test_run_opts(pfd, &opts);
+ if (!ASSERT_OK(err, "netpoll_setup_test run"))
+ goto fail;
+
+ if (!ASSERT_OK(skel->bss->status, "netpoll_setup_test status"))
+ goto fail;
+
+ /* Step 2: Attach the dummy xmit hook */
+ skel->links.netpoll_dummy_xmit = bpf_program__attach(skel->progs.netpoll_dummy_xmit);
+ if (!ASSERT_OK_PTR(skel->links.netpoll_dummy_xmit, "attach netpoll_dummy_xmit"))
+ goto fail;
+
+ /* Step 3: Attach the LSM prog and trigger via file_open */
+ skel->links.netpoll_send_test = bpf_program__attach(skel->progs.netpoll_send_test);
+ if (!ASSERT_OK_PTR(skel->links.netpoll_send_test, "attach netpoll_send_test"))
+ goto fail;
+
+ skel->bss->trigger_send = 1;
+
+ fd = open("/dev/null", O_RDONLY);
+ if (!ASSERT_GE(fd, 0, "open /dev/null"))
+ goto fail;
+ close(fd);
+
+ /* send_status should be 0 (NETDEV_TX_OK) -- dummy device accepts
+ * all packets.
+ */
+ ASSERT_OK(skel->bss->send_status, "netpoll_send_udp status");
+ /* dummy_xmit hooks the dummy driver ndo_start_xmit method called by
+ * netpoll and fetches the UDP payload.
+ */
+ ASSERT_STREQ(skel->bss->driver_xmit, skel->data->send_data, "dummy_xmit received");
+
+fail:
+ if (nstoken)
+ close_netns(nstoken);
+ SYS_NOFAIL("ip netns del " NS_TEST " &> /dev/null");
+ netpoll_sanity__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/netpoll_common.h b/tools/testing/selftests/bpf/progs/netpoll_common.h
new file mode 100644
index 000000000000..d1e4b2a58adb
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/netpoll_common.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#ifndef _NETPOLL_COMMON_H
+#define _NETPOLL_COMMON_H
+
+#include "errno.h"
+#include <stdbool.h>
+
+struct bpf_netpoll *bpf_netpoll_create(const struct bpf_netpoll_opts *opts,
+ u32 opts__sz, int *err) __ksym;
+struct bpf_netpoll *bpf_netpoll_acquire(struct bpf_netpoll *bnp) __ksym;
+void bpf_netpoll_release(struct bpf_netpoll *bnp) __ksym;
+int bpf_netpoll_send_udp(struct bpf_netpoll *bnp,
+ const void *data, u32 data__sz) __ksym;
+
+struct __netpoll_ctx_value {
+ struct bpf_netpoll __kptr * ctx;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct __netpoll_ctx_value);
+ __uint(max_entries, 1);
+} __netpoll_ctx_map SEC(".maps");
+
+static inline struct __netpoll_ctx_value *netpoll_ctx_value_lookup(void)
+{
+ u32 key = 0;
+
+ return bpf_map_lookup_elem(&__netpoll_ctx_map, &key);
+}
+
+static inline int netpoll_ctx_insert(struct bpf_netpoll *ctx)
+{
+ struct __netpoll_ctx_value local, *v;
+ struct bpf_netpoll *old;
+ u32 key = 0;
+ int err;
+
+ local.ctx = NULL;
+ err = bpf_map_update_elem(&__netpoll_ctx_map, &key, &local, 0);
+ if (err) {
+ bpf_netpoll_release(ctx);
+ return err;
+ }
+
+ v = bpf_map_lookup_elem(&__netpoll_ctx_map, &key);
+ if (!v) {
+ bpf_netpoll_release(ctx);
+ return -ENOENT;
+ }
+
+ old = bpf_kptr_xchg(&v->ctx, ctx);
+ if (old) {
+ bpf_netpoll_release(old);
+ return -EEXIST;
+ }
+
+ return 0;
+}
+
+#endif /* _NETPOLL_COMMON_H */
diff --git a/tools/testing/selftests/bpf/progs/netpoll_sanity.c b/tools/testing/selftests/bpf/progs/netpoll_sanity.c
new file mode 100644
index 000000000000..9e1e595eff2c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/netpoll_sanity.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+/* Copyright (c) 2026 Isovalent. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
+#include "netpoll_common.h"
+
+/* Globals for passing config from userspace */
+char dev_name[16] = {};
+__be32 remote_ip;
+__u16 local_port;
+__u16 remote_port;
+__u8 remote_mac[6] = {};
+
+/* Results */
+int status;
+int send_status;
+int trigger_send;
+char driver_xmit[64];
+
+char send_data[64] = "hello from bpf netpoll";
+
+/* SYSCALL prog: set up the netpoll context */
+SEC("syscall")
+int netpoll_setup_test(void *ctx)
+{
+ struct bpf_netpoll_opts opts = {};
+ struct bpf_netpoll *bnp;
+ int err = 0;
+
+ status = 0;
+
+ __builtin_memcpy(opts.dev_name, dev_name, 16);
+ opts.remote_ip = remote_ip;
+ opts.local_port = local_port;
+ opts.remote_port = remote_port;
+ __builtin_memcpy(opts.remote_mac, remote_mac, 6);
+
+ bnp = bpf_netpoll_create(&opts, sizeof(opts), &err);
+ if (!bnp) {
+ status = err;
+ return 0;
+ }
+
+ err = netpoll_ctx_insert(bnp);
+ if (err && err != -EEXIST)
+ status = err;
+ return 0;
+}
+
+/* LSM prog: send UDP via the stored netpoll context */
+SEC("lsm/file_open")
+int BPF_PROG(netpoll_send_test, struct file *file)
+{
+ struct __netpoll_ctx_value *v;
+ struct bpf_netpoll *bnp;
+
+ if (!trigger_send)
+ return 0;
+
+ trigger_send = 0;
+ send_status = -ENOENT;
+
+ v = netpoll_ctx_value_lookup();
+ if (!v)
+ return 0;
+
+ bpf_rcu_read_lock();
+ bnp = v->ctx;
+ if (!bnp) {
+ bpf_rcu_read_unlock();
+ return 0;
+ }
+
+ send_status = bpf_netpoll_send_udp(bnp, send_data, sizeof(send_data));
+ bpf_rcu_read_unlock();
+ return 0;
+}
+
+/* Fentry prog: hook the dummy driver xmit */
+SEC("fentry/dummy_xmit")
+int BPF_PROG(netpoll_dummy_xmit, struct sk_buff *skb, struct net_device *dev)
+{
+ unsigned char *data;
+ struct ethhdr eth;
+ struct iphdr ip;
+ struct udphdr udp;
+
+ if (bpf_probe_read_kernel(&data, sizeof(data), &skb->data) < 0)
+ return 0;
+ if (!data)
+ return 0;
+
+ if (bpf_probe_read_kernel(ð, sizeof(eth), data) < 0)
+ return 0;
+ if (eth.h_proto != bpf_htons(ETH_P_IP))
+ return 0;
+
+ if (bpf_probe_read_kernel(&ip, sizeof(ip), data + sizeof(struct ethhdr)) < 0)
+ return 0;
+ if (ip.protocol != IPPROTO_UDP)
+ return 0;
+
+ if (bpf_probe_read_kernel(&udp, sizeof(udp), data + sizeof(struct ethhdr) + (ip.ihl * 4)) < 0)
+ return 0;
+ if (udp.dest != bpf_htons(remote_port))
+ return 0;
+ if (bpf_probe_read_kernel(&driver_xmit, sizeof(driver_xmit), data + sizeof(struct ethhdr) + (ip.ihl * 4) + sizeof(struct udphdr)) < 0)
+ return 0;
+
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1
next prev parent reply other threads:[~2026-05-11 8:54 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-11 8:53 [PATCH v1 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
2026-05-11 8:53 ` [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets Mahe Tardy
2026-05-11 9:40 ` bot+bpf-ci
2026-05-11 9:51 ` Mahe Tardy
2026-05-11 12:05 ` Daniel Borkmann
2026-05-12 8:51 ` Mahe Tardy
2026-05-12 1:20 ` Jakub Kicinski
2026-05-12 1:59 ` Alexei Starovoitov
2026-05-12 2:36 ` Jakub Kicinski
2026-05-12 2:59 ` Alexei Starovoitov
2026-05-11 8:53 ` Mahe Tardy [this message]
2026-05-11 8:53 ` [PATCH v1 3/4] selftests/bpf: Add netpoll kfunc IPv6 variant test Mahe Tardy
2026-05-11 8:53 ` [PATCH v1 4/4] selftests/bpf: Add netpoll setup basic tests Mahe Tardy
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260511085344.3302-3-mahe.tardy@gmail.com \
--to=mahe.tardy@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=eddyz87@gmail.com \
--cc=edumazet@google.com \
--cc=john.fastabend@gmail.com \
--cc=kuba@kernel.org \
--cc=liamwisehart@meta.com \
--cc=martin.lau@linux.dev \
--cc=pabeni@redhat.com \
--cc=song@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox