• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
6 use cfg_if::cfg_if;
7 #[cfg(any(target_os = "ios", target_os = "macos"))]
8 use std::convert::TryFrom;
9 use std::ffi;
10 use std::iter::Iterator;
11 use std::mem;
12 use std::option::Option;
13 
14 use crate::net::if_::*;
15 use crate::sys::socket::{SockaddrLike, SockaddrStorage};
16 use crate::{Errno, Result};
17 
18 /// Describes a single address for an interface as returned by `getifaddrs`.
19 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
20 pub 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 
35 cfg_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"))]
workaround_xnu_bug(info: &libc::ifaddrs) -> Option<SockaddrStorage>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 
83 impl InterfaceAddress {
84     /// Create an `InterfaceAddress` from the libc struct.
from_libc_ifaddrs(info: &libc::ifaddrs) -> InterfaceAddress85     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)]
119 pub struct InterfaceAddressIterator {
120     base: *mut libc::ifaddrs,
121     next: *mut libc::ifaddrs,
122 }
123 
124 impl Drop for InterfaceAddressIterator {
drop(&mut self)125     fn drop(&mut self) {
126         unsafe { libc::freeifaddrs(self.base) };
127     }
128 }
129 
130 impl Iterator for InterfaceAddressIterator {
131     type Item = InterfaceAddress;
next(&mut self) -> Option<<Self as Iterator>::Item>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 /// ```
getifaddrs() -> Result<InterfaceAddressIterator>168 pub 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)]
181 mod tests {
182     use super::*;
183 
184     // Only checks if `getifaddrs` can be invoked without panicking.
185     #[test]
test_getifaddrs()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]
test_getifaddrs_netmask_correct()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