Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions libc-test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
7 changes: 7 additions & 0 deletions libc-test/semver/linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions libc-test/src/nlmsg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <linux/netlink.h>
#include <stddef.h>

// 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);
}
108 changes: 108 additions & 0 deletions libc-test/tests/nlmsg.rs
Original file line number Diff line number Diff line change
@@ -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::<nlmsghdr>() <= 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::<u8>()
.add(total)
.cast::<nlmsghdr>()
.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::<nlmsghdr>();
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::<nlmsghdr>();
assert_eq!(
unsafe { libc::NLMSG_OK(nlh, len) },
unsafe { nlmsg_ok_ffi(nlh, len) } != 0,
"nlmsg_len={nlmsg_len} len={len}"
);
}
}
41 changes: 41 additions & 0 deletions src/new/linux_uapi/linux/netlink.rs
Comment thread
sbogomolov marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -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::<nlmsghdr>());

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::<c_char>()
.wrapping_add(NLMSG_HDRLEN as usize)
.cast::<c_void>()
}

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::<c_char>()
.wrapping_add(NLMSG_ALIGN((*nlh).nlmsg_len as size_t) as usize)
.cast::<nlmsghdr>()
}

pub unsafe fn NLMSG_OK(nlh: *const nlmsghdr, size: c_int) -> bool {
size >= size_of::<nlmsghdr>() as c_int
&& (*nlh).nlmsg_len >= size_of::<nlmsghdr>() 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;
Expand Down