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>> {
33 let mut elements = HashMap::default();
34
35 for data in InterfaceIterator::new()? {
36 let current: &mut InterfaceData = elements.entry(data.name).or_default();
37
38 current.socket_addrs.extend(data.socket_addr);
39 assert!(!(current.mac.is_some() && data.mac.is_some()));
40 current.mac = current.mac.or(data.mac);
41 }
42
43 Ok(elements)
44}
45
46#[derive(Default, Debug)]
48pub struct InterfaceData {
49 socket_addrs: Vec<SocketAddr>,
50 mac: Option<[u8; 6]>,
51}
52
53impl InterfaceData {
54 #[must_use]
56 pub fn has_ip_addr(&self, address: IpAddr) -> bool {
57 self.socket_addrs
58 .iter()
59 .any(|socket_addr| socket_addr.ip() == address)
60 }
61
62 pub fn ips(&self) -> impl Iterator<Item = IpAddr> + '_ {
64 self.socket_addrs.iter().map(std::net::SocketAddr::ip)
65 }
66
67 #[must_use]
69 pub fn mac(&self) -> Option<[u8; 6]> {
70 self.mac
71 }
72}
73
74struct InterfaceIterator {
80 base: *mut libc::ifaddrs,
81 next: *const libc::ifaddrs,
82}
83
84impl InterfaceIterator {
85 pub fn new() -> std::io::Result<Self> {
86 let mut addrs: *mut libc::ifaddrs = std::ptr::null_mut();
87
88 unsafe {
96 cerr(libc::getifaddrs(&raw mut addrs))?;
97
98 assert!(!addrs.is_null());
99
100 Ok(Self {
101 base: addrs,
102 next: addrs,
103 })
104 }
105 }
106}
107
108impl Drop for InterfaceIterator {
109 fn drop(&mut self) {
110 unsafe { libc::freeifaddrs(self.base) };
113 }
114}
115
116struct InterfaceDataInternal {
117 name: InterfaceName,
118 mac: Option<[u8; 6]>,
119 socket_addr: Option<SocketAddr>,
120}
121
122impl Iterator for InterfaceIterator {
123 type Item = InterfaceDataInternal;
124
125 #[expect(
126 clippy::cast_possible_truncation,
127 reason = "libc constants are of inconsistent sizes."
128 )]
129 fn next(&mut self) -> Option<<Self as Iterator>::Item> {
130 let ifaddr = unsafe { self.next.as_ref() }?;
133
134 self.next = ifaddr.ifa_next;
138
139 let ifname = unsafe { std::ffi::CStr::from_ptr(ifaddr.ifa_name) };
142 let name = match std::str::from_utf8(ifname.to_bytes()) {
143 Err(_) => unreachable!("interface names must be ascii"),
144 Ok(name) => InterfaceName::from_str(name).expect("name from os"),
145 };
146
147 let family = unsafe { ifaddr.ifa_addr.as_ref() }.map(|a| a.sa_family);
150
151 #[allow(unused)]
152 let mac: Option<[u8; 6]> = None;
153
154 #[cfg(target_os = "linux")]
155 let mac = if family == Some(libc::AF_PACKET as _) {
159 let sockaddr_ll: libc::sockaddr_ll =
160 unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) };
161
162 Some([
163 sockaddr_ll.sll_addr[0],
164 sockaddr_ll.sll_addr[1],
165 sockaddr_ll.sll_addr[2],
166 sockaddr_ll.sll_addr[3],
167 sockaddr_ll.sll_addr[4],
168 sockaddr_ll.sll_addr[5],
169 ])
170 } else {
171 None
172 };
173
174 #[cfg(any(target_os = "freebsd", target_os = "macos"))]
175 #[expect(
176 clippy::cast_sign_loss,
177 reason = "length is communicated as signed number, even though it is always positive."
178 )]
179 let mac = if family == Some(libc::AF_LINK as _) {
180 const IFT_ETHER: u8 = 0x6;
182
183 let sockaddr_dl: libc::sockaddr_dl =
187 unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) };
188
189 if sockaddr_dl.sdl_type == IFT_ETHER
190 && sockaddr_dl.sdl_nlen.saturating_add(6) as usize <= sockaddr_dl.sdl_data.len()
191 {
192 Some([
193 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize] as u8,
194 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 1] as u8,
195 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 2] as u8,
196 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 3] as u8,
197 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 4] as u8,
198 sockaddr_dl.sdl_data[sockaddr_dl.sdl_nlen as usize + 5] as u8,
199 ])
200 } else {
201 None
202 }
203 } else {
204 None
205 };
206
207 let socket_addr = unsafe { sockaddr_to_socket_addr(ifaddr.ifa_addr) };
209
210 let data = InterfaceDataInternal {
211 name,
212 mac,
213 socket_addr,
214 };
215
216 Some(data)
217 }
218}
219
220#[derive(Clone, Copy, PartialEq, Eq, Hash)]
222pub struct InterfaceName {
223 bytes: [u8; libc::IFNAMSIZ],
224}
225
226impl InterfaceName {
227 #[cfg(all(test, target_os = "linux"))]
229 pub const LOOPBACK: Self = Self {
230 bytes: *b"lo\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
231 };
232
233 #[cfg(all(test, any(target_os = "freebsd", target_os = "macos")))]
235 pub const LOOPBACK: Self = Self {
236 bytes: *b"lo0\0\0\0\0\0\0\0\0\0\0\0\0\0",
237 };
238
239 #[cfg(test)]
241 pub const INVALID: Self = Self {
242 bytes: *b"123412341234123\0",
243 };
244
245 #[must_use]
247 pub fn as_str(&self) -> &str {
248 std::str::from_utf8(self.bytes.as_slice())
249 .unwrap_or_default()
250 .trim_end_matches('\0')
251 }
252
253 #[must_use]
258 pub fn as_cstr(&self) -> &std::ffi::CStr {
259 let first_null = self.bytes.iter().position(|b| *b == 0).unwrap();
264 std::ffi::CStr::from_bytes_with_nul(&self.bytes[..=first_null]).unwrap()
265 }
266
267 #[allow(
269 clippy::cast_possible_wrap,
270 reason = "libc::c_char can be either signed or unsigned"
271 )]
272 #[must_use]
273 pub fn to_ifr_name(self) -> [libc::c_char; libc::IFNAMSIZ] {
274 let mut it = self.bytes.iter().copied();
275 [0; libc::IFNAMSIZ].map(|_| it.next().unwrap_or(0) as libc::c_char)
276 }
277
278 pub fn from_socket_addr(local_addr: SocketAddr) -> std::io::Result<Option<Self>> {
283 let matches_inferface = |interface: &InterfaceDataInternal| match interface.socket_addr {
284 None => false,
285 Some(address) => address.ip() == local_addr.ip(),
286 };
287
288 match InterfaceIterator::new()?.find(matches_inferface) {
289 Some(interface) => Ok(Some(interface.name)),
290 None => Ok(None),
291 }
292 }
293
294 #[must_use]
296 pub fn get_index(&self) -> Option<libc::c_uint> {
297 match unsafe { libc::if_nametoindex(self.as_cstr().as_ptr()) } {
301 0 => None,
302 n => Some(n),
303 }
304 }
305
306 #[must_use]
308 pub fn lookup_phc(&self) -> Option<u32> {
309 lookup_phc(*self)
310 }
311}
312
313impl std::fmt::Debug for InterfaceName {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 f.debug_tuple("InterfaceName")
316 .field(&self.as_str())
317 .finish()
318 }
319}
320
321impl std::fmt::Display for InterfaceName {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 self.as_str().fmt(f)
324 }
325}
326
327impl std::str::FromStr for InterfaceName {
328 type Err = ();
329
330 fn from_str(s: &str) -> Result<Self, Self::Err> {
331 let mut bytes = [0; libc::IFNAMSIZ];
332
333 if s.len() >= bytes.len() {
335 return Err(());
336 }
337
338 if s.is_empty() {
339 return Err(());
341 }
342
343 let mut it = s.bytes();
344 bytes = bytes.map(|_| it.next().unwrap_or_default());
345
346 Ok(Self { bytes })
347 }
348}
349
350#[cfg(feature = "serde")]
351impl<'de> serde::Deserialize<'de> for InterfaceName {
352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
353 where
354 D: serde::Deserializer<'de>,
355 {
356 let s = String::deserialize(deserializer)?;
357 FromStr::from_str(&s).map_err(|()| serde::de::Error::custom("invalid interface name"))
358 }
359}
360
361unsafe fn sockaddr_to_socket_addr(sockaddr: *const libc::sockaddr) -> Option<SocketAddr> {
367 if sockaddr.is_null() {
374 return None;
375 }
376
377 match unsafe { libc::c_int::from((*sockaddr).sa_family) } {
379 libc::AF_INET => {
380 let inaddr: libc::sockaddr_in =
385 unsafe { std::ptr::read_unaligned(sockaddr.cast::<libc::sockaddr_in>()) };
386
387 let socketaddr = std::net::SocketAddrV4::new(
388 std::net::Ipv4Addr::from(inaddr.sin_addr.s_addr.to_ne_bytes()),
389 u16::from_be_bytes(inaddr.sin_port.to_ne_bytes()),
390 );
391
392 Some(std::net::SocketAddr::V4(socketaddr))
393 }
394 libc::AF_INET6 => {
395 let inaddr: libc::sockaddr_in6 =
399 unsafe { std::ptr::read_unaligned(sockaddr.cast::<libc::sockaddr_in6>()) };
400
401 let sin_addr = inaddr.sin6_addr.s6_addr;
404 let segment_bytes: [u8; 16] =
405 unsafe { std::ptr::read_unaligned((&raw const sin_addr).cast()) };
406
407 let socketaddr = std::net::SocketAddrV6::new(
408 std::net::Ipv6Addr::from(segment_bytes),
409 u16::from_be_bytes(inaddr.sin6_port.to_ne_bytes()),
410 inaddr.sin6_flowinfo, inaddr.sin6_scope_id,
412 );
413
414 Some(std::net::SocketAddr::V6(socketaddr))
415 }
416 _ => None,
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use std::net::Ipv4Addr;
423
424 use super::*;
425
426 #[test]
427 fn interface_name_from_string() {
428 assert!(InterfaceName::from_str("").is_err());
429 assert!(InterfaceName::from_str("a string that is too long").is_err());
430
431 let input = "enp0s31f6";
432 assert_eq!(InterfaceName::from_str(input).unwrap().as_str(), input);
433
434 #[allow(
435 clippy::cast_possible_wrap,
436 reason = "libc::c_char may be either signed or unsigned, and this is not specified"
437 )]
438 let ifr_name = (*b"enp0s31f6\0\0\0\0\0\0\0").map(|b| b as libc::c_char);
439 assert_eq!(
440 InterfaceName::from_str(input).unwrap().to_ifr_name(),
441 ifr_name
442 );
443 }
444
445 #[test]
446 fn test_mac_address_iterator() {
447 let v: Vec<_> = InterfaceIterator::new()
448 .unwrap()
449 .filter_map(|d| d.mac)
450 .collect();
451
452 assert!(!v.is_empty());
453 }
454
455 #[test]
456 fn test_interface_name_iterator() {
457 let v: Vec<_> = InterfaceIterator::new().unwrap().map(|d| d.name).collect();
458
459 assert!(v.contains(&InterfaceName::LOOPBACK));
460 }
461
462 #[test]
463 fn test_socket_addr_iterator() {
464 let v: Vec<_> = InterfaceIterator::new()
465 .unwrap()
466 .filter_map(|d| d.socket_addr)
467 .collect();
468
469 let localhost_0 = SocketAddr::from((Ipv4Addr::LOCALHOST, 0));
470
471 assert!(v.contains(&localhost_0));
472 }
473
474 #[test]
475 fn interface_index_ipv4() {
476 assert!(InterfaceName::LOOPBACK.get_index().is_some());
477 }
478
479 #[test]
480 fn interface_index_ipv6() {
481 assert!(InterfaceName::LOOPBACK.get_index().is_some());
482 }
483
484 #[test]
485 fn interface_index_invalid() {
486 assert!(InterfaceName::INVALID.get_index().is_none());
487 }
488}