From: Song Liu Add BPF kfuncs that allow BPF programs to send UDP packets via the netpoll infrastructure. This provides a mechanism for BPF programs (e.g., LSM hooks) to emit telemetry over UDP without depending on the regular networking stack. The API consists of four kfuncs: bpf_netpoll_create() - Allocate and set up a netpoll context (sleepable, SYSCALL prog type only) bpf_netpoll_acquire() - Acquire a reference to a netpoll context bpf_netpoll_release() - Release a reference (cleanup via queue_rcu_work since netpoll_cleanup sleeps) bpf_netpoll_send_udp() - Send a UDP packet (any context, LSM prog type only for now) The implementation follows the established kfunc lifecycle pattern (create/acquire/release with refcounting, kptr map storage, dtor registration). The netpoll context is wrapped in a refcounted bpf_netpoll struct. Cleanup is deferred via queue_rcu_work() because netpoll_cleanup() takes rtnl_lock. AI was used to generate the code and each line was manually reviewed. Reviewed-by: Mahe Tardy Signed-off-by: Song Liu --- drivers/net/Kconfig | 11 ++ include/linux/bpf_netpoll.h | 38 +++++++ kernel/bpf/verifier.c | 3 + net/core/Makefile | 1 + net/core/bpf_netpoll.c | 209 ++++++++++++++++++++++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 include/linux/bpf_netpoll.h create mode 100644 net/core/bpf_netpoll.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 17108c359216..1d7bcd07f60a 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -371,6 +371,17 @@ config NETCONSOLE_PREPEND_RELEASE message. See for details. +config BPF_NETPOLL + bool "BPF netpoll UDP support" + depends on BPF_SYSCALL && NET + select NETPOLL + help + Enable BPF kfuncs for sending UDP packets via netpoll. + This allows BPF programs to send UDP packets from any + context using the netpoll infrastructure. + + If unsure, say N. + config NETPOLL def_bool NETCONSOLE diff --git a/include/linux/bpf_netpoll.h b/include/linux/bpf_netpoll.h new file mode 100644 index 000000000000..b738032548c7 --- /dev/null +++ b/include/linux/bpf_netpoll.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#ifndef _BPF_NETPOLL_H +#define _BPF_NETPOLL_H + +#include + +#define BPF_NETPOLL_DEV_NAME_LEN 16 /* IFNAMSIZ */ + +/** + * struct bpf_netpoll_opts - BPF netpoll initialization parameters + * @dev_name: Network device name (e.g. "eth0"), null-terminated. + * @local_ip: Local IPv4 address in network byte order. 0 = auto-detect. + * @remote_ip: Remote IPv4 address in network byte order. + * @local_port: Local UDP port in host byte order. + * @remote_port: Remote UDP port in host byte order. + * @remote_mac: Remote MAC address (6 bytes). + * @ipv6: Set to 1 for IPv6, 0 for IPv4. + * @reserved: Must be zero. Reserved for future use. + * @local_ip6: Local IPv6 address (16 bytes). Used when ipv6=1. + * Zero = auto-detect. + * @remote_ip6: Remote IPv6 address (16 bytes). Used when ipv6=1. + */ +struct bpf_netpoll_opts { + char dev_name[BPF_NETPOLL_DEV_NAME_LEN]; + __be32 local_ip; + __be32 remote_ip; + __u16 local_port; + __u16 remote_port; + __u8 remote_mac[6]; + __u8 ipv6; + __u8 reserved; + __u8 local_ip6[16]; + __u8 remote_ip6[16]; +}; + +#endif /* _BPF_NETPOLL_H */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 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 +#include +#include +#include +#include +#include +#include +#include + +#define BPF_NETPOLL_MAX_UDP_CHUNK 1460 + +/** + * struct bpf_netpoll - refcounted BPF netpoll context + * @np: The underlying netpoll structure. + * @usage: Reference counter. + * @rwork: RCU work for deferred cleanup (netpoll_cleanup sleeps). + */ +struct bpf_netpoll { + struct netpoll np; + refcount_t usage; + struct rcu_work rwork; +}; + +static void netpoll_release_work_fn(struct work_struct *work) +{ + struct bpf_netpoll *bnp = container_of(to_rcu_work(work), + struct bpf_netpoll, rwork); + + netpoll_cleanup(&bnp->np); + kfree(bnp); +} + +__bpf_kfunc_start_defs(); + +/** + * bpf_netpoll_create() - Create a BPF netpoll context for sending UDP. + * + * Allocates and sets up a netpoll context that can be used to send UDP + * packets from BPF programs. The returned context must either be stored + * in a map as a kptr, or freed with bpf_netpoll_release(). + * + * This function calls netpoll_setup() which takes rtnl_lock and may + * sleep, so it can only be used in sleepable BPF programs (SYSCALL). + * + * @opts: Pointer to struct bpf_netpoll_opts with connection parameters. + * @opts__sz: Size of the opts struct. + * @err: Integer to store error code when NULL is returned. + */ +__bpf_kfunc struct bpf_netpoll * +bpf_netpoll_create(const struct bpf_netpoll_opts *opts, u32 opts__sz, int *err) +{ + struct bpf_netpoll *bnp; + + if (!opts || opts__sz != sizeof(struct bpf_netpoll_opts)) { + *err = -EINVAL; + return NULL; + } + + if (opts->reserved) { + *err = -EINVAL; + return NULL; + } + + bnp = kzalloc(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