if_addrs/
lib.rs

1// Copyright 2018 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
4// http://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
5// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
6// modified, or distributed except according to those terms. Please review the Licences for the
7// specific language governing permissions and limitations relating to use of the SAFE Network
8// Software.
9#![cfg_attr(docsrs, feature(doc_auto_cfg))]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11
12#[cfg(not(windows))]
13mod posix;
14#[cfg(all(
15    not(windows),
16    not(all(
17        target_vendor = "apple",
18        any(
19            target_os = "macos",
20            target_os = "ios",
21            target_os = "tvos",
22            target_os = "watchos",
23            target_os = "visionos"
24        )
25    )),
26    not(target_os = "freebsd"),
27    not(target_os = "netbsd"),
28    not(target_os = "openbsd"),
29    not(target_os = "illumos")
30))]
31mod posix_not_apple;
32mod sockaddr;
33#[cfg(windows)]
34mod windows;
35
36use std::io;
37use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
38
39/// The current operational state of the interface, as defined in RFC 2863 section 6.
40#[derive(Debug, PartialEq, Eq, Hash, Clone)]
41pub enum IfOperStatus {
42    /// The interface is up and running.
43    Up = 1,
44
45    /// The interface is down.
46    Down = 2,
47
48    /// The interface is testing.
49    Testing = 3,
50
51    /// The interface is unknown.
52    Unknown = 4,
53
54    /// The interface is in a "pending" state, waiting for some external event.
55    Dormant = 5,
56
57    /// A refinement on the down state which indicates that the relevant
58    /// interface is down specifically because some component (typically,
59    /// a hardware component) is not present in the managed system.
60    NotPresent = 6,
61
62    /// A refinement on the down state. This new state indicates
63    /// that this interface runs on top of one or more other interfaces and
64    /// that this interface is down specifically because one or more of these
65    /// lower-layer interfaces are down.
66    LowerLayerDown = 7,
67}
68
69impl From<i32> for IfOperStatus {
70    fn from(value: i32) -> Self {
71        match value {
72            1 => Self::Up,
73            2 => Self::Down,
74            3 => Self::Testing,
75            4 => Self::Unknown,
76            5 => Self::Dormant,
77            6 => Self::NotPresent,
78            7 => Self::LowerLayerDown,
79            _ => Self::Unknown,
80        }
81    }
82}
83
84/// Details about an interface on this host.
85#[derive(Debug, PartialEq, Eq, Hash, Clone)]
86pub struct Interface {
87    /// The name of the interface.
88    pub name: String,
89    /// The address details of the interface.
90    pub addr: IfAddr,
91    /// The index of the interface.
92    pub index: Option<u32>,
93
94    /// Whether the interface is operational up.
95    pub oper_status: IfOperStatus,
96
97    /// (Windows only) A permanent and unique identifier for the interface. It
98    /// cannot be modified by the user. It is typically a GUID string of the
99    /// form: "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", but this is not
100    /// guaranteed by the Windows API.
101    #[cfg(windows)]
102    pub adapter_name: String,
103}
104
105impl Interface {
106    /// Check whether this is a loopback interface.
107    #[must_use]
108    pub const fn is_loopback(&self) -> bool {
109        self.addr.is_loopback()
110    }
111
112    /// Check whether this is a link local interface.
113    #[must_use]
114    pub const fn is_link_local(&self) -> bool {
115        self.addr.is_link_local()
116    }
117
118    /// Get the IP address of this interface.
119    #[must_use]
120    pub const fn ip(&self) -> IpAddr {
121        self.addr.ip()
122    }
123
124    /// Check whether this interface is operationally up.
125    #[must_use]
126    pub fn is_oper_up(&self) -> bool {
127        self.oper_status == IfOperStatus::Up
128    }
129}
130
131/// Details about the address of an interface on this host.
132#[derive(Debug, PartialEq, Eq, Hash, Clone)]
133pub enum IfAddr {
134    /// This is an Ipv4 interface.
135    V4(Ifv4Addr),
136    /// This is an Ipv6 interface.
137    V6(Ifv6Addr),
138}
139
140impl IfAddr {
141    /// Check whether this is a loopback address.
142    #[must_use]
143    pub const fn is_loopback(&self) -> bool {
144        match *self {
145            IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_loopback(),
146            IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_loopback(),
147        }
148    }
149
150    /// Check whether this is a link local interface.
151    #[must_use]
152    pub const fn is_link_local(&self) -> bool {
153        match *self {
154            IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_link_local(),
155            IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_link_local(),
156        }
157    }
158
159    /// Get the IP address of this interface address.
160    #[must_use]
161    pub const fn ip(&self) -> IpAddr {
162        match *self {
163            IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip),
164            IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip),
165        }
166    }
167}
168
169/// Details about the ipv4 address of an interface on this host.
170#[derive(Debug, PartialEq, Eq, Hash, Clone)]
171pub struct Ifv4Addr {
172    /// The IP address of the interface.
173    pub ip: Ipv4Addr,
174    /// The netmask of the interface.
175    pub netmask: Ipv4Addr,
176    /// The CIDR prefix of the interface.
177    pub prefixlen: u8,
178    /// The broadcast address of the interface.
179    pub broadcast: Option<Ipv4Addr>,
180}
181
182impl Ifv4Addr {
183    /// Check whether this is a loopback address.
184    #[must_use]
185    pub const fn is_loopback(&self) -> bool {
186        self.ip.is_loopback()
187    }
188
189    /// Check whether this is a link local address.
190    #[must_use]
191    pub const fn is_link_local(&self) -> bool {
192        self.ip.is_link_local()
193    }
194}
195
196/// Details about the ipv6 address of an interface on this host.
197#[derive(Debug, PartialEq, Eq, Hash, Clone)]
198pub struct Ifv6Addr {
199    /// The IP address of the interface.
200    pub ip: Ipv6Addr,
201    /// The netmask of the interface.
202    pub netmask: Ipv6Addr,
203    /// The CIDR prefix of the interface.
204    pub prefixlen: u8,
205    /// The broadcast address of the interface.
206    pub broadcast: Option<Ipv6Addr>,
207}
208
209impl Ifv6Addr {
210    /// Check whether this is a loopback address.
211    #[must_use]
212    pub const fn is_loopback(&self) -> bool {
213        self.ip.is_loopback()
214    }
215
216    /// Check whether this is a link local address.
217    #[must_use]
218    pub const fn is_link_local(&self) -> bool {
219        let bytes = self.ip.octets();
220
221        bytes[0] == 0xfe && bytes[1] == 0x80
222    }
223}
224
225#[cfg(not(windows))]
226mod getifaddrs_posix {
227    use libc::if_nametoindex;
228
229    use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
230    use crate::posix::{self as ifaddrs, IfAddrs};
231    use crate::sockaddr;
232    use crate::IfOperStatus;
233    use std::ffi::CStr;
234    use std::io;
235    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
236
237    /// Defined in `<net/if.h>` on POSIX systems.
238    /// https://github.com/torvalds/linux/blob/18531f4d1c8c47c4796289dbbc1ab657ffa063d2/include/uapi/linux/if.h#L85
239    #[cfg(not(target_os = "illumos"))]
240    const POSIX_IFF_RUNNING: u32 = 0x40; // 1<<6
241    #[cfg(target_os = "illumos")]
242    const POSIX_IFF_RUNNING: u64 = 0x40; // 1<<6
243
244    /// Return a vector of IP details for all the valid interfaces on this host.
245    #[allow(unsafe_code)]
246    pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
247        let mut ret = Vec::<Interface>::new();
248        let ifaddrs = IfAddrs::new()?;
249
250        for ifaddr in ifaddrs.iter() {
251            let addr = match sockaddr::to_ipaddr(ifaddr.ifa_addr) {
252                None => continue,
253                Some(IpAddr::V4(ipv4_addr)) => {
254                    let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) {
255                        Some(IpAddr::V4(netmask)) => netmask,
256                        _ => Ipv4Addr::new(0, 0, 0, 0),
257                    };
258                    let broadcast = if (ifaddr.ifa_flags & 2) != 0 {
259                        match ifaddrs::do_broadcast(&ifaddr) {
260                            Some(IpAddr::V4(broadcast)) => Some(broadcast),
261                            _ => None,
262                        }
263                    } else {
264                        None
265                    };
266                    let prefixlen = if cfg!(target_endian = "little") {
267                        u32::from_le_bytes(netmask.octets()).count_ones() as u8
268                    } else {
269                        u32::from_be_bytes(netmask.octets()).count_ones() as u8
270                    };
271                    IfAddr::V4(Ifv4Addr {
272                        ip: ipv4_addr,
273                        netmask,
274                        prefixlen,
275                        broadcast,
276                    })
277                }
278                Some(IpAddr::V6(ipv6_addr)) => {
279                    let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) {
280                        Some(IpAddr::V6(netmask)) => netmask,
281                        _ => Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
282                    };
283                    let broadcast = if (ifaddr.ifa_flags & 2) != 0 {
284                        match ifaddrs::do_broadcast(&ifaddr) {
285                            Some(IpAddr::V6(broadcast)) => Some(broadcast),
286                            _ => None,
287                        }
288                    } else {
289                        None
290                    };
291                    let prefixlen = if cfg!(target_endian = "little") {
292                        u128::from_le_bytes(netmask.octets()).count_ones() as u8
293                    } else {
294                        u128::from_be_bytes(netmask.octets()).count_ones() as u8
295                    };
296                    IfAddr::V6(Ifv6Addr {
297                        ip: ipv6_addr,
298                        netmask,
299                        prefixlen,
300                        broadcast,
301                    })
302                }
303            };
304
305            let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) }
306                .to_string_lossy()
307                .into_owned();
308            let index = {
309                let index = unsafe { if_nametoindex(ifaddr.ifa_name) };
310
311                // From `man if_nametoindex 3`:
312                // The if_nametoindex() function maps the interface name specified in ifname to its
313                // corresponding index. If the specified interface does not exist, it returns 0.
314                if index == 0 {
315                    None
316                } else {
317                    Some(index)
318                }
319            };
320
321            let oper_status = if ifaddr.ifa_flags & POSIX_IFF_RUNNING != 0 {
322                IfOperStatus::Up
323            } else {
324                IfOperStatus::Unknown
325            };
326
327            ret.push(Interface {
328                name,
329                addr,
330                index,
331                oper_status,
332            });
333        }
334
335        Ok(ret)
336    }
337}
338
339/// Get a list of all the network interfaces on this machine along with their IP info.
340#[cfg(not(windows))]
341pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
342    getifaddrs_posix::get_if_addrs()
343}
344
345#[cfg(windows)]
346mod getifaddrs_windows {
347    use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface};
348    use crate::sockaddr;
349    use crate::windows::IfAddrs;
350    use std::io;
351    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
352    use windows_sys::Win32::Networking::WinSock::IpDadStatePreferred;
353
354    /// Return a vector of IP details for all the valid interfaces on this host.
355    pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
356        let mut ret = Vec::<Interface>::new();
357        let ifaddrs = IfAddrs::new()?;
358
359        for ifaddr in ifaddrs.iter() {
360            for addr in ifaddr.unicast_addresses() {
361                if addr.DadState != IpDadStatePreferred {
362                    continue;
363                }
364                let addr = match sockaddr::to_ipaddr(addr.Address.lpSockaddr) {
365                    None => continue,
366                    Some(IpAddr::V4(ipv4_addr)) => {
367                        let mut item_netmask = Ipv4Addr::new(0, 0, 0, 0);
368                        let mut item_broadcast = None;
369                        let item_prefix = addr.OnLinkPrefixLength;
370
371                        // Search prefixes for a prefix matching addr
372                        'prefixloopv4: for prefix in ifaddr.prefixes() {
373                            let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr);
374                            match ipprefix {
375                                Some(IpAddr::V4(ref a)) => {
376                                    let mut netmask: [u8; 4] = [0; 4];
377                                    for (n, netmask_elt) in netmask
378                                        .iter_mut()
379                                        .enumerate()
380                                        .take((prefix.PrefixLength as usize + 7) / 8)
381                                    {
382                                        let x_byte = ipv4_addr.octets()[n];
383                                        let y_byte = a.octets()[n];
384                                        for m in 0..8 {
385                                            if (n * 8) + m >= prefix.PrefixLength as usize {
386                                                break;
387                                            }
388                                            let bit = 1 << (7 - m);
389                                            if (x_byte & bit) == (y_byte & bit) {
390                                                *netmask_elt |= bit;
391                                            } else {
392                                                continue 'prefixloopv4;
393                                            }
394                                        }
395                                    }
396                                    item_netmask = Ipv4Addr::new(
397                                        netmask[0], netmask[1], netmask[2], netmask[3],
398                                    );
399                                    let mut broadcast: [u8; 4] = ipv4_addr.octets();
400                                    for n in 0..4 {
401                                        broadcast[n] |= !netmask[n];
402                                    }
403                                    item_broadcast = Some(Ipv4Addr::new(
404                                        broadcast[0],
405                                        broadcast[1],
406                                        broadcast[2],
407                                        broadcast[3],
408                                    ));
409                                    break 'prefixloopv4;
410                                }
411                                _ => continue,
412                            };
413                        }
414                        IfAddr::V4(Ifv4Addr {
415                            ip: ipv4_addr,
416                            netmask: item_netmask,
417                            prefixlen: item_prefix,
418                            broadcast: item_broadcast,
419                        })
420                    }
421                    Some(IpAddr::V6(ipv6_addr)) => {
422                        let mut item_netmask = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
423                        let item_prefix = addr.OnLinkPrefixLength;
424                        // Search prefixes for a prefix matching addr
425                        'prefixloopv6: for prefix in ifaddr.prefixes() {
426                            let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr);
427                            match ipprefix {
428                                Some(IpAddr::V6(ref a)) => {
429                                    // Iterate the bits in the prefix, if they all match this prefix
430                                    // is the right one, else try the next prefix
431                                    let mut netmask: [u16; 8] = [0; 8];
432                                    for (n, netmask_elt) in netmask
433                                        .iter_mut()
434                                        .enumerate()
435                                        .take((prefix.PrefixLength as usize + 15) / 16)
436                                    {
437                                        let x_word = ipv6_addr.segments()[n];
438                                        let y_word = a.segments()[n];
439                                        for m in 0..16 {
440                                            if (n * 16) + m >= prefix.PrefixLength as usize {
441                                                break;
442                                            }
443                                            let bit = 1 << (15 - m);
444                                            if (x_word & bit) == (y_word & bit) {
445                                                *netmask_elt |= bit;
446                                            } else {
447                                                continue 'prefixloopv6;
448                                            }
449                                        }
450                                    }
451                                    item_netmask = Ipv6Addr::new(
452                                        netmask[0], netmask[1], netmask[2], netmask[3], netmask[4],
453                                        netmask[5], netmask[6], netmask[7],
454                                    );
455                                    break 'prefixloopv6;
456                                }
457                                _ => continue,
458                            };
459                        }
460                        IfAddr::V6(Ifv6Addr {
461                            ip: ipv6_addr,
462                            netmask: item_netmask,
463                            prefixlen: item_prefix,
464                            broadcast: None,
465                        })
466                    }
467                };
468
469                let index = match addr {
470                    IfAddr::V4(_) => ifaddr.ipv4_index(),
471                    IfAddr::V6(_) => ifaddr.ipv6_index(),
472                };
473                let oper_status = ifaddr.oper_status();
474
475                ret.push(Interface {
476                    name: ifaddr.name(),
477                    addr,
478                    index,
479                    oper_status,
480                    adapter_name: ifaddr.adapter_name(),
481                });
482            }
483        }
484
485        Ok(ret)
486    }
487}
488
489/// Get a list of all the network interfaces on this machine along with their IP info.
490#[cfg(windows)]
491pub fn get_if_addrs() -> io::Result<Vec<Interface>> {
492    getifaddrs_windows::get_if_addrs()
493}
494
495#[cfg(not(any(
496    all(
497        target_vendor = "apple",
498        any(
499            target_os = "macos",
500            target_os = "ios",
501            target_os = "tvos",
502            target_os = "watchos",
503            target_os = "visionos"
504        )
505    ),
506    target_os = "freebsd",
507    target_os = "netbsd",
508    target_os = "openbsd",
509    target_os = "illumos"
510)))]
511#[cfg_attr(
512    docsrs,
513    doc(cfg(any(
514        not(target_vendor = "apple"),
515        not(target_os = "freebsd"),
516        not(target_os = "netbsd"),
517        not(target_os = "openbsd"),
518        not(target_os = "illumos")
519    )))
520)]
521mod if_change_notifier {
522    use super::Interface;
523    use std::collections::HashSet;
524    use std::io;
525    use std::time::{Duration, Instant};
526
527    #[derive(Debug, PartialEq, Eq, Hash, Clone)]
528    pub enum IfChangeType {
529        Added(Interface),
530        Removed(Interface),
531    }
532
533    #[cfg(windows)]
534    type InternalIfChangeNotifier = crate::windows::WindowsIfChangeNotifier;
535    #[cfg(not(windows))]
536    type InternalIfChangeNotifier = crate::posix_not_apple::PosixIfChangeNotifier;
537
538    /// (Not available on iOS/macOS) A utility to monitor for interface changes
539    /// and report them, so you can handle events such as WiFi
540    /// disconnection/flight mode/route changes
541    pub struct IfChangeNotifier {
542        inner: InternalIfChangeNotifier,
543        last_ifs: HashSet<Interface>,
544    }
545
546    impl IfChangeNotifier {
547        /// Create a new interface change notifier. Returns an OS specific error
548        /// if the network notifier could not be set up.
549        pub fn new() -> io::Result<Self> {
550            Ok(Self {
551                inner: InternalIfChangeNotifier::new()?,
552                last_ifs: HashSet::from_iter(super::get_if_addrs()?),
553            })
554        }
555
556        /// (Not available on iOS/macOS) Block until the OS reports that the
557        /// network interface list has changed, or until an optional timeout.
558        ///
559        /// For example, if an ethernet connector is plugged/unplugged, or a
560        /// WiFi network is connected to.
561        ///
562        /// The changed interfaces are returned. If an interface has both IPv4
563        /// and IPv6 addresses, you can expect both of them to be returned from
564        /// a single call to `wait`.
565        ///
566        /// Returns an [`io::ErrorKind::WouldBlock`] error on timeout, or
567        /// another error if the network notifier could not be read from.
568        pub fn wait(&mut self, timeout: Option<Duration>) -> io::Result<Vec<IfChangeType>> {
569            let start = Instant::now();
570            loop {
571                self.inner
572                    .wait(timeout.map(|t| t.saturating_sub(start.elapsed())))?;
573
574                // something has changed - now we find out what (or whether it was spurious)
575                let new_ifs = HashSet::from_iter(super::get_if_addrs()?);
576                let mut changes: Vec<IfChangeType> = new_ifs
577                    .difference(&self.last_ifs)
578                    .cloned()
579                    .map(IfChangeType::Added)
580                    .collect();
581                changes.extend(
582                    self.last_ifs
583                        .difference(&new_ifs)
584                        .cloned()
585                        .map(IfChangeType::Removed),
586                );
587                self.last_ifs = new_ifs;
588
589                if !changes.is_empty() {
590                    return Ok(changes);
591                }
592            }
593        }
594    }
595}
596
597#[cfg(not(any(
598    all(
599        target_vendor = "apple",
600        any(
601            target_os = "macos",
602            target_os = "ios",
603            target_os = "tvos",
604            target_os = "watchos",
605            target_os = "visionos"
606        )
607    ),
608    target_os = "freebsd",
609    target_os = "netbsd",
610    target_os = "openbsd",
611    target_os = "illumos"
612)))]
613#[cfg_attr(
614    docsrs,
615    doc(cfg(any(
616        not(target_vendor = "apple"),
617        not(target_os = "freebsd"),
618        not(target_os = "netbsd"),
619        not(target_os = "openbsd"),
620        not(target_os = "illumos")
621    )))
622)]
623pub use if_change_notifier::{IfChangeNotifier, IfChangeType};
624
625#[cfg(test)]
626mod tests {
627    use super::{get_if_addrs, Interface};
628    use std::io::Read;
629    use std::net::{IpAddr, Ipv4Addr};
630    use std::process::{Command, Stdio};
631    use std::str::FromStr;
632    use std::thread;
633    use std::time::Duration;
634
635    fn list_system_interfaces(cmd: &str, args: &[&str]) -> String {
636        let start_cmd = if args.is_empty() {
637            Command::new(cmd).stdout(Stdio::piped()).spawn()
638        } else if args.len() == 1 {
639            let arg1 = args[0];
640            if arg1.is_empty() {
641                Command::new(cmd).stdout(Stdio::piped()).spawn()
642            } else {
643                Command::new(cmd).arg(arg1).stdout(Stdio::piped()).spawn()
644            }
645        } else {
646            Command::new(cmd).args(args).stdout(Stdio::piped()).spawn()
647        };
648        let mut process = match start_cmd {
649            Err(why) => {
650                println!("couldn't start cmd {} : {}", cmd, why);
651                return String::new();
652            }
653            Ok(process) => process,
654        };
655        thread::sleep(Duration::from_millis(1000));
656        let _ = process.kill();
657        let result: Vec<u8> = process
658            .stdout
659            .unwrap()
660            .bytes()
661            .map(|x| x.unwrap())
662            .collect();
663        String::from_utf8(result).unwrap()
664    }
665
666    #[cfg(windows)]
667    /// Returns (IP-addr-list, IPv4-interface-status-list)
668    fn list_system_addrs() -> (Vec<IpAddr>, Vec<(String, bool)>) {
669        use std::net::Ipv6Addr;
670        let intf_list = list_system_interfaces("ipconfig", &[""]);
671        let ipaddr_list = intf_list
672            .lines()
673            .filter_map(|line| {
674                println!("{}", line);
675                if line.contains("Address") && !line.contains("Link-local") {
676                    let addr_s: Vec<&str> = line.split(" : ").collect();
677                    if line.contains("IPv6") {
678                        return Some(IpAddr::V6(Ipv6Addr::from_str(addr_s[1]).unwrap()));
679                    } else if line.contains("IPv4") {
680                        return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap()));
681                    }
682                }
683                None
684            })
685            .collect();
686
687        /* An example on Windows:
688           > netsh interface ipv4 show interfaces
689
690           Idx     Met         MTU          State                Name
691           ---  ----------  ----------  ------------  ---------------------------
692           1          75  4294967295  connected     Loopback Pseudo-Interface 1
693           17          35        1500  connected     Wi-Fi
694           13          25        1500  disconnected  Local Area Connection* 1
695           14          25        1500  disconnected  Local Area Connection* 2
696           12          65        1500  disconnected  Bluetooth Network Connection
697        */
698        let netsh_list =
699            list_system_interfaces("netsh", &["interface", "ipv4", "show", "interfaces"]);
700
701        let ipv4_status_vec: Vec<_> = netsh_list
702            .lines()
703            .filter_map(|line| {
704                if !line.contains("Idx") && !line.contains("---") && !line.is_empty() {
705                    let columns: Vec<&str> = line.split_whitespace().collect();
706                    let status = columns[3].trim().to_string();
707                    let is_up = status == "connected";
708
709                    // concat the strings in columns[4] and beyond
710                    let intf_name = columns[4..].join(" ");
711                    return Some((intf_name, is_up));
712                }
713                None
714            })
715            .collect();
716
717        (ipaddr_list, ipv4_status_vec)
718    }
719
720    #[cfg(any(target_os = "linux", target_os = "android"))]
721    /// Returns (IP-addr-list, interface-status-list)
722    fn list_system_addrs() -> (Vec<IpAddr>, Vec<(String, bool)>) {
723        let intf_list = list_system_interfaces("ip", &["addr"]);
724        let ipaddr_list = intf_list
725            .lines()
726            .filter_map(|line| {
727                println!("{}", line);
728                if line.contains("inet ") {
729                    let addr_s: Vec<&str> = line.split_whitespace().collect();
730                    let addr: Vec<&str> = addr_s[1].split('/').collect();
731                    return Some(IpAddr::V4(Ipv4Addr::from_str(addr[0]).unwrap()));
732                }
733                None
734            })
735            .collect();
736        let mut intf_status_vec = Vec::new();
737        for line in intf_list.lines() {
738            if !line.starts_with(' ') && !line.is_empty() {
739                let name_s: Vec<&str> = line.split(':').collect();
740                let is_up = !line.contains("state DOWN");
741                intf_status_vec.push((name_s[1].trim().to_string(), is_up));
742            }
743        }
744
745        (ipaddr_list, intf_status_vec)
746    }
747
748    #[cfg(any(
749        target_os = "freebsd",
750        target_os = "netbsd",
751        target_os = "openbsd",
752        target_os = "illumos",
753        all(
754            target_vendor = "apple",
755            any(
756                target_os = "macos",
757                target_os = "ios",
758                target_os = "tvos",
759                target_os = "watchos",
760                target_os = "visionos"
761            )
762        )
763    ))]
764    /// Returns (IP-addr-list, interface-status-list)
765    fn list_system_addrs() -> (Vec<IpAddr>, Vec<(String, bool)>) {
766        let intf_list = list_system_interfaces("ifconfig", &[""]);
767        let ipaddr_list = intf_list
768            .lines()
769            .filter_map(|line| {
770                println!("{}", line);
771                if line.contains("inet ") {
772                    let addr_s: Vec<&str> = line.split_whitespace().collect();
773                    return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap()));
774                }
775                None
776            })
777            .collect();
778
779        let mut intf_status_vec = Vec::new();
780        for line in intf_list.lines() {
781            // One example on macOS:
782            /*
783            en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
784                options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
785                ether c6:0e:5e:f8:5d:f4
786                inet6 fe80::c02:ea58:92f4:be76%en0 prefixlen 64 secured scopeid 0xb
787                inet 192.168.0.112 netmask 0xffffff00 broadcast 192.168.0.255
788                nd6 options=201<PERFORMNUD,DAD>
789                media: autoselect
790                status: active
791             */
792            if !line.starts_with('\t') && !line.is_empty() {
793                let name_s: Vec<&str> = line.split(':').collect();
794                let is_admin_up = line.contains("<UP");
795                intf_status_vec.push((name_s[0].to_string(), is_admin_up));
796            } else if line.contains("status: inactive") {
797                if let Some(current_intf) = intf_status_vec.last_mut() {
798                    current_intf.1 = false; // overwrite the admin up
799                }
800            }
801        }
802
803        (ipaddr_list, intf_status_vec)
804    }
805
806    #[test]
807    fn test_get_if_addrs() {
808        let ifaces = get_if_addrs().unwrap();
809        println!("Local interfaces:");
810        println!("{:#?}", ifaces);
811        // at least one loop back address
812        assert!(
813            1 <= ifaces
814                .iter()
815                .filter(|interface| interface.is_loopback())
816                .count()
817        );
818        // if index is set, it is non-zero
819        for interface in &ifaces {
820            if let Some(idx) = interface.index {
821                assert!(idx > 0);
822            }
823        }
824
825        // one address of IpV4(127.0.0.1)
826        let is_loopback =
827            |interface: &&Interface| interface.addr.ip() == IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
828        assert_eq!(1, ifaces.iter().filter(is_loopback).count());
829
830        // each system address shall be listed
831        let (system_addrs, intf_status_list) = list_system_addrs();
832        assert!(!system_addrs.is_empty());
833        for addr in system_addrs {
834            let mut listed = false;
835            println!("\n checking whether {:?} has been properly listed \n", addr);
836            for interface in &ifaces {
837                if interface.addr.ip() == addr {
838                    listed = true;
839                }
840
841                assert!(interface.index.is_some());
842            }
843            assert!(listed);
844        }
845
846        println!("Interface status list: {:#?}", intf_status_list);
847        for (intf_name, is_up) in intf_status_list {
848            for interface in &ifaces {
849                if interface.name == intf_name {
850                    if interface.is_oper_up() != is_up {
851                        println!(
852                            "Interface {} status mismatch: listed {}, detected {:?}",
853                            intf_name, is_up, interface.oper_status
854                        );
855                    }
856                    assert_eq!(interface.is_oper_up(), is_up);
857                }
858            }
859        }
860    }
861
862    #[cfg(not(any(
863        all(
864            target_vendor = "apple",
865            any(
866                target_os = "macos",
867                target_os = "ios",
868                target_os = "tvos",
869                target_os = "watchos",
870                target_os = "visionos"
871            )
872        ),
873        target_os = "freebsd",
874        target_os = "netbsd",
875        target_os = "openbsd",
876        target_os = "illumos"
877    )))]
878    #[test]
879    fn test_if_notifier() {
880        // Check that the interface notifier can start up and time out. No easy
881        // way to programmatically add/remove interfaces, so set a timeout of 0.
882        // Will cover a potential case of inadequate setup leading to an
883        // immediate change notification.
884        //
885        // There is a small race condition from creation -> check that an
886        // interface change *actually* occurs, so this test may spuriously fail
887        // extremely rarely.
888
889        let notifier = crate::IfChangeNotifier::new();
890        assert!(notifier.is_ok());
891        let mut notifier = notifier.unwrap();
892
893        assert!(notifier.wait(Some(Duration::ZERO)).is_err());
894    }
895}