* [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-03-09 13:16 [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
@ 2026-03-09 13:16 ` Mahe Tardy
2026-03-09 13:57 ` bot+bpf-ci
2026-03-09 16:26 ` Alexei Starovoitov
2026-03-09 13:16 ` [RFC PATCH bpf-next 2/4] selftests/bpf: Add netpoll kfunc sanity test Mahe Tardy
` (4 subsequent siblings)
5 siblings, 2 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-03-09 13:16 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, martin.lau, daniel,
john.fastabend, ast, andrii, eddyz87, song, 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 17108c359216..1d7bcd07f60a 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -371,6 +371,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 fc4ccd1de569..07cfb69e3946 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6037,6 +6037,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..1d01d5f43ee1
--- /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(sizeof(*bnp), GFP_KERNEL);
+ 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, 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_LSM,
+ &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] 13+ messages in thread* Re: [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-03-09 13:16 ` [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets Mahe Tardy
@ 2026-03-09 13:57 ` bot+bpf-ci
2026-03-09 16:26 ` Alexei Starovoitov
1 sibling, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-03-09 13:57 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, martin.lau, daniel,
john.fastabend, ast, andrii, eddyz87, song, mahe.tardy, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2184 bytes --]
> diff --git a/net/core/bpf_netpoll.c b/net/core/bpf_netpoll.c
> new file mode 100644
> index 000000000000..1d01d5f43ee1
> --- /dev/null
> +++ b/net/core/bpf_netpoll.c
[ ... ]
> +/**
> + * 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, negative errno on error.
> + */
Is the return value documentation here complete? Tracing the call
chain through netpoll_send_udp() -> netpoll_send_skb() ->
__netpoll_send_skb(), the latter initializes its return value as
NET_XMIT_DROP (which is 1) and returns it when the device is not
running or not present:
net/core/netpoll.c:__netpoll_send_skb() {
netdev_tx_t ret = NET_XMIT_DROP;
...
if (!npinfo || !netif_running(dev) || !netif_device_present(dev)) {
dev_kfree_skb_irq(skb);
goto out;
}
...
ret = NETDEV_TX_OK;
out:
...
return ret;
}
That positive 1 propagates back through netpoll_send_udp() which
casts it to int and returns it unchanged.
A BPF program following the documented contract and checking
ret < 0 for errors would silently miss the case where the packet
was dropped because the device went down. Should the comment
document this, or should the return value be normalized to a
negative errno before returning to the caller?
> +__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;
> +}
---
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/22855649225
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-03-09 13:16 ` [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets Mahe Tardy
2026-03-09 13:57 ` bot+bpf-ci
@ 2026-03-09 16:26 ` Alexei Starovoitov
2026-03-09 18:52 ` Mahe Tardy
1 sibling, 1 reply; 13+ messages in thread
From: Alexei Starovoitov @ 2026-03-09 16:26 UTC (permalink / raw)
To: Mahe Tardy
Cc: bpf, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Martin KaFai Lau, Daniel Borkmann, John Fastabend,
Alexei Starovoitov, Andrii Nakryiko, Eduard, Song Liu
On Mon, Mar 9, 2026 at 6:17 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
>
> +
> + bnp = kzalloc(sizeof(*bnp), GFP_KERNEL);
AI generated it and humans reviewed it ?
Sigh. AI does a better job at this.
Think about this line again.
Also, don't bother with RFC. If you want the patches to
land don't use RFC tag.
RFC means "I don't know what I'm doing and I'm not asking
to land it in this shape".
pw-bot: cr
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-03-09 16:26 ` Alexei Starovoitov
@ 2026-03-09 18:52 ` Mahe Tardy
2026-03-10 5:07 ` Song Liu
0 siblings, 1 reply; 13+ messages in thread
From: Mahe Tardy @ 2026-03-09 18:52 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: bpf, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Martin KaFai Lau, Daniel Borkmann, John Fastabend,
Alexei Starovoitov, Andrii Nakryiko, Eduard, Song Liu
On Mon, Mar 09, 2026 at 09:26:00AM -0700, Alexei Starovoitov wrote:
> On Mon, Mar 9, 2026 at 6:17 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
> >
> > +
> > + bnp = kzalloc(sizeof(*bnp), GFP_KERNEL);
>
> AI generated it and humans reviewed it ?
> Sigh. AI does a better job at this.
> Think about this line again.
Yes I reviewed this, but Song might know more on how this was generated,
my experience with handling kernel memory is fairly limited (I'm mostly
learning) but this looked good to me.
As I'm seeing it we could have used more modern wrapper like:
bnp = kzalloc_obj(*bnp)
But I'm not sure it would made a fundamental difference here since it
would be the exact same flag.
It's called from bpf_netpoll_create, that can be only called from
sleepable programs (like SYSCALL here). And it's similar to how the
crypto context is allocated in the bpf_crypto_ctx_create kfunc so I'm
not sure I understand what's wrong in our variant.
Could you help me and be more explicit on what you think is wrong?
> Also, don't bother with RFC. If you want the patches to
> land don't use RFC tag.
>
> RFC means "I don't know what I'm doing and I'm not asking
> to land it in this shape".
Okay thanks for the info, I'll remove the RFC for v2.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-03-09 18:52 ` Mahe Tardy
@ 2026-03-10 5:07 ` Song Liu
2026-03-10 5:22 ` Alexei Starovoitov
0 siblings, 1 reply; 13+ messages in thread
From: Song Liu @ 2026-03-10 5:07 UTC (permalink / raw)
To: Mahe Tardy
Cc: Alexei Starovoitov, bpf, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Martin KaFai Lau,
Daniel Borkmann, John Fastabend, Alexei Starovoitov,
Andrii Nakryiko, Eduard
On Mon, Mar 9, 2026 at 11:52 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
>
> On Mon, Mar 09, 2026 at 09:26:00AM -0700, Alexei Starovoitov wrote:
> > On Mon, Mar 9, 2026 at 6:17 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
> > >
> > > +
> > > + bnp = kzalloc(sizeof(*bnp), GFP_KERNEL);
> >
> > AI generated it and humans reviewed it ?
> > Sigh. AI does a better job at this.
> > Think about this line again.
>
> Yes I reviewed this, but Song might know more on how this was generated,
> my experience with handling kernel memory is fairly limited (I'm mostly
> learning) but this looked good to me.
Claude wrote this version before the tree wide kzalloc_obj change. If we
redo the vibe coding now, it should use kzalloc_obj instead.
Thanks,
Song
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets
2026-03-10 5:07 ` Song Liu
@ 2026-03-10 5:22 ` Alexei Starovoitov
0 siblings, 0 replies; 13+ messages in thread
From: Alexei Starovoitov @ 2026-03-10 5:22 UTC (permalink / raw)
To: Song Liu
Cc: Mahe Tardy, bpf, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Martin KaFai Lau, Daniel Borkmann,
John Fastabend, Alexei Starovoitov, Andrii Nakryiko, Eduard
On Mon, Mar 9, 2026 at 10:07 PM Song Liu <song@kernel.org> wrote:
>
> On Mon, Mar 9, 2026 at 11:52 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
> >
> > On Mon, Mar 09, 2026 at 09:26:00AM -0700, Alexei Starovoitov wrote:
> > > On Mon, Mar 9, 2026 at 6:17 AM Mahe Tardy <mahe.tardy@gmail.com> wrote:
> > > >
> > > > +
> > > > + bnp = kzalloc(sizeof(*bnp), GFP_KERNEL);
> > >
> > > AI generated it and humans reviewed it ?
> > > Sigh. AI does a better job at this.
> > > Think about this line again.
> >
> > Yes I reviewed this, but Song might know more on how this was generated,
> > my experience with handling kernel memory is fairly limited (I'm mostly
> > learning) but this looked good to me.
>
> Claude wrote this version before the tree wide kzalloc_obj change. If we
> redo the vibe coding now, it should use kzalloc_obj instead.
That's what I expected :)
^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH bpf-next 2/4] selftests/bpf: Add netpoll kfunc sanity test
2026-03-09 13:16 [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
2026-03-09 13:16 ` [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets Mahe Tardy
@ 2026-03-09 13:16 ` Mahe Tardy
2026-03-09 13:16 ` [RFC PATCH bpf-next 3/4] selftests/bpf: Add netpoll kfunc IPv6 variant test Mahe Tardy
` (3 subsequent siblings)
5 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-03-09 13:16 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, martin.lau, daniel,
john.fastabend, ast, andrii, eddyz87, song, 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] 13+ messages in thread* [RFC PATCH bpf-next 3/4] selftests/bpf: Add netpoll kfunc IPv6 variant test
2026-03-09 13:16 [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
2026-03-09 13:16 ` [RFC PATCH bpf-next 1/4] bpf: Add netpoll kfuncs for sending UDP packets Mahe Tardy
2026-03-09 13:16 ` [RFC PATCH bpf-next 2/4] selftests/bpf: Add netpoll kfunc sanity test Mahe Tardy
@ 2026-03-09 13:16 ` Mahe Tardy
2026-03-09 13:16 ` [RFC PATCH bpf-next 4/4] selftests/bpf: Add netpoll setup basic tests Mahe Tardy
` (2 subsequent siblings)
5 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-03-09 13:16 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, martin.lau, daniel,
john.fastabend, ast, andrii, eddyz87, song, 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 | 43 +++++++++++++++----
.../selftests/bpf/progs/netpoll_sanity.c | 33 ++++++++++----
2 files changed, 59 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..f2548f1f9349 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,16 @@ 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)
+{
+ run_netpoll_test(NS_TEST, DUMMY_IP, REMOTE_IP, false);
+}
+
+void test_netpoll_sanity_ipv6(void)
+{
+ 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] 13+ messages in thread* [RFC PATCH bpf-next 4/4] selftests/bpf: Add netpoll setup basic tests
2026-03-09 13:16 [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
` (2 preceding siblings ...)
2026-03-09 13:16 ` [RFC PATCH bpf-next 3/4] selftests/bpf: Add netpoll kfunc IPv6 variant test Mahe Tardy
@ 2026-03-09 13:16 ` Mahe Tardy
2026-03-09 17:59 ` [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Jakub Kicinski
2026-03-09 19:29 ` Martin KaFai Lau
5 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-03-09 13:16 UTC (permalink / raw)
To: bpf
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, martin.lau, daniel,
john.fastabend, ast, andrii, eddyz87, song, 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 f2548f1f9349..af40c4b21be7 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] 13+ messages in thread* Re: [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll
2026-03-09 13:16 [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
` (3 preceding siblings ...)
2026-03-09 13:16 ` [RFC PATCH bpf-next 4/4] selftests/bpf: Add netpoll setup basic tests Mahe Tardy
@ 2026-03-09 17:59 ` Jakub Kicinski
2026-03-09 19:29 ` Martin KaFai Lau
5 siblings, 0 replies; 13+ messages in thread
From: Jakub Kicinski @ 2026-03-09 17:59 UTC (permalink / raw)
To: Mahe Tardy
Cc: bpf, andrew+netdev, davem, edumazet, pabeni, martin.lau, daniel,
john.fastabend, ast, andrii, eddyz87, song
On Mon, 9 Mar 2026 13:16:31 +0000 Mahe Tardy wrote:
> This patch series introduces bpf_netpoll, a set of 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.
>
> For reference, this was discussed at LSF/MM/BPF 2025[^1] in Montreal,
> and again at Plumbers 2025 in Tokyo. Liam Wisehart mentioned this work
> during his presentation of BpfJailer[^2].
>
> The main use case is to be able to completely dispense with
> agents/daemons for BPF programs after startup. In the case of
> Isovalent's Tetragon, the idea would be to be able to emit security
> alerts or export data from BPF even when the agent is down. For meta,
> according to Liam presentation[^2], this could replace logging via
> ringbuffers which created cross-binary versioning issues.
>
> The implementation follows the established kfunc lifecycle pattern
> (create/acquire/release with refcounting, kptr map storage, dtor
> registration), for example used by the network bpf_crypto kfuncs.
>
> Further patches would extend the bpf_netpoll_send kfunc to more program
> types. Note that network program types should not encounter recursion
> issues as netpoll bypasses the network stack and sends directly to the
> driver.
netpoll is a fairly constrained and tricky vehicle for sending data out.
Its built to export logs and crash info, not arbitrary (potentially high
rate) logs.
Plus you will still need user space components in any modern
deployments to establish security associations and/or add some sort of
"security proxy".
Long story short this may be a fun PoC to vibe code but architecturally
having a standard-ish exporter or something integrated with systemd
seems like a much better system architecture.
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll
2026-03-09 13:16 [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
` (4 preceding siblings ...)
2026-03-09 17:59 ` [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll Jakub Kicinski
@ 2026-03-09 19:29 ` Martin KaFai Lau
2026-03-10 4:56 ` Song Liu
5 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2026-03-09 19:29 UTC (permalink / raw)
To: Mahe Tardy, song
Cc: bpf, andrew+netdev, davem, edumazet, kuba, pabeni, daniel,
john.fastabend, ast, andrii, eddyz87
On 3/9/26 6:16 AM, Mahe Tardy wrote:
> This patch series introduces bpf_netpoll, a set of 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.
>
> For reference, this was discussed at LSF/MM/BPF 2025[^1] in Montreal,
> and again at Plumbers 2025 in Tokyo. Liam Wisehart mentioned this work
> during his presentation of BpfJailer[^2].
>
> The main use case is to be able to completely dispense with
> agents/daemons for BPF programs after startup. In the case of
> Isovalent's Tetragon, the idea would be to be able to emit security
> alerts or export data from BPF even when the agent is down. For meta,
> according to Liam presentation[^2], this could replace logging via
> ringbuffers which created cross-binary versioning issues.
>
> The implementation follows the established kfunc lifecycle pattern
> (create/acquire/release with refcounting, kptr map storage, dtor
> registration), for example used by the network bpf_crypto kfuncs.
>
> Further patches would extend the bpf_netpoll_send kfunc to more program
> types. Note that network program types should not encounter recursion
> issues as netpoll bypasses the network stack and sends directly to the
> driver.
netpoll may be an easy replacement for bpf_ringbuf_output, which can be
used in different running contexts. If it is to replace the user space
daemon in production, it loses too many things from the networking
stack: routing, tc, etc. It could escape the current tc/tracing bpf
prog, monitoring, QoS marking, qdisc, etc. There is still the encryption
piece as well.
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [RFC PATCH bpf-next 0/4] bpf: Introduce bpf_netpoll
2026-03-09 19:29 ` Martin KaFai Lau
@ 2026-03-10 4:56 ` Song Liu
0 siblings, 0 replies; 13+ messages in thread
From: Song Liu @ 2026-03-10 4:56 UTC (permalink / raw)
To: Martin KaFai Lau, kuba
Cc: Mahe Tardy, bpf, andrew+netdev, davem, edumazet, pabeni, daniel,
john.fastabend, ast, andrii, eddyz87
Hi Martin and Jakub,
Thanks for your comments.
On Mon, Mar 9, 2026 at 12:29 PM Martin KaFai Lau <martin.lau@linux.dev> wrote:
>
> On 3/9/26 6:16 AM, Mahe Tardy wrote:
> > This patch series introduces bpf_netpoll, a set of 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.
> >
> > For reference, this was discussed at LSF/MM/BPF 2025[^1] in Montreal,
> > and again at Plumbers 2025 in Tokyo. Liam Wisehart mentioned this work
> > during his presentation of BpfJailer[^2].
> >
> > The main use case is to be able to completely dispense with
> > agents/daemons for BPF programs after startup. In the case of
> > Isovalent's Tetragon, the idea would be to be able to emit security
> > alerts or export data from BPF even when the agent is down. For meta,
> > according to Liam presentation[^2], this could replace logging via
> > ringbuffers which created cross-binary versioning issues.
> >
> > The implementation follows the established kfunc lifecycle pattern
> > (create/acquire/release with refcounting, kptr map storage, dtor
> > registration), for example used by the network bpf_crypto kfuncs.
> >
> > Further patches would extend the bpf_netpoll_send kfunc to more program
> > types. Note that network program types should not encounter recursion
> > issues as netpoll bypasses the network stack and sends directly to the
> > driver.
>
> netpoll may be an easy replacement for bpf_ringbuf_output, which can be
> used in different running contexts. If it is to replace the user space
> daemon in production, it loses too many things from the networking
> stack: routing, tc, etc. It could escape the current tc/tracing bpf
> prog, monitoring, QoS marking, qdisc, etc. There is still the encryption
> piece as well.
netpoll kfuncs alone are not enough to make a good solution. However,
I wonder whether these kfuncs can be useful building blocks to enable
good enough solutions for some use cases. These solutions should
include kfuncs, bpf-c components, and user space components.
Specifically, the user space components can handle security
handshake, and the bpf-c component can provide basic rate limit logic.
If we can create a library with the bpf-c and userspace components,
would it make a useful solution for some use cases (data rate not too
high, etc.). Or does it still miss some essential capabilities?
Thanks,
Song
^ permalink raw reply [flat|nested] 13+ messages in thread