From nobody Thu Apr 9 18:39:21 2026 Received: from mail-ej1-f73.google.com (mail-ej1-f73.google.com [209.85.218.73]) (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 77DA63BFE2B for ; Wed, 8 Apr 2026 12:20:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650860; cv=none; b=IXJXQehvLIEHJKie/j6BIBdrbfc2piboSzN61Mf7vyraeIC/7I5GdSoRRxndti0TAlKo+hVIRHEPHPn6AcCs0kVguMoG/NHTZaXzAjWO2fQ9MzzWr5Dy6YXxQpNbj2lAWmxqQrtuu0/3CrFxuDecQxIbTYYMqvSIKXvG3KZ6sGE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650860; c=relaxed/simple; bh=yZvYdVWBDvLIZ6HEqld1jyrWz2gsf4uAQfc6Q7CCOwQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=EiPuWHG13i0xGHKLYX3as6Zg3PXLB8d60tlaUeLvhks7kIm3Xjh4DDWVMZqOvF57+uLzGH5ZiVhMCToofkmymlzjWIRY6D1MZS3J/A0exLTKm+hdI0MqW+MY0ARplSky+TYTPh2eBc9VI0JOJ8l3BnD5tVqazrNfQsaS0JWf38Q= 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=ITc+eMit; arc=none smtp.client-ip=209.85.218.73 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="ITc+eMit" Received: by mail-ej1-f73.google.com with SMTP id a640c23a62f3a-b9c0787da95so436723666b.1 for ; Wed, 08 Apr 2026 05:20:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1775650857; x=1776255657; 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=fXdrcxOY+ra4AhqbeBBn+7QQvi+9RB9g/FL0JO9wGJU=; b=ITc+eMitcRtVP02kNkN+Q2JXWrZcuWaobYqroFUa5z8lrf2gnTLSbXgqkp49kDEcO6 Xn/iX9RItjb0H2A+N9hld8u4RWVuVd0NP/X/l5Nu0zi5Z7gP+D4GySU303GnjRK9yCXk Bhexr5NeDZ/v7DWtwXwnvLvJP+6RzFRuEBIjr1/1s2ZPerk7aZly6dKZz7w9pOf30CWz tUQ2l92D45EVlUH3HKSbWiQ+j8jamWG4CRRQ9azorEHfizl36sl3jEJa6/oe5tnqQSp/ JZNEAK9Rs79TGw+YVT7+o3fbeljtx/Ow4ZU3qUvIOQ7BIgp7/cI09EgaPXVlFXT12ZxZ YJeQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775650857; x=1776255657; 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=fXdrcxOY+ra4AhqbeBBn+7QQvi+9RB9g/FL0JO9wGJU=; b=U+Wh+Qgv3aWjfqADaNNLd73pvSmGl7K6JtBIB7LKVSKk7ADkKXCNVhL6cXxpAD4xJh x4NlMzmMnfSNXfFoix69IVVz4HnMbxdeLvdhKtmXo4rqss1Iv3pHYT42JN7iSJQ/w9HM AR9tyShF2E/mOBMV/YfPO7wPMujsNAXkAh6o3zSLWY6rd4QpRMKaktj1tH/Z8m4RZ8a8 f7PWGsVL8UhOL/8Erp2AYNI+92Olz7nDyULY/j563NVffDIItJcrNPj/e+PcrzqfDEAy l9aAbcUGvMi776hyhI5bO7krB1H4U68HHOUo7jdlE9ti7Cz3J16cEeDDrNSvTFUUOyx2 Of5Q== X-Gm-Message-State: AOJu0YxI3iWhNOrrVsbFcHOoHrSBT50J+4FMPHHWFN56cg03JHKAPRGM ZS7ODAgeynOlP3R/xy2nfkijJ2NpTue1Pewl5bRddrfL7uLn2UzF8PH7YmbgyRgP69UYR6gveeZ qZWAoI3VBaq+trJ1R+g== X-Received: from ejchq38.prod.google.com ([2002:a17:907:3f26:b0:b97:9d7b:f134]) (user=aliceryhl job=prod-delivery.src-stubby-dispatcher) by 2002:a17:907:60cb:b0:b9c:6e68:c19c with SMTP id a640c23a62f3a-b9c6e68c265mr918053766b.52.1775650855973; Wed, 08 Apr 2026 05:20:55 -0700 (PDT) Date: Wed, 08 Apr 2026 12:20:44 +0000 In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=15250; i=aliceryhl@google.com; h=from:subject:message-id; bh=yZvYdVWBDvLIZ6HEqld1jyrWz2gsf4uAQfc6Q7CCOwQ=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBp1kgkLGR+M+zp9ckR55knfR3pmLMuxCQkP82qv EpygYkoBXKJAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCadZIJAAKCRAEWL7uWMY5 Ro3JD/4szf5ScqrCLDwjwvyZvsl9uV8IAX0g3O8SPe/LL94YoMZLAgaGUqCoSOqnBeRkl3Dnb5B jC3E9xnyLNYPrYcQqc7slgElTYm2xPhl95IwdO6ieHztkXGbyzEmtwNfksRnpKdcUnDk5j6DSO4 8FbOxa9pnOWWxNvxEYSMhgceytnSwTE17cw9jJA8WHB+fdmjmxIGi6+UNpoT1F1ldegu9a3SABv +fdtCV4kjJwCUoWfvj/JrMrcXD+Iat3VakB9Fnni/dQudlkK9nY9lE5W5dTwY+IYGQKO3ee6tWl v43UxUZ4Nq9YIWWHHVdYOI+bVpFvTiNiPpffM0NkcCVo0DCbDMdcqJwfYPrke37GGcA5fQVnrXu Uh0i9xgDuI4F67oS1h4anV5ENAmjGicTrUFZTfGBkWAXPoFSsEXGJPJHicSiYNk8TMz+q6EN/He 5+aSoZC057vyUrXSm6kHOM3IUMtuWVH+NlNKb9LBrkkFkqohWfB9d4TZljYfi0hxJsvxvzdAGLC zafxTlIfCFhDxJyMvnc0gH7K0DSuBTk+FJJyrLMhK8q/Uy79OLI8qezk4hti3JZpsnlgnCg9Vxy S5+vCCath0DlEnF4NwD5nBG/ylb3jYGmRmpocVHygIQqjnZ0iZeqlF3RK67ghsq6ZFZ+VUtfTWu tEVqrFiGJ7FuZOA== X-Mailer: b4 0.14.3 Message-ID: <20260408-binder-netlink-v2-1-c0d327d15435@google.com> Subject: [PATCH v2 1/4] rust: netlink: add raw netlink abstraction From: Alice Ryhl To: Miguel Ojeda , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Benno Lossin , Andreas Hindborg , Trevor Gross , Danilo Krummrich , Donald Hunter , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Greg Kroah-Hartman , "=?utf-8?q?Arve_Hj=C3=B8nnev=C3=A5g?=" , Todd Kjos , Christian Brauner , Carlos Llamas Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, netdev@vger.kernel.org, Alice Ryhl Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 --- 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 | 329 ++++++++++++++++++++++++++++++++++++= ++++ 5 files changed, 380 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 083cc44aa952..8abb626fce6c 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -88,6 +88,8 @@ #include #include #include +#include +#include #include =20 /* @@ -105,6 +107,7 @@ const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN =3D ARCH_SLAB_MINALIGN; const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN =3D ARCH_KMALLOC_MINA= LIGN; const size_t RUST_CONST_HELPER_PAGE_SIZE =3D PAGE_SIZE; +const size_t RUST_CONST_HELPER_GENLMSG_DEFAULT_SIZE =3D GENLMSG_DEFAULT_SI= ZE; const gfp_t RUST_CONST_HELPER_GFP_ATOMIC =3D GFP_ATOMIC; const gfp_t RUST_CONST_HELPER_GFP_KERNEL =3D GFP_KERNEL; const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT =3D 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 + +#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 *h= dr) +{ + 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 d93292d47420..f5ea0ae0b6b7 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -122,6 +122,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..21f959c95fdc --- /dev/null +++ b/rust/kernel/netlink.rs @@ -0,0 +1,329 @@ +// 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 =3D 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 onl= y, 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 i= ntegers. + let skb =3D unsafe { bindings::genlmsg_new(size, flags.as_raw()) }; + let skb =3D 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 =3D self.skb.as_ptr(); + // SAFETY: The skb and family pointers are valid. + let hdr =3D unsafe { bindings::genlmsg_put(skb, portid, seq, famil= y.as_raw(), 0, cmd) }; + let hdr =3D 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 =3D self.skb.skb.as_ptr(); + let len =3D size_of_val(value); + let ptr =3D 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 =3D 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 =3D ManuallyDrop::new(self); + // SAFETY: The `skb` and `family` pointers are valid. We pass owne= rship 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_fa= mily`. + const fn into_bitfield(self) -> bindings::__BindgenBitfieldUnit<[u8; 1= ]> { + // The below shifts are verified correct by test_family_flags_bitf= ield() below. + // + // Although bindgen generates helpers to change bitfields based on= the C headers, these + // helpers unfortunately can't be used in const context. Since `Fa= mily` needs to be filled + // out at build-time, we use this helper instead. + let mut bits =3D 0; + if self.netnsok { + bits |=3D 1 << 0; + } + if self.parallel_ops { + bits |=3D 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. + pub const fn const_new( + module: &ThisModule, + name: &[u8], + version: u32, + mcgrps: &'static [MulticastGroup], + ) -> Family { + let n_mcgrps =3D mcgrps.len() as u8; + if n_mcgrps as usize !=3D mcgrps.len() { + panic!("too many mcgrps"); + } + let mut genl_family =3D 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 =3D 0; + while i < name.len() { + genl_family.name[i] =3D name[i]; + i +=3D 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) !=3D 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 =3D pin_init::zeroed= (); + + let name =3D name.to_bytes_with_nul(); + if group.name.len() < name.len() { + panic!("genl_multicast_group name too long"); + } + let mut i =3D 0; + while i < name.len() { + group.name[i] =3D name[i]; + i +=3D 1; + } + + MulticastGroup { group } + } +} + +/// A registration of a generic netlink family. +/// +/// This type represents the registration of a [`Family`]. When an instanc= e of this type is +/// dropped, its respective generic netlink family will be unregistered fr= om the system. +/// +/// # Invariants +/// +/// `self.family` always holds a valid reference to an initialized and reg= istered [`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` s= truct. + // The `genl_family` struct is static, so it will outlive the regi= stration. + 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 register= ed `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 =3D 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 =3D FamilyFlags { + netnsok, + parallel_ops, + } + .into_bitfield(); + + // SAFETY: The bit field is stored as u8. + let b_val: u8 =3D unsafe { core::mem::transmute(b_fam._bit= field_1) }; + // SAFETY: The bit field is stored as u8. + let c_val: u8 =3D unsafe { core::mem::transmute(c_bitfield= ) }; + assert_eq!(b_val, c_val); + } + } + } +} --=20 2.53.0.1213.gd9a14994de-goog From nobody Thu Apr 9 18:39:21 2026 Received: from mail-wr1-f74.google.com (mail-wr1-f74.google.com [209.85.221.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 1CD853C0639 for ; Wed, 8 Apr 2026 12:20:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650860; cv=none; b=Rz81mFTWpY98SdSF9uFc4vnHbNzlC8b6LfqM5J2AAI6QesWR8xHQ70tSPvtRM/3ZowUvKjIxdIYCkzyavhx3+4jqO5ZCwXFNwRv4Rr7XnRjkFAJl+oebEWXMnjrJuMt+VigF8J2IbiHPCbZYsHWjQyehFb7EUO+D9hh6rtNUE3o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650860; c=relaxed/simple; bh=wZSfHnb0WFA82Shw+hfej3jBicvTJMZBLptOwNaRHVM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=EEXe7u8OLTBq+oyLXK/81J3gCzuE5jEOSpY+WGbrrDyByu+jraO9yJjcrk3YeWMyhtMhKM+Kr0N+6gF4pxRzNDNPqtdt6Lz1g4ODcOaKR9oYMVL1r7+jf4AxCG02vmtXVFGgi2fnXq4ja8LX4Q0a26oj96eZhUjU4jjV7sHYiYE= 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=demiRG6n; arc=none smtp.client-ip=209.85.221.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="demiRG6n" Received: by mail-wr1-f74.google.com with SMTP id ffacd0b85a97d-43d1fec59c9so550327f8f.0 for ; Wed, 08 Apr 2026 05:20:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1775650857; x=1776255657; 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=XkmVhqTIndPqmzNXhrJEUquBBrmigEyy/3MslXmYI/0=; b=demiRG6nEI1CaIBR0j7Cbn9H+teeBr1q91isDFZT+8R5vN1ydo4TUTAWHFFjr1VFRf uM0Ql495BWd/iOwo0vvL3ksiDJ4qc/yUZlOYf6oTseKtCrMIB/P9abotxnBc5ZReaeTv P4usKjeNLQXtMXalBrz58hmKP8Zl3un0me84a4RreWZSDS66H21s9Zih9CeWXQUrVNn1 V8eUqLM67G8CUEhwiO8l//JpJXTo7fTWodOi8JT9pqrF1vigWjqMkFIcNx9MOqQWToJu eyaqnIFKgZvXd6G/Ex177OL9uwKxR5pfv8fhLnHhEMEZZWaD8IqpeZTOt8P6vGlRPjHx JDjQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775650857; x=1776255657; 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=XkmVhqTIndPqmzNXhrJEUquBBrmigEyy/3MslXmYI/0=; b=OeeyV8ZVH8hnRgiEfduVhWaYxTO4cq8EVXHX8+Uyfnv9QpS4dWc8ozCOB77Igdzqid ut4N+hq3c6RrdsTRaj/XMha2VXhznFtaSzfaLRgWeXkLqzYejms/mKPM6I7LEkDlcZFJ 9MoeQNLs6jr7mnlKn+CtYLhcOPfcSCm6m98fPsX+DpsALKZrKfpOpYN+oFeOtsFbZfHS G5qam/4rWfFqPdrwtMSz/6D8ZIXw2PcsbZOqcVBaBFWocC/CIMjF8vjaIpFRUWoIoUwH Acw2hpFCKP5gqQAiD3uDbUfBk2Y1ZZjyWutjP++txa2E+70SzudUSTHLxmolVLrb+OP9 RfVg== X-Gm-Message-State: AOJu0Yw0DJC+SZKm+wCDcoX0etGRnGbzdCkLdMaxWyGywV7jkxQchklV 1flWqi6woZnWeo42wB3RG8PKeeEKwEcO1ahCqFAK3Tu+6hVh5PBf8Qif2KZS5uAW2qS9lhidW1f jIcTW0QrhzeQTOmURsg== X-Received: from wrpd7.prod.google.com ([2002:adf:f2c7:0:b0:43b:465d:97b6]) (user=aliceryhl job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6000:401e:b0:43c:cda2:4f2a with SMTP id ffacd0b85a97d-43d28f8df8dmr30840975f8f.4.1775650857410; Wed, 08 Apr 2026 05:20:57 -0700 (PDT) Date: Wed, 08 Apr 2026 12:20:45 +0000 In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=7631; i=aliceryhl@google.com; h=from:subject:message-id; bh=wZSfHnb0WFA82Shw+hfej3jBicvTJMZBLptOwNaRHVM=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBp1kgkY6hKLbppEH2Jt3n4927kVD63A9S+doFBU 26tKNv0Am+JAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCadZIJAAKCRAEWL7uWMY5 Rq3aD/9e2btAznxLvztedA8gQSPhb2OdjrzgL0ehqRSj5nIIP2ygd2ctrV5KvSOhvQVjWC9VSn2 GeawxnqPJFOylVBpsmMBefGtqvn6dQpP5QqT6NFtFW9ebRIrTg5goipwLZ5gthWHguBjWgRE1oO W/R+0q4KY9wM4DNjfm1TmFLNHCGtPqJeWqrYz5IJhEuYav4mQfDCvXAKB/Ft5zZaL3rTAzAhI7I oBv+07xhiza57bCUNVlKu5RDpC3lD0QVsaCWxpO7XjO/6aRBkAtEleFDN20lHxKCjblilIpzrGi BuXglnUIL2JdZ1ajuP0eNVSVDjdhd3QuXjWgAMcCjhCQ7ngdpejZJ1lBBz9WI3lObMZLn47cvAy xHnC1RRPMFW8oBvz6HtGgGALi47FxChBnIJLybhUzoHKM4xJf7DDh+YVyg4D1w5XkUMSBo6H7BJ k7TXWofSs1ZTqcwc1pr6tqZZiyBQ4FQjceQxKeR9frSkADGyic7huXg35LuJX6BvVD/IrP97PQ7 TS5HwhrctdRwKNb5FYpxMS3Nr1mngCzsm+Mc0Gepq70XykcHePr4PshYdaZRsC9qx8O9Mr7XmXh zry+xv/jUb69MpoORGu5SS8pSU3myK9RP4Wbju0mpaFN2Hh0tt54RZJ1kIapNXWHI+cWu6QhxtJ BoKwBN6CbLJYfTA== X-Mailer: b4 0.14.3 Message-ID: <20260408-binder-netlink-v2-2-c0d327d15435@google.com> Subject: [PATCH v2 2/4] ynl_gen: generate Rust files from yaml files From: Alice Ryhl To: Miguel Ojeda , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Benno Lossin , Andreas Hindborg , Trevor Gross , Danilo Krummrich , Donald Hunter , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Greg Kroah-Hartman , "=?utf-8?q?Arve_Hj=C3=B8nnev=C3=A5g?=" , Todd Kjos , Christian Brauner , Carlos Llamas Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, netdev@vger.kernel.org, Alice Ryhl Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable To generate netlink frames from Rust code easily, generate Rust libraries with methods for generating different netlink messages as appropriate. The new 'rust' type corresponds to a Rust version of the C target 'kernel'. There is no Rust version of the 'uapi' target since Rust code exports its uapi via C headers - choice of language is opaque to userspace. This logic is kept in the existing ynl_gen_c.py file to reuse CodeWriter and other shared pieces of logic in the existing python file. This has the disadvantage that the gen_c part of the name is now wrong, as it also generates Rust. One possible solution to this could be to rename the file. Signed-off-by: Alice Ryhl --- tools/net/ynl/pyynl/ynl_gen_c.py | 132 +++++++++++++++++++++++++++++++++++= +++- tools/net/ynl/ynl-regen.sh | 2 +- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen= _c.py index 0e1e486c1185..702d34727b6e 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -19,6 +19,7 @@ import pathlib import os import re import shutil +import subprocess import sys import tempfile import yaml as pyyaml @@ -1744,6 +1745,19 @@ class CodeWriter: else: self.p('}' + line) =20 + def array_start(self, line=3D''): + if line: + line =3D line + ' ' + self.p(line + '[') + self._ind +=3D 1 + + def array_end(self, line=3D''): + if line and line[0] not in {';', ','}: + line =3D ' ' + line + self._ind -=3D 1 + self._nl =3D False + self.p(']' + line) + def write_doc_line(self, doc, indent=3DTrue): words =3D doc.split() line =3D ' *' @@ -3415,10 +3429,119 @@ def find_kernel_root(full_path): return full_path, sub_path[:-1] =20 =20 +def render_rust(family, cw): + cw.p('#![allow(unreachable_pub, clippy::wrong_self_convention)]') + cw.p('use kernel::netlink::{Family, MulticastGroup};') + cw.p('use kernel::prelude::*;') + cw.nl() + + family_upper =3D c_upper(family.ident_name) + family_name =3D f'{family_upper}_NL_FAMILY' + mcgrps_name =3D f'{family_name}_MCGRPS' + + cw.p(f'pub static {family_name}: Family =3D Family::const_new(') + cw._ind +=3D 1 + cw.p('&crate::THIS_MODULE,') + cw.p(f'kernel::uapi::{family.fam_key},') + cw.p(f'kernel::uapi::{family.ver_key},') + cw.p(f'&{mcgrps_name},') + cw._ind -=3D 1 + cw.p(');') + cw.nl() + + if family.mcgrps['list']: + cw.array_start(f'static {mcgrps_name}: [MulticastGroup; {len(famil= y.mcgrps["list"])}] =3D ') + for grp in family.mcgrps['list']: + cw.p(f'MulticastGroup::const_new(c"{grp["name"]}"),') + cw.array_end(';') + cw.nl() + + for idx, (op_name, op) in enumerate(item for item in family.msgs.items= () if 'event' in item[1]): + struct_name =3D op_name.capitalize() + + if 'doc' in op: + doc_lines =3D op['doc'].strip().split('\n') + for line in doc_lines: + cw.p(f'/// {line.strip()}') + + cw.block_start(f'pub struct {struct_name}') + cw.p('skb: kernel::netlink::GenlMsg,') + cw.block_end() + cw.nl() + + cw.block_start(f'impl {struct_name}') + cw.p('/// Create a new multicast message.') + cw.p('pub fn new(') + cw._ind +=3D 1 + cw.p('size: usize,') + cw.p('portid: u32,') + cw.p('seq: u32,') + cw.p('flags: kernel::alloc::Flags,') + cw._ind -=3D 1 + cw.block_start(') -> Result') + cw.p(f'const {op.enum_name}: u8 =3D kernel::uapi::{op.enum_name} a= s u8;') + cw.p('let skb =3D kernel::netlink::NetlinkSkBuff::new(size, flags)= ?;') + cw.p(f'let skb =3D skb.genlmsg_put(portid, seq, &{family_name}, {o= p.enum_name})?;') + cw.p('Ok(Self { skb })') + cw.block_end() + cw.nl() + + cw.p('/// Broadcast this message.') + cw.block_start('pub fn multicast(self, portid: u32, flags: kernel:= :alloc::Flags) -> Result') + cw.p(f'self.skb.multicast(&{family_name}, portid, {idx}, flags)') + cw.block_end() + cw.nl() + + cw.p('/// Check if this message type has listeners.') + cw.block_start('pub fn has_listeners() -> bool') + cw.p(f'{family_name}.has_listeners({idx})') + cw.block_end() + + attr_set_name =3D op['attribute-set'] + attr_set =3D family.attr_sets[attr_set_name] + event_attrs =3D op['event']['attributes'] + + for attr_name in event_attrs: + attr =3D attr_set[attr_name] + method_name =3D attr_name.replace('-', '_') + + if attr.type =3D=3D 'u32': + put_fn =3D 'put_u32' + arg_str =3D ', val' + method_args =3D '(&mut self, val: u32)' + elif attr.type =3D=3D 'string': + put_fn =3D 'put_string' + arg_str =3D ', val' + method_args =3D '(&mut self, val: &CStr)' + elif attr.type =3D=3D 'flag': + put_fn =3D 'put_flag' + arg_str =3D '' + method_args =3D '(&mut self)' + else: + put_fn =3D 'put_u32' + arg_str =3D ', val' + method_args =3D f'(&mut self, val: {attr.type})' + + cw.nl() + if 'doc' in attr.yaml: + doc_lines =3D attr.yaml['doc'].strip().split('\n') + for line in doc_lines: + cw.p(f'/// {line.strip()}') + + cw.block_start(f'pub fn {method_name}{method_args} -> Result') + cw.p(f'const {attr.enum_name}: c_int =3D kernel::uapi::{attr.e= num_name} as c_int;') + cw.p(f'self.skb.{put_fn}({attr.enum_name}{arg_str})') + cw.block_end() + + cw.block_end() + cw.nl() + cw.p(' ') + + def main(): parser =3D argparse.ArgumentParser(description=3D'Netlink simple parsi= ng generator') parser.add_argument('--mode', dest=3D'mode', type=3Dstr, required=3DTr= ue, - choices=3D('user', 'kernel', 'uapi')) + choices=3D('user', 'kernel', 'uapi', 'rust')) parser.add_argument('--spec', dest=3D'spec', type=3Dstr, required=3DTr= ue) parser.add_argument('--header', dest=3D'header', action=3D'store_true'= , default=3DNone) parser.add_argument('--source', dest=3D'header', action=3D'store_false= ') @@ -3471,6 +3594,13 @@ def main(): render_uapi(parsed, cw) return =20 + if args.mode =3D=3D 'rust': + render_rust(parsed, cw) + cw.close_out_file() + if args.out_file: + subprocess.run(['rustfmt', '--edition', '2021', args.out_file]) + return + hdr_prot =3D f"_LINUX_{parsed.c_name.upper()}_GEN_H" if args.header: cw.p('#ifndef ' + hdr_prot) diff --git a/tools/net/ynl/ynl-regen.sh b/tools/net/ynl/ynl-regen.sh index d9809276db98..4f5ceb4fe147 100755 --- a/tools/net/ynl/ynl-regen.sh +++ b/tools/net/ynl/ynl-regen.sh @@ -17,7 +17,7 @@ done KDIR=3D$(dirname $(dirname $(dirname $(dirname $(realpath $0))))) pushd ${search:-$KDIR} >>/dev/null =20 -files=3D$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user= \)') +files=3D$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user= \|rust\)') for f in $files; do # params: 0 1 2 3 # $YAML YNL-GEN kernel $mode --=20 2.53.0.1213.gd9a14994de-goog From nobody Thu Apr 9 18:39:21 2026 Received: from mail-wr1-f73.google.com (mail-wr1-f73.google.com [209.85.221.73]) (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 677353C1996 for ; Wed, 8 Apr 2026 12:21:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650862; cv=none; b=XY6NgFJujZ4TJkx63U2I8jwRkcrT3y2OEoUzBNHeOJpnUQxevti+PNckXPqfIfOJhYnpIKiyDQcCzI7jbWqqePEoh+7aCukSI6Natu9aDOE95jBQ/oDg47v6/mKt46T3j6ssgWhtKLthabUIg+tJRtKePDByWWFPz3LH/pyx7xM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650862; c=relaxed/simple; bh=CzcAYIaFHkE+89TDt97IaYCt9YKdTOLMvtVgqltTBvU=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=lbrlIl6L3xDKMfVJCarB37ZcWeFgIFOSQWkADEZh8xCohStE1TIyV3eQZ3RmEzKP3K8DN8UDhYTf4Ha+KdPQar9ydspBpgn5C8lIz/tWP7qzvEKgnGpVto0VyAjTuFD00R1rPQcDO4bNGiYopXQNi40iFNMRx/Kp1X7GTUnDCHE= 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=DWEdTESG; arc=none smtp.client-ip=209.85.221.73 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="DWEdTESG" Received: by mail-wr1-f73.google.com with SMTP id ffacd0b85a97d-43cff5bc312so4562310f8f.1 for ; Wed, 08 Apr 2026 05:21:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1775650859; x=1776255659; 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=otAjrBmCGps90JtJ0ZRC66OaIMxDxpfCCAO6f/cO6a4=; b=DWEdTESG0b3z+i0cd+8ZVhm7knN/U9qRdqHS9zfXzW20vItSs/t5lQSf9LamgVkooH Q796/C7UzpYcSglnjQ3ptxIRP2CgOcJ2UKpdYQuy0xo1HSADGzNgQySF6FgPJ8oHOq1b y9ByhjL9/LGHVX6byFjSi7kCQm0nUOCPe1DrryqLycKjSLqKAXAsu1erKltjB86YLgeJ Lbm9jl0p1zzJpoTxL8y/hAm21KUpO5GKlLP8Z7hErWDs0EhrsoSXiya8+gKlQnqfMtRr sHX8tnzrgASA+xdTawe2veEAAHc0blsgvWEHwN+sqFK46yvbsd8fAHKqM4qNg19usbme uVMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775650859; x=1776255659; 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=otAjrBmCGps90JtJ0ZRC66OaIMxDxpfCCAO6f/cO6a4=; b=dTCN1GQ6efIL9rAFaxi1xrgUBlwA6IVD8cDal+YUDKv61o0mNLgyRyA+cfi6er43xj NtjrOeaXvGRqiXNILrHK6424ZQHS1a5Q9hbgHCPLLHk5Y6WMkSNEktQhr6GEU7LvXJ6h zYdu+JDm+Ur4L43YSt1H0xFlToKd4S+iEJ/4fbnmflALc0DF0LMQoUvRbdki5r7RrEJm dWZdtwjz/kUxxuP29IVE6EkqBuaE0/mODAQRokh7YtSXYCNtXmMF4ZRsSEvo+d3JjKJh h9Z+tQzYvBfTaDbywaIdfOI+DR9pR5UVcRU03tmmSPIZUAripG5BcSOquj7Uex1mZxzA S0RQ== X-Gm-Message-State: AOJu0Yy3sVGnrDVVTmkke/0za1l1RQa2lDCAw1oYbbG13y1xJ8cnUb5q 2E09Z+h45/Zxx3UVEnnykCOTGEpoaQCa8NK+V0rqGiU6+7Qf03S5oniTWMRNFxSc7boZv658/AH NOx05Kf1pOWxd6q2UzQ== X-Received: from wraj2.prod.google.com ([2002:a5d:4522:0:b0:43c:8e1f:502e]) (user=aliceryhl job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6000:1202:b0:43d:301c:ecbb with SMTP id ffacd0b85a97d-43d301ced27mr18928068f8f.14.1775650858756; Wed, 08 Apr 2026 05:20:58 -0700 (PDT) Date: Wed, 08 Apr 2026 12:20:46 +0000 In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=7773; i=aliceryhl@google.com; h=from:subject:message-id; bh=CzcAYIaFHkE+89TDt97IaYCt9YKdTOLMvtVgqltTBvU=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBp1kgksm8OAwqfLYcwRRuQZVq9VDzEGCvXmTvnt kpN8+PYQpuJAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCadZIJAAKCRAEWL7uWMY5 RugjD/9y97CFtvhw239xA/YZ56CT3/0860IukRTSyXH04axbppu9lj9a0nlG8MyRWD9qo0ETuGi EVwQ5nP/0j8nDV610RnvoMTn8R+STeORSpdKE6MH3cTLL2JJCzqz9NHGvmObWiKE4lKU6nM7T56 xnf8NxrVh09Z1Iowr9MbzUFVT2GvLqRJPdoz/ENoZHxTBOYMIzxw2TDLK4CJDoXhu5shjiAa7+F 9Hnduecl1pnoS6oIoKPnTXc2c34nGnFdgvf/DTYQ0N1Vaop8vEeZhA+CLkZNXX713rMc0DFrjaA 2wGrZIPAU6yABZH9gsS7cnkQ6ZqSa1VUSXwW8ZRALC+3/EA494pJU3DaaTuk5KbCRTav8XWrJ+L MP94+vzLiD4DfQ/VuRyxA0LN6Dq4YyxIzWC9jNfsubbanF7omXkXdFf08c0vZwdsSwE3e7O5Cyw PmfSS+Qdso4daiOaADPQLlDRjtNuQcUXT/ZM1lS4L2TOE3VRw9uPrSgSDKUfaqJQO6C2j5SOia+ opkgg2+AizkdpPC8ByFFqhodiBSnwSIJiR9LNtPOHxp5P7b4rQHl8UxEhTDslYJ9NIZ3W0AmZjM pZhQjVIWBBSV+rpjBi/TDO+oS0IlkMaUkZGw7+oS8Mu2VW6DHwwpGkY28ZgC3lx+DHwvRg+v3d+ u0lbTHhOwpHTctQ== X-Mailer: b4 0.14.3 Message-ID: <20260408-binder-netlink-v2-3-c0d327d15435@google.com> Subject: [PATCH v2 3/4] rust_binder: add generated netlink.rs file From: Alice Ryhl To: Miguel Ojeda , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Benno Lossin , Andreas Hindborg , Trevor Gross , Danilo Krummrich , Donald Hunter , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Greg Kroah-Hartman , "=?utf-8?q?Arve_Hj=C3=B8nnev=C3=A5g?=" , Todd Kjos , Christian Brauner , Carlos Llamas Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, netdev@vger.kernel.org, Alice Ryhl Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable To use netlink from Rust Binder, add a new generated netlink file using the new script and Documentation/netlink/specs/binder.yaml. Signed-off-by: Alice Ryhl --- drivers/android/Kconfig | 2 +- drivers/android/binder/netlink.rs | 113 +++++++++++++++++++++++++= ++++ drivers/android/binder/rust_binder_main.rs | 9 ++- rust/uapi/uapi_helper.h | 1 + 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index e2e402c9d175..606a9d07f774 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -16,7 +16,7 @@ config ANDROID_BINDER_IPC =20 config ANDROID_BINDER_IPC_RUST bool "Rust version of Android Binder IPC Driver" - depends on RUST && MMU && !ANDROID_BINDER_IPC + depends on RUST && MMU && NET && !ANDROID_BINDER_IPC help This enables the Rust implementation of the Binder driver. =20 diff --git a/drivers/android/binder/netlink.rs b/drivers/android/binder/net= link.rs new file mode 100644 index 000000000000..2a842c7b1b33 --- /dev/null +++ b/drivers/android/binder/netlink.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Cl= ause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/binder.yaml */ +/* YNL-GEN rust source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#![allow(unreachable_pub, clippy::wrong_self_convention)] +use kernel::netlink::{Family, MulticastGroup}; +use kernel::prelude::*; + +pub static BINDER_NL_FAMILY: Family =3D Family::const_new( + &crate::THIS_MODULE, + kernel::uapi::BINDER_FAMILY_NAME, + kernel::uapi::BINDER_FAMILY_VERSION, + &BINDER_NL_FAMILY_MCGRPS, +); + +static BINDER_NL_FAMILY_MCGRPS: [MulticastGroup; 1] =3D [MulticastGroup::c= onst_new(c"report")]; + +/// A multicast event sent to userspace subscribers to notify them about +/// binder transaction failures. The generated report provides the full +/// details of the specific transaction that failed. The intention is for +/// programs to monitor these events and react to the failures as needed. +pub struct Report { + skb: kernel::netlink::GenlMsg, +} + +impl Report { + /// Create a new multicast message. + pub fn new( + size: usize, + portid: u32, + seq: u32, + flags: kernel::alloc::Flags, + ) -> Result { + const BINDER_CMD_REPORT: u8 =3D kernel::uapi::BINDER_CMD_REPORT as= u8; + let skb =3D kernel::netlink::NetlinkSkBuff::new(size, flags)?; + let skb =3D skb.genlmsg_put(portid, seq, &BINDER_NL_FAMILY, BINDER= _CMD_REPORT)?; + Ok(Self { skb }) + } + + /// Broadcast this message. + pub fn multicast(self, portid: u32, flags: kernel::alloc::Flags) -> Re= sult { + self.skb.multicast(&BINDER_NL_FAMILY, portid, 0, flags) + } + + /// Check if this message type has listeners. + pub fn has_listeners() -> bool { + BINDER_NL_FAMILY.has_listeners(0) + } + + /// The enum binder_driver_return_protocol returned to the sender. + pub fn error(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_ERROR: c_int =3D kernel::uapi::BINDER_A_REPO= RT_ERROR as c_int; + self.skb.put_u32(BINDER_A_REPORT_ERROR, val) + } + + /// The binder context where the transaction occurred. + pub fn context(&mut self, val: &CStr) -> Result { + const BINDER_A_REPORT_CONTEXT: c_int =3D kernel::uapi::BINDER_A_RE= PORT_CONTEXT as c_int; + self.skb.put_string(BINDER_A_REPORT_CONTEXT, val) + } + + /// The PID of the sender process. + pub fn from_pid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_FROM_PID: c_int =3D kernel::uapi::BINDER_A_R= EPORT_FROM_PID as c_int; + self.skb.put_u32(BINDER_A_REPORT_FROM_PID, val) + } + + /// The TID of the sender thread. + pub fn from_tid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_FROM_TID: c_int =3D kernel::uapi::BINDER_A_R= EPORT_FROM_TID as c_int; + self.skb.put_u32(BINDER_A_REPORT_FROM_TID, val) + } + + /// The PID of the recipient process. This attribute may not be present + /// if the target could not be determined. + pub fn to_pid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_TO_PID: c_int =3D kernel::uapi::BINDER_A_REP= ORT_TO_PID as c_int; + self.skb.put_u32(BINDER_A_REPORT_TO_PID, val) + } + + /// The TID of the recipient thread. This attribute may not be present + /// if the target could not be determined. + pub fn to_tid(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_TO_TID: c_int =3D kernel::uapi::BINDER_A_REP= ORT_TO_TID as c_int; + self.skb.put_u32(BINDER_A_REPORT_TO_TID, val) + } + + /// When present, indicates the failed transaction is a reply. + pub fn is_reply(&mut self) -> Result { + const BINDER_A_REPORT_IS_REPLY: c_int =3D kernel::uapi::BINDER_A_R= EPORT_IS_REPLY as c_int; + self.skb.put_flag(BINDER_A_REPORT_IS_REPLY) + } + + /// The bitmask of enum transaction_flags from the transaction. + pub fn flags(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_FLAGS: c_int =3D kernel::uapi::BINDER_A_REPO= RT_FLAGS as c_int; + self.skb.put_u32(BINDER_A_REPORT_FLAGS, val) + } + + /// The application-defined code from the transaction. + pub fn code(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_CODE: c_int =3D kernel::uapi::BINDER_A_REPOR= T_CODE as c_int; + self.skb.put_u32(BINDER_A_REPORT_CODE, val) + } + + /// The transaction payload size in bytes. + pub fn data_size(&mut self, val: u32) -> Result { + const BINDER_A_REPORT_DATA_SIZE: c_int =3D kernel::uapi::BINDER_A_= REPORT_DATA_SIZE as c_int; + self.skb.put_u32(BINDER_A_REPORT_DATA_SIZE, val) + } +} diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/b= inder/rust_binder_main.rs index 678e987902aa..9057e5dba7ed 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -36,6 +36,8 @@ mod deferred_close; mod defs; mod error; +#[allow(dead_code)] +mod netlink; mod node; mod page_range; mod process; @@ -286,19 +288,22 @@ fn ptr_align(value: usize) -> Option { // SAFETY: We call register in `init`. static BINDER_SHRINKER: Shrinker =3D unsafe { Shrinker::new() }; =20 -struct BinderModule {} +struct BinderModule { + _netlink: kernel::netlink::Registration, +} =20 impl kernel::Module for BinderModule { fn init(_module: &'static kernel::ThisModule) -> Result { // SAFETY: The module initializer never runs twice, so we only cal= l this once. unsafe { crate::context::CONTEXTS.init() }; =20 + let netlink =3D crate::netlink::BINDER_NL_FAMILY.register()?; BINDER_SHRINKER.register(c"android-binder")?; =20 // SAFETY: The module is being loaded, so we can initialize binder= fs. unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())?= }; =20 - Ok(Self {}) + Ok(Self { _netlink: netlink }) } } =20 diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h index 06d7d1a2e8da..86c7b6b284b0 100644 --- a/rust/uapi/uapi_helper.h +++ b/rust/uapi/uapi_helper.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include --=20 2.53.0.1213.gd9a14994de-goog From nobody Thu Apr 9 18:39:21 2026 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 047BE38E5EB for ; Wed, 8 Apr 2026 12:21:01 +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=1775650863; cv=none; b=ibl6nGjARrzN3kkvdq0pvLlCZGFS4vtcg/X3NBspvwUrLgVQsFySQ+ey6GygQtXmndl9okyUPlxgloQhF/evzIPvP0NBXCXhdEEC+/h6/4ZlYMx6PBVDm5pqBJWEtSoeeHQUsSBfbOlwcs8tEhYL3AqavjNr9eY5KuFAaLY7y9w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775650863; c=relaxed/simple; bh=F7f94fTfMSJCY3wmlwluznC0FF90gOMcL3zlAtjwpw4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=HFmgC5fqis9twMymktOTIExjFMhHgXFV+BDZWhQzid+KxkibAb9Q8dhefsbFA30CnrToFL6SskjVziXjf7PRpLjfZR9M+mODerWzZJ8WNAwVv2i43J7ElOp+nYJfA7eeB4QTj21KdsC2kiQxK64l1iMu1ISH0DA43ZLNjUGlBeE= 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=huRpiYMP; 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="huRpiYMP" Received: by mail-wm1-f74.google.com with SMTP id 5b1f17b1804b1-488c768a9a9so3998385e9.1 for ; Wed, 08 Apr 2026 05:21:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1775650860; x=1776255660; 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=ZBYY3QvrCgPn9EPvO5/3tLsHRY+z899eCOLcISOcO7I=; b=huRpiYMPhPcbw0/smYBOrUlf4gwuOxSxWrxQY11H16/5mdrmtEUMFl8p4PG+Z982mJ rNeePg8RxlIp+MnnBYgWfgE4bSiJw/MynYMUmM2iiX5o/T4/3tRR6uBh9uQkmdXCpaKf V5/q+yVdIvzWPrPXlA2AAJ1rp4A2vabixPFoxP9nKZf9+H3b91GhAsq0yxdcpTDQJBXd uUCXAwuuSdSO22YCB2Fvuoo+OpWqiYOzgh2vWAvlVp+++bqpfIDnVjiq1fobXEvrrq3d ZMum0zRXG2R8rcH72AGDOw/uPAJUiJtQNE4umptMmYyajVQ2jD772bp2GbwUPBQ2fAmY 7ReA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775650860; x=1776255660; 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=ZBYY3QvrCgPn9EPvO5/3tLsHRY+z899eCOLcISOcO7I=; b=ZMkqXMrZ2LSbeeqot6i5jOVohwskQm5rQGZA2LZWkaY/edEG1SYAfvpSG15rLod93S hpHevA4Vw4V3W95/5TkK0e+tYlnR6LGiaTynocusiNRzuD+Alf8J7QwyYW7/9WaB7K34 C4buxc70B7U9HDE4zIji0oGNfss2yDGW8mdyOnJF8BQb7+Jx/XO2U0TpVDysukYUs/ey Q4LNZ4lOQVX5G83pLF74lob5Lhsg9wpQz/D2SPf2J0eA/UAhZevVgRduK0nSQZI05EpO Ro4yxkh4/h4tgOGEDlATglDPzwzYARfah4SK56nGLU+QNbiJKPkIYo1OT9+0vYKh6+aq Srhw== X-Gm-Message-State: AOJu0Yx6KqAo46FOnWG7LVM87cQm84c/IhhwiYOAk3Z/pgIinBriB9d2 YxFf9apz3eJVReN0U5u9/CDiv9A/1XjWcxmeR8WgFLvTIQDRPrfhXE2HSf0qLcVwqHDwXZnplLa wUnknlWue17s21U3opA== X-Received: from wrxm1.prod.google.com ([2002:a05:6000:81:b0:43d:2a3:461a]) (user=aliceryhl job=prod-delivery.src-stubby-dispatcher) by 2002:a05:600c:c8d:b0:486:ffa3:594 with SMTP id 5b1f17b1804b1-488997a6883mr281760915e9.23.1775650860293; Wed, 08 Apr 2026 05:21:00 -0700 (PDT) Date: Wed, 08 Apr 2026 12:20:47 +0000 In-Reply-To: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260408-binder-netlink-v2-0-c0d327d15435@google.com> X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=4005; i=aliceryhl@google.com; h=from:subject:message-id; bh=1Ltud0DFd3FPi7N3hRuEEuwQSMUXr9ABKDC9dGaJ4cE=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBp1kgk4LO5HLhqZ54TgromOML55f8Rta56qfp/X 8Oj7TaPvDyJAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCadZIJAAKCRAEWL7uWMY5 Ri3VEAC0ni7lJPZGIMUoJmkzj8liJFDdboT66VViQYnK97KHVhmpSLziVblb3o8bAIC/nZKGEf8 D3335KeXl9fOruzsAFbxFHSXxEhbA1EODisgSHH7PeQFFUm5vWth7aU8xWJJUMj7V+EmrW2GIXk oWZrMj56YSOpdhNKu3XHQC1rPkDiX5xBo43G6EnyuuGtAcqx8XnSNmJGPG3IQudRYNH+Vtyluyb AvemSCTXL4eWuuNbTJuNwj7It4t3FrnFUOpgvM1XWorJdiiXfAWrkWeuZmG9bgaEtbBRo6udnZ6 4TREjucV+0Rd41DwqsLvHisr/mIN+8lQ0LZLGPEVfTUkADu0XvHnCR6MeYzBwpBVSi+Ex0D17lu mfw2+8J/QkA6Vliu3ZpHNIGUoDp+cOkLS+gO8WczxoBUN25zw25lyyTrgPxPxmnjjplE90rVzO1 55k1LBaum9d5cMWYAVPLCYjdZoJE04dZazTtEP7vN1KxX56C21CoGYsRtlKyCibbIvWNn8+1vkF 2myKoLL8SBdDhMChbZUqeRWOauAU9kProNhtLKaN6F/D0vzZzd3OsBzUX9by/2gPceP7rwEgoZh U7IiVaTB72TgX9vzMU+pEJ0/S2ZQJhHlDwKwdCeI72d2GkjjcmDs8jE2KI81aMW9idHwHC1PXwI LzsvXUgkgonx3MQ== X-Mailer: b4 0.14.3 Message-ID: <20260408-binder-netlink-v2-4-c0d327d15435@google.com> Subject: [PATCH v2 4/4] rust_binder: report netlink transactions From: Alice Ryhl To: Miguel Ojeda , Boqun Feng , Gary Guo , "=?utf-8?q?Bj=C3=B6rn_Roy_Baron?=" , Benno Lossin , Andreas Hindborg , Trevor Gross , Danilo Krummrich , Donald Hunter , Jakub Kicinski , "David S. Miller" , Eric Dumazet , Paolo Abeni , Simon Horman , Greg Kroah-Hartman , "=?utf-8?q?Arve_Hj=C3=B8nnev=C3=A5g?=" , Todd Kjos , Christian Brauner , Carlos Llamas Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, netdev@vger.kernel.org, Alice Ryhl Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Carlos Llamas The Android Binder driver supports a netlink API that reports transaction *failures* to a userapce daemon. This allows devices to monitor processes with many failed transactions so that it can e.g. kill misbehaving apps. One very important thing that this monitors is when many oneway messages are sent to a frozen process, so there is special handling to ensure this scenario is surfaced over netlink. Signed-off-by: Carlos Llamas Signed-off-by: Alice Ryhl --- drivers/android/binder/rust_binder_main.rs | 1 - drivers/android/binder/thread.rs | 9 +++++++ drivers/android/binder/transaction.rs | 40 ++++++++++++++++++++++++++= ++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/b= inder/rust_binder_main.rs index 9057e5dba7ed..407cda7bd766 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -36,7 +36,6 @@ mod deferred_close; mod defs; mod error; -#[allow(dead_code)] mod netlink; mod node; mod page_range; diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thre= ad.rs index 97d5f31e8fe3..e9bff4956ac8 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -1263,6 +1263,15 @@ fn transaction(self: &Arc, cmd: u32, reader: &= mut UserSliceReader) -> Resu } } =20 + if info.oneway_spam_suspect { + // If this is both a oneway spam suspect and a failure, we rep= ort it twice. This is + // useful in case the transaction failed with BR_TRANSACTION_P= ENDING_FROZEN. + info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx); + } + if info.reply !=3D 0 { + info.report_netlink(info.reply, &self.process.ctx); + } + Ok(()) } =20 diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder= /transaction.rs index 47d5e4d88b07..3fa7091ed8a6 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -3,6 +3,7 @@ // Copyright (C) 2025 Google LLC. =20 use kernel::{ + netlink::GENLMSG_DEFAULT_SIZE, prelude::*, seq_file::SeqFile, seq_print, @@ -17,6 +18,7 @@ allocation::{Allocation, TranslatedFds}, defs::*, error::{BinderError, BinderResult}, + netlink::Report, node::{Node, NodeRef}, process::{Process, ProcessInner}, ptr_align, @@ -49,6 +51,44 @@ impl TransactionInfo { pub(crate) fn is_oneway(&self) -> bool { self.flags & TF_ONE_WAY !=3D 0 } + + pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) { + if let Err(err) =3D self.report_netlink_inner(reply, ctx) { + pr_warn!( + "{}:{} netlink report failed: {err:?}\n", + self.from_pid, + self.from_tid + ); + } + } + + fn report_netlink_inner(&self, reply: u32, ctx: &crate::Context) -> ke= rnel::error::Result { + if !Report::has_listeners() { + return Ok(()); + } + let mut report =3D Report::new(GENLMSG_DEFAULT_SIZE, 0, 0, GFP_KER= NEL)?; + + report.error(reply)?; + report.context(&ctx.name)?; + report.from_pid(self.from_pid as u32)?; + report.from_tid(self.from_tid as u32)?; + if self.to_pid !=3D 0 { + report.to_pid(self.to_pid as u32)?; + } + if self.to_tid !=3D 0 { + report.to_tid(self.to_tid as u32)?; + } + + if self.is_reply { + report.is_reply()?; + } + report.flags(self.flags)?; + report.code(self.code)?; + report.data_size(self.data_size as u32)?; + + report.multicast(0, GFP_KERNEL)?; + Ok(()) + } } =20 use core::mem::offset_of; --=20 2.53.0.1213.gd9a14994de-goog