From: Song Liu Add BPF kfuncs that allow BPF programs to send UDP packets via the netpoll infrastructure. This provides a mechanism for BPF programs (e.g., LSM hooks) to emit telemetry over UDP without depending on the regular networking stack. The API consists of four kfuncs: bpf_netpoll_create() - Allocate and set up a netpoll context (sleepable, SYSCALL prog type only) bpf_netpoll_acquire() - Acquire a reference to a netpoll context bpf_netpoll_release() - Release a reference (cleanup via queue_rcu_work since netpoll_cleanup sleeps) bpf_netpoll_send_udp() - Send a UDP packet (any context, LSM prog type only for now) The implementation follows the established kfunc lifecycle pattern (create/acquire/release with refcounting, kptr map storage, dtor registration). The netpoll context is wrapped in a refcounted bpf_netpoll struct. Cleanup is deferred via queue_rcu_work() because netpoll_cleanup() takes rtnl_lock. AI was used to generate the code and each line was manually reviewed. Reviewed-by: Mahe Tardy Signed-off-by: Song Liu --- drivers/net/Kconfig | 11 ++ include/linux/bpf_netpoll.h | 38 +++++++ kernel/bpf/verifier.c | 3 + net/core/Makefile | 1 + net/core/bpf_netpoll.c | 209 ++++++++++++++++++++++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 include/linux/bpf_netpoll.h create mode 100644 net/core/bpf_netpoll.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index edaab759dc50..8d94c9d58f32 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -364,6 +364,17 @@ config NETCONSOLE_PREPEND_RELEASE message. See for details. +config BPF_NETPOLL + bool "BPF netpoll UDP support" + depends on BPF_SYSCALL && NET + select NETPOLL + help + Enable BPF kfuncs for sending UDP packets via netpoll. + This allows BPF programs to send UDP packets from any + context using the netpoll infrastructure. + + If unsure, say N. + config NETPOLL def_bool NETCONSOLE diff --git a/include/linux/bpf_netpoll.h b/include/linux/bpf_netpoll.h new file mode 100644 index 000000000000..b738032548c7 --- /dev/null +++ b/include/linux/bpf_netpoll.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#ifndef _BPF_NETPOLL_H +#define _BPF_NETPOLL_H + +#include + +#define BPF_NETPOLL_DEV_NAME_LEN 16 /* IFNAMSIZ */ + +/** + * struct bpf_netpoll_opts - BPF netpoll initialization parameters + * @dev_name: Network device name (e.g. "eth0"), null-terminated. + * @local_ip: Local IPv4 address in network byte order. 0 = auto-detect. + * @remote_ip: Remote IPv4 address in network byte order. + * @local_port: Local UDP port in host byte order. + * @remote_port: Remote UDP port in host byte order. + * @remote_mac: Remote MAC address (6 bytes). + * @ipv6: Set to 1 for IPv6, 0 for IPv4. + * @reserved: Must be zero. Reserved for future use. + * @local_ip6: Local IPv6 address (16 bytes). Used when ipv6=1. + * Zero = auto-detect. + * @remote_ip6: Remote IPv6 address (16 bytes). Used when ipv6=1. + */ +struct bpf_netpoll_opts { + char dev_name[BPF_NETPOLL_DEV_NAME_LEN]; + __be32 local_ip; + __be32 remote_ip; + __u16 local_port; + __u16 remote_port; + __u8 remote_mac[6]; + __u8 ipv6; + __u8 reserved; + __u8 local_ip6[16]; + __u8 remote_ip6[16]; +}; + +#endif /* _BPF_NETPOLL_H */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 185210b73385..2dc8d2fb4a01 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4641,6 +4641,9 @@ BTF_ID(struct, task_struct) #ifdef CONFIG_CRYPTO BTF_ID(struct, bpf_crypto_ctx) #endif +#ifdef CONFIG_BPF_NETPOLL +BTF_ID(struct, bpf_netpoll) +#endif BTF_SET_END(rcu_protected_types) static bool rcu_protected_object(const struct btf *btf, u32 btf_id) diff --git a/net/core/Makefile b/net/core/Makefile index dc17c5a61e9a..6484eb595698 100644 --- a/net/core/Makefile +++ b/net/core/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_PAGE_POOL) += page_pool.o page_pool_user.o obj-$(CONFIG_PROC_FS) += net-procfs.o obj-$(CONFIG_NET_PKTGEN) += pktgen.o obj-$(CONFIG_NETPOLL) += netpoll.o +obj-$(CONFIG_BPF_NETPOLL) += bpf_netpoll.o obj-$(CONFIG_FIB_RULES) += fib_rules.o obj-$(CONFIG_TRACEPOINTS) += net-traces.o obj-$(CONFIG_NET_DROP_MONITOR) += drop_monitor.o diff --git a/net/core/bpf_netpoll.c b/net/core/bpf_netpoll.c new file mode 100644 index 000000000000..05f4dcbd95f3 --- /dev/null +++ b/net/core/bpf_netpoll.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BPF_NETPOLL_MAX_UDP_CHUNK 1460 + +/** + * struct bpf_netpoll - refcounted BPF netpoll context + * @np: The underlying netpoll structure. + * @usage: Reference counter. + * @rwork: RCU work for deferred cleanup (netpoll_cleanup sleeps). + */ +struct bpf_netpoll { + struct netpoll np; + refcount_t usage; + struct rcu_work rwork; +}; + +static void netpoll_release_work_fn(struct work_struct *work) +{ + struct bpf_netpoll *bnp = container_of(to_rcu_work(work), + struct bpf_netpoll, rwork); + + netpoll_cleanup(&bnp->np); + kfree(bnp); +} + +__bpf_kfunc_start_defs(); + +/** + * bpf_netpoll_create() - Create a BPF netpoll context for sending UDP. + * + * Allocates and sets up a netpoll context that can be used to send UDP + * packets from BPF programs. The returned context must either be stored + * in a map as a kptr, or freed with bpf_netpoll_release(). + * + * This function calls netpoll_setup() which takes rtnl_lock and may + * sleep, so it can only be used in sleepable BPF programs (SYSCALL). + * + * @opts: Pointer to struct bpf_netpoll_opts with connection parameters. + * @opts__sz: Size of the opts struct. + * @err: Integer to store error code when NULL is returned. + */ +__bpf_kfunc struct bpf_netpoll * +bpf_netpoll_create(const struct bpf_netpoll_opts *opts, u32 opts__sz, int *err) +{ + struct bpf_netpoll *bnp; + + if (!opts || opts__sz != sizeof(struct bpf_netpoll_opts)) { + *err = -EINVAL; + return NULL; + } + + if (opts->reserved) { + *err = -EINVAL; + return NULL; + } + + bnp = kzalloc_obj(*bnp); + if (!bnp) { + *err = -ENOMEM; + return NULL; + } + + bnp->np.name = "bpf_netpoll"; + strscpy(bnp->np.dev_name, opts->dev_name, IFNAMSIZ); + bnp->np.local_port = opts->local_port; + bnp->np.remote_port = opts->remote_port; + memcpy(bnp->np.remote_mac, opts->remote_mac, ETH_ALEN); + + bnp->np.ipv6 = !!opts->ipv6; + if (opts->ipv6) { + memcpy(&bnp->np.local_ip.in6, opts->local_ip6, + sizeof(struct in6_addr)); + memcpy(&bnp->np.remote_ip.in6, opts->remote_ip6, + sizeof(struct in6_addr)); + } else { + bnp->np.local_ip.ip = opts->local_ip; + bnp->np.remote_ip.ip = opts->remote_ip; + } + + *err = netpoll_setup(&bnp->np); + if (*err) { + kfree(bnp); + return NULL; + } + + refcount_set(&bnp->usage, 1); + return bnp; +} + +/** + * bpf_netpoll_acquire() - Acquire a reference to a BPF netpoll context. + * @bnp: The BPF netpoll context to acquire. Must be a trusted pointer. + * + * The acquired context must either be stored in a map as a kptr, or + * freed with bpf_netpoll_release(). + */ +__bpf_kfunc struct bpf_netpoll * +bpf_netpoll_acquire(struct bpf_netpoll *bnp) +{ + if (!refcount_inc_not_zero(&bnp->usage)) + return NULL; + return bnp; +} + +/** + * bpf_netpoll_release() - Release a BPF netpoll context. + * @bnp: The BPF netpoll context to release. + * + * When the final reference is released, the netpoll context is cleaned + * up via queue_rcu_work() (since netpoll_cleanup takes rtnl_lock and + * must run in process context). + */ +__bpf_kfunc void bpf_netpoll_release(struct bpf_netpoll *bnp) +{ + if (refcount_dec_and_test(&bnp->usage)) { + INIT_RCU_WORK(&bnp->rwork, netpoll_release_work_fn); + queue_rcu_work(system_wq, &bnp->rwork); + } +} + +__bpf_kfunc void bpf_netpoll_release_dtor(void *bnp) +{ + bpf_netpoll_release(bnp); +} +CFI_NOSEAL(bpf_netpoll_release_dtor); + +/** + * bpf_netpoll_send_udp() - Send a UDP packet via netpoll. + * @bnp: The BPF netpoll context. Must be an RCU-protected pointer. + * @data: Pointer to the data to send. + * @data__sz: Size of the data to send (max 1460 bytes). + * + * Sends a UDP packet using the netpoll infrastructure. Can be called + * from any context (process, softirq, hardirq). + * + * Return: 0 on success, 1 (NET_XMIT_DROP) on drop, negative errno on error. + */ +__bpf_kfunc int bpf_netpoll_send_udp(struct bpf_netpoll *bnp, + const void *data, u32 data__sz) +{ + unsigned long flags; + int ret; + + if (data__sz > BPF_NETPOLL_MAX_UDP_CHUNK) + return -E2BIG; + + local_irq_save(flags); + ret = netpoll_send_udp(&bnp->np, data, data__sz); + local_irq_restore(flags); + + return ret; +} + +__bpf_kfunc_end_defs(); + +BTF_KFUNCS_START(netpoll_init_kfunc_btf_ids) +BTF_ID_FLAGS(func, bpf_netpoll_create, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_netpoll_release, KF_RELEASE) +BTF_ID_FLAGS(func, bpf_netpoll_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL) +BTF_KFUNCS_END(netpoll_init_kfunc_btf_ids) + +static const struct btf_kfunc_id_set netpoll_init_kfunc_set = { + .owner = THIS_MODULE, + .set = &netpoll_init_kfunc_btf_ids, +}; + +BTF_KFUNCS_START(netpoll_kfunc_btf_ids) +BTF_ID_FLAGS(func, bpf_netpoll_send_udp, KF_RCU) +BTF_KFUNCS_END(netpoll_kfunc_btf_ids) + +static const struct btf_kfunc_id_set netpoll_kfunc_set = { + .owner = THIS_MODULE, + .set = &netpoll_kfunc_btf_ids, +}; + +BTF_ID_LIST(bpf_netpoll_dtor_ids) +BTF_ID(struct, bpf_netpoll) +BTF_ID(func, bpf_netpoll_release_dtor) + +static int __init bpf_netpoll_kfunc_init(void) +{ + int ret; + const struct btf_id_dtor_kfunc bpf_netpoll_dtors[] = { + { + .btf_id = bpf_netpoll_dtor_ids[0], + .kfunc_btf_id = bpf_netpoll_dtor_ids[1], + }, + }; + + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, + &netpoll_init_kfunc_set); + ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, + &netpoll_kfunc_set); + return ret ?: register_btf_id_dtor_kfuncs(bpf_netpoll_dtors, + ARRAY_SIZE(bpf_netpoll_dtors), + THIS_MODULE); +} + +late_initcall(bpf_netpoll_kfunc_init); -- 2.34.1 From: Song Liu 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 Co-developed-by: Mahe Tardy Signed-off-by: Mahe Tardy --- 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 +#include + +#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 + +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 +#include +#include +#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 Add a selftest that exercises the bpf_netpoll kfuncs end-to-end with IPv6, reusing the code that was added for IPv4. Signed-off-by: Mahe Tardy --- .../selftests/bpf/prog_tests/netpoll.c | 42 +++++++++++++++---- .../selftests/bpf/progs/netpoll_sanity.c | 33 +++++++++++---- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/netpoll.c b/tools/testing/selftests/bpf/prog_tests/netpoll.c index 1cfac4b13e7d..eac0378c426a 100644 --- a/tools/testing/selftests/bpf/prog_tests/netpoll.c +++ b/tools/testing/selftests/bpf/prog_tests/netpoll.c @@ -9,15 +9,20 @@ #include "netpoll_sanity.skel.h" #define NS_TEST "netpoll_sanity_ns" +#define NS_TEST_V6 "netpoll_sanity_ns_v6" #define DUMMY_DEV "dummy0" #define DUMMY_IP "10.0.0.1" #define REMOTE_IP "10.0.0.2" +#define DUMMY_IP6 "fd00::1" +#define REMOTE_IP6 "fd00::2" -void test_netpoll_sanity(void) +static void run_netpoll_test(const char *ns_name, const char *local_ip, + const char *remote_ip, bool ipv6) { LIBBPF_OPTS(bpf_test_run_opts, opts); struct nstoken *nstoken = NULL; struct netpoll_sanity *skel; + struct in6_addr addr6; int err, pfd, fd; skel = netpoll_sanity__open_and_load(); @@ -25,18 +30,28 @@ void test_netpoll_sanity(void) 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); + SYS(fail, "ip netns add %s", ns_name); + SYS(fail, "ip -net %s link add %s type dummy", ns_name, DUMMY_DEV); + if (ipv6) + SYS(fail, "ip -net %s addr add %s/64 dev %s", ns_name, local_ip, DUMMY_DEV); + else + SYS(fail, "ip -net %s addr add %s/24 dev %s", ns_name, local_ip, DUMMY_DEV); + SYS(fail, "ip -net %s link set %s up", ns_name, DUMMY_DEV); + + nstoken = open_netns(ns_name); 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); + if (ipv6) { + if (inet_pton(AF_INET6, remote_ip, &addr6) != 1) + goto fail; + __builtin_memcpy(&skel->bss->remote_ip6, &addr6, sizeof(addr6)); + skel->bss->ipv6 = 1; + } else { + skel->bss->remote_ip = inet_addr(remote_ip); + } skel->bss->local_port = 5555; skel->bss->remote_port = 6666; skel->bss->remote_mac[0] = 0xaa; @@ -87,6 +102,15 @@ void test_netpoll_sanity(void) fail: if (nstoken) close_netns(nstoken); - SYS_NOFAIL("ip netns del " NS_TEST " &> /dev/null"); + SYS_NOFAIL("ip netns del %s &> /dev/null", ns_name); netpoll_sanity__destroy(skel); } + +void test_netpoll_sanity(void) +{ + if (test__start_subtest("ipv4")) + run_netpoll_test(NS_TEST, DUMMY_IP, REMOTE_IP, false); + + if (test__start_subtest("ipv6")) + run_netpoll_test(NS_TEST_V6, DUMMY_IP6, REMOTE_IP6, true); +} diff --git a/tools/testing/selftests/bpf/progs/netpoll_sanity.c b/tools/testing/selftests/bpf/progs/netpoll_sanity.c index 9e1e595eff2c..26303632ebde 100644 --- a/tools/testing/selftests/bpf/progs/netpoll_sanity.c +++ b/tools/testing/selftests/bpf/progs/netpoll_sanity.c @@ -12,9 +12,11 @@ /* Globals for passing config from userspace */ char dev_name[16] = {}; __be32 remote_ip; +struct in6_addr remote_ip6; __u16 local_port; __u16 remote_port; __u8 remote_mac[6] = {}; +int ipv6; /* Results */ int status; @@ -35,10 +37,14 @@ int netpoll_setup_test(void *ctx) status = 0; __builtin_memcpy(opts.dev_name, dev_name, 16); - opts.remote_ip = remote_ip; + if (ipv6) + __builtin_memcpy(&opts.remote_ip6, &remote_ip6, sizeof(remote_ip6)); + else + opts.remote_ip = remote_ip; opts.local_port = local_port; opts.remote_port = remote_port; __builtin_memcpy(opts.remote_mac, remote_mac, 6); + opts.ipv6 = ipv6; bnp = bpf_netpoll_create(&opts, sizeof(opts), &err); if (!bnp) { @@ -88,7 +94,9 @@ int BPF_PROG(netpoll_dummy_xmit, struct sk_buff *skb, struct net_device *dev) unsigned char *data; struct ethhdr eth; struct iphdr ip; + struct ipv6hdr ip6; struct udphdr udp; + unsigned int offset; if (bpf_probe_read_kernel(&data, sizeof(data), &skb->data) < 0) return 0; @@ -97,19 +105,28 @@ int BPF_PROG(netpoll_dummy_xmit, struct sk_buff *skb, struct net_device *dev) 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) + if (eth.h_proto == bpf_htons(ETH_P_IP)) { + if (bpf_probe_read_kernel(&ip, sizeof(ip), data + sizeof(struct ethhdr)) < 0) + return 0; + if (ip.protocol != IPPROTO_UDP) + return 0; + offset = sizeof(struct ethhdr) + (ip.ihl * 4); + } else if (eth.h_proto == bpf_htons(ETH_P_IPV6)) { + if (bpf_probe_read_kernel(&ip6, sizeof(ip6), data + sizeof(struct ethhdr)) < 0) + return 0; + if (ip6.nexthdr != IPPROTO_UDP) + return 0; + offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr); + } else { return 0; + } - if (bpf_probe_read_kernel(&udp, sizeof(udp), data + sizeof(struct ethhdr) + (ip.ihl * 4)) < 0) + if (bpf_probe_read_kernel(&udp, sizeof(udp), data + offset) < 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) + if (bpf_probe_read_kernel(&driver_xmit, sizeof(driver_xmit), data + offset + sizeof(struct udphdr)) < 0) return 0; return 0; -- 2.34.1 Add a pair of BPF progs, netpoll_release and netpoll_acquire to test that the respective bpf_netpoll_release and bpf_netpoll_acquire behave as expected and that the verifier has been configured to catch unreleased references. Signed-off-by: Mahe Tardy --- .../selftests/bpf/prog_tests/netpoll.c | 23 +++++++++ .../selftests/bpf/progs/netpoll_basic.c | 47 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/netpoll_basic.c diff --git a/tools/testing/selftests/bpf/prog_tests/netpoll.c b/tools/testing/selftests/bpf/prog_tests/netpoll.c index eac0378c426a..daea1068a739 100644 --- a/tools/testing/selftests/bpf/prog_tests/netpoll.c +++ b/tools/testing/selftests/bpf/prog_tests/netpoll.c @@ -7,15 +7,38 @@ #include "test_progs.h" #include "network_helpers.h" #include "netpoll_sanity.skel.h" +#include "netpoll_basic.skel.h" #define NS_TEST "netpoll_sanity_ns" #define NS_TEST_V6 "netpoll_sanity_ns_v6" +#define NS_BASIC_TEST "netpoll_basic_ns" #define DUMMY_DEV "dummy0" #define DUMMY_IP "10.0.0.1" #define REMOTE_IP "10.0.0.2" #define DUMMY_IP6 "fd00::1" #define REMOTE_IP6 "fd00::2" +void test_netpoll_basic(void) +{ + struct nstoken *nstoken = NULL; + + SYS(fail, "ip netns add %s", NS_BASIC_TEST); + SYS(fail, "ip -net %s link add %s type dummy", NS_BASIC_TEST, DUMMY_DEV); + SYS(fail, "ip -net %s addr add %s/24 dev %s", NS_BASIC_TEST, DUMMY_IP, DUMMY_DEV); + SYS(fail, "ip -net %s link set %s up", NS_BASIC_TEST, DUMMY_DEV); + + nstoken = open_netns(NS_BASIC_TEST); + if (!ASSERT_OK_PTR(nstoken, "open_netns")) + goto fail; + + RUN_TESTS(netpoll_basic); + +fail: + if (nstoken) + close_netns(nstoken); + SYS_NOFAIL("ip netns del %s &> /dev/null", NS_BASIC_TEST); +} + static void run_netpoll_test(const char *ns_name, const char *local_ip, const char *remote_ip, bool ipv6) { diff --git a/tools/testing/selftests/bpf/progs/netpoll_basic.c b/tools/testing/selftests/bpf/progs/netpoll_basic.c new file mode 100644 index 000000000000..2650e1e5410a --- /dev/null +++ b/tools/testing/selftests/bpf/progs/netpoll_basic.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent. */ + +#include "vmlinux.h" +#include +#include "bpf_misc.h" +#include "errno.h" + +SEC("syscall") +__success __retval(0) +int netpoll_release(void *ctx) +{ + struct bpf_netpoll_opts opts = { .dev_name = "dummy0" }; + struct bpf_netpoll *bnp; + int err = 0; + + bnp = bpf_netpoll_create(&opts, sizeof(opts), &err); + if (!bnp) + return err; + + bpf_netpoll_release(bnp); + + return 0; +} + +SEC("syscall") +__failure __msg("Unreleased reference") +int netpoll_acquire(void *ctx) +{ + struct bpf_netpoll_opts opts = { .dev_name = "dummy0" }; + struct bpf_netpoll *bnp; + int err = 0; + + bnp = bpf_netpoll_create(&opts, sizeof(opts), &err); + if (!bnp) + return err; + + bnp = bpf_netpoll_acquire(bnp); + if (!bnp) + return -EINVAL; + + bpf_netpoll_release(bnp); + + return 0; +} + +char __license[] SEC("license") = "GPL"; -- 2.34.1