All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mahe Tardy <mahe.tardy@gmail.com>
To: bpf@vger.kernel.org
Cc: andrew+netdev@lunn.ch, andrii@kernel.org, ast@kernel.org,
	daniel@iogearbox.net, davem@davemloft.net, eddyz87@gmail.com,
	edumazet@google.com, john.fastabend@gmail.com, kuba@kernel.org,
	martin.lau@linux.dev, pabeni@redhat.com, song@kernel.org,
	liamwisehart@meta.com, Mahe Tardy <mahe.tardy@gmail.com>
Subject: [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets
Date: Mon, 11 May 2026 08:53:41 +0000	[thread overview]
Message-ID: <20260511085344.3302-2-mahe.tardy@gmail.com> (raw)
In-Reply-To: <20260511085344.3302-1-mahe.tardy@gmail.com>

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


  reply	other threads:[~2026-05-11  8:54 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-11  8:53 [PATCH v1 0/4] bpf: Introduce bpf_netpoll Mahe Tardy
2026-05-11  8:53 ` Mahe Tardy [this message]
2026-05-11  9:40   ` [PATCH v1 1/4] bpf: Add netpoll kfuncs for sending UDP packets bot+bpf-ci
2026-05-11  9:51     ` Mahe Tardy
2026-05-11 12:05   ` Daniel Borkmann
2026-05-12  8:51     ` Mahe Tardy
2026-05-12  1:20   ` Jakub Kicinski
2026-05-12  1:59     ` Alexei Starovoitov
2026-05-12  2:36       ` Jakub Kicinski
2026-05-12  2:59         ` Alexei Starovoitov
2026-05-12 13:53           ` Jakub Kicinski
2026-05-12 20:25             ` Alexei Starovoitov
2026-05-12 23:32               ` Jakub Kicinski
2026-05-13  1:16                 ` Alexei Starovoitov
2026-05-13  1:31                   ` Song Liu
2026-05-11  8:53 ` [PATCH v1 2/4] selftests/bpf: Add netpoll kfunc sanity test 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260511085344.3302-2-mahe.tardy@gmail.com \
    --to=mahe.tardy@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=eddyz87@gmail.com \
    --cc=edumazet@google.com \
    --cc=john.fastabend@gmail.com \
    --cc=kuba@kernel.org \
    --cc=liamwisehart@meta.com \
    --cc=martin.lau@linux.dev \
    --cc=pabeni@redhat.com \
    --cc=song@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.