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.
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
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 | 267 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 318 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 083cc44aa952..8abb626fce6c 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -88,6 +88,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>
/*
@@ -105,6 +107,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..99ada80cfa41
--- /dev/null
+++ b/rust/helpers/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)
+{
+ return genlmsg_cancel(skb, hdr);
+}
+
+__rust_helper void rust_helper_genlmsg_end(struct sk_buff *skb, void *hdr)
+{
+ return genlmsg_end(skb, hdr);
+}
+
+__rust_helper void rust_helper_nlmsg_free(struct sk_buff *skb)
+{
+ return 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 a3c42e51f00a..0813185d8760 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -32,6 +32,7 @@
#include "err.c"
#include "irq.c"
#include "fs.c"
+#include "genetlink.c"
#include "io.c"
#include "jump_label.c"
#include "kunit.c"
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 3da92f18f4ee..c94658051d42 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -118,6 +118,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..4ac9e6d919fa
--- /dev/null
+++ b/rust/kernel/netlink.rs
@@ -0,0 +1,267 @@
+// 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.
+///
+/// # Invariants
+///
+/// The pointer has ownership over a valid `sk_buff`.
+pub struct SkBuff {
+ skb: NonNull<kernel::bindings::sk_buff>,
+}
+
+impl SkBuff {
+ /// Creates a new `SkBuff` with the given size.
+ pub fn new(size: usize, flags: alloc::Flags) -> Result<SkBuff, 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(SkBuff { skb })
+ }
+
+ /// Puts a generic netlink header into the `SkBuff`.
+ 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 SkBuff {
+ 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: SkBuff,
+ 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 `SkBuff` 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 `SkBuff` 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()) };
+ }
+}
+
+/// 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.
+ 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,
+ // SAFETY: This bitfield is represented as an u8.
+ _bitfield_1: unsafe {
+ core::mem::transmute::<u8, bindings::__BindgenBitfieldUnit<[u8; 1]>>(0b11u8)
+ },
+ 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.
+ 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 [`genl_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()) };
+ }
+}
--
2.53.0.473.g4a7958ca14-goog
> diff --git a/rust/kernel/netlink.rs b/rust/kernel/netlink.rs
...
> +/// 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.
> +///
> +/// # Invariants
> +///
> +/// The pointer has ownership over a valid `sk_buff`.
> +pub struct SkBuff {
> + skb: NonNull<kernel::bindings::sk_buff>,
> +}
struct sk_buff is a core data structure which appears all over the
networking stack, but also other places like crypto, scsi, tty, file
systems, etc. Since it is a top level data structure, it seems odd
Rust puts it into netlink.rs.
How do you see the Rust SkBuff evolving to a general purpose data
structure which can be used everywhere?
Andrew
On Sat, Mar 07, 2026 at 04:43:02PM +0100, Andrew Lunn wrote:
> > diff --git a/rust/kernel/netlink.rs b/rust/kernel/netlink.rs
>
> ...
>
> > +/// 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.
> > +///
> > +/// # Invariants
> > +///
> > +/// The pointer has ownership over a valid `sk_buff`.
> > +pub struct SkBuff {
> > + skb: NonNull<kernel::bindings::sk_buff>,
> > +}
>
> struct sk_buff is a core data structure which appears all over the
> networking stack, but also other places like crypto, scsi, tty, file
> systems, etc. Since it is a top level data structure, it seems odd
> Rust puts it into netlink.rs.
>
> How do you see the Rust SkBuff evolving to a general purpose data
> structure which can be used everywhere?
We can make a kernel::net module (rust/kernel/net/) and put it there
instead? I guess netlink.rs can also be a submodule of that.
Hmm ... but I'm currently using genlmsg_new() / nlmsg_free(), and I
assume the other use-cases do not go through those methods, since they
sound netlink specific.
To be honest, I'm new to the kernel's networking stack, so I probably
can't design Rust wrapper for sk_buff that supports all those different
usecases without someone walking me through how it works. It may or may
not be best to write a netlink-specific struct now and expand it later.
Alice
> We can make a kernel::net module (rust/kernel/net/) and put it there
> instead? I guess netlink.rs can also be a submodule of that.
>
> Hmm ... but I'm currently using genlmsg_new() / nlmsg_free(), and I
> assume the other use-cases do not go through those methods, since they
> sound netlink specific.
>
> To be honest, I'm new to the kernel's networking stack, so I probably
> can't design Rust wrapper for sk_buff that supports all those different
> usecases without someone walking me through how it works. It may or may
> not be best to write a netlink-specific struct now and expand it later.
At its core, an skbuff represents a block of memory. Normally that
memory would be packet data flowing through the network stack. You can
append to the front and back of the data in the buffer. You can add
the buffer to linked lists, there are reference counting operations,
so the buffer can have multiple users, there are places you can store
per protocol data, and places you can store data while within one
protocol layer, etc.
When using sk_buff for packet data, you are generally on a fast path,
so much of the code is in header files as inline functions. This is
going to make rust binding harder, my current understanding is that it
is hard to make use of such functions from rust.
However, for netlink, its is slow path code, it seems like all the
functions you need to call are in object files, not inline headers.
I also suspect, but don't know, that the netlink upper API is disjoint
to the packet handling upper API. How you allocate a skbuff for
netlink is different to packet data. It is very likely an error if you
try to transmit out an interface an skbuff allocated for netlink
usage, etc.
So, maybe you can have a base definition of skbuff in rust/kernel/net,
and build on top of that to make a netlink specific skbuff, with a
limited list of methods which can access it, those needed for netlink?
Make use of the Rust type system? And leave the messy fast path packet
data things for somebody else.
Andrew
On Sun, Mar 08, 2026 at 03:48:40PM +0100, Andrew Lunn wrote: > > We can make a kernel::net module (rust/kernel/net/) and put it there > > instead? I guess netlink.rs can also be a submodule of that. > > > > Hmm ... but I'm currently using genlmsg_new() / nlmsg_free(), and I > > assume the other use-cases do not go through those methods, since they > > sound netlink specific. > > > > To be honest, I'm new to the kernel's networking stack, so I probably > > can't design Rust wrapper for sk_buff that supports all those different > > usecases without someone walking me through how it works. It may or may > > not be best to write a netlink-specific struct now and expand it later. > > At its core, an skbuff represents a block of memory. Normally that > memory would be packet data flowing through the network stack. You can > append to the front and back of the data in the buffer. You can add > the buffer to linked lists, there are reference counting operations, > so the buffer can have multiple users, there are places you can store > per protocol data, and places you can store data while within one > protocol layer, etc. > > When using sk_buff for packet data, you are generally on a fast path, > so much of the code is in header files as inline functions. This is > going to make rust binding harder, my current understanding is that it > is hard to make use of such functions from rust. > > However, for netlink, its is slow path code, it seems like all the > functions you need to call are in object files, not inline headers. There are several static inline methods that I need to call. Here is the list: - genlmsg_new - genlmsg_multicast - genlmsg_cancel - genlmsg_end - nlmsg_free - genl_has_listeners And on Android devices running this code, they will in fact have been inlined into Rust code, permitting fast code without function call overhead. Inlining happens via my other (recent) patchset [1], which the Android kernel is already using in production. [1]: https://lore.kernel.org/all/20260203-inline-helpers-v2-0-beb8547a03c9@google.com/ > I also suspect, but don't know, that the netlink upper API is disjoint > to the packet handling upper API. How you allocate a skbuff for > netlink is different to packet data. It is very likely an error if you > try to transmit out an interface an skbuff allocated for netlink > usage, etc. If that is the case, then it sounds like the correct API design would be to have a separate NetlinkSkBuff type so that you cannot mix them up with an sk_buff used for packet data. > So, maybe you can have a base definition of skbuff in rust/kernel/net, > and build on top of that to make a netlink specific skbuff, with a > limited list of methods which can access it, those needed for netlink? > Make use of the Rust type system? And leave the messy fast path packet > data things for somebody else. That makes sense to me. If there is duplication between the two sk_buff types, then one can be defined in terms of the other (or maybe it's easier to not do that). Alice
> There are several static inline methods that I need to call. Here is the > list: > > - genlmsg_new > - genlmsg_multicast > - genlmsg_cancel > - genlmsg_end > - nlmsg_free > - genl_has_listeners > > And on Android devices running this code, they will in fact have been > inlined into Rust code, permitting fast code without function call > overhead. Inlining happens via my other (recent) patchset [1], which the > Android kernel is already using in production. > > [1]: https://lore.kernel.org/all/20260203-inline-helpers-v2-0-beb8547a03c9@google.com/ Ah, cool. That is going to be very useful in networking. > If that is the case, then it sounds like the correct API design would be > to have a separate NetlinkSkBuff type so that you cannot mix them up > with an sk_buff used for packet data. > > > So, maybe you can have a base definition of skbuff in rust/kernel/net, > > and build on top of that to make a netlink specific skbuff, with a > > limited list of methods which can access it, those needed for netlink? > > Make use of the Rust type system? And leave the messy fast path packet > > data things for somebody else. > > That makes sense to me. If there is duplication between the two sk_buff > types, then one can be defined in terms of the other (or maybe it's > easier to not do that). Probably struct sk_buff is one of those complex things where we make our best guess design now, and be prepared to redesign and refactor the code as we learn more. To me, it feels like we need a base SkBuff with full access to all API methods. And then a restricted type for NetlinkSkBuff on top of that for users of netlink. Andrew
On Sun, Mar 08, 2026 at 06:24:16PM +0100, Andrew Lunn wrote: > > If that is the case, then it sounds like the correct API design would be > > to have a separate NetlinkSkBuff type so that you cannot mix them up > > with an sk_buff used for packet data. > > > > > So, maybe you can have a base definition of skbuff in rust/kernel/net, > > > and build on top of that to make a netlink specific skbuff, with a > > > limited list of methods which can access it, those needed for netlink? > > > Make use of the Rust type system? And leave the messy fast path packet > > > data things for somebody else. > > > > That makes sense to me. If there is duplication between the two sk_buff > > types, then one can be defined in terms of the other (or maybe it's > > easier to not do that). > > Probably struct sk_buff is one of those complex things where we make > our best guess design now, and be prepared to redesign and refactor > the code as we learn more. To me, it feels like we need a base SkBuff > with full access to all API methods. And then a restricted type for > NetlinkSkBuff on top of that for users of netlink. Agreed. Though in this patchset, I don't believe I use any of the base skbuff methods, except for the fact that the nlmsg methods seem to wrap them. So it is probably dead code to add much right now unless I bypass the nlmsg wrappers. Alice
© 2016 - 2026 Red Hat, Inc.