timestamped_socket/
interface.rs1use std::{
2 collections::HashMap,
3 net::{IpAddr, SocketAddr},
4 str::FromStr,
5};
6
7use super::cerr;
8
9#[cfg(target_os = "linux")]
10mod linux;
11#[cfg(target_os = "linux")]
12pub use linux::{lookup_phc, ChangeDetector};
13
14#[cfg(target_os = "freebsd")]
16mod freebsd;
17#[cfg(target_os = "freebsd")]
18pub use freebsd::{lookup_phc, ChangeDetector};
19
20#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
21mod fallback;
22#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
23pub use fallback::{lookup_phc, ChangeDetector};
24
25pub fn interfaces() -> std::io::Result<HashMap<InterfaceName, InterfaceData>> {
26 let mut elements = HashMap::default();
27
28 for data in InterfaceIterator::new()? {
29 let current: &mut InterfaceData = elements.entry(data.name).or_default();
30
31 current.socket_addrs.extend(data.socket_addr);
32 assert!(!(current.mac.is_some() && data.mac.is_some()));
33 current.mac = current.mac.or(data.mac);
34 }
35
36 Ok(elements)
37}
38
39#[derive(Default, Debug)]
40pub struct InterfaceData {
41 socket_addrs: Vec<SocketAddr>,
42 mac: Option<[u8; 6]>,
43}
44
45impl InterfaceData {
46 pub fn has_ip_addr(&self, address: IpAddr) -> bool {
47 self.socket_addrs
48 .iter()
49 .any(|socket_addr| socket_addr.ip() == address)
50 }
51
52 pub fn ips(&self) -> impl Iterator<Item = IpAddr> + '_ {
53 self.socket_addrs.iter().map(|a| a.ip())
54 }
55
56 pub fn mac(&self) -> Option<[u8; 6]> {
57 self.mac
58 }
59}
60
61struct InterfaceIterator {
67 base: *mut libc::ifaddrs,
68 next: *const libc::ifaddrs,
69}
70
71impl InterfaceIterator {
72 pub fn new() -> std::io::Result<Self> {
73 let mut addrs: *mut libc::ifaddrs = std::ptr::null_mut();
74
75 unsafe {
83 cerr(libc::getifaddrs(&mut addrs))?;
84
85 assert!(!addrs.is_null());
86
87 Ok(Self {
88 base: addrs,
89 next: addrs,
90 })
91 }
92 }
93}
94
95impl Drop for InterfaceIterator {
96 fn drop(&mut self) {
97 unsafe { libc::freeifaddrs(self.base) };
100 }
101}
102
103struct InterfaceDataInternal {
104 name: InterfaceName,
105 mac: Option<[u8; 6]>,
106 socket_addr: Option<SocketAddr>,
107}
108
109impl Iterator for InterfaceIterator {
110 type Item = InterfaceDataInternal;
111
112 fn next(&mut self) -> Option<<Self as Iterator>::Item> {
113 let ifaddr = unsafe { self.next.as_ref() }?;
116
117 self.next = ifaddr.ifa_next;
121
122 let ifname = unsafe { std::ffi::CStr::from_ptr(ifaddr.ifa_name) };
125 let name = match std::str::from_utf8(ifname.to_bytes()) {
126 Err(_) => unreachable!("interface names must be ascii"),
127 Ok(name) => InterfaceName::from_str(name).expect("name from os"),
128 };
129
130 let family = unsafe { ifaddr.ifa_addr.as_ref() }.map(|a| a.sa_family);
133
134 #[allow(unused)]
135 let mac: Option<[u8; 6]> = None;
136
137 #[cfg(target_os = "linux")]
138 let mac = if family == Some(libc::AF_PACKET as _) {
142 let sockaddr_ll: libc::sockaddr_ll =
143 unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) };
144
145 Some([
146 sockaddr_ll.sll_addr[0],
147 sockaddr_ll.sll_addr[1],
148 sockaddr_ll.sll_addr[2],
149 sockaddr_ll.sll_addr[3],
150 sockaddr_ll.sll_addr[4],
151 sockaddr_ll.sll_addr[5],
152 ])
153 } else {
154 None
155 };
156
157 #[cfg(any(target_os = "freebsd", target_os = "macos"))]
158 let mac = if family == Some(libc::AF_LINK as _) {
159 let sockaddr_dl: libc::sockaddr_dl =
163 unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) };
164
165 const IFT_ETHER: u8 = 0x6;
167
168 if sockaddr_dl.sdl_type == IFT_ETHER
169 && sockaddr_dl.sdl_nlen.saturating_add(6) as usize <= sockaddr_dl.sdl_data.len()
170 {
171 Some([
172 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize] as u8,
173 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 1] as u8,
174 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 2] as u8,
175 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 3] as u8,
176 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 4] as u8,
177 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 5] as u8,
178 ])
179 } else {
180 None
181 }
182 } else {
183 None
184 };
185
186 let socket_addr = unsafe { sockaddr_to_socket_addr(ifaddr.ifa_addr) };
188
189 let data = InterfaceDataInternal {
190 name,
191 mac,
192 socket_addr,
193 };
194
195 Some(data)
196 }
197}
198
199#[derive(Clone, Copy, PartialEq, Eq, Hash)]
200pub struct InterfaceName {
201 bytes: [u8; libc::IFNAMSIZ],
202}
203
204impl InterfaceName {
205 #[cfg(all(test, target_os = "linux"))]
206 pub const LOOPBACK: Self = Self {
207 bytes: *b"lo\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
208 };
209
210 #[cfg(all(test, any(target_os = "freebsd", target_os = "macos")))]
211 pub const LOOPBACK: Self = Self {
212 bytes: *b"lo0\0\0\0\0\0\0\0\0\0\0\0\0\0",
213 };
214
215 #[cfg(test)]
216 pub const INVALID: Self = Self {
217 bytes: *b"123412341234123\0",
218 };
219
220 pub fn as_str(&self) -> &str {
221 std::str::from_utf8(self.bytes.as_slice())
222 .unwrap_or_default()
223 .trim_end_matches('\0')
224 }
225
226 pub fn as_cstr(&self) -> &std::ffi::CStr {
227 let first_null = self.bytes.iter().position(|b| *b == 0).unwrap();
232 std::ffi::CStr::from_bytes_with_nul(&self.bytes[..=first_null]).unwrap()
233 }
234
235 pub fn to_ifr_name(self) -> [libc::c_char; libc::IFNAMSIZ] {
236 let mut it = self.bytes.iter().copied();
237 [0; libc::IFNAMSIZ].map(|_| it.next().unwrap_or(0) as libc::c_char)
238 }
239
240 pub fn from_socket_addr(local_addr: SocketAddr) -> std::io::Result<Option<Self>> {
241 let matches_inferface = |interface: &InterfaceDataInternal| match interface.socket_addr {
242 None => false,
243 Some(address) => address.ip() == local_addr.ip(),
244 };
245
246 match InterfaceIterator::new()?.find(matches_inferface) {
247 Some(interface) => Ok(Some(interface.name)),
248 None => Ok(None),
249 }
250 }
251
252 pub fn get_index(&self) -> Option<libc::c_uint> {
253 match unsafe { libc::if_nametoindex(self.as_cstr().as_ptr()) } {
257 0 => None,
258 n => Some(n),
259 }
260 }
261
262 pub fn lookup_phc(&self) -> Option<u32> {
264 lookup_phc(*self)
265 }
266}
267
268impl std::fmt::Debug for InterfaceName {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.debug_tuple("InterfaceName")
271 .field(&self.as_str())
272 .finish()
273 }
274}
275
276impl std::fmt::Display for InterfaceName {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 self.as_str().fmt(f)
279 }
280}
281
282impl std::str::FromStr for InterfaceName {
283 type Err = ();
284
285 fn from_str(s: &str) -> Result<Self, Self::Err> {
286 let mut bytes = [0; libc::IFNAMSIZ];
287
288 if s.len() >= bytes.len() {
290 return Err(());
291 }
292
293 if s.is_empty() {
294 return Err(());
296 }
297
298 let mut it = s.bytes();
299 bytes = bytes.map(|_| it.next().unwrap_or_default());
300
301 Ok(Self { bytes })
302 }
303}
304
305#[cfg(feature = "serde")]
306impl<'de> serde::Deserialize<'de> for InterfaceName {
307 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
308 where
309 D: serde::Deserializer<'de>,
310 {
311 let s = String::deserialize(deserializer)?;
312 FromStr::from_str(&s).map_err(|_| serde::de::Error::custom("invalid interface name"))
313 }
314}
315
316unsafe fn sockaddr_to_socket_addr(sockaddr: *const libc::sockaddr) -> Option<SocketAddr> {
322 if sockaddr.is_null() {
329 return None;
330 }
331
332 match unsafe { (*sockaddr).sa_family as libc::c_int } {
334 libc::AF_INET => {
335 let inaddr: libc::sockaddr_in =
340 unsafe { std::ptr::read_unaligned(sockaddr as *const libc::sockaddr_in) };
341
342 let socketaddr = std::net::SocketAddrV4::new(
343 std::net::Ipv4Addr::from(inaddr.sin_addr.s_addr.to_ne_bytes()),
344 u16::from_be_bytes(inaddr.sin_port.to_ne_bytes()),
345 );
346
347 Some(std::net::SocketAddr::V4(socketaddr))
348 }
349 libc::AF_INET6 => {
350 let inaddr: libc::sockaddr_in6 =
354 unsafe { std::ptr::read_unaligned(sockaddr as *const libc::sockaddr_in6) };
355
356 let sin_addr = inaddr.sin6_addr.s6_addr;
359 let segment_bytes: [u8; 16] =
360 unsafe { std::ptr::read_unaligned(&sin_addr as *const _ as *const _) };
361
362 let socketaddr = std::net::SocketAddrV6::new(
363 std::net::Ipv6Addr::from(segment_bytes),
364 u16::from_be_bytes(inaddr.sin6_port.to_ne_bytes()),
365 inaddr.sin6_flowinfo, inaddr.sin6_scope_id,
367 );
368
369 Some(std::net::SocketAddr::V6(socketaddr))
370 }
371 _ => None,
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use std::net::Ipv4Addr;
378
379 use super::*;
380
381 #[test]
382 fn interface_name_from_string() {
383 assert!(InterfaceName::from_str("").is_err());
384 assert!(InterfaceName::from_str("a string that is too long").is_err());
385
386 let input = "enp0s31f6";
387 assert_eq!(InterfaceName::from_str(input).unwrap().as_str(), input);
388
389 let ifr_name = (*b"enp0s31f6\0\0\0\0\0\0\0").map(|b| b as libc::c_char);
390 assert_eq!(
391 InterfaceName::from_str(input).unwrap().to_ifr_name(),
392 ifr_name
393 );
394 }
395
396 #[test]
397 fn test_mac_address_iterator() {
398 let v: Vec<_> = InterfaceIterator::new()
399 .unwrap()
400 .filter_map(|d| d.mac)
401 .collect();
402
403 assert!(!v.is_empty());
404 }
405
406 #[test]
407 fn test_interface_name_iterator() {
408 let v: Vec<_> = InterfaceIterator::new().unwrap().map(|d| d.name).collect();
409
410 assert!(v.contains(&InterfaceName::LOOPBACK));
411 }
412
413 #[test]
414 fn test_socket_addr_iterator() {
415 let v: Vec<_> = InterfaceIterator::new()
416 .unwrap()
417 .filter_map(|d| d.socket_addr)
418 .collect();
419
420 let localhost_0 = SocketAddr::from((Ipv4Addr::LOCALHOST, 0));
421
422 assert!(v.contains(&localhost_0));
423 }
424
425 #[test]
426 fn interface_index_ipv4() {
427 assert!(InterfaceName::LOOPBACK.get_index().is_some());
428 }
429
430 #[test]
431 fn interface_index_ipv6() {
432 assert!(InterfaceName::LOOPBACK.get_index().is_some());
433 }
434
435 #[test]
436 fn interface_index_invalid() {
437 assert!(InterfaceName::INVALID.get_index().is_none());
438 }
439}