timestamped_socket/
interface.rs

1use 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// NOTE: this detection logic is not sharable with macos!
15#[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
61// Invariants:
62// self.base always contains a pointer received from libc::getifaddrs that is not NULL. The region pointed to is never modified in rust code.
63// self.next always contains either a pointer pointing to a valid ifaddr received from libc::getifaddrs or null.
64//
65// These invariants are setup by InterfaceIterator::new and guaranteed by drop and next, which are the only places these pointers are used.
66struct 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        // Safety:
76        // addrs lives for the duration of the call to getifaddrs.
77        //
78        // Invariant preservation:
79        // we validate that the received address is not null, and
80        // by the guarantees from getifaddrs points to a valid
81        // ifaddr returned from getifaddrs
82        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        // Safety:
98        // By the invariants, self.base is guaranteed to point to a memory region allocated by getifaddrs
99        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        // Safety:
114        // By the invariants, self.next is guaranteed to be a valid pointer to an ifaddrs struct or null.
115        let ifaddr = unsafe { self.next.as_ref() }?;
116
117        // Invariant preservation
118        // By the guarantees given by getifaddrs, ifaddr.ifa_next is either null or points to a valid
119        // ifaddr.
120        self.next = ifaddr.ifa_next;
121
122        // Safety:
123        // getifaddrs guarantees that ifa_name is not null and points to a valid C string.
124        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        // Safety:
131        // getifaddrs guarantees that ifa_addr either points to a valid address or is NULL.
132        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        // Safety: getifaddrs ensures that, if an address is present, it is valid. A valid address
139        // of type AF_PACKET is always reinterpret castable to sockaddr_ll, and we know an address
140        // is present since family is not None
141        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            // Safety: getifaddrs ensures that, if an address is present, it is valid. A valid address
160            // of type AF_LINK is always reinterpret castable to sockaddr_ll, and we know an address
161            // is present since family is not None
162            let sockaddr_dl: libc::sockaddr_dl =
163                unsafe { std::ptr::read_unaligned(ifaddr.ifa_addr as *const _) };
164
165            // From sys/net/if_types.h in freebsd:
166            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        // Safety: ifaddr.ifa_addr is always either NULL, or by the guarantees of getifaddrs, points to a valid address.
187        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        // TODO: in rust 1.69.0, use
228        // std::ffi::CStr::from_bytes_until_nul(&self.bytes[..]).unwrap()
229
230        // it is an invariant of InterfaceName that the bytes are null-terminated
231        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        // # SAFETY
254        //
255        // self lives for the duration of the call, and is null terminated.
256        match unsafe { libc::if_nametoindex(self.as_cstr().as_ptr()) } {
257            0 => None,
258            n => Some(n),
259        }
260    }
261
262    /// Do a lookup for the Physical Hardware Clock index for this interface.
263    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        // >= so that we always retain a NUL byte at the end
289        if s.len() >= bytes.len() {
290            return Err(());
291        }
292
293        if s.is_empty() {
294            // this causes problems down the line when giving the interface name to tokio
295            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
316/// Convert a libc::sockaddr to a rust std::net::SocketAddr
317///
318/// # Safety
319///
320/// This function assumes that sockaddr is either NULL or points to a valid address.
321unsafe fn sockaddr_to_socket_addr(sockaddr: *const libc::sockaddr) -> Option<SocketAddr> {
322    // Most (but not all) of the fields in a socket addr are in network byte
323    // ordering. As such, when doing conversions here, we should start from the
324    // NATIVE byte representation, as this will actualy be the big-endian
325    // representation of the underlying value regardless of platform.
326
327    // Check for null pointers
328    if sockaddr.is_null() {
329        return None;
330    }
331
332    // Safety: by the previous check, sockaddr is not NULL and hence points to a valid address
333    match unsafe { (*sockaddr).sa_family as libc::c_int } {
334        libc::AF_INET => {
335            // SAFETY: we cast from a libc::sockaddr (alignment 2) to a libc::sockaddr_in (alignment 4)
336            // that means that the pointer is now potentially unaligned. We must used read_unaligned!
337            // However, the rest of the cast is safe as a valid AF_INET address is always reinterpret castable
338            // as a sockaddr_in
339            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            // SAFETY: we cast from a libc::sockaddr (alignment 2) to a libc::sockaddr_in6 (alignment 4)
351            // that means that the pointer is now potentially unaligned. We must used read_unaligned!
352            // However, the cast is safe as a valid AF_INET6 address is always reinterpret catable as a sockaddr_in6
353            let inaddr: libc::sockaddr_in6 =
354                unsafe { std::ptr::read_unaligned(sockaddr as *const libc::sockaddr_in6) };
355
356            // Safety:
357            // sin_addr lives for the duration fo the call and matches type
358            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, /* NOTE: Despite network byte order, no conversion is needed (see https://github.com/rust-lang/rust/issues/101605) */
366                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}