From 9a05de557bcc21a17dae8d8374680a99b69c1d39 Mon Sep 17 00:00:00 2001 From: roblabla Date: Wed, 10 Aug 2022 16:47:51 +0200 Subject: [PATCH] Workaround XNU bug in getifaddrs netmasks --- src/ifaddrs.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/ifaddrs.rs b/src/ifaddrs.rs index f834d30762..1f261515de 100644 --- a/src/ifaddrs.rs +++ b/src/ifaddrs.rs @@ -4,6 +4,7 @@ //! of interfaces and their associated addresses. use cfg_if::cfg_if; +use std::convert::TryFrom; use std::ffi; use std::iter::Iterator; use std::mem; @@ -42,11 +43,46 @@ cfg_if! { } } +/// Workaround a bug in XNU where netmasks will always have the wrong size in +/// the sa_len field due to the kernel ignoring trailing zeroes in the structure +/// when setting the field. See https://github.com/nix-rust/nix/issues/1709#issuecomment-1199304470 +/// +/// To fix this, we stack-allocate a new sockaddr_storage, zero it out, and +/// memcpy sa_len of the netmask to that new storage. Finally, we reset the +/// ss_len field to sizeof(sockaddr_storage). This is supposedly valid as all +/// members of the sockaddr_storage are "ok" with being zeroed out (there are +/// no pointers). +#[cfg(any(target_os = "ios", target_os = "macos"))] +unsafe fn workaround_xnu_bug(info: &libc::ifaddrs) -> Option { + let src_sock = info.ifa_netmask; + if src_sock.is_null() { + return None; + } + + let mut dst_sock = mem::MaybeUninit::::zeroed(); + + // memcpy only sa_len bytes, assume the rest is zero + std::ptr::copy_nonoverlapping(src_sock as *const u8, dst_sock.as_mut_ptr() as *mut u8, (*src_sock).sa_len.into()); + + // Initialize ss_len to sizeof(libc::sockaddr_storage). + (*dst_sock.as_mut_ptr()).ss_len = u8::try_from(mem::size_of::()).unwrap(); + let dst_sock = dst_sock.assume_init(); + + let dst_sock_ptr = &dst_sock as *const libc::sockaddr_storage as *const libc::sockaddr; + + SockaddrStorage::from_raw(dst_sock_ptr, None) +} + impl InterfaceAddress { /// Create an `InterfaceAddress` from the libc struct. fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress { let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) }; let address = unsafe { SockaddrStorage::from_raw(info.ifa_addr, None) }; + #[cfg(any(target_os = "ios", target_os = "macos"))] + let netmask = unsafe { + workaround_xnu_bug(info) + }; + #[cfg(not(any(target_os = "ios", target_os = "macos")))] let netmask = unsafe { SockaddrStorage::from_raw(info.ifa_netmask, None) }; let mut addr = InterfaceAddress { interface_name: ifname.to_string_lossy().to_string(), @@ -144,4 +180,26 @@ mod tests { fn test_getifaddrs() { let _ = getifaddrs(); } + + // Ensures getting the netmask works, and in particular that + // `workaround_xnu_bug` works properly. + #[test] + fn test_getifaddrs_netmask_correct() { + let addrs = getifaddrs().unwrap(); + for iface in addrs { + let sock = if let Some(sock) = iface.netmask { + sock + } else { + continue + }; + if sock.family() == Some(crate::sys::socket::AddressFamily::Inet) { + let _ = sock.as_sockaddr_in().unwrap(); + return; + } else if sock.family() == Some(crate::sys::socket::AddressFamily::Inet6) { + let _ = sock.as_sockaddr_in6().unwrap(); + return; + } + } + panic!("No address?"); + } }