From a1b5ad2f3f1bcec370bb53806e9d3cf27927bc14 Mon Sep 17 00:00:00 2001 From: Adam Martinez <149513579+dybucc@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:29:01 +0200 Subject: [PATCH 1/2] freebsd(13.2): add `netlink/netlink.h` support This is an early subset of the Netlink interface, but it proves sufficient for monitoring changes in IP addresses. Coverage can be extended later as needed. See [^1] and [^2]. [^1]: [^2]: A small workaround has been necessary in the SemVer tests to ensure we get the right paths to the public submodules for the `netlink/netlink.h` and `netlink/netling_generic.h` interfaces. Those symbols now are prepended a `netlink` super module path. Signed-off-by: Yann Dirson Co-authored-by: Yann Dirson --- libc-test/semver/freebsd.txt | 80 ++++++++++++++++++++++ src/new/freebsd/mod.rs | 1 + src/new/freebsd/netlink/mod.rs | 6 ++ src/new/freebsd/netlink/netlink.rs | 77 +++++++++++++++++++++ src/new/freebsd/netlink/netlink_generic.rs | 43 ++++++++++++ src/new/freebsd/sys/socket.rs | 3 + src/new/mod.rs | 9 ++- 7 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/new/freebsd/netlink/mod.rs create mode 100644 src/new/freebsd/netlink/netlink.rs create mode 100644 src/new/freebsd/netlink/netlink_generic.rs diff --git a/libc-test/semver/freebsd.txt b/libc-test/semver/freebsd.txt index 7edf6575d00b9..18772828b925f 100644 --- a/libc-test/semver/freebsd.txt +++ b/libc-test/semver/freebsd.txt @@ -44,6 +44,7 @@ AF_LOCAL AF_NATM AF_NETBIOS AF_NETGRAPH +AF_NETLINK AF_OSI AF_PUP AF_ROUTE @@ -1024,6 +1025,7 @@ PF_LOCAL PF_NATM PF_NETBIOS PF_NETGRAPH +PF_NETLINK PF_OSI PF_PIP PF_PUP @@ -2180,6 +2182,84 @@ msgrcv msgsnd msqid_ds nallocx +netlink::NETLINK_ADD_MEMBERSHIP +netlink::NETLINK_AUDIT +netlink::NETLINK_BROADCAST_ERROR +netlink::NETLINK_CAP_ACK +netlink::NETLINK_CONNECTOR +netlink::NETLINK_DNRTMSG +netlink::NETLINK_DROP_MEMBERSHIP +netlink::NETLINK_EXT_ACK +netlink::NETLINK_FIB_LOOKUP +netlink::NETLINK_FIREWALL +netlink::NETLINK_GENERIC +netlink::NETLINK_GET_STRICT_CHK +netlink::NETLINK_IP6_FW +netlink::NETLINK_ISCSI +netlink::NETLINK_KOBJECT_UEVENT +netlink::NETLINK_LISTEN_ALL_NSID +netlink::NETLINK_LIST_MEMBERSHIPS +netlink::NETLINK_NETFILTER +netlink::NETLINK_NFLOG +netlink::NETLINK_NO_ENOBUFS +netlink::NETLINK_PKTINFO +netlink::NETLINK_ROUTE +netlink::NETLINK_RX_RING +netlink::NETLINK_SELINUX +netlink::NETLINK_SOCK_DIAG +netlink::NETLINK_TX_RING +netlink::NETLINK_UNUSED +netlink::NETLINK_USERSOCK +netlink::NETLINK_XFRM +netlink::NLMSG_ALIGNTO +netlink::NLMSG_DONE +netlink::NLMSG_ERROR +netlink::NLMSG_NOOP +netlink::NLMSG_OVERRUN +netlink::NLM_F_ACK +netlink::NLM_F_ACK_TLVS +netlink::NLM_F_APPEND +netlink::NLM_F_ATOMIC +netlink::NLM_F_CAPPED +netlink::NLM_F_CREATE +netlink::NLM_F_DUMP +netlink::NLM_F_DUMP_FILTERED +netlink::NLM_F_DUMP_INTR +netlink::NLM_F_ECHO +netlink::NLM_F_EXCL +netlink::NLM_F_MATCH +netlink::NLM_F_MULTI +netlink::NLM_F_NONREC +netlink::NLM_F_REPLACE +netlink::NLM_F_REQUEST +netlink::NLM_F_ROOT +netlink::NL_ITEM_ALIGN_SIZE +netlink::SOL_NETLINK +netlink::CTRL_ATTR_FAMILY_ID +netlink::CTRL_ATTR_FAMILY_NAME +netlink::CTRL_ATTR_HDRSIZE +netlink::CTRL_ATTR_MAXATTR +netlink::CTRL_ATTR_MCAST_GROUPS +netlink::CTRL_ATTR_MCAST_GRP_ID +netlink::CTRL_ATTR_MCAST_GRP_NAME +netlink::CTRL_ATTR_MCAST_GRP_UNSPEC +netlink::CTRL_ATTR_OP +netlink::CTRL_ATTR_OPS +netlink::CTRL_ATTR_OP_POLICY +netlink::CTRL_ATTR_POLICY +netlink::CTRL_ATTR_UNSPEC +netlink::CTRL_ATTR_VERSION +netlink::CTRL_CMD_DELFAMILY +netlink::CTRL_CMD_DELMCAST_GRP +netlink::CTRL_CMD_DELOPS +netlink::CTRL_CMD_GETFAMILY +netlink::CTRL_CMD_GETMCAST_GRP +netlink::CTRL_CMD_GETOPS +netlink::CTRL_CMD_GETPOLICY +netlink::CTRL_CMD_NEWFAMILY +netlink::CTRL_CMD_NEWMCAST_GRP +netlink::CTRL_CMD_NEWOPS +netlink::CTRL_CMD_UNSPEC newlocale nice nl_item diff --git a/src/new/freebsd/mod.rs b/src/new/freebsd/mod.rs index c0036c2a4e362..a8bfb036095cd 100644 --- a/src/new/freebsd/mod.rs +++ b/src/new/freebsd/mod.rs @@ -5,5 +5,6 @@ pub(crate) mod net; pub(crate) mod netinet6; +pub mod netlink; pub(crate) mod sys; pub(crate) mod unistd; diff --git a/src/new/freebsd/netlink/mod.rs b/src/new/freebsd/netlink/mod.rs new file mode 100644 index 0000000000000..1957137f4109b --- /dev/null +++ b/src/new/freebsd/netlink/mod.rs @@ -0,0 +1,6 @@ +//! Directory: `netlink/` +//! +//! + +pub mod netlink; +pub mod netlink_generic; diff --git a/src/new/freebsd/netlink/netlink.rs b/src/new/freebsd/netlink/netlink.rs new file mode 100644 index 0000000000000..958250634aa28 --- /dev/null +++ b/src/new/freebsd/netlink/netlink.rs @@ -0,0 +1,77 @@ +//! Header: `netlink/netlink.h` +//! +//! + +use crate::prelude::*; + +s! { + pub struct sockaddr_nl { + pub nl_len: u8, + pub nl_family: crate::sa_family_t, + nl_pad: Padding, + pub nl_pid: u32, + pub nl_groups: u32, + } +} + +pub const SOL_NETLINK: c_int = 270; +pub const NETLINK_ADD_MEMBERSHIP: c_int = 1; +pub const NETLINK_DROP_MEMBERSHIP: c_int = 2; +pub const NETLINK_PKTINFO: c_int = 3; +pub const NETLINK_BROADCAST_ERROR: c_int = 4; +pub const NETLINK_NO_ENOBUFS: c_int = 5; +pub const NETLINK_RX_RING: c_int = 6; +pub const NETLINK_TX_RING: c_int = 7; +pub const NETLINK_LISTEN_ALL_NSID: c_int = 8; +pub const NETLINK_LIST_MEMBERSHIPS: c_int = 9; +pub const NETLINK_CAP_ACK: c_int = 10; +pub const NETLINK_EXT_ACK: c_int = 11; +pub const NETLINK_GET_STRICT_CHK: c_int = 12; + +pub const NLM_F_REQUEST: c_int = 0x01; +pub const NLM_F_MULTI: c_int = 0x02; +pub const NLM_F_ACK: c_int = 0x04; +pub const NLM_F_ECHO: c_int = 0x08; +pub const NLM_F_DUMP_INTR: c_int = 0x10; +pub const NLM_F_DUMP_FILTERED: c_int = 0x20; + +pub const NLM_F_ROOT: c_int = 0x100; +pub const NLM_F_MATCH: c_int = 0x200; +pub const NLM_F_ATOMIC: c_int = 0x400; +pub const NLM_F_DUMP: c_int = NLM_F_ROOT | NLM_F_MATCH; + +pub const NLM_F_REPLACE: c_int = 0x100; +pub const NLM_F_EXCL: c_int = 0x200; +pub const NLM_F_CREATE: c_int = 0x400; +pub const NLM_F_APPEND: c_int = 0x800; + +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; + +pub const NLMSG_NOOP: c_int = 0x1; +pub const NLMSG_ERROR: c_int = 0x2; +pub const NLMSG_DONE: c_int = 0x3; +pub const NLMSG_OVERRUN: c_int = 0x4; + +pub const NETLINK_ROUTE: c_int = 0; +pub const NETLINK_UNUSED: c_int = 1; +pub const NETLINK_USERSOCK: c_int = 2; +pub const NETLINK_FIREWALL: c_int = 3; +pub const NETLINK_SOCK_DIAG: c_int = 4; +pub const NETLINK_NFLOG: c_int = 5; +pub const NETLINK_XFRM: c_int = 6; +pub const NETLINK_SELINUX: c_int = 7; +pub const NETLINK_ISCSI: c_int = 8; +pub const NETLINK_AUDIT: c_int = 9; +pub const NETLINK_FIB_LOOKUP: c_int = 10; +pub const NETLINK_CONNECTOR: c_int = 11; +pub const NETLINK_NETFILTER: c_int = 12; +pub const NETLINK_IP6_FW: c_int = 13; +pub const NETLINK_DNRTMSG: c_int = 14; +pub const NETLINK_KOBJECT_UEVENT: c_int = 15; +pub const NETLINK_GENERIC: c_int = 16; + +pub const NL_ITEM_ALIGN_SIZE: c_int = size_of::() as c_int; +pub const NLMSG_ALIGNTO: c_int = NL_ITEM_ALIGN_SIZE; diff --git a/src/new/freebsd/netlink/netlink_generic.rs b/src/new/freebsd/netlink/netlink_generic.rs new file mode 100644 index 0000000000000..fdd1517fea991 --- /dev/null +++ b/src/new/freebsd/netlink/netlink_generic.rs @@ -0,0 +1,43 @@ +//! Header: `netlink/netlink_generic.h` +//! +//! + +c_enum! { + pub enum #anon { + pub CTRL_CMD_UNSPEC, + pub CTRL_CMD_NEWFAMILY, + pub CTRL_CMD_DELFAMILY, + pub CTRL_CMD_GETFAMILY, + pub CTRL_CMD_NEWOPS, + pub CTRL_CMD_DELOPS, + pub CTRL_CMD_GETOPS, + pub CTRL_CMD_NEWMCAST_GRP, + pub CTRL_CMD_DELMCAST_GRP, + pub CTRL_CMD_GETMCAST_GRP, + pub CTRL_CMD_GETPOLICY, + } +} + +c_enum! { + pub enum #anon { + pub CTRL_ATTR_UNSPEC, + pub CTRL_ATTR_FAMILY_ID, + pub CTRL_ATTR_FAMILY_NAME, + pub CTRL_ATTR_VERSION, + pub CTRL_ATTR_HDRSIZE, + pub CTRL_ATTR_MAXATTR, + pub CTRL_ATTR_OPS, + pub CTRL_ATTR_MCAST_GROUPS, + pub CTRL_ATTR_POLICY, + pub CTRL_ATTR_OP_POLICY, + pub CTRL_ATTR_OP, + } +} + +c_enum! { + pub enum #anon { + pub CTRL_ATTR_MCAST_GRP_UNSPEC, + pub CTRL_ATTR_MCAST_GRP_NAME, + pub CTRL_ATTR_MCAST_GRP_ID, + } +} diff --git a/src/new/freebsd/sys/socket.rs b/src/new/freebsd/sys/socket.rs index 8b3394f37c5dc..1fe8560fd717e 100644 --- a/src/new/freebsd/sys/socket.rs +++ b/src/new/freebsd/sys/socket.rs @@ -5,3 +5,6 @@ use crate::prelude::*; pub const SO_RERROR: c_int = 0x0002_0000; + +pub const AF_NETLINK: c_int = 38; +pub const PF_NETLINK: c_int = AF_NETLINK; diff --git a/src/new/mod.rs b/src/new/mod.rs index 397275932c509..c68b0c05fe676 100644 --- a/src/new/mod.rs +++ b/src/new/mod.rs @@ -65,7 +65,14 @@ cfg_if! { // pub(crate) use espidf::*; } else if #[cfg(target_os = "freebsd")] { mod freebsd; - pub(crate) use freebsd::*; + pub(crate) use freebsd::{net, netinet6, sys, unistd}; + // FIXME(1.0,remove): should be part of the above reexport + pub mod netlink { + use super::freebsd; + + pub use freebsd::netlink::netlink::*; + pub use freebsd::netlink::netlink_generic::*; + } } else if #[cfg(target_os = "fuchsia")] { mod fuchsia; pub(crate) use fuchsia::*; From d22b942f3709ec49990b7dd2bcf2c1c9175f8c4e Mon Sep 17 00:00:00 2001 From: Adam Martinez <149513579+dybucc@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:57:42 +0200 Subject: [PATCH 2/2] freebsd: add `netlink/netlink.h` tests Add specific test for `netlink/netlink.h` bindings. This is necessary to avoid conflicts with the bindings for `net/if_mib.h`. libc-test now builds two different `TestGenerator` instances. One of the instances builds tests for the same set of bindings as before this patchset, while the other builds tests only for the `netlink/netlink.h` bindings. --- libc-test/Cargo.toml | 4 ++ libc-test/build/main.rs | 110 +++++++++++++++++++++++++++++ libc-test/tests/freebsd_netlink.rs | 17 +++++ 3 files changed, 131 insertions(+) create mode 100644 libc-test/tests/freebsd_netlink.rs diff --git a/libc-test/Cargo.toml b/libc-test/Cargo.toml index 04ea3cdd6a883..0878c86bf4d99 100644 --- a/libc-test/Cargo.toml +++ b/libc-test/Cargo.toml @@ -32,6 +32,10 @@ extra_traits = ["libc/extra_traits"] name = "ctest" harness = false +[[test]] +name = "freebsd_netlink" +harness = false + [[test]] name = "linux_fcntl" harness = false diff --git a/libc-test/build/main.rs b/libc-test/build/main.rs index 3f94b99e28cbe..6f48aff2a5b31 100755 --- a/libc-test/build/main.rs +++ b/libc-test/build/main.rs @@ -2470,6 +2470,98 @@ fn test_android(t: &Target) { } fn test_freebsd(t: &Target) { + // Ensures we have only one place where the `netlink/netlink.h` constants + // are checked in. This is used both when generating the full test suite, + // and when generating the `netlink`-specific test suite. + #[inline] + fn is_netlink_const(it: &ctest::Const) -> bool { + matches!( + it.ident(), + "CTRL_CMD_UNSPEC" + | "CTRL_CMD_NEWFAMILY" + | "CTRL_CMD_DELFAMILY" + | "CTRL_CMD_GETFAMILY" + | "CTRL_CMD_NEWOPS" + | "CTRL_CMD_DELOPS" + | "CTRL_CMD_GETOPS" + | "CTRL_CMD_NEWMCAST_GRP" + | "CTRL_CMD_DELMCAST_GRP" + | "CTRL_CMD_GETMCAST_GRP" + | "CTRL_CMD_GETPOLICY" + | "CTRL_ATTR_UNSPEC" + | "CTRL_ATTR_FAMILY_ID" + | "CTRL_ATTR_FAMILY_NAME" + | "CTRL_ATTR_VERSION" + | "CTRL_ATTR_HDRSIZE" + | "CTRL_ATTR_MAXATTR" + | "CTRL_ATTR_OPS" + | "CTRL_ATTR_MCAST_GROUPS" + | "CTRL_ATTR_POLICY" + | "CTRL_ATTR_OP_POLICY" + | "CTRL_ATTR_OP" + | "CTRL_ATTR_MCAST_GRP_UNSPEC" + | "CTRL_ATTR_MCAST_GRP_NAME" + | "CTRL_ATTR_MCAST_GRP_ID" + | "SOL_NETLINK" + | "NETLINK_ADD_MEMBERSHIP" + | "NETLINK_DROP_MEMBERSHIP" + | "NETLINK_PKTINFO" + | "NETLINK_BROADCAST_ERROR" + | "NETLINK_NO_ENOBUFS" + | "NETLINK_RX_RING" + | "NETLINK_TX_RING" + | "NETLINK_LISTEN_ALL_NSID" + | "NETLINK_LIST_MEMBERSHIPS" + | "NETLINK_CAP_ACK" + | "NETLINK_EXT_ACK" + | "NETLINK_GET_STRICT_CHK" + | "NLM_F_REQUEST" + | "NLM_F_MULTI" + | "NLM_F_ACK" + | "NLM_F_ECHO" + | "NLM_F_DUMP_INTR" + | "NLM_F_DUMP_FILTERED" + | "NLM_F_ROOT" + | "NLM_F_MATCH" + | "NLM_F_ATOMIC" + | "NLM_F_DUMP" + | "NLM_F_REPLACE" + | "NLM_F_EXCL" + | "NLM_F_CREATE" + | "NLM_F_APPEND" + | "NLM_F_NONREC" + | "NLM_F_CAPPED" + | "NLM_F_ACK_TLVS" + | "NLMSG_NOOP" + | "NLMSG_ERROR" + | "NLMSG_DONE" + | "NLMSG_OVERRUN" + | "NETLINK_ROUTE" + | "NETLINK_UNUSED" + | "NETLINK_USERSOCK" + | "NETLINK_FIREWALL" + | "NETLINK_SOCK_DIAG" + | "NETLINK_NFLOG" + | "NETLINK_XFRM" + | "NETLINK_SELINUX" + | "NETLINK_ISCSI" + | "NETLINK_AUDIT" + | "NETLINK_FIB_LOOKUP" + | "NETLINK_CONNECTOR" + | "NETLINK_NETFILTER" + | "NETLINK_IP6_FW" + | "NETLINK_DNRTMSG" + | "NETLINK_KOBJECT_UEVENT" + | "NETLINK_GENERIC" + | "NL_ITEM_ALIGN_SIZE" + | "NLMSG_ALIGNTO" + ) + } + #[inline] + fn is_netlink_struct(it: &ctest::Struct) -> bool { + matches!(it.ident(), "sockaddr_nl") + } + assert!(t.freebsd()); let mut cfg = ctest_cfg(); @@ -2517,6 +2609,18 @@ fn test_freebsd(t: &Target) { let freebsd14 = matches!(freebsd_ver, Some(n) if n >= 14); let freebsd15 = matches!(freebsd_ver, Some(n) if n >= 15); + let mut netlink_cfg = cfg.clone(); + headers!(netlink_cfg, "netlink/netlink.h",); + netlink_cfg + .skip_struct(|ty| !is_netlink_struct(ty)) + .skip_const(|c| !is_netlink_const(c)) + .skip_union(|_| true) + .skip_alias(|_| true) + .skip_static(|_| true) + .skip_fn(|_| true) + .skip_c_enum(|_| true); + ctest::generate_test(&mut netlink_cfg, "../src/lib.rs", "netlink_ctest_output.rs").unwrap(); + headers!( cfg, "aio.h", @@ -2689,6 +2793,10 @@ fn test_freebsd(t: &Target) { cfg.skip_const(move |constant| { match constant.ident() { + // `netlink/netlink.h` constants. These are tests in a separate ctest + // invocation. + _ if is_netlink_const(constant) => true, + // These constants were introduced in FreeBSD 13: "F_ADD_SEALS" | "F_GET_SEALS" | "F_SEAL_SEAL" | "F_SEAL_SHRINK" | "F_SEAL_GROW" | "F_SEAL_WRITE" @@ -2974,6 +3082,8 @@ fn test_freebsd(t: &Target) { cfg.skip_struct(move |struct_| { match struct_.ident() { + _ if is_netlink_struct(struct_) => true, + // `procstat` is a private struct "procstat" => true, diff --git a/libc-test/tests/freebsd_netlink.rs b/libc-test/tests/freebsd_netlink.rs new file mode 100644 index 0000000000000..50370d7b1faab --- /dev/null +++ b/libc-test/tests/freebsd_netlink.rs @@ -0,0 +1,17 @@ +#![allow(deprecated)] +#![allow(unused)] + +#[cfg(target_os = "freebsd")] +#[allow(unused_imports)] +use libc::{ + netlink::*, + netlink_generic::*, +}; + +#[cfg(target_os = "freebsd")] +include!(concat!(env!("OUT_DIR"), "/netlink_ctest_output.rs")); + +#[cfg(not(target_os = "freebsd"))] +fn main() { + println!("PASSED 0 tests"); +}