From 316e5986293923356bab9386818c2c83a151a529 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.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.rs b/libc-test/build.rs index 94db8ab0552ab..490a62e941768 100644 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -65,6 +65,22 @@ fn do_cc() { { cc::Build::new().file("src/sigrt.c").compile("sigrt"); } + if (target.contains("linux") && !target.contains("wasm32")) + || target.contains("android") + || target.contains("apple") + || target.contains("dragonfly") + || target.contains("emscripten") + || target.contains("freebsd") + || target.contains("fuschia") + || target.contains("illumos") + || target.contains("netbsd") + || target.contains("openbsd") + || target.contains("solaris") + { + cc::Build::new() + .file("src/icmp6_filter.c") + .compile("icmp6_filter"); + } } fn do_ctest() { @@ -270,6 +286,7 @@ fn test_apple(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet6/in6_var.h", "os/clock.h", "os/lock.h", @@ -495,6 +512,7 @@ fn test_openbsd(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "net/bpf.h", "regex.h", "resolv.h", @@ -1028,6 +1046,7 @@ fn test_solarish(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "poll.h", "port.h", "pthread.h", @@ -1323,6 +1342,7 @@ fn test_netbsd(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "poll.h", "pthread.h", "pwd.h", @@ -1624,6 +1644,7 @@ fn test_dragonflybsd(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "poll.h", "pthread.h", "pthread_np.h", @@ -2077,6 +2098,7 @@ fn test_android(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netpacket/packet.h", "poll.h", "pthread.h", @@ -2611,6 +2633,7 @@ fn test_freebsd(target: &str) { "netinet/sctp.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet6/in6_var.h", "poll.h", "pthread.h", @@ -3259,6 +3282,7 @@ fn test_emscripten(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netpacket/packet.h", "poll.h", "pthread.h", @@ -3529,6 +3553,7 @@ fn test_neutrino(target: &str) { "netinet/ip.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet/ip_var.h", "sys/poll.h", "pthread.h", @@ -3812,6 +3837,7 @@ fn test_vxworks(target: &str) { "sys/mman.h", "netinet/tcp.h", "netinet/udp.h", + "netinet/icmp6.h", "netinet/in.h", "netinet6/in6.h", "syslog.h", @@ -4065,6 +4091,7 @@ fn test_linux(target: &str) { "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 229020abe5083..1ebbbda4dd229 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; @@ -522,6 +527,34 @@ safe_f! { pub const fn QCMD(cmd: c_int, type_: c_int) -> c_int { (cmd << 8) | (type_ & 0x00ff) } + + pub const fn ICMP6_FILTER_WILLPASS(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) != 0 + } + + pub const fn ICMP6_FILTER_WILLBLOCK(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) == 0 + } + + pub fn ICMP6_FILTER_SETPASSALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = u32::MAX; + } + } + + pub fn ICMP6_FILTER_SETBLOCKALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = 0; + } + } + + pub fn ICMP6_FILTER_SETPASS(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] |= 1 << (typ & 31); + } + + pub 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 af5f06c7eada9..7a55c2f788173 100644 --- a/src/unix/linux_like/mod.rs +++ b/src/unix/linux_like/mod.rs @@ -233,6 +233,10 @@ s! { pub if_index: c_uint, pub if_name: *mut c_char, } + + pub struct icmp6_filter { + icmp6_filt: [u32; 8], + } } cfg_if! { @@ -997,6 +1001,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; @@ -1930,6 +1936,34 @@ safe_f! { pub const fn KERNEL_VERSION(a: u32, b: u32, c: u32) -> u32 { ((a << 16) + (b << 8)) + if c > 255 { 255 } else { c } } + + pub const fn ICMP6_FILTER_WILLPASS(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) == 0 + } + + pub const fn ICMP6_FILTER_WILLBLOCK(typ: u8, filt: &icmp6_filter) -> bool { + (filt.icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) != 0 + } + + pub fn ICMP6_FILTER_SETPASS(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] &= !(1 << (typ & 31)); + } + + pub fn ICMP6_FILTER_SETBLOCK(typ: u8, filt: &mut icmp6_filter) -> () { + filt.icmp6_filt[(typ >> 5) as usize] |= 1 << (typ & 31); + } + + pub fn ICMP6_FILTER_SETPASSALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.icmp6_filt { + *i = 0; + } + } + + pub 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 507ca30198940..9944535a070a6 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; @@ -2320,6 +2325,34 @@ safe_f! { pub const fn MR_GET_TYPE(flags: c_uint) -> c_uint { flags & 0x0000ffff } + + pub const fn ICMP6_FILTER_WILLPASS(typ: u8, filt: &icmp6_filter) -> bool { + (filt.__icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) != 0 + } + + pub const fn ICMP6_FILTER_WILLBLOCK(typ: u8, filt: &icmp6_filter) -> bool { + (filt.__icmp6_filt[(typ >> 5) as usize] & (1 << (typ & 31))) == 0 + } + + pub fn ICMP6_FILTER_SETPASSALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.__icmp6_filt { + *i = u32::MAX; + } + } + + pub fn ICMP6_FILTER_SETBLOCKALL(filt: &mut icmp6_filter) -> () { + for i in &mut filt.__icmp6_filt { + *i = 0; + } + } + + pub fn ICMP6_FILTER_SETPASS(typ: u8, filt: &mut icmp6_filter) -> () { + filt.__icmp6_filt[(typ >> 5) as usize] |= 1 << (typ & 31); + } + + pub fn ICMP6_FILTER_SETBLOCK(typ: u8, filt: &mut icmp6_filter) -> () { + filt.__icmp6_filt[(typ >> 5) as usize] &= !(1 << (typ & 31)); + } } extern "C" {