From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f74.google.com (mail-wm1-f74.google.com [209.85.128.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5CF9C355F5F for ; Mon, 4 May 2026 09:05:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.74 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777885511; cv=none; b=r0ipq3jll371d1VbLqiCtLKSrrfvTZu22b8jrg8+eea3GjeCfV/UqyjahetSE5CpEawHk4nXfxxp7+Iuog8OLfSDfRsiXieoDqG+kMKQLsxJxAUCTU23biDF2i2teQwqauCb2DKEGiqt5Yn8B/tPTJUhHlSREHoG7J2I/oCsoE0= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777885511; c=relaxed/simple; bh=aNab5duyaOPqhLDWZ8/VUW/u5s/n1TryMWaLJDhduV0=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=fq21tXSTUdwzyNzuSLF6D3rYj54SMAVqpP4EfG2qREYo24P/mw6Hkzibu8FGpS86b86o3wFHY4pd1PTr6UCg/Yqrucsfmecpx/EF6avgpOuAeBnEFz2CVfQkNflpznmVf/Z7JfSqTqmQcABEzepJQ9/vPhlGNSUuCBNdSswgbRg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--aliceryhl.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=LLYqteSE; arc=none smtp.client-ip=209.85.128.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--aliceryhl.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="LLYqteSE" Received: by mail-wm1-f74.google.com with SMTP id 5b1f17b1804b1-488c2cc0cbaso32581895e9.3 for ; Mon, 04 May 2026 02:05:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1777885508; x=1778490308; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=Gv8OdnGsAYLQJgROgPIvQlwmVjXR+mbfDvwidHE+2tU=; b=LLYqteSEuTTSNuVjPesxBV5ttGVqcl6WyR3nQ/jAV7rhQkj+6deFM1n1Jyp3k4qBeU 2aJYNT9cqB9a2hRZn+3NfS6t/RU8wkY5I21N3DWW/wqsfaGMA1qyCjDUFpOTMxOaG6ha HgUs8atouH6Q/pS1ysy04XyrKKTRssAr8hKCy5/LRB1b9+ov1UOtuY5vJBuPwMCC7bRL xC2xtjGbVqEZY57JxfBj4EGOJpBtlZzptZflOyul5mRVEj2C9ZOC2lw9Mzc1enuJHbpi kuCz019slVEP/4ePPpnkAfJ/jHxZIRf+paCE1B35ek0OZp4pnJHLhCdgqTomys7c4p0d vf7g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777885508; x=1778490308; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=Gv8OdnGsAYLQJgROgPIvQlwmVjXR+mbfDvwidHE+2tU=; b=D52kcuQzamag6oWZSycyJl6kWDe0jbNwj+rpyvt5SCfaCHgu1rt9cdI6Szt10BD32G htYXOSz2WXABEi1kCmKBrRGaFkC0kIc7R/kdzkl3Bm/RGF3XurzK2Tot9BETRyhm4nW0 YA1zajkzF7nfIzyLXDqpHqrOEeh+I/2Z83w5OwaTzL4yGOOsQeEMrlllIvqAQN5pth28 w0dAU+omMvtDHQKx30nWXv3UMPTgOMVndhsExFb7gQR3tBC4AgAVB4s5btMmzKQccylb /+05nZ6Zja6fB375zrmzqi6NyzMWEM521ZE2V2zYg9rj+53EHsm8aOr2z9Yu7XWlCESE dmIw== X-Forwarded-Encrypted: i=1; AFNElJ9L+tz1cLofYFwmEeJvePQBUKNaNUR7aimYUtGZ2n9IwluD2JeExX9aE5q/aWoxGdqij1lE1k0=@vger.kernel.org X-Gm-Message-State: AOJu0YwEij5QdfR5IhKJeahUFwQuGb7pYLxF/M03+QZspvB8t9kgapO9 F/0vAVDsPdW6iN2wFNMVV7TsA5a6rQaDRNUKei8xTKc0aTQ8/oC4LWnV4Eq1Bzr2+gXT4hq6DS9 nNGs6+zG3IxQ1YctRBg== X-Received: from wmog7.prod.google.com ([2002:a05:600c:3107:b0:488:a71c:cf48]) (user=aliceryhl job=prod-delivery.src-stubby-dispatcher) by 2002:a05:600d:8408:b0:48a:56de:d63c with SMTP id 5b1f17b1804b1-48a988bdd98mr112245025e9.27.1777885507550; Mon, 04 May 2026 02:05:07 -0700 (PDT) Date: Mon, 04 May 2026 09:04:54 +0000 In-Reply-To: <20260504-binder-netlink-v4-0-601b41cd25b2@google.com> Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260504-binder-netlink-v4-0-601b41cd25b2@google.com> X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=15720; i=aliceryhl@google.com; h=from:subject:message-id; bh=aNab5duyaOPqhLDWZ8/VUW/u5s/n1TryMWaLJDhduV0=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBp+GFArgBvHeCoG0CT5V5kqdaMSYj9i9yhkXSY4 FWpfhfT+LiJAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCafhhQAAKCRAEWL7uWMY5 RsSFD/9kAHCxW0POTk4wO56oYHpkbeISckGvEPme2/AZOqgQwcUUiCvHCp0rvnjffJ2HKKNwMyc Mc81yHY1cuY83rpskXyH14ooYHQRM7KD2D2+NUbFcXcA5yfrmg7tiLJg46m984cUGilhZcCPGRk ElN65d4pGydvTv7xt90opOvsmcUq8TGEsuVbKm4mBPyrteWBODwv1rQv1u63l0+ly1fcyO4qhLX e5zj4mvfkgO63/BajNgsuB1SohqtKJmaKbUHjJ+AOfGwyy8rVgNE/63hbOWSV0PaNXVndtHzsm3 28C+aK8Jn8t3Fe6uslSpNchAN6v2tEDXbZ0LMImjlXBdIJMVPgoVLM/dcV0gqBrVesNjDb5qqzb qG2EUydqXuq0CINalGmsVcz8Xw1i8H+AVMYkxhI30STVvl4Xgyp8EmmG/fhE5utgPln7C4x4LMf 745mX5RK+4PeeTBPxgf6XPVB3FjF0h2WxUhsPVxLjUOYezkbkLHLkLSJANQMpi6ogjI6JeRaBph ehBSPs18hd6F/Pkm8K5LkAvIiBxTeC/krL1NTATGl2DwSy/RIWBdn0BRSM2EwFN4qWyeLoVmuVX 9Vluj52DAAPlMy2vLOnd5TFelsQ5tA48qK0/qB1a7oYoJ5oq8SWMk4XPo9FOjm7iO/qS2rjHdf2 K8bImVJ177M0RSQ== X-Mailer: b4 0.14.3 Message-ID: <20260504-binder-netlink-v4-1-601b41cd25b2@google.com> Subject: [PATCH v4 1/4] rust: netlink: add raw netlink abstraction From: Alice Ryhl To: Carlos Llamas , Greg Kroah-Hartman , Andrew Lunn , Donald Hunter , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Matthew Maurer Cc: Miguel Ojeda , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Benno Lossin , Andreas Hindborg , Trevor Gross , Danilo Krummrich , Christian Brauner , linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, netdev@vger.kernel.org, Alice Ryhl Content-Type: text/plain; charset="utf-8" This implements a safe and relatively simple API over the netlink API, that allows you to add different attributes to a netlink message and broadcast it. As the first user of this API only makes use of broadcast, only broadcast messages are supported here. This API is intended to be safe and to be easy to use in *generated* code. This is because netlink is generally used with yaml files that describe the underlying API, and the python generator outputs C code (or, soon, Rust code) that lets you use the API more easily. So for example, if there is a string field, the code generator will output a method that internall calls `put_string()` with the right attr type. Reviewed-by: Matthew Maurer Reviewed-by: Andrew Lunn Signed-off-by: Alice Ryhl --- rust/bindings/bindings_helper.h | 3 + rust/helpers/genetlink.c | 46 ++++++ rust/helpers/helpers.c | 1 + rust/kernel/lib.rs | 1 + rust/kernel/netlink.rs | 336 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 387 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 446dbeaf0866..612fa5388b7d 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -92,6 +92,8 @@ #include #include #include +#include +#include #include /* @@ -109,6 +111,7 @@ const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN; const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN = ARCH_KMALLOC_MINALIGN; const size_t RUST_CONST_HELPER_PAGE_SIZE = PAGE_SIZE; +const size_t RUST_CONST_HELPER_GENLMSG_DEFAULT_SIZE = GENLMSG_DEFAULT_SIZE; const gfp_t RUST_CONST_HELPER_GFP_ATOMIC = GFP_ATOMIC; const gfp_t RUST_CONST_HELPER_GFP_KERNEL = GFP_KERNEL; const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT; diff --git a/rust/helpers/genetlink.c b/rust/helpers/genetlink.c new file mode 100644 index 000000000000..3530b69f6cf7 --- /dev/null +++ b/rust/helpers/genetlink.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2026 Google LLC. + */ + +#include + +#ifdef CONFIG_NET + +__rust_helper struct sk_buff *rust_helper_genlmsg_new(size_t payload, gfp_t flags) +{ + return genlmsg_new(payload, flags); +} + +__rust_helper +int rust_helper_genlmsg_multicast(const struct genl_family *family, + struct sk_buff *skb, u32 portid, + unsigned int group, gfp_t flags) +{ + return genlmsg_multicast(family, skb, portid, group, flags); +} + +__rust_helper void rust_helper_genlmsg_cancel(struct sk_buff *skb, void *hdr) +{ + genlmsg_cancel(skb, hdr); +} + +__rust_helper void rust_helper_genlmsg_end(struct sk_buff *skb, void *hdr) +{ + genlmsg_end(skb, hdr); +} + +__rust_helper void rust_helper_nlmsg_free(struct sk_buff *skb) +{ + nlmsg_free(skb); +} + +__rust_helper +int rust_helper_genl_has_listeners(const struct genl_family *family, + struct net *net, unsigned int group) +{ + return genl_has_listeners(family, net, group); +} + +#endif diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 625921e27dfb..8de05ae7d928 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -62,6 +62,7 @@ #include "err.c" #include "irq.c" #include "fs.c" +#include "genetlink.c" #include "gpu.c" #include "io.c" #include "jump_label.c" diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index b72b2fbe046d..d69f13b77845 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -96,6 +96,7 @@ pub mod module_param; #[cfg(CONFIG_NET)] pub mod net; +pub mod netlink; pub mod num; pub mod of; #[cfg(CONFIG_PM_OPP)] diff --git a/rust/kernel/netlink.rs b/rust/kernel/netlink.rs new file mode 100644 index 000000000000..24177fb685b4 --- /dev/null +++ b/rust/kernel/netlink.rs @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Google LLC. + +//! Rust support for generic netlink. +//! +//! Currently only supports exposing multicast groups. +//! +//! C header: [`include/net/genetlink.h`](srctree/include/net/genetlink.h) +#![cfg(CONFIG_NET)] + +use kernel::{ + alloc::{self, AllocError}, + error::to_result, + prelude::*, + transmute::AsBytes, + types::Opaque, + ThisModule, +}; + +use core::{ + mem::ManuallyDrop, + ptr::NonNull, // +}; + +/// The default netlink message size. +pub const GENLMSG_DEFAULT_SIZE: usize = bindings::GENLMSG_DEFAULT_SIZE; + +/// A wrapper around `struct sk_buff` for generic netlink messages. +/// +/// This type is intended to be specific for buffers used with netlink only, and other usecases for +/// `struct sk_buff` are out-of-scope for this abstraction. +/// +/// # Invariants +/// +/// The pointer has ownership over a valid `sk_buff`. +pub struct NetlinkSkBuff { + skb: NonNull, +} + +impl NetlinkSkBuff { + /// Creates a new `NetlinkSkBuff` with the given size. + pub fn new(size: usize, flags: alloc::Flags) -> Result { + // SAFETY: `genlmsg_new` only requires its arguments to be valid integers. + let skb = unsafe { bindings::genlmsg_new(size, flags.as_raw()) }; + let skb = NonNull::new(skb).ok_or(AllocError)?; + Ok(NetlinkSkBuff { skb }) + } + + /// Puts a generic netlink header into the `NetlinkSkBuff`. + pub fn genlmsg_put( + self, + portid: u32, + seq: u32, + family: &'static Family, + cmd: u8, + ) -> Result { + let skb = self.skb.as_ptr(); + // SAFETY: The skb and family pointers are valid. + let hdr = unsafe { bindings::genlmsg_put(skb, portid, seq, family.as_raw(), 0, cmd) }; + let hdr = NonNull::new(hdr).ok_or(AllocError)?; + Ok(GenlMsg { skb: self, hdr }) + } +} + +impl Drop for NetlinkSkBuff { + fn drop(&mut self) { + // SAFETY: We have ownership over the `sk_buff`, so we may free it. + unsafe { bindings::nlmsg_free(self.skb.as_ptr()) } + } +} + +/// A generic netlink message being constructed. +/// +/// # Invariants +/// +/// `hdr` references the header in this netlink message. +pub struct GenlMsg { + skb: NetlinkSkBuff, + hdr: NonNull, +} + +impl GenlMsg { + /// Puts an attribute into the message. + #[inline] + fn put(&mut self, attrtype: c_int, value: &T) -> Result + where + T: ?Sized + AsBytes, + { + let skb = self.skb.skb.as_ptr(); + let len = size_of_val(value); + let ptr = core::ptr::from_ref(value).cast::(); + // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and the provided value is + // readable and initialized for its `size_of` bytes. + to_result(unsafe { bindings::nla_put(skb, attrtype, len as c_int, ptr) }) + } + + /// Puts a `u32` attribute into the message. + #[inline] + pub fn put_u32(&mut self, attrtype: c_int, value: u32) -> Result { + self.put(attrtype, &value) + } + + /// Puts a string attribute into the message. + #[inline] + pub fn put_string(&mut self, attrtype: c_int, value: &CStr) -> Result { + self.put(attrtype, value.to_bytes_with_nul()) + } + + /// Puts a flag attribute into the message. + #[inline] + pub fn put_flag(&mut self, attrtype: c_int) -> Result { + let skb = self.skb.skb.as_ptr(); + // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and a null pointer is valid + // when the length is zero. + to_result(unsafe { bindings::nla_put(skb, attrtype, 0, core::ptr::null()) }) + } + + /// Sends the generic netlink message as a multicast message. + #[inline] + pub fn multicast( + self, + family: &'static Family, + portid: u32, + group: u32, + flags: alloc::Flags, + ) -> Result { + let me = ManuallyDrop::new(self); + // SAFETY: The `skb` and `family` pointers are valid. We pass ownership of the `skb` to + // `genlmsg_multicast` by not dropping `self`. + unsafe { + bindings::genlmsg_end(me.skb.skb.as_ptr(), me.hdr.as_ptr()); + to_result(bindings::genlmsg_multicast( + family.as_raw(), + me.skb.skb.as_ptr(), + portid, + group, + flags.as_raw(), + )) + } + } +} +impl Drop for GenlMsg { + fn drop(&mut self) { + // SAFETY: The `hdr` pointer references the header of this generic netlink message. + unsafe { bindings::genlmsg_cancel(self.skb.skb.as_ptr(), self.hdr.as_ptr()) }; + } +} + +/// Flags for a generic netlink family. +struct FamilyFlags { + /// Whether the family supports network namespaces. + netnsok: bool, + /// Whether the family supports parallel operations. + parallel_ops: bool, +} + +impl FamilyFlags { + /// Converts the flags to the bitfield representation used by `genl_family`. + const fn into_bitfield(self) -> bindings::__BindgenBitfieldUnit<[u8; 1]> { + // The below shifts are verified correct by test_family_flags_bitfield() below. + // + // Although bindgen generates helpers to change bitfields based on the C headers, these + // helpers unfortunately can't be used in const context. Since `Family` needs to be filled + // out at build-time, we use this helper instead. + let mut bits = 0; + if self.netnsok { + bits |= 1 << 0; + } + if self.parallel_ops { + bits |= 1 << 1; + } + // SAFETY: This bitfield is represented as an u8. + unsafe { core::mem::transmute::>(bits) } + } +} + +/// A generic netlink family. +#[repr(transparent)] +pub struct Family { + inner: Opaque, +} + +// SAFETY: The `Family` type is thread safe. +unsafe impl Sync for Family {} + +impl Family { + /// Creates a new `Family` instance. + /// + /// Intended to be used from const context only. Will panic if provided with invalid arguments. + /// + /// The name must be a nul-terminated string, but it is taken as `&[u8]` so that it can be used + /// more conveniently with the strings generated by bindgen. + pub const fn const_new( + module: &ThisModule, + name: &[u8], + version: u32, + mcgrps: &'static [MulticastGroup], + ) -> Family { + let n_mcgrps = mcgrps.len() as u8; + if n_mcgrps as usize != mcgrps.len() { + panic!("too many mcgrps"); + } + let mut genl_family = bindings::genl_family { + version, + _bitfield_1: FamilyFlags { + netnsok: true, + parallel_ops: true, + } + .into_bitfield(), + module: module.as_ptr(), + mcgrps: mcgrps.as_ptr().cast(), + n_mcgrps, + ..pin_init::zeroed() + }; + if CStr::from_bytes_with_nul(name).is_err() { + panic!("genl_family name not nul-terminated"); + } + if genl_family.name.len() < name.len() { + panic!("genl_family name too long"); + } + let mut i = 0; + while i < name.len() { + genl_family.name[i] = name[i]; + i += 1; + } + Family { + inner: Opaque::new(genl_family), + } + } + + /// Checks if there are any listeners for the given multicast group. + pub fn has_listeners(&self, group: u32) -> bool { + // SAFETY: The family and init_net pointers are valid. + unsafe { + bindings::genl_has_listeners(self.as_raw(), &raw mut bindings::init_net, group) != 0 + } + } + + /// Returns a raw pointer to the underlying `genl_family` structure. + pub fn as_raw(&self) -> *mut bindings::genl_family { + self.inner.get() + } +} + +/// A generic netlink multicast group. +#[repr(transparent)] +pub struct MulticastGroup { + // No Opaque because fully immutable + group: bindings::genl_multicast_group, +} + +// SAFETY: Pure data so thread safe. +unsafe impl Sync for MulticastGroup {} + +impl MulticastGroup { + /// Creates a new `MulticastGroup` instance. + /// + /// Intended to be used from const context only. Will panic if provided with invalid arguments. + pub const fn const_new(name: &CStr) -> MulticastGroup { + let mut group: bindings::genl_multicast_group = pin_init::zeroed(); + + let name = name.to_bytes_with_nul(); + if group.name.len() < name.len() { + panic!("genl_multicast_group name too long"); + } + let mut i = 0; + while i < name.len() { + group.name[i] = name[i]; + i += 1; + } + + MulticastGroup { group } + } +} + +/// A registration of a generic netlink family. +/// +/// This type represents the registration of a [`Family`]. When an instance of this type is +/// dropped, its respective generic netlink family will be unregistered from the system. +/// +/// # Invariants +/// +/// `self.family` always holds a valid reference to an initialized and registered [`Family`]. +pub struct Registration { + family: &'static Family, +} + +impl Family { + /// Registers the generic netlink family with the kernel. + pub fn register(&'static self) -> Result { + // SAFETY: `self.as_raw()` is a valid pointer to a `genl_family` struct. + // The `genl_family` struct is static, so it will outlive the registration. + to_result(unsafe { bindings::genl_register_family(self.as_raw()) })?; + Ok(Registration { family: self }) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + // SAFETY: `self.family.as_raw()` is a valid pointer to a registered `genl_family` struct. + // The `Registration` struct ensures that `genl_unregister_family` is called exactly once + // for this family when it goes out of scope. + unsafe { bindings::genl_unregister_family(self.family.as_raw()) }; + } +} + +#[macros::kunit_tests(rust_netlink)] +mod tests { + use super::*; + + #[test] + fn test_family_flags_bitfield() { + for netnsok in [false, true] { + for parallel_ops in [false, true] { + let mut b_fam = bindings::genl_family { + ..Default::default() + }; + b_fam.set_netnsok(if netnsok { 1 } else { 0 }); + b_fam.set_parallel_ops(if parallel_ops { 1 } else { 0 }); + + let c_bitfield = FamilyFlags { + netnsok, + parallel_ops, + } + .into_bitfield(); + + // SAFETY: The bit field is stored as u8. + let b_val: u8 = unsafe { core::mem::transmute(b_fam._bitfield_1) }; + // SAFETY: The bit field is stored as u8. + let c_val: u8 = unsafe { core::mem::transmute(c_bitfield) }; + assert_eq!(b_val, c_val); + } + } + } +} -- 2.54.0.545.g6539524ca2-goog