|
4 | 4 | //! of interfaces and their associated addresses.
|
5 | 5 |
|
6 | 6 | use cfg_if::cfg_if;
|
| 7 | +#[cfg(any(target_os = "ios", target_os = "macos"))] |
| 8 | +use std::convert::TryFrom; |
7 | 9 | use std::ffi;
|
8 | 10 | use std::iter::Iterator;
|
9 | 11 | use std::mem;
|
@@ -42,11 +44,50 @@ cfg_if! {
|
42 | 44 | }
|
43 | 45 | }
|
44 | 46 |
|
| 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( |
| 67 | + src_sock as *const u8, |
| 68 | + dst_sock.as_mut_ptr() as *mut u8, |
| 69 | + (*src_sock).sa_len.into() |
| 70 | + ); |
| 71 | + |
| 72 | + // Initialize ss_len to sizeof(libc::sockaddr_storage). |
| 73 | + (*dst_sock.as_mut_ptr()).ss_len = |
| 74 | + u8::try_from(mem::size_of::<libc::sockaddr_storage>()).unwrap(); |
| 75 | + let dst_sock = dst_sock.assume_init(); |
| 76 | + |
| 77 | + let dst_sock_ptr = |
| 78 | + &dst_sock as *const libc::sockaddr_storage as *const libc::sockaddr; |
| 79 | + |
| 80 | + SockaddrStorage::from_raw(dst_sock_ptr, None) |
| 81 | +} |
| 82 | + |
45 | 83 | impl InterfaceAddress {
|
46 | 84 | /// Create an `InterfaceAddress` from the libc struct.
|
47 | 85 | fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress {
|
48 | 86 | let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) };
|
49 | 87 | let address = unsafe { SockaddrStorage::from_raw(info.ifa_addr, None) };
|
| 88 | + #[cfg(any(target_os = "ios", target_os = "macos"))] |
| 89 | + let netmask = unsafe { workaround_xnu_bug(info) }; |
| 90 | + #[cfg(not(any(target_os = "ios", target_os = "macos")))] |
50 | 91 | let netmask =
|
51 | 92 | unsafe { SockaddrStorage::from_raw(info.ifa_netmask, None) };
|
52 | 93 | let mut addr = InterfaceAddress {
|
@@ -145,4 +186,28 @@ mod tests {
|
145 | 186 | fn test_getifaddrs() {
|
146 | 187 | let _ = getifaddrs();
|
147 | 188 | }
|
| 189 | + |
| 190 | + // Ensures getting the netmask works, and in particular that |
| 191 | + // `workaround_xnu_bug` works properly. |
| 192 | + #[test] |
| 193 | + fn test_getifaddrs_netmask_correct() { |
| 194 | + let addrs = getifaddrs().unwrap(); |
| 195 | + for iface in addrs { |
| 196 | + let sock = if let Some(sock) = iface.netmask { |
| 197 | + sock |
| 198 | + } else { |
| 199 | + continue; |
| 200 | + }; |
| 201 | + if sock.family() |
| 202 | + == Some(crate::sys::socket::AddressFamily::Inet) |
| 203 | + { |
| 204 | + let _ = sock.as_sockaddr_in().unwrap(); |
| 205 | + return; |
| 206 | + } else if sock.family() == Some(crate::sys::socket::AddressFamily::Inet6) { |
| 207 | + let _ = sock.as_sockaddr_in6().unwrap(); |
| 208 | + return; |
| 209 | + } |
| 210 | + } |
| 211 | + panic!("No address?"); |
| 212 | + } |
148 | 213 | }
|
0 commit comments