From 1995d3a9c3c2410b9217b44ca9689b7ab808aa4c Mon Sep 17 00:00:00 2001 From: Skye Soss Date: Thu, 2 Apr 2026 16:13:41 -0500 Subject: [PATCH] Add types and macros for ICMPv6 filtering This commit adds the icmp6_filter type and the corresponding ICMP6_FILTER macros. These functions are tested in a similar manner to the CMSG macros. --- libc-test/build/main.rs | 27 ++++++++ libc-test/semver/unix.txt | 1 + libc-test/src/icmp6_filter.c | 41 ++++++++++++ libc-test/tests/icmp6_filter.rs | 115 ++++++++++++++++++++++++++++++++ src/unix/bsd/mod.rs | 33 +++++++++ src/unix/linux_like/mod.rs | 34 ++++++++++ src/unix/solarish/mod.rs | 33 +++++++++ 7 files changed, 284 insertions(+) create mode 100644 libc-test/src/icmp6_filter.c create mode 100644 libc-test/tests/icmp6_filter.rs diff --git a/libc-test/build/main.rs b/libc-test/build/main.rs index fc05c0df16c19..18979acebc39b 100755 --- a/libc-test/build/main.rs +++ b/libc-test/build/main.rs @@ -53,6 +53,22 @@ fn do_cc(t: &Target) { { cc::Build::new().file("src/sigrt.c").compile("sigrt"); } + if (t.linux() && !t.wasm32()) + || t.android() + || t.apple() + || t.dragonfly() + || t.emscripten() + || t.freebsd() + || t.fuchsia() + || t.illumos() + || t.netbsd() + || t.openbsd() + || t.solaris() + { + cc::Build::new() + .file("src/icmp6_filter.c") + .compile("icmp6_filter"); + } } fn do_ctest(t: &Target) { @@ -190,6 +206,7 @@ fn test_apple(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet6/in6_var.h", "os/clock.h", "os/lock.h", @@ -411,6 +428,7 @@ fn test_openbsd(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "net/bpf.h", "regex.h", "resolv.h", @@ -944,6 +962,7 @@ fn test_solarish(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "poll.h", "port.h", "pthread.h", @@ -1237,6 +1256,7 @@ fn test_netbsd(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "poll.h", "pthread.h", "pwd.h", @@ -1537,6 +1557,7 @@ fn test_dragonflybsd(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "poll.h", "pthread.h", "pthread_np.h", @@ -2003,6 +2024,7 @@ fn test_android(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netpacket/packet.h", "poll.h", "pthread.h", @@ -2545,6 +2567,7 @@ fn test_freebsd(t: &Target) { "netinet/sctp.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet6/in6_var.h", "poll.h", "pthread.h", @@ -3181,6 +3204,7 @@ fn test_emscripten(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netpacket/packet.h", "poll.h", "pthread.h", @@ -3449,6 +3473,7 @@ fn test_neutrino(t: &Target) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet/ip_var.h", "sys/poll.h", "pthread.h", @@ -3755,6 +3780,7 @@ fn test_vxworks(t: &Target) { "sys/mman.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet/in.h", "netinet6/in6.h", "syslog.h", @@ -3998,6 +4024,7 @@ fn test_linux(t: &Target) { "netinet/tcp.h", "netinet/udp.h", (gnu, "netiucv/iucv.h"), + "netinet/icmp6.h", (l4re, "netpacket/packet.h"), "poll.h", "pthread.h", diff --git a/libc-test/semver/unix.txt b/libc-test/semver/unix.txt index 052c24178dfcc..77e7f62f173f1 100644 --- a/libc-test/semver/unix.txt +++ b/libc-test/semver/unix.txt @@ -150,6 +150,7 @@ F_SETLKW GRPQUOTA HUPCL ICANON +ICMP6_FILTER ICRNL IEXTEN IFNAMSIZ diff --git a/libc-test/src/icmp6_filter.c b/libc-test/src/icmp6_filter.c new file mode 100644 index 0000000000000..b4ac69c3528da --- /dev/null +++ b/libc-test/src/icmp6_filter.c @@ -0,0 +1,41 @@ +#include +#include +#include + +typedef uint8_t u_int8_t; +typedef uint16_t u_int16_t; +typedef uint32_t u_int32_t; + +#include +#include +#include + +// Since the ICMP6_FILTER macros 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 reimplementations. + +bool icmp6_filter_willpass(uint8_t typ, const struct icmp6_filter *filt) { + return ICMP6_FILTER_WILLPASS(typ, filt); +} + +bool icmp6_filter_willblock(uint8_t typ, const struct icmp6_filter *filt) { + return ICMP6_FILTER_WILLBLOCK(typ, filt); +} + +void icmp6_filter_setpassall(struct icmp6_filter *filt) { + ICMP6_FILTER_SETPASSALL(filt); +} + +void icmp6_filter_setblockall(struct icmp6_filter *filt) { + ICMP6_FILTER_SETBLOCKALL(filt); +} + +void icmp6_filter_setpass(uint8_t typ, struct icmp6_filter *filt) { + ICMP6_FILTER_SETPASS(typ, filt); +} + +void icmp6_filter_setblock(uint8_t typ, struct icmp6_filter *filt) { + ICMP6_FILTER_SETBLOCK(typ, filt); +} + diff --git a/libc-test/tests/icmp6_filter.rs b/libc-test/tests/icmp6_filter.rs new file mode 100644 index 0000000000000..75c4c7115ab8f --- /dev/null +++ b/libc-test/tests/icmp6_filter.rs @@ -0,0 +1,115 @@ +//! Compare libc's ICMP6_FILTER functions against the actual C macros, for +//! various inputs. + +#[cfg(unix)] +mod t { + + use std::mem; + + use libc::{ + self, + icmp6_filter, + }; + + extern "C" { + pub fn icmp6_filter_willpass(typ: u8, filt: *const icmp6_filter) -> bool; + pub fn icmp6_filter_willblock(typ: u8, filt: *const icmp6_filter) -> bool; + pub fn icmp6_filter_setpassall(filt: *mut icmp6_filter); + pub fn icmp6_filter_setblockall(filt: *mut icmp6_filter); + pub fn icmp6_filter_setpass(typ: u8, filt: *mut icmp6_filter); + pub fn icmp6_filter_setblock(typ: u8, filt: *mut icmp6_filter); + } + + // Two ICMPv6 filters are equal if they agree on all ICMPv6 types + // There are only 255 types so we can be complete. + fn assert_filters_eq(filt1: &icmp6_filter, filt2: &icmp6_filter) { + for typ in 0..255 { + unsafe { + assert_eq!( + icmp6_filter_willpass(typ, filt1), + icmp6_filter_willpass(typ, filt2) + ); + assert_eq!( + icmp6_filter_willblock(typ, filt1), + icmp6_filter_willblock(typ, filt2) + ); + } + } + } + + #[test] + fn test_icmp6_filter_setpassall() { + unsafe { + let mut filt1 = mem::zeroed::(); + let mut filt2 = mem::zeroed::(); + libc::ICMP6_FILTER_SETPASSALL(&mut filt1); + icmp6_filter_setpassall(&mut filt2); + assert_filters_eq(&filt1, &filt2); + } + } + + #[test] + fn test_icmp6_filter_setblockall() { + unsafe { + let mut filt1 = mem::zeroed::(); + let mut filt2 = mem::zeroed::(); + libc::ICMP6_FILTER_SETBLOCKALL(&mut filt1); + icmp6_filter_setblockall(&mut filt2); + assert_filters_eq(&filt1, &filt2); + } + } + + #[test] + fn test_icmp6_filter_setblock() { + for typ in 0..255 { + unsafe { + let mut filt1 = mem::zeroed::(); + let mut filt2 = mem::zeroed::(); + icmp6_filter_setpassall(&mut filt1); + icmp6_filter_setpassall(&mut filt2); + libc::ICMP6_FILTER_SETBLOCK(typ, &mut filt1); + icmp6_filter_setblock(typ, &mut filt2); + assert_filters_eq(&filt1, &filt2); + } + } + } + + #[test] + fn test_icmp6_filter_setpass() { + for typ in 0..255 { + unsafe { + let mut filt1 = mem::zeroed::(); + let mut filt2 = mem::zeroed::(); + icmp6_filter_setblockall(&mut filt1); + icmp6_filter_setblockall(&mut filt2); + libc::ICMP6_FILTER_SETPASS(typ, &mut filt1); + icmp6_filter_setpass(typ, &mut filt2); + assert_filters_eq(&filt1, &filt2); + } + } + } + + #[test] + fn test_icmp6_filter_willpass_willblock() { + unsafe { + let mut filt1 = mem::zeroed::(); + let mut filt2 = mem::zeroed::(); + icmp6_filter_setblockall(&mut filt1); + icmp6_filter_setblockall(&mut filt2); + + let mut seed = 0xdeadbeefu32; + for _ in 0..255 { + seed = seed.wrapping_mul(0x915f77f5); + let typ = (seed >> 23) as u8; + if (seed >> 31) & 1 == 0 { + icmp6_filter_setblock(typ, &mut filt1); + libc::ICMP6_FILTER_SETBLOCK(typ, &mut filt2); + } else { + icmp6_filter_setpass(typ, &mut filt1); + libc::ICMP6_FILTER_SETPASS(typ, &mut filt2); + } + assert_filters_eq(&filt1, &filt2); + } + } + } +} diff --git a/src/unix/bsd/mod.rs b/src/unix/bsd/mod.rs index bc8abeca1c916..eb49c1e43e520 100644 --- a/src/unix/bsd/mod.rs +++ b/src/unix/bsd/mod.rs @@ -148,6 +148,10 @@ s! { #[cfg(target_os = "dragonfly")] pub machine: [c_char; 32], } + + pub struct icmp6_filter { + icmp6_filt: [u32; 8], + } } pub const LC_ALL: c_int = 0; @@ -217,6 +221,7 @@ pub const IPV6_UNICAST_HOPS: c_int = 4; pub const IPV6_MULTICAST_IF: c_int = 9; pub const IPV6_MULTICAST_HOPS: c_int = 10; pub const IPV6_MULTICAST_LOOP: c_int = 11; +pub const ICMP6_FILTER: c_int = 18; pub const IPV6_V6ONLY: c_int = 27; pub const IPV6_DONTFRAG: c_int = 62; @@ -520,6 +525,34 @@ f! { pub const safe fn QCMD(cmd: c_int, type_: c_int) -> c_int { (cmd << 8) | (type_ & 0x00ff) } + + pub const safe fn ICMP6_FILTER_WILLPASS(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) != 0 + } + + pub const safe fn ICMP6_FILTER_WILLBLOCK(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) == 0 + } + + pub safe fn ICMP6_FILTER_SETPASSALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = u32::MAX; + } + } + + pub safe fn ICMP6_FILTER_SETBLOCKALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = 0; + } + } + + pub safe fn ICMP6_FILTER_SETPASS(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] |= 1 << (typ & 31); + } + + pub safe fn ICMP6_FILTER_SETBLOCK(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] &= !(1 << (typ & 31)); + } } extern "C" { diff --git a/src/unix/linux_like/mod.rs b/src/unix/linux_like/mod.rs index d9e0a682a937c..10db9b7d3444b 100644 --- a/src/unix/linux_like/mod.rs +++ b/src/unix/linux_like/mod.rs @@ -245,6 +245,10 @@ s! { pub if_index: c_uint, pub if_name: *mut c_char, } + + pub struct icmp6_filter { + icmp6_filt: [u32; 8], + } } cfg_if! { @@ -1014,6 +1018,8 @@ pub const IPV6_PMTUDISC_PROBE: c_int = 3; pub const IPV6_PMTUDISC_INTERFACE: c_int = 4; pub const IPV6_PMTUDISC_OMIT: c_int = 5; +pub const ICMP6_FILTER: c_int = 1; + pub const TCP_NODELAY: c_int = 1; pub const TCP_MAXSEG: c_int = 2; pub const TCP_CORK: c_int = 3; @@ -1946,6 +1952,34 @@ f! { pub const safe fn KERNEL_VERSION(a: u32, b: u32, c: u32) -> u32 { ((a << 16) + (b << 8)) + if c > 255 { 255 } else { c } } + + pub const safe fn ICMP6_FILTER_WILLPASS(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) == 0 + } + + pub const safe fn ICMP6_FILTER_WILLBLOCK(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) != 0 + } + + pub safe fn ICMP6_FILTER_SETPASS(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] &= !(1 << (typ & 31)); + } + + pub safe fn ICMP6_FILTER_SETBLOCK(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] |= 1 << (typ & 31); + } + + pub safe fn ICMP6_FILTER_SETPASSALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = 0; + } + } + + pub safe fn ICMP6_FILTER_SETBLOCKALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = u32::MAX; + } + } } extern "C" { diff --git a/src/unix/solarish/mod.rs b/src/unix/solarish/mod.rs index b54de0f0d3d8b..4deaea105e4db 100644 --- a/src/unix/solarish/mod.rs +++ b/src/unix/solarish/mod.rs @@ -530,6 +530,10 @@ s! { pub sigev_notify_attributes: *const crate::pthread_attr_t, __sigev_pad2: Padding, } + + pub struct icmp6_filter { + __icmp6_filt: [u32; 8], + } } s_no_extra_traits! { @@ -879,6 +883,7 @@ pub const IP_PKTINFO: c_int = 0x1a; pub const IP_DONTFRAG: c_int = 0x1b; pub const IP_SEC_OPT: c_int = 0x22; +pub const ICMP6_FILTER: c_int = 0x1; pub const IPV6_UNICAST_HOPS: c_int = 0x5; pub const IPV6_MULTICAST_IF: c_int = 0x6; pub const IPV6_MULTICAST_HOPS: c_int = 0x7; @@ -2319,6 +2324,34 @@ f! { pub const safe fn MR_GET_TYPE(flags: c_uint) -> c_uint { flags & 0x0000ffff } + + pub const safe fn ICMP6_FILTER_WILLPASS(typ: u8, filt: &icmp6_filter) -> bool { + (filt.__icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) != 0 + } + + pub const safe fn ICMP6_FILTER_WILLBLOCK(typ: u8, filt: &icmp6_filter) -> bool { + (filt.__icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) == 0 + } + + pub safe fn ICMP6_FILTER_SETPASSALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.__icmp6_filt { + *i = u32::MAX; + } + } + + pub safe fn ICMP6_FILTER_SETBLOCKALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.__icmp6_filt { + *i = 0; + } + } + + pub safe fn ICMP6_FILTER_SETPASS(typ: u8, filt: &mut icmp6_filter) -> () { + filt.__icmp6_filt[(typ >> 5) as usize] |= 1 << (typ & 31); + } + + pub safe fn ICMP6_FILTER_SETBLOCK(typ: u8, filt: &mut icmp6_filter) -> () { + filt.__icmp6_filt[(typ >> 5) as usize] &= !(1 << (typ & 31)); + } } extern "C" {