sysinfo/unix/
network_helper.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::collections::HashMap;
4use std::collections::HashSet;
5use std::ffi::CStr;
6use std::mem::MaybeUninit;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
9use std::os::raw::c_char;
10use std::ptr::null_mut;
11use std::str::from_utf8_unchecked;
12use std::{io, mem};
13
14use crate::{IpNetwork, MacAddr};
15
16/// This iterator yields an interface name and address.
17pub(crate) struct InterfaceAddressIterator {
18    /// Pointer to the current `ifaddrs` struct.
19    ifap: *mut libc::ifaddrs,
20    /// Pointer to the first element in linked list.
21    buf: *mut libc::ifaddrs,
22}
23
24impl Iterator for InterfaceAddressIterator {
25    type Item = (String, MacAddr);
26
27    fn next(&mut self) -> Option<Self::Item> {
28        unsafe {
29            while !self.ifap.is_null() {
30                // advance the pointer until a MAC address is found
31                let ifap = self.ifap;
32                self.ifap = (*ifap).ifa_next;
33
34                if let Some(addr) = parse_interface_address(ifap) {
35                    let ifa_name = (*ifap).ifa_name;
36                    if ifa_name.is_null() {
37                        continue;
38                    }
39                    // libc::IFNAMSIZ + 6
40                    // This size refers to ./apple/network.rs:75
41                    let mut name = vec![0u8; libc::IFNAMSIZ + 6];
42                    libc::strcpy(name.as_mut_ptr() as _, (*ifap).ifa_name);
43                    name.set_len(libc::strlen((*ifap).ifa_name));
44                    let name = String::from_utf8_unchecked(name);
45
46                    return Some((name, addr));
47                }
48            }
49            None
50        }
51    }
52}
53
54impl Drop for InterfaceAddressIterator {
55    fn drop(&mut self) {
56        unsafe {
57            libc::freeifaddrs(self.buf);
58        }
59    }
60}
61
62#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))]
63impl From<&libc::sockaddr_dl> for MacAddr {
64    fn from(value: &libc::sockaddr_dl) -> Self {
65        let sdl_data = value.sdl_data;
66        // interface name length, NO trailing 0
67        let sdl_nlen = value.sdl_nlen as usize;
68        // make sure that it is never out of bound
69        if sdl_nlen + 5 < 12 {
70            MacAddr([
71                sdl_data[sdl_nlen] as u8,
72                sdl_data[sdl_nlen + 1] as u8,
73                sdl_data[sdl_nlen + 2] as u8,
74                sdl_data[sdl_nlen + 3] as u8,
75                sdl_data[sdl_nlen + 4] as u8,
76                sdl_data[sdl_nlen + 5] as u8,
77            ])
78        } else {
79            MacAddr::UNSPECIFIED
80        }
81    }
82}
83
84#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))]
85unsafe fn parse_interface_address(ifap: *const libc::ifaddrs) -> Option<MacAddr> {
86    let sock_addr = (*ifap).ifa_addr;
87    if sock_addr.is_null() {
88        return None;
89    }
90    match (*sock_addr).sa_family as libc::c_int {
91        libc::AF_LINK => {
92            let addr = sock_addr as *const libc::sockaddr_dl;
93            Some(MacAddr::from(&*addr))
94        }
95        _ => None,
96    }
97}
98
99#[cfg(any(target_os = "linux", target_os = "android"))]
100unsafe fn parse_interface_address(ifap: *const libc::ifaddrs) -> Option<MacAddr> {
101    use libc::sockaddr_ll;
102
103    let sock_addr = (*ifap).ifa_addr;
104    if sock_addr.is_null() {
105        return None;
106    }
107    match (*sock_addr).sa_family as libc::c_int {
108        libc::AF_PACKET => {
109            let addr = sock_addr as *const sockaddr_ll;
110            // Take the first 6 bytes
111            let [addr @ .., _, _] = (*addr).sll_addr;
112            Some(MacAddr(addr))
113        }
114        _ => None,
115    }
116}
117
118/// Return an iterator on (interface_name, address) pairs
119pub(crate) unsafe fn get_interface_address() -> Result<InterfaceAddressIterator, String> {
120    let mut ifap = null_mut();
121    if retry_eintr!(libc::getifaddrs(&mut ifap)) == 0 && !ifap.is_null() {
122        Ok(InterfaceAddressIterator { ifap, buf: ifap })
123    } else {
124        Err("failed to call getifaddrs()".to_string())
125    }
126}
127
128pub(crate) unsafe fn get_interface_ip_networks() -> HashMap<String, HashSet<IpNetwork>> {
129    let mut ifaces: HashMap<String, HashSet<IpNetwork>> = HashMap::new();
130    let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit();
131
132    // Safety: addrs.as_mut_ptr() is valid, it points to addrs.
133    if libc::getifaddrs(addrs.as_mut_ptr()) != 0 {
134        sysinfo_debug!("Failed to operate libc::getifaddrs as ifaddrs Uninitialized");
135        return ifaces;
136    }
137
138    // Safety: If there was an error, we would have already returned.
139    // Therefore, getifaddrs has initialized `addrs`.
140    let addrs = addrs.assume_init();
141
142    let mut addr = addrs;
143    while !addr.is_null() {
144        // Safety: We assume that addr is valid for the lifetime of this loop
145        // body, and is not mutated.
146        let addr_ref: &libc::ifaddrs = &*addr;
147
148        let c_str = addr_ref.ifa_name as *const c_char;
149
150        // Safety: ifa_name is a null terminated interface name
151        let bytes = CStr::from_ptr(c_str).to_bytes();
152
153        // Safety: Interfaces on unix must be valid UTF-8
154        let mut name = from_utf8_unchecked(bytes).to_owned();
155        // Interfaces names may be formatted as <interface name>:<sub-interface index>
156        if name.contains(':') {
157            if let Some(interface_name) = name.split(':').next() {
158                name = interface_name.to_string()
159            } else {
160                // The sub-interface is malformed, skipping to the next addr
161                addr = addr_ref.ifa_next;
162                continue;
163            }
164        }
165
166        let ip = sockaddr_to_network_addr(addr_ref.ifa_addr as *const libc::sockaddr);
167        let netmask = sockaddr_to_network_addr(addr_ref.ifa_netmask as *const libc::sockaddr);
168        let prefix = netmask
169            .and_then(|netmask| ip_mask_to_prefix(netmask).ok())
170            .unwrap_or(0);
171        if let Some(ip) = ip {
172            ifaces
173                .entry(name)
174                .and_modify(|values| {
175                    values.insert(IpNetwork { addr: ip, prefix });
176                })
177                .or_insert(HashSet::from([IpNetwork { addr: ip, prefix }]));
178        }
179        addr = addr_ref.ifa_next;
180    }
181
182    // Safety: addrs has been previously allocated through getifaddrs
183    libc::freeifaddrs(addrs);
184    ifaces
185}
186
187#[cfg(any(target_os = "linux", target_os = "android"))]
188fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> Option<IpAddr> {
189    unsafe {
190        if sa.is_null() || (*sa).sa_family as libc::c_int == libc::AF_PACKET {
191            None
192        } else {
193            let addr = sockaddr_to_addr(
194                &(sa as *const libc::sockaddr_storage).read_unaligned(),
195                mem::size_of::<libc::sockaddr_storage>(),
196            );
197
198            match addr {
199                Ok(SocketAddr::V4(sa)) => Some(IpAddr::V4(*sa.ip())),
200                Ok(SocketAddr::V6(sa)) => Some(IpAddr::V6(*sa.ip())),
201                _ => None,
202            }
203        }
204    }
205}
206#[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
207pub type InAddrType = libc::c_uint;
208#[cfg(any(target_os = "illumos", target_os = "solaris"))]
209pub type InAddrType = libc::c_ulonglong;
210
211fn sockaddr_to_addr(storage: &libc::sockaddr_storage, len: usize) -> io::Result<SocketAddr> {
212    match storage.ss_family as libc::c_int {
213        libc::AF_INET => {
214            assert!(len >= mem::size_of::<libc::sockaddr_in>());
215            let storage: &libc::sockaddr_in = unsafe { mem::transmute(storage) };
216            let ip = (storage.sin_addr.s_addr as InAddrType).to_be();
217            let a = (ip >> 24) as u8;
218            let b = (ip >> 16) as u8;
219            let c = (ip >> 8) as u8;
220            let d = ip as u8;
221            let sockaddrv4 = SocketAddrV4::new(Ipv4Addr::new(a, b, c, d), storage.sin_port.to_be());
222            Ok(SocketAddr::V4(sockaddrv4))
223        }
224        libc::AF_INET6 => {
225            assert!(len >= mem::size_of::<libc::sockaddr_in6>());
226            let storage: &libc::sockaddr_in6 = unsafe { mem::transmute(storage) };
227            let arr: [u16; 8] = unsafe { mem::transmute(storage.sin6_addr.s6_addr) };
228            let ip = Ipv6Addr::new(
229                arr[0].to_be(),
230                arr[1].to_be(),
231                arr[2].to_be(),
232                arr[3].to_be(),
233                arr[4].to_be(),
234                arr[5].to_be(),
235                arr[6].to_be(),
236                arr[7].to_be(),
237            );
238            Ok(SocketAddr::V6(SocketAddrV6::new(
239                ip,
240                storage.sin6_port.to_be(),
241                u32::from_be(storage.sin6_flowinfo),
242                storage.sin6_scope_id,
243            )))
244        }
245        _ => Err(io::Error::new(
246            io::ErrorKind::InvalidData,
247            "expected IPv4 or IPv6 socket",
248        )),
249    }
250}
251
252#[cfg(any(
253    target_os = "openbsd",
254    target_os = "freebsd",
255    target_os = "netbsd",
256    target_os = "illumos",
257    target_os = "solaris",
258    target_os = "macos",
259    target_os = "ios"
260))]
261fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> Option<IpAddr> {
262    unsafe {
263        if sa.is_null() || (*sa).sa_family as libc::c_int == 18 {
264            None
265        } else {
266            let addr = sockaddr_to_addr(
267                &(sa as *const libc::sockaddr_storage).read_unaligned(),
268                mem::size_of::<libc::sockaddr_storage>(),
269            );
270
271            match addr {
272                Ok(SocketAddr::V4(sa)) => Some(IpAddr::V4(*sa.ip())),
273                Ok(SocketAddr::V6(sa)) => Some(IpAddr::V6(*sa.ip())),
274                _ => None,
275            }
276        }
277    }
278}
279
280pub(crate) fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, &'static str> {
281    match mask {
282        IpAddr::V4(mask) => ipv4_mask_to_prefix(mask),
283        IpAddr::V6(mask) => ipv6_mask_to_prefix(mask),
284    }
285}
286
287pub(crate) fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, &'static str> {
288    let mask = u32::from(mask);
289
290    let prefix = (!mask).leading_zeros() as u8;
291    if (u64::from(mask) << prefix) & 0xffff_ffff != 0 {
292        Err("invalid ipv4 prefix")
293    } else {
294        Ok(prefix)
295    }
296}
297
298pub(crate) fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, &'static str> {
299    let mask = mask.segments();
300    let mut mask_iter = mask.iter();
301
302    // Count the number of set bits from the start of the address
303    let mut prefix = 0;
304    for &segment in &mut mask_iter {
305        if segment == 0xffff {
306            prefix += 16;
307        } else if segment == 0 {
308            // Prefix finishes on a segment boundary
309            break;
310        } else {
311            let prefix_bits = (!segment).leading_zeros() as u8;
312            // Check that the remainder of the bits are all unset
313            if segment << prefix_bits != 0 {
314                return Err("invalid ipv6 prefix");
315            }
316            prefix += prefix_bits;
317            break;
318        }
319    }
320
321    // Now check all the remaining bits are unset
322    for &segment in mask_iter {
323        if segment != 0 {
324            return Err("invalid ipv6 prefix");
325        }
326    }
327
328    Ok(prefix)
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn ipv4_mask() {
337        let mask = Ipv4Addr::new(255, 255, 255, 0);
338        let prefix = ipv4_mask_to_prefix(mask).unwrap();
339        assert_eq!(prefix, 24);
340    }
341
342    #[test]
343    fn ipv4_mask_another() {
344        let mask = Ipv4Addr::new(255, 255, 255, 128);
345        let prefix = ipv4_mask_to_prefix(mask).unwrap();
346        assert_eq!(prefix, 25);
347    }
348
349    #[test]
350    fn v4_mask_to_prefix_invalid() {
351        let mask = Ipv4Addr::new(255, 128, 255, 0);
352        assert!(ipv4_mask_to_prefix(mask).is_err());
353    }
354
355    #[test]
356    fn ipv6_mask() {
357        let mask = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0);
358        let prefix = ipv6_mask_to_prefix(mask).unwrap();
359        assert_eq!(prefix, 48);
360    }
361
362    #[test]
363    fn ipv6_mask_invalid() {
364        let mask = Ipv6Addr::new(0, 0xffff, 0xffff, 0, 0, 0, 0, 0);
365        assert!(ipv6_mask_to_prefix(mask).is_err());
366    }
367
368    #[test]
369    fn ip_mask_enum_ipv4() {
370        let mask = IpAddr::from(Ipv4Addr::new(255, 255, 255, 0));
371        let prefix = ip_mask_to_prefix(mask).unwrap();
372        assert_eq!(prefix, 24);
373    }
374
375    #[test]
376    fn ip_mask_enum_ipv4_invalid() {
377        let mask = IpAddr::from(Ipv4Addr::new(255, 0, 255, 0));
378        assert!(ip_mask_to_prefix(mask).is_err());
379    }
380
381    #[test]
382    fn ip_mask_enum_ipv6() {
383        let mask = IpAddr::from(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0));
384        let prefix = ip_mask_to_prefix(mask).unwrap();
385        assert_eq!(prefix, 48);
386    }
387
388    #[test]
389    fn ip_mask_enum_ipv6_invalid() {
390        let mask = IpAddr::from(Ipv6Addr::new(0xffff, 0xffff, 0xff00, 0xffff, 0, 0, 0, 0));
391        assert!(ip_mask_to_prefix(mask).is_err());
392    }
393}