netif/
lib.rs

1use std::net::IpAddr;
2
3#[cfg(test)]
4mod test;
5
6#[derive(Clone, Debug, Eq, Hash, PartialEq)]
7pub struct Interface {
8    name: String,
9    flags: u64,
10    mac: [u8; 6],
11    address: IpAddr,
12    scope_id: Option<u32>,
13    netmask: IpAddr,
14}
15
16impl Interface {
17    /// Interface name, e.g., "lo".
18    pub fn name(&self) -> &str {
19        &self.name
20    }
21
22    /// Interface flags. See libc::IFF_* flags.
23    pub fn flags(&self) -> u64 {
24        self.flags
25    }
26
27    /// MAC address, a.k.a., link-layer address, a.k.a., physical address.
28    pub fn mac(&self) -> [u8; 6] {
29        self.mac
30    }
31
32    /// Interface address.
33    ///
34    /// Note that [`ifa.address().is_loopback()`](std::net::IpAddr::is_loopback)
35    /// returns false for link-local addresses such as fe80::1%lo0 although
36    /// they are what would usually be thought of as "local" addresses.
37    pub fn address(&self) -> &IpAddr {
38        &self.address
39    }
40
41    /// IPv6 scope id or None.
42    pub fn scope_id(&self) -> Option<u32> {
43        self.scope_id
44    }
45
46    pub fn netmask(&self) -> &IpAddr {
47        &self.netmask
48    }
49
50    /// Caveat emptor: follows the Node.js "192.168.0.42/24" convention
51    /// instead of the arguably more common "192.168.0.0/24" notation.
52    pub fn cidr(&self) -> (&IpAddr, u8) {
53        let range = match self.netmask {
54            IpAddr::V4(addr) => u32::from_be_bytes(addr.octets()).count_ones(),
55            IpAddr::V6(addr) => u128::from_be_bytes(addr.octets()).count_ones(),
56        };
57        (&self.address, range as u8)
58    }
59}
60
61#[cfg(target_os = "windows")]
62pub use windows::*;
63
64#[cfg(not(target_os = "windows"))]
65pub use unix::*;
66
67#[cfg(target_os = "windows")]
68mod windows {
69    use super::Interface;
70    use std::io;
71    use std::net::IpAddr;
72    use std::net::Ipv4Addr;
73    use std::net::Ipv6Addr;
74    use std::ptr::null_mut;
75    use std::ptr::NonNull;
76    use winapi::shared::ifdef::IfOperStatusUp;
77    use winapi::shared::ws2def::SOCKADDR;
78    use winapi::shared::ws2def::SOCKADDR_IN;
79    use winapi::shared::ws2ipdef::SOCKADDR_IN6;
80    use winapi::um::iphlpapi::GetAdaptersAddresses;
81    use winapi::um::iptypes::GAA_FLAG_SKIP_ANYCAST;
82    use winapi::um::iptypes::GAA_FLAG_SKIP_DNS_SERVER;
83    use winapi::um::iptypes::GAA_FLAG_SKIP_MULTICAST;
84    use winapi::um::iptypes::IP_ADAPTER_ADDRESSES;
85    use winapi::um::iptypes::IP_ADAPTER_UNICAST_ADDRESS;
86    use winapi::um::winsock2::PF_INET;
87    use winapi::um::winsock2::PF_INET6;
88    use winapi::um::winsock2::PF_UNSPEC;
89
90    /// Returns an iterator that produces the list of interfaces that the
91    /// operating system considers "up", that is, configured and active.
92    pub fn up() -> io::Result<Up> {
93        let mut len = 0;
94
95        let flags = GAA_FLAG_SKIP_ANYCAST
96            + GAA_FLAG_SKIP_DNS_SERVER
97            + GAA_FLAG_SKIP_MULTICAST;
98
99        // Fails with ERROR_BUFFER_OVERFLOW but updates |len| with actual size.
100        unsafe {
101            GetAdaptersAddresses(
102                PF_UNSPEC as _,
103                flags,
104                null_mut(),
105                null_mut(),
106                &mut len,
107            );
108        }
109
110        // Over-allocates 8x but easiest for proper alignment.
111        let mut buf = vec![0usize; len as _];
112
113        let result = unsafe {
114            GetAdaptersAddresses(
115                PF_UNSPEC as _,
116                flags,
117                null_mut(),
118                buf.as_mut_ptr() as *mut _,
119                &mut len,
120            )
121        };
122
123        if result != 0 {
124            return Err(io::Error::from_raw_os_error(result as _));
125        }
126
127        let adapter =
128            NonNull::new(buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES);
129
130        let address = adapter.and_then(|adapter| {
131            let adapter = unsafe { adapter.as_ref() };
132            NonNull::new(adapter.FirstUnicastAddress)
133        });
134
135        let iter = Iter { adapter, address };
136
137        Ok(Up { _buf: buf, iter })
138    }
139
140    pub struct Up {
141        _buf: Vec<usize>, // Over-allocates 8x but easiest for proper alignment.
142        iter: Iter,
143    }
144
145    impl Iterator for Up {
146        type Item = Interface;
147
148        fn next(&mut self) -> Option<Self::Item> {
149            self.iter.find_map(to_interface)
150        }
151    }
152
153    impl Drop for Up {
154        fn drop(&mut self) {}
155    }
156
157    struct Iter {
158        adapter: Option<NonNull<IP_ADAPTER_ADDRESSES>>,
159        address: Option<NonNull<IP_ADAPTER_UNICAST_ADDRESS>>,
160    }
161
162    impl Iterator for Iter {
163        type Item = (
164            NonNull<IP_ADAPTER_ADDRESSES>,
165            NonNull<IP_ADAPTER_UNICAST_ADDRESS>,
166        );
167
168        fn next(&mut self) -> Option<Self::Item> {
169            loop {
170                let adapter = self.adapter?;
171
172                if let Some(address) = self.address.take() {
173                    self.address =
174                        NonNull::new(unsafe { address.as_ref().Next });
175                    return Some((adapter, address));
176                }
177
178                self.adapter = NonNull::new(unsafe { adapter.as_ref().Next });
179
180                self.address = self.adapter.and_then(|adapter| {
181                    let adapter = unsafe { adapter.as_ref() };
182                    NonNull::new(adapter.FirstUnicastAddress)
183                });
184            }
185        }
186    }
187
188    fn ip(addr: NonNull<SOCKADDR>) -> Option<IpAddr> {
189        let family = unsafe { addr.as_ref().sa_family };
190
191        match family as _ {
192            PF_INET => {
193                let addr = addr.as_ptr() as *mut SOCKADDR_IN;
194                let addr = unsafe { *(*addr).sin_addr.S_un.S_addr() };
195                let addr = Ipv4Addr::from(u32::from_be(addr));
196                Some(IpAddr::V4(addr))
197            }
198            PF_INET6 => {
199                let addr = addr.as_ptr() as *mut SOCKADDR_IN6;
200                let [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15] =
201                    *unsafe { (*addr).sin6_addr.u.Byte() };
202                let s0 = 256 * b0 as u16 + b1 as u16;
203                let s1 = 256 * b2 as u16 + b3 as u16;
204                let s2 = 256 * b4 as u16 + b5 as u16;
205                let s3 = 256 * b6 as u16 + b7 as u16;
206                let s4 = 256 * b8 as u16 + b9 as u16;
207                let s5 = 256 * b10 as u16 + b11 as u16;
208                let s6 = 256 * b12 as u16 + b13 as u16;
209                let s7 = 256 * b14 as u16 + b15 as u16;
210                let addr = Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7);
211                Some(IpAddr::V6(addr))
212            }
213            _ => None,
214        }
215    }
216
217    fn to_interface(
218        (adapter, addr): (
219            NonNull<IP_ADAPTER_ADDRESSES>,
220            NonNull<IP_ADAPTER_UNICAST_ADDRESS>,
221        ),
222    ) -> Option<Interface> {
223        let adapter = unsafe { adapter.as_ref() };
224
225        if adapter.OperStatus != IfOperStatusUp {
226            return None;
227        }
228
229        let addr = unsafe { addr.as_ref() };
230        let sockaddr = NonNull::new(addr.Address.lpSockaddr)?;
231        let prefixlen = addr.OnLinkPrefixLength as _;
232
233        let address = ip(sockaddr)?;
234
235        let netmask = match address {
236            IpAddr::V4(_) => {
237                let ones = !0u32;
238                let mask = ones & !ones.checked_shr(prefixlen).unwrap_or(0);
239                IpAddr::V4(Ipv4Addr::from(mask))
240            }
241            IpAddr::V6(_) => {
242                let ones = !0u128;
243                let mask = ones & !ones.checked_shr(prefixlen).unwrap_or(0);
244                IpAddr::V6(Ipv6Addr::from(mask))
245            }
246        };
247
248        let name =
249            unsafe { std::slice::from_raw_parts(adapter.FriendlyName, 256) };
250        let len = name.iter().position(|&b| b == 0).unwrap_or(name.len());
251        let name = String::from_utf16_lossy(&name[..len]);
252
253        let scope_id = address.is_ipv6().then(|| {
254            let addr = addr.Address.lpSockaddr as *const SOCKADDR_IN6;
255            unsafe { *(*addr).u.sin6_scope_id() }
256        });
257
258        let [b0, b1, b2, b3, b4, b5, _, _] = adapter.PhysicalAddress;
259        let mac = [b0, b1, b2, b3, b4, b5];
260
261        let flags = 0;
262
263        Some(Interface {
264            name,
265            flags,
266            mac,
267            address,
268            scope_id,
269            netmask,
270        })
271    }
272}
273
274#[cfg(not(target_os = "windows"))]
275mod unix {
276    use super::Interface;
277    use libc as c;
278    use std::ffi::CStr;
279    use std::io;
280    use std::mem;
281    use std::net::IpAddr;
282    use std::net::Ipv4Addr;
283    use std::net::Ipv6Addr;
284    use std::ptr;
285    use std::ptr::NonNull;
286
287    #[cfg(any(target_os = "android", target_os = "linux"))]
288    use crate::linux::*;
289
290    // Yes, wrong for Solaris's vile offspring. Don't complain, send patches.
291    #[cfg(not(any(target_os = "android", target_os = "linux")))]
292    use crate::bsd::*;
293
294    /// Returns an iterator that produces the list of interfaces that the
295    /// operating system considers "up", that is, configured and active.
296    pub fn up() -> io::Result<Up> {
297        let mut base = ptr::null_mut();
298
299        if 0 != unsafe { c::getifaddrs(&mut base) } {
300            return Err(io::Error::last_os_error());
301        }
302
303        let base = NonNull::new(base);
304        let iter = Iter(base);
305
306        Ok(Up { base, iter })
307    }
308
309    pub struct Up {
310        base: Option<NonNull<c::ifaddrs>>,
311        iter: Iter,
312    }
313
314    impl Iterator for Up {
315        type Item = Interface;
316
317        fn next(&mut self) -> Option<Self::Item> {
318            self.iter.find_map(|curr| to_interface(self.base, curr))
319        }
320    }
321
322    impl Drop for Up {
323        fn drop(&mut self) {
324            if let Some(mut base) = self.base {
325                unsafe { c::freeifaddrs(base.as_mut()) };
326            }
327        }
328    }
329
330    struct Iter(Option<NonNull<c::ifaddrs>>);
331
332    impl Iterator for Iter {
333        type Item = NonNull<c::ifaddrs>;
334
335        fn next(&mut self) -> Option<Self::Item> {
336            let curr = self.0?;
337            let next = unsafe { curr.as_ref().ifa_next };
338            mem::replace(&mut self.0, NonNull::new(next))
339        }
340    }
341
342    fn ip(addr: NonNull<c::sockaddr>) -> Option<IpAddr> {
343        let family = unsafe { addr.as_ref().sa_family };
344
345        match family as _ {
346            c::AF_INET => {
347                let addr = unsafe { &*(addr.as_ptr() as *mut c::sockaddr_in) };
348                let addr = Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr));
349                Some(IpAddr::V4(addr))
350            }
351            c::AF_INET6 => {
352                let addr = unsafe { &*(addr.as_ptr() as *mut c::sockaddr_in6) };
353                let [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15] =
354                    addr.sin6_addr.s6_addr;
355                let s0 = 256 * b0 as u16 + b1 as u16;
356                let s1 = 256 * b2 as u16 + b3 as u16;
357                let s2 = 256 * b4 as u16 + b5 as u16;
358                let s3 = 256 * b6 as u16 + b7 as u16;
359                let s4 = 256 * b8 as u16 + b9 as u16;
360                let s5 = 256 * b10 as u16 + b11 as u16;
361                let s6 = 256 * b12 as u16 + b13 as u16;
362                let s7 = 256 * b14 as u16 + b15 as u16;
363                let addr = Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7);
364                Some(IpAddr::V6(addr))
365            }
366            _ => None,
367        }
368    }
369
370    fn to_interface(
371        base: Option<NonNull<c::ifaddrs>>,
372        curr: NonNull<c::ifaddrs>,
373    ) -> Option<Interface> {
374        let curr = unsafe { curr.as_ref() };
375        let addr = NonNull::new(curr.ifa_addr)?;
376
377        if is_link(addr) {
378            return None;
379        }
380
381        let address = ip(addr)?;
382        let netmask = NonNull::new(curr.ifa_netmask).and_then(ip)?;
383
384        let name = unsafe { CStr::from_ptr(curr.ifa_name) };
385        let mac = Iter(base)
386            .find_map(|link| mac_of(name, link))
387            .unwrap_or_default();
388        let name = name.to_string_lossy().into_owned();
389
390        let flags = From::from(curr.ifa_flags);
391
392        let scope_id = address.is_ipv6().then(|| {
393            let addr = addr.as_ptr() as *const c::sockaddr_in6;
394            unsafe { (*addr).sin6_scope_id }
395        });
396
397        Some(Interface {
398            name,
399            flags,
400            mac,
401            address,
402            scope_id,
403            netmask,
404        })
405    }
406}
407
408#[cfg(any(target_os = "android", target_os = "linux"))]
409mod linux {
410    use libc as c;
411    use std::ffi::CStr;
412    use std::ptr::NonNull;
413
414    pub(crate) fn is_link(addr: NonNull<c::sockaddr>) -> bool {
415        c::AF_PACKET == unsafe { addr.as_ref().sa_family } as _
416    }
417
418    pub(crate) fn mac_of(
419        name: &CStr,
420        link: NonNull<c::ifaddrs>,
421    ) -> Option<[u8; 6]> {
422        let link = unsafe { link.as_ref() };
423        let addr = NonNull::new(link.ifa_addr)?;
424
425        if !is_link(addr) {
426            return None;
427        }
428
429        let ok = unsafe { CStr::from_ptr(link.ifa_name) }
430            .to_bytes()
431            .strip_prefix(name.to_bytes())
432            .filter(|suffix| suffix.is_empty() || suffix.starts_with(b":"))
433            .is_some();
434
435        if !ok {
436            return None;
437        }
438
439        let addr = link.ifa_addr as *const _ as *const c::sockaddr_ll;
440        let addr = unsafe { &*addr };
441
442        if addr.sll_halen != 6 {
443            return None;
444        }
445
446        let [b0, b1, b2, b3, b4, b5, _, _] = addr.sll_addr;
447
448        Some([b0, b1, b2, b3, b4, b5])
449    }
450}
451
452#[cfg(all(unix, not(any(target_os = "android", target_os = "linux"))))]
453mod bsd {
454    use libc as c;
455    use std::ffi::CStr;
456    use std::ptr::NonNull;
457
458    pub(crate) fn is_link(addr: NonNull<c::sockaddr>) -> bool {
459        c::AF_LINK == unsafe { addr.as_ref().sa_family } as _
460    }
461
462    pub(crate) fn mac_of(
463        name: &CStr,
464        link: NonNull<c::ifaddrs>,
465    ) -> Option<[u8; 6]> {
466        let link = unsafe { link.as_ref() };
467        let addr = NonNull::new(link.ifa_addr)?;
468
469        if !is_link(addr) {
470            return None;
471        }
472
473        let ok = unsafe { CStr::from_ptr(link.ifa_name) }
474            .to_bytes()
475            .strip_prefix(name.to_bytes())
476            .filter(|suffix| suffix.is_empty() || suffix.starts_with(b":"))
477            .is_some();
478
479        if !ok {
480            return None;
481        }
482
483        let addr = link.ifa_addr as *const _ as *const c::sockaddr_dl;
484        let addr = unsafe { &*addr };
485
486        if addr.sdl_alen != 6 {
487            return None;
488        }
489
490        // sdl data contains both the if name and link-level address.
491        // See: https://illumos.org/man/3socket/sockaddr_dl
492        let start = addr.sdl_nlen as usize; // length of the if name.
493        let end = start + addr.sdl_alen as usize;
494        let data = unsafe {
495            std::slice::from_raw_parts(
496                &addr.sdl_data as *const _ as *const u8,
497                end,
498            )
499        };
500
501        if let [b0, b1, b2, b3, b4, b5] = data[start..end] {
502            Some([b0, b1, b2, b3, b4, b5])
503        } else {
504            None
505        }
506    }
507}
508
509#[test]
510fn basic() {
511    for ifa in up().unwrap() {
512        println!("{:?} {:?}", ifa, ifa.cidr());
513
514        assert!(!ifa.name().is_empty());
515        assert!(ifa.address().is_ipv4() ^ ifa.scope_id().is_some());
516        assert_eq!(ifa.address().is_ipv4(), ifa.netmask().is_ipv4());
517
518        let link_local = "fe80::1" == &format!("{:?}", ifa.address());
519
520        if link_local || ifa.address().is_loopback() {
521            let (address, range) = ifa.cidr();
522            assert_eq!(address, ifa.address());
523            match address {
524                IpAddr::V6(_) if link_local => assert_eq!(range, 64),
525                IpAddr::V6(_) => assert_eq!(range, 128),
526                IpAddr::V4(_) => assert_eq!(range, 8),
527            }
528        }
529    }
530}