nix/
ifaddrs.rs

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}