* [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-11 8:53 [PATCH v1 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
@ 2026-05-11 8:53 ` Mahe Tardy
2026-05-11 9:40 ` bot+bpf-ci
` (2 more replies)
2026-05-11 8:53 ` [PATCH v1 2/4] selftests/bpf: Add netpoll kfunc sanity test Mahe Tardy
` (2 subsequent siblings)
3 siblings, 3 replies; 18+ messages in thread
From: Mahe Tardy @ 2026-05-11 8:53 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart,
Mahe Tardy
From: Song Liu <song@kernel.org>
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 <mahe.tardy@gmail.com>
Signed-off-by: Song Liu <song@kernel.org>
---
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 <file:Documentation/networking/netconsole.rst> 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 <linux/types.h>
+
+#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 <linux/bpf.h>
+#include <linux/bpf_netpoll.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/netpoll.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#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
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
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 1:20 ` Jakub Kicinski
2 siblings, 1 reply; 18+ messages in thread
From: bot+bpf-ci @ 2026-05-11 9:40 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart,
mahe.tardy, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3335 bytes --]
> 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 commit message states bpf_netpoll_send_udp() is "LSM prog type only
for now," but looking at the implementation in bpf_netpoll_kfunc_init(),
the function registers netpoll_kfunc_set using BPF_PROG_TYPE_UNSPEC:
net/core/bpf_netpoll.c:bpf_netpoll_kfunc_init() {
...
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC,
&netpoll_kfunc_set);
...
}
Per bpf_prog_type_to_kfunc_hook() in kernel/bpf/btf.c, BPF_PROG_TYPE_UNSPEC
maps to BTF_KFUNC_HOOK_COMMON, which makes the kfunc available to ALL BPF
program types, not just LSM.
The check in btf_kfunc_is_allowed() tests BTF_KFUNC_HOOK_COMMON first and
returns true if the kfunc is present there, so any program type (tracing,
kprobe, XDP, sched-cls, etc.) can call bpf_netpoll_send_udp().
Is the commit message stale, or should the registration be
BPF_PROG_TYPE_LSM instead of BPF_PROG_TYPE_UNSPEC to match the stated
design?
> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> --- a/drivers/net/Kconfig
> +++ b/drivers/net/Kconfig
[ ... ]
> diff --git a/include/linux/bpf_netpoll.h b/include/linux/bpf_netpoll.h
> --- /dev/null
> +++ b/include/linux/bpf_netpoll.h
[ ... ]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> diff --git a/net/core/Makefile b/net/core/Makefile
> --- a/net/core/Makefile
> +++ b/net/core/Makefile
[ ... ]
> diff --git a/net/core/bpf_netpoll.c b/net/core/bpf_netpoll.c
> --- /dev/null
> +++ b/net/core/bpf_netpoll.c
> @@ -0,0 +1,209 @@
[ ... ]
> +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);
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/25660780250
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-11 9:40 ` bot+bpf-ci
@ 2026-05-11 9:51 ` Mahe Tardy
0 siblings, 0 replies; 18+ messages in thread
From: Mahe Tardy @ 2026-05-11 9:51 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart,
martin.lau, yonghong.song, clm, ihor.solodrai
On Mon, May 11, 2026 at 09:40:32AM +0000, bot+bpf-ci@kernel.org wrote:
> > 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 commit message states bpf_netpoll_send_udp() is "LSM prog type only
> for now," but looking at the implementation in bpf_netpoll_kfunc_init(),
> the function registers netpoll_kfunc_set using BPF_PROG_TYPE_UNSPEC:
>
> net/core/bpf_netpoll.c:bpf_netpoll_kfunc_init() {
> ...
> ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC,
> &netpoll_kfunc_set);
> ...
> }
>
> Per bpf_prog_type_to_kfunc_hook() in kernel/bpf/btf.c, BPF_PROG_TYPE_UNSPEC
> maps to BTF_KFUNC_HOOK_COMMON, which makes the kfunc available to ALL BPF
> program types, not just LSM.
>
> The check in btf_kfunc_is_allowed() tests BTF_KFUNC_HOOK_COMMON first and
> returns true if the kfunc is present there, so any program type (tracing,
> kprobe, XDP, sched-cls, etc.) can call bpf_netpoll_send_udp().
>
> Is the commit message stale, or should the registration be
> BPF_PROG_TYPE_LSM instead of BPF_PROG_TYPE_UNSPEC to match the stated
> design?
Ah indeed, this a stale commit message, will fix in v2. The real
intention is to make it BPF_PROG_TYPE_UNSPEC. Will wait on some more
reviews before submitting.
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/25660780250
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
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 12:05 ` Daniel Borkmann
2026-05-12 8:51 ` Mahe Tardy
2026-05-12 1:20 ` Jakub Kicinski
2 siblings, 1 reply; 18+ messages in thread
From: Daniel Borkmann @ 2026-05-11 12:05 UTC (permalink / raw)
To: Mahe Tardy, bpf
Cc: andrew+netdev, andrii, ast, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart
On 5/11/26 10:53 AM, Mahe Tardy wrote:
> From: Song Liu <song@kernel.org>
>
> 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 <mahe.tardy@gmail.com>
> Signed-off-by: Song Liu <song@kernel.org>
> ---
> 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 <file:Documentation/networking/netconsole.rst> 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.
Do we need the extra Kconfig knob? For most other BPF functionality we don't
have such convention. BPF_NETPOLL could be a hidden config enabled when both
BPF_SYSCALL && NETPOLL is enabled?
> 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 <linux/types.h>
> +
> +#define BPF_NETPOLL_DEV_NAME_LEN 16 /* IFNAMSIZ */
Do we need this, can't we just reuse 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];
> +};
Could we detangle this a bit and structure the design similar to what was
done for the bpf fib lookup helpers? __u8 family and then union.
Is the remote_mac strictly needed? Feels a bit like a burden, maybe for the
selftest, we should first do a bpf_fib_lookup before the bpf_netpoll_create
so that the use case where the user only cares about giving remote_ip/remote_port
and maybe local_port (e.g. to as indicator for different event types and/or
RSS hashing) works, but everything else is derived via kernel fib lookup.
Thanks,
Daniel
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-11 12:05 ` Daniel Borkmann
@ 2026-05-12 8:51 ` Mahe Tardy
0 siblings, 0 replies; 18+ messages in thread
From: Mahe Tardy @ 2026-05-12 8:51 UTC (permalink / raw)
To: Daniel Borkmann
Cc: bpf, andrew+netdev, andrii, ast, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart
On Mon, May 11, 2026 at 02:05:26PM +0200, Daniel Borkmann wrote:
> On 5/11/26 10:53 AM, Mahe Tardy wrote:
> > From: Song Liu <song@kernel.org>
> >
> > 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.
> >
> > [...]
> >
> > +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.
>
> Do we need the extra Kconfig knob? For most other BPF functionality we don't
> have such convention. BPF_NETPOLL could be a hidden config enabled when both
> BPF_SYSCALL && NETPOLL is enabled?
Okay I changed it to:
+config BPF_NETPOLL
+ def_bool BPF_SYSCALL && NETPOLL
So now we need to explicitely enable NETCONSOLE since NETPOL is also a
hidden config defined by:
config NETPOLL
def_bool NETCONSOLE
I somehow initially wanted to define NETPOL by having def_bool
NETCONSOLE || BPF_NETPOLL but then we have a circular dependency...
> > 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 <linux/types.h>
> > +
> > +#define BPF_NETPOLL_DEV_NAME_LEN 16 /* IFNAMSIZ */
>
> Do we need this, can't we just reuse IFNAMSIZ?
We don't really need it, done!
> > +/**
> > + * 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];
> > +};
>
> Could we detangle this a bit and structure the design similar to what was
> done for the bpf fib lookup helpers? __u8 family and then union.
Yeah I think initially it was heavily inspired by the struct netpoll for
IPv6 boolean but they have the union, let's do it, I wrote this one that
is similar to the fib lookup params:
struct bpf_netpoll_opts {
char dev_name[IFNAMSIZ];
__u8 family;
__u8 reserved;
__u16 local_port;
__u16 remote_port;
__u8 remote_mac[ETH_ALEN];
union {
__be32 ipv4_local;
__u32 ipv6_local[4]; /* in6_addr; network order */
};
union {
__be32 ipv4_remote;
__u32 ipv6_remote[4]; /* in6_addr; network order */
};
};
Also used family instead of the bool with this in the kfunc:
switch (opts->family) {
case AF_INET:
bnp->np.local_ip.ip = opts->ipv4_local;
bnp->np.remote_ip.ip = opts->ipv4_remote;
break;
case AF_INET6:
memcpy(&bnp->np.local_ip.in6, opts->ipv6_local,
sizeof(struct in6_addr));
memcpy(&bnp->np.remote_ip.in6, opts->ipv6_remote,
sizeof(struct in6_addr));
bnp->np.ipv6 = true;
break;
default:
*err = -EAFNOSUPPORT;
return NULL;
}
> Is the remote_mac strictly needed?
I think it's indeed needed since netpoll is pushing the MAC address in
the skb at the send step. It's not reusing the network stack for lookup.
I think netconsole is using the broadcast address by default, maybe the
kfunc could default to it as well.
> Feels a bit like a burden, maybe for the
> selftest, we should first do a bpf_fib_lookup before the bpf_netpoll_create
> so that the use case where the user only cares about giving remote_ip/remote_port
> and maybe local_port (e.g. to as indicator for different event types and/or
> RSS hashing) works, but everything else is derived via kernel fib lookup.
Yeah that would be nice but then it would mean we need to call fib
lookup from the SYSCALL programs without any network context so I'm not
sure how it would look like.
> Thanks,
> Daniel
Thanks for the review!
Mahe
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
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 12:05 ` Daniel Borkmann
@ 2026-05-12 1:20 ` Jakub Kicinski
2026-05-12 1:59 ` Alexei Starovoitov
2 siblings, 1 reply; 18+ messages in thread
From: Jakub Kicinski @ 2026-05-12 1:20 UTC (permalink / raw)
To: Mahe Tardy
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, martin.lau, pabeni, song, liamwisehart
On Mon, 11 May 2026 08:53:41 +0000 Mahe Tardy wrote:
> 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.
We have enough bug reports as is, let's not merge unusable toys.
For any use of netpoll by BPF:
Nacked-by: Jakub Kicinski <kuba@kernel.org>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 1:20 ` Jakub Kicinski
@ 2026-05-12 1:59 ` Alexei Starovoitov
2026-05-12 2:36 ` Jakub Kicinski
0 siblings, 1 reply; 18+ messages in thread
From: Alexei Starovoitov @ 2026-05-12 1:59 UTC (permalink / raw)
To: Jakub Kicinski, Mahe Tardy
Cc: bpf, andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, martin.lau, pabeni, song, liamwisehart
On Mon May 11, 2026 at 6:20 PM PDT, Jakub Kicinski wrote:
> On Mon, 11 May 2026 08:53:41 +0000 Mahe Tardy wrote:
>> 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.
>
> We have enough bug reports as is, let's not merge unusable toys.
> For any use of netpoll by BPF:
>
> Nacked-by: Jakub Kicinski <kuba@kernel.org>
unusable toys? What are you talking about?
netpoll is already called from everywhere.
bpf_netpoll_send_udp() won't add any more bugs.
I like this netpoll approach way more then creating and keeping proper socket
within bpf and all head aches of keeping it around and doing send from
good context. With netpoll all of these issues are gone.
Just bpf_netpoll_send_udp() from anywhere and it works just like it does
for dmesg.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 1:59 ` Alexei Starovoitov
@ 2026-05-12 2:36 ` Jakub Kicinski
2026-05-12 2:59 ` Alexei Starovoitov
0 siblings, 1 reply; 18+ messages in thread
From: Jakub Kicinski @ 2026-05-12 2:36 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Mahe Tardy, bpf, andrew+netdev, andrii, ast, daniel, davem,
eddyz87, edumazet, john.fastabend, martin.lau, pabeni, song,
liamwisehart
On Mon, 11 May 2026 18:59:56 -0700 Alexei Starovoitov wrote:
> unusable toys? What are you talking about?
Please refer to the comments on the RFC. netpoll bypasses most of
the networking stack. And there either has to be some user space
to negotiate crypto or this is unusable for anyone serious.
> netpoll is already called from everywhere.
You mean all contexts because it's used by console?
> bpf_netpoll_send_udp() won't add any more bugs.
Same as TLS + sockmap didn't? I spend enough time mopping up after
people.
> I like this netpoll approach way more then creating and keeping proper socket
> within bpf and all head aches of keeping it around and doing send from
> good context. With netpoll all of these issues are gone.
> Just bpf_netpoll_send_udp() from anywhere and it works just like it does
> for dmesg.
No, netpoll is high risk / brittle and tightly integrated with NAPI.
"I don't want to run a tiny user space program" is nowhere near strong
enough justification to expose this API. SMH.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 2:36 ` Jakub Kicinski
@ 2026-05-12 2:59 ` Alexei Starovoitov
2026-05-12 13:53 ` Jakub Kicinski
0 siblings, 1 reply; 18+ messages in thread
From: Alexei Starovoitov @ 2026-05-12 2:59 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Mahe Tardy, bpf, andrew+netdev, andrii, ast, daniel, davem,
eddyz87, edumazet, john.fastabend, martin.lau, pabeni, song,
liamwisehart
On Mon May 11, 2026 at 7:36 PM PDT, Jakub Kicinski wrote:
> On Mon, 11 May 2026 18:59:56 -0700 Alexei Starovoitov wrote:
>> unusable toys? What are you talking about?
>
> Please refer to the comments on the RFC. netpoll bypasses most of
> the networking stack. And there either has to be some user space
> to negotiate crypto or this is unusable for anyone serious.
encryption inside UDP packet is orthogonal.
netpoll as-is is usable and useful to send plain text
best effort messages.
>> netpoll is already called from everywhere.
>
> You mean all contexts because it's used by console?
yes
>> bpf_netpoll_send_udp() won't add any more bugs.
>
> Same as TLS + sockmap didn't? I spend enough time mopping up after
> people.
huh? comparing this to sockmap is apples and oranges.
>> I like this netpoll approach way more then creating and keeping proper socket
>> within bpf and all head aches of keeping it around and doing send from
>> good context. With netpoll all of these issues are gone.
>> Just bpf_netpoll_send_udp() from anywhere and it works just like it does
>> for dmesg.
>
> No, netpoll is high risk / brittle and tightly integrated with NAPI.
>
> "I don't want to run a tiny user space program" is nowhere near strong
> enough justification to expose this API. SMH.
No. Nothing to do with 'user space prog'.
What's brittle in netpoll? It works today in any context and
that's the main point. All the gotchas were resolved over the years
by heavy netconsole usage. Sure, it only works in mainstream
drivers like mlx, brcm and likely broken in niche drivers.
That's not obstacle at all.
netpoll_send_udp() is a perfect building block for best effort
try_to_sendmsg. I don't see the reason to reinvent the whole thing.
Because alternatives are much worse.
bpf progs would need to create UDP socket, split udp_sendmsg
into who knows what. Lots of design questions, code reviews, etc.
Way more headaches for everyone involved.
Doing full networking with dst,fib,nh from kernel context is complicated.
Works for NFS, but it's an uncharted territory for bpf.
While netpoll_send_udp() is available, easy to use and proven to work.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 2:59 ` Alexei Starovoitov
@ 2026-05-12 13:53 ` Jakub Kicinski
2026-05-12 20:25 ` Alexei Starovoitov
0 siblings, 1 reply; 18+ messages in thread
From: Jakub Kicinski @ 2026-05-12 13:53 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Mahe Tardy, bpf, andrew+netdev, andrii, ast, daniel, davem,
eddyz87, edumazet, john.fastabend, martin.lau, pabeni, song,
liamwisehart
On Mon, 11 May 2026 19:59:55 -0700 Alexei Starovoitov wrote:
> On Mon May 11, 2026 at 7:36 PM PDT, Jakub Kicinski wrote:
> > On Mon, 11 May 2026 18:59:56 -0700 Alexei Starovoitov wrote:
> >> unusable toys? What are you talking about?
> >
> > Please refer to the comments on the RFC. netpoll bypasses most of
> > the networking stack. And there either has to be some user space
> > to negotiate crypto or this is unusable for anyone serious.
>
> encryption inside UDP packet is orthogonal.
> netpoll as-is is usable and useful to send plain text
> best effort messages.
>
> >> netpoll is already called from everywhere.
> >
> > You mean all contexts because it's used by console?
>
> yes
>
> >> bpf_netpoll_send_udp() won't add any more bugs.
> >
> > Same as TLS + sockmap didn't? I spend enough time mopping up after
> > people.
>
> huh? comparing this to sockmap is apples and oranges.
There's code which has a lot of users and barely any bugs.
And then there's code which has more bugs than users.
IMO this will be firmly in the later group.
> >> I like this netpoll approach way more then creating and keeping proper socket
> >> within bpf and all head aches of keeping it around and doing send from
> >> good context. With netpoll all of these issues are gone.
> >> Just bpf_netpoll_send_udp() from anywhere and it works just like it does
> >> for dmesg.
> >
> > No, netpoll is high risk / brittle and tightly integrated with NAPI.
> >
> > "I don't want to run a tiny user space program" is nowhere near strong
> > enough justification to expose this API. SMH.
>
> No. Nothing to do with 'user space prog'.
I'm sorry, are you saying that the motivation in the cover letter is
not the motivation? It'd be great if someone could reveal the real
motivation then.
> What's brittle in netpoll? It works today in any context and
> that's the main point. All the gotchas were resolved over the years
> by heavy netconsole usage. Sure, it only works in mainstream
> drivers like mlx, brcm and likely broken in niche drivers.
> That's not obstacle at all.
> netpoll_send_udp() is a perfect building block for best effort
> try_to_sendmsg.
It's not a generic building block for random subsystems to use.
netconsole is very important so we suffer its needs.
> I don't see the reason to reinvent the whole thing.
> Because alternatives are much worse.
> bpf progs would need to create UDP socket, split udp_sendmsg
> into who knows what. Lots of design questions, code reviews, etc.
In kernel sockets are used broadly. I accept that it's more work
for you but it's also the correct way to go about this if you want
to send traffic from BPF.
> Way more headaches for everyone involved.
> Doing full networking with dst,fib,nh from kernel context is complicated.
Why would you have to worry about any of the low level details with
sockets?
> Works for NFS, but it's an uncharted territory for bpf.
> While netpoll_send_udp() is available, easy to use and proven to work.
It's not "available", the only reason it's exported is because
netconsole is a module.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 13:53 ` Jakub Kicinski
@ 2026-05-12 20:25 ` Alexei Starovoitov
2026-05-12 23:32 ` Jakub Kicinski
0 siblings, 1 reply; 18+ messages in thread
From: Alexei Starovoitov @ 2026-05-12 20:25 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Mahe Tardy, bpf, andrew+netdev, andrii, ast, daniel, davem,
eddyz87, edumazet, john.fastabend, martin.lau, pabeni, song,
liamwisehart
On Tue May 12, 2026 at 6:53 AM PDT, Jakub Kicinski wrote:
>> >> I like this netpoll approach way more then creating and keeping proper socket
>> >> within bpf and all head aches of keeping it around and doing send from
>> >> good context. With netpoll all of these issues are gone.
>> >> Just bpf_netpoll_send_udp() from anywhere and it works just like it does
>> >> for dmesg.
>> >
>> > No, netpoll is high risk / brittle and tightly integrated with NAPI.
>> >
>> > "I don't want to run a tiny user space program" is nowhere near strong
>> > enough justification to expose this API. SMH.
>>
>> No. Nothing to do with 'user space prog'.
>
> I'm sorry, are you saying that the motivation in the cover letter is
> not the motivation? It'd be great if someone could reveal the real
> motivation then.
AI makes commit logs sound very convincing, so I don't read them anymore
to avoid bias. Only looking at the code.
And what I see in this set is a facility for emergency notifications
that is safe to use in any context.
If people bolt crypto on top and make a toy prototype out of it
it doesn't take away from usefulness of sending UDP packets out.
I want to use it out of bpf core to notify of things like divide by zero,
arena fault, etc. Currently they are sent via bpf_stream, but
user space has to be there to pick up from the stream.
We cannot use printk, since it's not safe.
So netcons is the only option available today for 'bad things happen'
notifications.
Theoretically con->write_atomic() can be wrapped in such helper/kfunc
that both bpf core and bpf progs can call.
progs also need emergency notifications.
Like sched-ext might detect something bad and notify.
But at the end netconsole_write() is netpoll_send_udp(),
so more natural and simpler to just use that.
> In kernel sockets are used broadly. I accept that it's more work
> for you but it's also the correct way to go about this if you want
> to send traffic from BPF.
proper sockets and full TCP/UDP back and forth is a different use case.
Maybe it will work for Mahe/Liam. I don't know.
I'm arguing that emergency UDP is necessary too.
We can skip netpoll_send_udp() and let bpf core prepare skb
earlier in a good context, then at div-by-0 time populate it
with a message and call netpoll_send_skb().
Looks like netcons, bridge, vlan are using it, so why not let
bpf core use it too?
__netpoll_send_skb() is the main value here because
it's all trylock based at every step.
The div-by-0 notification using normal sockets would have to be
done async and that's the main downside.
We'd need to irq_work, then schedule_work and then send it via socket.
Way too many steps to do in emergency. If sched-ext messed it up
kthread may or may not wakeup.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 20:25 ` Alexei Starovoitov
@ 2026-05-12 23:32 ` Jakub Kicinski
2026-05-13 1:16 ` Alexei Starovoitov
0 siblings, 1 reply; 18+ messages in thread
From: Jakub Kicinski @ 2026-05-12 23:32 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Mahe Tardy, bpf, andrew+netdev, andrii, ast, daniel, davem,
eddyz87, edumazet, john.fastabend, martin.lau, pabeni, song,
liamwisehart
On Tue, 12 May 2026 13:25:04 -0700 Alexei Starovoitov wrote:
> On Tue May 12, 2026 at 6:53 AM PDT, Jakub Kicinski wrote:
> >> No. Nothing to do with 'user space prog'.
> >
> > I'm sorry, are you saying that the motivation in the cover letter is
> > not the motivation? It'd be great if someone could reveal the real
> > motivation then.
>
> AI makes commit logs sound very convincing, so I don't read them anymore
> to avoid bias. Only looking at the code.
> And what I see in this set is a facility for emergency notifications
> that is safe to use in any context.
True, but I hope a real use case does exist for this given it's posted?
> If people bolt crypto on top and make a toy prototype out of it
> it doesn't take away from usefulness of sending UDP packets out.
>
> I want to use it out of bpf core to notify of things like divide by zero,
> arena fault, etc. Currently they are sent via bpf_stream, but
> user space has to be there to pick up from the stream.
> We cannot use printk, since it's not safe.
printk is unsafe but netconsole is safe?
> So netcons is the only option available today for 'bad things happen'
> notifications.
> Theoretically con->write_atomic() can be wrapped in such helper/kfunc
> that both bpf core and bpf progs can call.
> progs also need emergency notifications.
> Like sched-ext might detect something bad and notify.
> But at the end netconsole_write() is netpoll_send_udp(),
> so more natural and simpler to just use that.
This sounds very scary. I mean it sounds exacting and technically
challenging, but I really don't want this to hinge on netpoll.
netpoll is not a good solution to synchronization problems.
> > In kernel sockets are used broadly. I accept that it's more work
> > for you but it's also the correct way to go about this if you want
> > to send traffic from BPF.
>
> proper sockets and full TCP/UDP back and forth is a different use case.
> Maybe it will work for Mahe/Liam. I don't know.
> I'm arguing that emergency UDP is necessary too.
> We can skip netpoll_send_udp() and let bpf core prepare skb
> earlier in a good context, then at div-by-0 time populate it
> with a message and call netpoll_send_skb().
> Looks like netcons, bridge, vlan are using it, so why not let
> bpf core use it too?
Those drivers are not really using it, they are just trying to pass thru
the skb to the lower dev. And I'd be surprised if most of that stuff
even works, don't think it has any users. Most people try to pipe
netcons to eth0.
> __netpoll_send_skb() is the main value here because
> it's all trylock based at every step.
> The div-by-0 notification using normal sockets would have to be
> done async and that's the main downside.
> We'd need to irq_work, then schedule_work and then send it via socket.
> Way too many steps to do in emergency. If sched-ext messed it up
> kthread may or may not wakeup.
I think you may be overly optimistic on the dependability of netpoll.
Rik has added support for serial scraping at Meta because netconsole
misses a lot of crucial alerts. And I can confirm there's a bunch of
errors that appear in that dataset that are missed by netcons.
Then there are little nuggets like some generations of bnxt NICs
having to discard all Rx frames when netcons is trying to send
and Tx is full (bnxt_force_rx_discard()). netpoll is really meant
for very narrow use cases, please trust me, opening it up for broader
use is not going to end well.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-12 23:32 ` Jakub Kicinski
@ 2026-05-13 1:16 ` Alexei Starovoitov
2026-05-13 1:31 ` Song Liu
0 siblings, 1 reply; 18+ messages in thread
From: Alexei Starovoitov @ 2026-05-13 1:16 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Mahe Tardy, bpf, andrew+netdev, andrii, ast, daniel, davem,
eddyz87, edumazet, john.fastabend, martin.lau, pabeni, song,
liamwisehart
On Tue May 12, 2026 at 4:32 PM PDT, Jakub Kicinski wrote:
>
> I think you may be overly optimistic on the dependability of netpoll.
> Rik has added support for serial scraping at Meta because netconsole
> misses a lot of crucial alerts. And I can confirm there's a bunch of
> errors that appear in that dataset that are missed by netcons.
>
> Then there are little nuggets like some generations of bnxt NICs
> having to discard all Rx frames when netcons is trying to send
> and Tx is full (bnxt_force_rx_discard()).
horrors. since bnxt is doing that then indeed it's not something
to rely on. I thought that over the years major vendors cleaned up
the driver side well. Looks like it's still not the case.
Sigh. Moving to sock_create_kern() and friends then.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-05-13 1:16 ` Alexei Starovoitov
@ 2026-05-13 1:31 ` Song Liu
0 siblings, 0 replies; 18+ messages in thread
From: Song Liu @ 2026-05-13 1:31 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Jakub Kicinski, Mahe Tardy, bpf, andrew+netdev, andrii, ast,
daniel, davem, eddyz87, edumazet, john.fastabend, martin.lau,
pabeni, liamwisehart
Hi Jakub and Alexei,
On Tue, May 12, 2026 at 6:17 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue May 12, 2026 at 4:32 PM PDT, Jakub Kicinski wrote:
> >
> > I think you may be overly optimistic on the dependability of netpoll.
> > Rik has added support for serial scraping at Meta because netconsole
> > misses a lot of crucial alerts. And I can confirm there's a bunch of
> > errors that appear in that dataset that are missed by netcons.
> >
> > Then there are little nuggets like some generations of bnxt NICs
> > having to discard all Rx frames when netcons is trying to send
> > and Tx is full (bnxt_force_rx_discard()).
>
> horrors. since bnxt is doing that then indeed it's not something
> to rely on. I thought that over the years major vendors cleaned up
> the driver side well. Looks like it's still not the case.
> Sigh. Moving to sock_create_kern() and friends then.
Thanks for sharing these thoughts and background with netpoll.
We will look into sock_create_kern() based solutions.
Song
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v1 2/4] selftests/bpf: Add netpoll kfunc sanity test
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 8:53 ` Mahe Tardy
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
3 siblings, 0 replies; 18+ messages in thread
From: Mahe Tardy @ 2026-05-11 8:53 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart,
Mahe Tardy
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
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v1 3/4] selftests/bpf: Add netpoll kfunc IPv6 variant test
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 8:53 ` [PATCH v1 2/4] selftests/bpf: Add netpoll kfunc sanity test Mahe Tardy
@ 2026-05-11 8:53 ` Mahe Tardy
2026-05-11 8:53 ` [PATCH v1 4/4] selftests/bpf: Add netpoll setup basic tests Mahe Tardy
3 siblings, 0 replies; 18+ messages in thread
From: Mahe Tardy @ 2026-05-11 8:53 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart,
Mahe Tardy
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 <mahe.tardy@gmail.com>
---
.../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
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v1 4/4] selftests/bpf: Add netpoll setup basic tests
2026-05-11 8:53 [PATCH v1 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
` (2 preceding siblings ...)
2026-05-11 8:53 ` [PATCH v1 3/4] selftests/bpf: Add netpoll kfunc IPv6 variant test Mahe Tardy
@ 2026-05-11 8:53 ` Mahe Tardy
3 siblings, 0 replies; 18+ messages in thread
From: Mahe Tardy @ 2026-05-11 8:53 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, andrii, ast, daniel, davem, eddyz87, edumazet,
john.fastabend, kuba, martin.lau, pabeni, song, liamwisehart,
Mahe Tardy
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 <mahe.tardy@gmail.com>
---
.../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 <bpf/bpf_helpers.h>
+#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
^ permalink raw reply related [flat|nested] 18+ messages in thread