From 5abd5d4f3ef623bb6216cb6c8ed33350007bf173 Mon Sep 17 00:00:00 2001 From: Sergii Bogomolov Date: Thu, 23 Jul 2026 22:04:58 +0200 Subject: [PATCH] linux: add `NLMSG_*` helpers --- libc-test/build.rs | 3 + libc-test/semver/linux.txt | 7 ++ libc-test/src/nlmsg.c | 35 +++++++++ libc-test/tests/nlmsg.rs | 108 ++++++++++++++++++++++++++++ src/new/linux_uapi/linux/netlink.rs | 41 +++++++++++ 5 files changed, 194 insertions(+) create mode 100644 libc-test/src/nlmsg.c create mode 100644 libc-test/tests/nlmsg.rs diff --git a/libc-test/build.rs b/libc-test/build.rs index c1bfa119282b4..4ea8bf9bfc990 100755 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -56,6 +56,9 @@ fn do_cc() { if target.contains("android") || (target.contains("linux") && !target.contains("wasm32")) { cc::Build::new().file("src/errqueue.c").compile("errqueue"); } + if target.contains("linux") && !target.contains("android") && !target.contains("wasm32") { + cc::Build::new().file("src/nlmsg.c").compile("nlmsg"); + } if (target.contains("linux") && !target.contains("wasm32")) || target.contains("l4re") || target.contains("android") diff --git a/libc-test/semver/linux.txt b/libc-test/semver/linux.txt index 83d3f080e515c..56b1c2afc8298 100644 --- a/libc-test/semver/linux.txt +++ b/libc-test/semver/linux.txt @@ -2148,11 +2148,18 @@ NLA_F_NESTED NLA_F_NET_BYTEORDER NLA_TYPE_MASK NLDLY +NLMSG_ALIGN +NLMSG_DATA NLMSG_DONE NLMSG_ERROR +NLMSG_LENGTH NLMSG_MIN_TYPE +NLMSG_NEXT NLMSG_NOOP +NLMSG_OK NLMSG_OVERRUN +NLMSG_PAYLOAD +NLMSG_SPACE NLM_F_ACK NLM_F_ACK_TLVS NLM_F_APPEND diff --git a/libc-test/src/nlmsg.c b/libc-test/src/nlmsg.c new file mode 100644 index 0000000000000..a10c61b9b6c20 --- /dev/null +++ b/libc-test/src/nlmsg.c @@ -0,0 +1,35 @@ +#include +#include + +// Since the NLMSG_* helpers are macros instead of functions, they aren't +// available to FFI. libc must reimplement them, which is error-prone. This +// file provides FFI access to the actual macros so they can be tested against +// the Rust reimplementation. + +int nlmsg_align_ffi(size_t size) { + return NLMSG_ALIGN(size); +} + +int nlmsg_length_ffi(size_t size) { + return NLMSG_LENGTH(size); +} + +int nlmsg_space_ffi(size_t size) { + return NLMSG_SPACE(size); +} + +void *nlmsg_data_ffi(struct nlmsghdr *nlh) { + return NLMSG_DATA(nlh); +} + +struct nlmsghdr *nlmsg_next_ffi(struct nlmsghdr *nlh, int *size) { + return NLMSG_NEXT(nlh, *size); +} + +int nlmsg_ok_ffi(struct nlmsghdr *nlh, int size) { + return NLMSG_OK(nlh, size); +} + +int nlmsg_payload_ffi(struct nlmsghdr *nlh, int size) { + return NLMSG_PAYLOAD(nlh, size); +} diff --git a/libc-test/tests/nlmsg.rs b/libc-test/tests/nlmsg.rs new file mode 100644 index 0000000000000..5d33e21653551 --- /dev/null +++ b/libc-test/tests/nlmsg.rs @@ -0,0 +1,108 @@ +//! Compare libc's NLMSG_* functions against the actual C macros, for various inputs. + +#![cfg(target_os = "linux")] + +use libc::{ + self, + c_int, + c_void, + nlmsghdr, + size_t, +}; + +extern "C" { + fn nlmsg_align_ffi(size: size_t) -> c_int; + fn nlmsg_length_ffi(size: size_t) -> c_int; + fn nlmsg_space_ffi(size: size_t) -> c_int; + fn nlmsg_data_ffi(nlh: *mut nlmsghdr) -> *mut c_void; + fn nlmsg_next_ffi(nlh: *mut nlmsghdr, size: *mut c_int) -> *mut nlmsghdr; + fn nlmsg_ok_ffi(nlh: *mut nlmsghdr, size: c_int) -> c_int; + fn nlmsg_payload_ffi(nlh: *mut nlmsghdr, size: c_int) -> c_int; +} + +#[test] +fn test_sizes() { + for size in [0, 1, 2, 3, 4, 5, 15, 16, 17, 100, 4095, 4096] { + assert_eq!(libc::NLMSG_ALIGN(size), unsafe { nlmsg_align_ffi(size) }); + assert_eq!(libc::NLMSG_LENGTH(size), unsafe { nlmsg_length_ffi(size) }); + assert_eq!(libc::NLMSG_SPACE(size), unsafe { nlmsg_space_ffi(size) }); + } +} + +/// Write message headers with lengths `lens` into `buf`, at NLMSG_ALIGN spacing. +/// +/// `[u32]` keeps the buffer 4-byte aligned for `nlmsghdr`; NLMSG_ALIGN keeps every +/// message offset a multiple of 4, so each header dereference stays aligned. +fn fill(buf: &mut [u32], lens: &[u32]) -> c_int { + let mut total = 0; + for len in lens { + assert!(total + size_of::() <= size_of_val(buf)); + let hdr = nlmsghdr { + nlmsg_len: *len, + nlmsg_type: 0, + nlmsg_flags: 0, + nlmsg_seq: 0, + nlmsg_pid: 0, + }; + unsafe { + buf.as_mut_ptr() + .cast::() + .add(total) + .cast::() + .write(hdr); + } + total += libc::NLMSG_ALIGN(*len as size_t) as usize; + } + assert!(total <= size_of_val(buf)); + total as c_int +} + +// Walk a multipart message with the Rust and C implementations in lockstep, +// comparing every step. +#[test] +fn test_walk() { + let mut buf = [0u32; 32]; + let total = fill(&mut buf, &[21, 16, 30]); + + let mut nlh = buf.as_mut_ptr().cast::(); + let mut len = total; + let mut len_c = total; + let mut seen = 0; + loop { + let ok = unsafe { libc::NLMSG_OK(nlh, len) }; + assert_eq!(ok, unsafe { nlmsg_ok_ffi(nlh, len_c) } != 0); + if !ok { + break; + } + unsafe { + assert_eq!(libc::NLMSG_DATA(nlh), nlmsg_data_ffi(nlh)); + for size in [0, 4, 8] { + assert_eq!(libc::NLMSG_PAYLOAD(nlh, size), nlmsg_payload_ffi(nlh, size)); + } + } + let next = unsafe { libc::NLMSG_NEXT(nlh, &mut len) }; + let next_c = unsafe { nlmsg_next_ffi(nlh, &mut len_c) }; + assert_eq!(next, next_c); + assert_eq!(len, len_c); + nlh = next; + seen += 1; + } + assert_eq!(seen, 3); + assert_eq!(len, 0); +} + +// Boundary cases for the validity check: exact fit, short buffer, truncated +// header, message longer than the remaining buffer. +#[test] +fn test_ok_boundaries() { + let mut buf = [0u32; 32]; + for (nlmsg_len, len) in [(16, 16), (16, 15), (8, 16), (100, 16)] { + fill(&mut buf, &[nlmsg_len]); + let nlh = buf.as_mut_ptr().cast::(); + assert_eq!( + unsafe { libc::NLMSG_OK(nlh, len) }, + unsafe { nlmsg_ok_ffi(nlh, len) } != 0, + "nlmsg_len={nlmsg_len} len={len}" + ); + } +} diff --git a/src/new/linux_uapi/linux/netlink.rs b/src/new/linux_uapi/linux/netlink.rs index eb78ecac964d2..2e517dae1476d 100644 --- a/src/new/linux_uapi/linux/netlink.rs +++ b/src/new/linux_uapi/linux/netlink.rs @@ -67,6 +67,47 @@ pub const NLM_F_NONREC: c_int = 0x100; pub const NLM_F_CAPPED: c_int = 0x100; pub const NLM_F_ACK_TLVS: c_int = 0x200; +const NLMSG_ALIGNTO: c_uint = 4; + +const NLMSG_HDRLEN: c_int = NLMSG_ALIGN(size_of::()); + +f! { + pub const safe fn NLMSG_ALIGN(size: size_t) -> c_int { + ((size + NLMSG_ALIGNTO as size_t - 1) & !(NLMSG_ALIGNTO as size_t - 1)) as c_int + } + + pub const safe fn NLMSG_LENGTH(size: size_t) -> c_int { + size as c_int + NLMSG_HDRLEN + } + + pub const safe fn NLMSG_SPACE(size: size_t) -> c_int { + NLMSG_ALIGN(NLMSG_LENGTH(size) as size_t) + } + + pub unsafe fn NLMSG_DATA(nlh: *mut nlmsghdr) -> *mut c_void { + nlh.cast::() + .wrapping_add(NLMSG_HDRLEN as usize) + .cast::() + } + + pub unsafe fn NLMSG_NEXT(nlh: *mut nlmsghdr, size: &mut c_int) -> *mut nlmsghdr { + *size -= NLMSG_ALIGN((*nlh).nlmsg_len as size_t); + nlh.cast::() + .wrapping_add(NLMSG_ALIGN((*nlh).nlmsg_len as size_t) as usize) + .cast::() + } + + pub unsafe fn NLMSG_OK(nlh: *const nlmsghdr, size: c_int) -> bool { + size >= size_of::() as c_int + && (*nlh).nlmsg_len >= size_of::() as c_uint + && (*nlh).nlmsg_len <= size as c_uint + } + + pub unsafe fn NLMSG_PAYLOAD(nlh: *const nlmsghdr, size: c_int) -> c_int { + (*nlh).nlmsg_len as c_int - NLMSG_SPACE(size as size_t) + } +} + pub const NLMSG_NOOP: c_int = 0x1; pub const NLMSG_ERROR: c_int = 0x2; pub const NLMSG_DONE: c_int = 0x3;