Skip to content

Commit b017f1c

Browse files
committed
Workaround XNU bug in getifaddrs netmasks
1 parent 1eb589f commit b017f1c

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

src/ifaddrs.rs

+59
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//! of interfaces and their associated addresses.
55
66
use cfg_if::cfg_if;
7+
#[cfg(any(target_os = "ios", target_os = "macos"))]
8+
use std::convert::TryFrom;
79
use std::ffi;
810
use std::iter::Iterator;
911
use std::mem;
@@ -42,11 +44,46 @@ cfg_if! {
4244
}
4345
}
4446

47+
/// Workaround a bug in XNU where netmasks will always have the wrong size in
48+
/// the sa_len field due to the kernel ignoring trailing zeroes in the structure
49+
/// when setting the field. See https://github.com/nix-rust/nix/issues/1709#issuecomment-1199304470
50+
///
51+
/// To fix this, we stack-allocate a new sockaddr_storage, zero it out, and
52+
/// memcpy sa_len of the netmask to that new storage. Finally, we reset the
53+
/// ss_len field to sizeof(sockaddr_storage). This is supposedly valid as all
54+
/// members of the sockaddr_storage are "ok" with being zeroed out (there are
55+
/// no pointers).
56+
#[cfg(any(target_os = "ios", target_os = "macos"))]
57+
unsafe fn workaround_xnu_bug(info: &libc::ifaddrs) -> Option<SockaddrStorage> {
58+
let src_sock = info.ifa_netmask;
59+
if src_sock.is_null() {
60+
return None;
61+
}
62+
63+
let mut dst_sock = mem::MaybeUninit::<libc::sockaddr_storage>::zeroed();
64+
65+
// memcpy only sa_len bytes, assume the rest is zero
66+
std::ptr::copy_nonoverlapping(src_sock as *const u8, dst_sock.as_mut_ptr() as *mut u8, (*src_sock).sa_len.into());
67+
68+
// Initialize ss_len to sizeof(libc::sockaddr_storage).
69+
(*dst_sock.as_mut_ptr()).ss_len = u8::try_from(mem::size_of::<libc::sockaddr_storage>()).unwrap();
70+
let dst_sock = dst_sock.assume_init();
71+
72+
let dst_sock_ptr = &dst_sock as *const libc::sockaddr_storage as *const libc::sockaddr;
73+
74+
SockaddrStorage::from_raw(dst_sock_ptr, None)
75+
}
76+
4577
impl InterfaceAddress {
4678
/// Create an `InterfaceAddress` from the libc struct.
4779
fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress {
4880
let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) };
4981
let address = unsafe { SockaddrStorage::from_raw(info.ifa_addr, None) };
82+
#[cfg(any(target_os = "ios", target_os = "macos"))]
83+
let netmask = unsafe {
84+
workaround_xnu_bug(info)
85+
};
86+
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
5087
let netmask = unsafe { SockaddrStorage::from_raw(info.ifa_netmask, None) };
5188
let mut addr = InterfaceAddress {
5289
interface_name: ifname.to_string_lossy().to_string(),
@@ -144,4 +181,26 @@ mod tests {
144181
fn test_getifaddrs() {
145182
let _ = getifaddrs();
146183
}
184+
185+
// Ensures getting the netmask works, and in particular that
186+
// `workaround_xnu_bug` works properly.
187+
#[test]
188+
fn test_getifaddrs_netmask_correct() {
189+
let addrs = getifaddrs().unwrap();
190+
for iface in addrs {
191+
let sock = if let Some(sock) = iface.netmask {
192+
sock
193+
} else {
194+
continue
195+
};
196+
if sock.family() == Some(crate::sys::socket::AddressFamily::Inet) {
197+
let _ = sock.as_sockaddr_in().unwrap();
198+
return;
199+
} else if sock.family() == Some(crate::sys::socket::AddressFamily::Inet6) {
200+
let _ = sock.as_sockaddr_in6().unwrap();
201+
return;
202+
}
203+
}
204+
panic!("No address?");
205+
}
147206
}

0 commit comments

Comments
 (0)