1//! Query network interface addresses 2//! 3//! Uses the Linux and/or BSD specific function `getifaddrs` to query the list 4//! of interfaces and their associated addresses. 5 6use cfg_if::cfg_if; 7#[cfg(any(target_os = "ios", target_os = "macos"))] 8use std::convert::TryFrom; 9use std::ffi; 10use std::iter::Iterator; 11use std::mem; 12use std::option::Option; 13 14use crate::net::if_::*; 15use crate::sys::socket::{SockaddrLike, SockaddrStorage}; 16use crate::{Errno, Result}; 17 18/// Describes a single address for an interface as returned by `getifaddrs`. 19#[derive(Clone, Debug, Eq, Hash, PartialEq)] 20pub struct InterfaceAddress { 21 /// Name of the network interface 22 pub interface_name: String, 23 /// Flags as from `SIOCGIFFLAGS` ioctl 24 pub flags: InterfaceFlags, 25 /// Network address of this interface 26 pub address: Option<SockaddrStorage>, 27 /// Netmask of this interface 28 pub netmask: Option<SockaddrStorage>, 29 /// Broadcast address of this interface, if applicable 30 pub broadcast: Option<SockaddrStorage>, 31 /// Point-to-point destination address 32 pub destination: Option<SockaddrStorage>, 33} 34 35cfg_if! { 36 if #[cfg(any(target_os = "android", target_os = "emscripten", target_os = "fuchsia", target_os = "linux"))] { 37 fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { 38 info.ifa_ifu 39 } 40 } else { 41 fn get_ifu_from_sockaddr(info: &libc::ifaddrs) -> *const libc::sockaddr { 42 info.ifa_dstaddr 43 } 44 } 45} 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"))] 57unsafe 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 83impl InterfaceAddress { 84 /// Create an `InterfaceAddress` from the libc struct. 85 fn from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress { 86 let ifname = unsafe { ffi::CStr::from_ptr(info.ifa_name) }; 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")))] 91 let netmask = 92 unsafe { SockaddrStorage::from_raw(info.ifa_netmask, None) }; 93 let mut addr = InterfaceAddress { 94 interface_name: ifname.to_string_lossy().to_string(), 95 flags: InterfaceFlags::from_bits_truncate(info.ifa_flags as i32), 96 address, 97 netmask, 98 broadcast: None, 99 destination: None, 100 }; 101 102 let ifu = get_ifu_from_sockaddr(info); 103 if addr.flags.contains(InterfaceFlags::IFF_POINTOPOINT) { 104 addr.destination = unsafe { SockaddrStorage::from_raw(ifu, None) }; 105 } else if addr.flags.contains(InterfaceFlags::IFF_BROADCAST) { 106 addr.broadcast = unsafe { SockaddrStorage::from_raw(ifu, None) }; 107 } 108 109 addr 110 } 111} 112 113/// Holds the results of `getifaddrs`. 114/// 115/// Use the function `getifaddrs` to create this Iterator. Note that the 116/// actual list of interfaces can be iterated once and will be freed as 117/// soon as the Iterator goes out of scope. 118#[derive(Debug, Eq, Hash, PartialEq)] 119pub struct InterfaceAddressIterator { 120 base: *mut libc::ifaddrs, 121 next: *mut libc::ifaddrs, 122} 123 124impl Drop for InterfaceAddressIterator { 125 fn drop(&mut self) { 126 unsafe { libc::freeifaddrs(self.base) }; 127 } 128} 129 130impl Iterator for InterfaceAddressIterator { 131 type Item = InterfaceAddress; 132 fn next(&mut self) -> Option<<Self as Iterator>::Item> { 133 match unsafe { self.next.as_ref() } { 134 Some(ifaddr) => { 135 self.next = ifaddr.ifa_next; 136 Some(InterfaceAddress::from_libc_ifaddrs(ifaddr)) 137 } 138 None => None, 139 } 140 } 141} 142 143/// Get interface addresses using libc's `getifaddrs` 144/// 145/// Note that the underlying implementation differs between OSes. Only the 146/// most common address families are supported by the nix crate (due to 147/// lack of time and complexity of testing). The address family is encoded 148/// in the specific variant of `SockaddrStorage` returned for the fields 149/// `address`, `netmask`, `broadcast`, and `destination`. For any entry not 150/// supported, the returned list will contain a `None` entry. 151/// 152/// # Example 153/// ``` 154/// let addrs = nix::ifaddrs::getifaddrs().unwrap(); 155/// for ifaddr in addrs { 156/// match ifaddr.address { 157/// Some(address) => { 158/// println!("interface {} address {}", 159/// ifaddr.interface_name, address); 160/// }, 161/// None => { 162/// println!("interface {} with unsupported address family", 163/// ifaddr.interface_name); 164/// } 165/// } 166/// } 167/// ``` 168pub fn getifaddrs() -> Result<InterfaceAddressIterator> { 169 let mut addrs = mem::MaybeUninit::<*mut libc::ifaddrs>::uninit(); 170 unsafe { 171 Errno::result(libc::getifaddrs(addrs.as_mut_ptr())).map(|_| { 172 InterfaceAddressIterator { 173 base: addrs.assume_init(), 174 next: addrs.assume_init(), 175 } 176 }) 177 } 178} 179 180#[cfg(test)] 181mod tests { 182 use super::*; 183 184 // Only checks if `getifaddrs` can be invoked without panicking. 185 #[test] 186 fn test_getifaddrs() { 187 let _ = getifaddrs(); 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() == Some(crate::sys::socket::AddressFamily::Inet) { 202 let _ = sock.as_sockaddr_in().unwrap(); 203 return; 204 } else if sock.family() 205 == Some(crate::sys::socket::AddressFamily::Inet6) 206 { 207 let _ = sock.as_sockaddr_in6().unwrap(); 208 return; 209 } 210 } 211 panic!("No address?"); 212 } 213} 214