| // 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, | |
| num::casts::u16_as_usize, | |
| 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, | |
| { | |
| // `nla_len` is a 16-bit field that encodes the total attribute length | |
| // (header + payload). Subtracting the header size from `u16::MAX` gives | |
| // the largest payload that still fits within that field. | |
| const MAX_PAYLOAD_LEN: usize = u16_as_usize(u16::MAX) - size_of::<bindings::nlattr>(); | |
| let skb = self.skb.skb.as_ptr(); | |
| let len = size_of_val(value); | |
| let ptr = core::ptr::from_ref(value).cast::<c_void>(); | |
| if len > MAX_PAYLOAD_LEN { | |
| return Err(EMSGSIZE); | |
| } | |
| // 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); | |
| } | |
| } | |
| } | |
| } |