summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rust/bindings/bindings_helper.h3
-rw-r--r--rust/helpers/helpers.c1
-rw-r--r--rust/helpers/net/genetlink.c46
-rw-r--r--rust/kernel/net/mod.rs2
-rw-r--r--rust/kernel/net/netlink.rs337
5 files changed, 389 insertions, 0 deletions
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1124785e210b..ec96e811610d 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -93,6 +93,8 @@
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/xarray.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
#include <trace/events/rust_sample.h>
/*
@@ -110,6 +112,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/helpers.c b/rust/helpers/helpers.c
index 998e31052e66..1d4ee51f576b 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -72,6 +72,7 @@
#include "maple_tree.c"
#include "mm.c"
#include "mutex.c"
+#include "net/genetlink.c"
#include "of.c"
#include "page.c"
#include "pci.c"
diff --git a/rust/helpers/net/genetlink.c b/rust/helpers/net/genetlink.c
new file mode 100644
index 000000000000..3530b69f6cf7
--- /dev/null
+++ b/rust/helpers/net/genetlink.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2026 Google LLC.
+ */
+
+#include <net/genetlink.h>
+
+#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/kernel/net/mod.rs b/rust/kernel/net/mod.rs
index fe415cb369d3..8ecae7577ed2 100644
--- a/rust/kernel/net/mod.rs
+++ b/rust/kernel/net/mod.rs
@@ -4,3 +4,5 @@
#[cfg(CONFIG_RUST_PHYLIB_ABSTRACTIONS)]
pub mod phy;
+
+pub mod netlink;
diff --git a/rust/kernel/net/netlink.rs b/rust/kernel/net/netlink.rs
new file mode 100644
index 000000000000..22ef3dde36fa
--- /dev/null
+++ b/rust/kernel/net/netlink.rs
@@ -0,0 +1,337 @@
+// 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)
+
+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<kernel::bindings::sk_buff>,
+}
+
+impl NetlinkSkBuff {
+ /// Creates a new `NetlinkSkBuff` with the given size.
+ pub fn new(size: usize, flags: alloc::Flags) -> Result<NetlinkSkBuff, AllocError> {
+ // 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<GenlMsg, AllocError> {
+ 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<c_void>,
+}
+
+impl GenlMsg {
+ /// Puts an attribute into the message.
+ #[inline]
+ fn put<T>(&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::<c_void>();
+ // 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;
+ }
+ // Convert from little endian to the target's endianness.
+ bits = u8::from_le(bits);
+ // SAFETY: This bitfield is represented as an u8.
+ unsafe { core::mem::transmute::<u8, bindings::__BindgenBitfieldUnit<[u8; 1]>>(bits) }
+ }
+}
+
+/// A generic netlink family.
+#[repr(transparent)]
+pub struct Family {
+ inner: Opaque<bindings::genl_family>,
+}
+
+// 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<Registration> {
+ // 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);
+ }
+ }
+ }
+}