Skip to main content

nex_core/
interface.rs

1use crate::ip::{is_global_ip, is_global_ipv4, is_global_ipv6};
2use crate::mac::MacAddr;
3pub use ipnet::{self, Ipv4Net, Ipv6Net};
4use std::convert::TryFrom;
5use std::fmt;
6use std::io;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8use std::time::SystemTime;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// Errors returned when discovering system network interfaces or gateways.
14#[derive(Clone, Eq, PartialEq, Debug)]
15#[non_exhaustive]
16pub enum InterfaceError {
17    /// The operating system did not provide a default network interface.
18    DefaultInterfaceUnavailable { message: String },
19    /// The operating system did not provide a default gateway.
20    DefaultGatewayUnavailable { message: String },
21}
22
23impl fmt::Display for InterfaceError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::DefaultInterfaceUnavailable { message } => {
27                write!(f, "default interface is unavailable: {message}")
28            }
29            Self::DefaultGatewayUnavailable { message } => {
30                write!(f, "default gateway is unavailable: {message}")
31            }
32        }
33    }
34}
35
36impl std::error::Error for InterfaceError {}
37
38#[cfg(unix)]
39pub const IFF_UP: u32 = nex_sys::IFF_UP as u32;
40#[cfg(windows)]
41pub const IFF_UP: u32 = nex_sys::IFF_UP;
42
43#[cfg(unix)]
44pub const IFF_BROADCAST: u32 = nex_sys::IFF_BROADCAST as u32;
45#[cfg(windows)]
46pub const IFF_BROADCAST: u32 = nex_sys::IFF_BROADCAST;
47
48#[cfg(unix)]
49pub const IFF_LOOPBACK: u32 = nex_sys::IFF_LOOPBACK as u32;
50#[cfg(windows)]
51pub const IFF_LOOPBACK: u32 = nex_sys::IFF_LOOPBACK;
52
53#[cfg(unix)]
54pub const IFF_POINTOPOINT: u32 = nex_sys::IFF_POINTOPOINT as u32;
55#[cfg(windows)]
56pub const IFF_POINTOPOINT: u32 = nex_sys::IFF_POINTOPOINT;
57
58#[cfg(unix)]
59pub const IFF_MULTICAST: u32 = nex_sys::IFF_MULTICAST as u32;
60#[cfg(windows)]
61pub const IFF_MULTICAST: u32 = nex_sys::IFF_MULTICAST;
62
63#[cfg(unix)]
64pub const IFF_RUNNING: u32 = libc::IFF_RUNNING as u32;
65
66/// Operational state of a network interface.
67#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[non_exhaustive]
70pub enum OperState {
71    Unknown,
72    NotPresent,
73    Down,
74    LowerLayerDown,
75    Testing,
76    Dormant,
77    Up,
78}
79
80impl OperState {
81    pub fn as_str(&self) -> &'static str {
82        match self {
83            OperState::Unknown => "unknown",
84            OperState::NotPresent => "notpresent",
85            OperState::Down => "down",
86            OperState::LowerLayerDown => "lowerlayerdown",
87            OperState::Testing => "testing",
88            OperState::Dormant => "dormant",
89            OperState::Up => "up",
90        }
91    }
92
93    pub fn from_if_flags(if_flags: u32) -> Self {
94        #[cfg(unix)]
95        {
96            if if_flags & IFF_UP != 0 {
97                if if_flags & IFF_RUNNING != 0 {
98                    OperState::Up
99                } else {
100                    OperState::Dormant
101                }
102            } else {
103                OperState::Down
104            }
105        }
106
107        #[cfg(windows)]
108        {
109            if if_flags & IFF_UP != 0 {
110                OperState::Up
111            } else {
112                OperState::Down
113            }
114        }
115    }
116}
117
118impl std::fmt::Display for OperState {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        f.write_str(self.as_str())
121    }
122}
123
124impl std::str::FromStr for OperState {
125    type Err = ();
126
127    fn from_str(s: &str) -> Result<Self, Self::Err> {
128        match s {
129            "unknown" => Ok(OperState::Unknown),
130            "notpresent" => Ok(OperState::NotPresent),
131            "down" => Ok(OperState::Down),
132            "lowerlayerdown" => Ok(OperState::LowerLayerDown),
133            "testing" => Ok(OperState::Testing),
134            "dormant" => Ok(OperState::Dormant),
135            "up" => Ok(OperState::Up),
136            _ => Err(()),
137        }
138    }
139}
140
141impl From<netdev::interface::state::OperState> for OperState {
142    fn from(value: netdev::interface::state::OperState) -> Self {
143        match value {
144            netdev::interface::state::OperState::Unknown => OperState::Unknown,
145            netdev::interface::state::OperState::NotPresent => OperState::NotPresent,
146            netdev::interface::state::OperState::Down => OperState::Down,
147            netdev::interface::state::OperState::LowerLayerDown => OperState::LowerLayerDown,
148            netdev::interface::state::OperState::Testing => OperState::Testing,
149            netdev::interface::state::OperState::Dormant => OperState::Dormant,
150            netdev::interface::state::OperState::Up => OperState::Up,
151        }
152    }
153}
154
155/// Cross-platform classification of a network interface.
156#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
157#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
158#[non_exhaustive]
159pub enum InterfaceType {
160    Unknown,
161    Ethernet,
162    TokenRing,
163    Fddi,
164    BasicIsdn,
165    PrimaryIsdn,
166    Ppp,
167    Loopback,
168    Ethernet3Megabit,
169    Slip,
170    Atm,
171    GenericModem,
172    ProprietaryVirtual,
173    FastEthernetT,
174    Isdn,
175    FastEthernetFx,
176    Wireless80211,
177    AsymmetricDsl,
178    RateAdaptDsl,
179    SymmetricDsl,
180    VeryHighSpeedDsl,
181    IPOverAtm,
182    GigabitEthernet,
183    Tunnel,
184    MultiRateSymmetricDsl,
185    HighPerformanceSerialBus,
186    Wman,
187    Wwan,
188    Wwanpp,
189    Wwanpp2,
190    Bridge,
191    Can,
192    PeerToPeerWireless,
193    UnknownWithValue(u32),
194}
195
196impl InterfaceType {
197    pub fn name(&self) -> String {
198        match *self {
199            InterfaceType::Unknown => String::from("Unknown"),
200            InterfaceType::Ethernet => String::from("Ethernet"),
201            InterfaceType::TokenRing => String::from("Token Ring"),
202            InterfaceType::Fddi => String::from("FDDI"),
203            InterfaceType::BasicIsdn => String::from("Basic ISDN"),
204            InterfaceType::PrimaryIsdn => String::from("Primary ISDN"),
205            InterfaceType::Ppp => String::from("PPP"),
206            InterfaceType::Loopback => String::from("Loopback"),
207            InterfaceType::Ethernet3Megabit => String::from("Ethernet 3 megabit"),
208            InterfaceType::Slip => String::from("SLIP"),
209            InterfaceType::Atm => String::from("ATM"),
210            InterfaceType::GenericModem => String::from("Generic Modem"),
211            InterfaceType::ProprietaryVirtual => String::from("Proprietary Virtual/Internal"),
212            InterfaceType::FastEthernetT => String::from("Fast Ethernet T"),
213            InterfaceType::Isdn => String::from("ISDN"),
214            InterfaceType::FastEthernetFx => String::from("Fast Ethernet FX"),
215            InterfaceType::Wireless80211 => String::from("Wireless IEEE 802.11"),
216            InterfaceType::AsymmetricDsl => String::from("Asymmetric DSL"),
217            InterfaceType::RateAdaptDsl => String::from("Rate Adaptive DSL"),
218            InterfaceType::SymmetricDsl => String::from("Symmetric DSL"),
219            InterfaceType::VeryHighSpeedDsl => String::from("Very High Data Rate DSL"),
220            InterfaceType::IPOverAtm => String::from("IP over ATM"),
221            InterfaceType::GigabitEthernet => String::from("Gigabit Ethernet"),
222            InterfaceType::Tunnel => String::from("Tunnel"),
223            InterfaceType::MultiRateSymmetricDsl => String::from("Multi-Rate Symmetric DSL"),
224            InterfaceType::HighPerformanceSerialBus => String::from("High Performance Serial Bus"),
225            InterfaceType::Wman => String::from("WMAN"),
226            InterfaceType::Wwan => String::from("WWAN"),
227            InterfaceType::Wwanpp => String::from("WWANPP"),
228            InterfaceType::Wwanpp2 => String::from("WWANPP2"),
229            InterfaceType::Bridge => String::from("Bridge"),
230            InterfaceType::Can => String::from("CAN"),
231            InterfaceType::PeerToPeerWireless => String::from("Peer-to-Peer Wireless"),
232            InterfaceType::UnknownWithValue(v) => format!("Unknown ({v})"),
233        }
234    }
235}
236
237impl From<netdev::interface::types::InterfaceType> for InterfaceType {
238    fn from(value: netdev::interface::types::InterfaceType) -> Self {
239        match value {
240            netdev::interface::types::InterfaceType::Unknown => InterfaceType::Unknown,
241            netdev::interface::types::InterfaceType::Ethernet => InterfaceType::Ethernet,
242            netdev::interface::types::InterfaceType::TokenRing => InterfaceType::TokenRing,
243            netdev::interface::types::InterfaceType::Fddi => InterfaceType::Fddi,
244            netdev::interface::types::InterfaceType::BasicIsdn => InterfaceType::BasicIsdn,
245            netdev::interface::types::InterfaceType::PrimaryIsdn => InterfaceType::PrimaryIsdn,
246            netdev::interface::types::InterfaceType::Ppp => InterfaceType::Ppp,
247            netdev::interface::types::InterfaceType::Loopback => InterfaceType::Loopback,
248            netdev::interface::types::InterfaceType::Ethernet3Megabit => {
249                InterfaceType::Ethernet3Megabit
250            }
251            netdev::interface::types::InterfaceType::Slip => InterfaceType::Slip,
252            netdev::interface::types::InterfaceType::Atm => InterfaceType::Atm,
253            netdev::interface::types::InterfaceType::GenericModem => InterfaceType::GenericModem,
254            netdev::interface::types::InterfaceType::ProprietaryVirtual => {
255                InterfaceType::ProprietaryVirtual
256            }
257            netdev::interface::types::InterfaceType::FastEthernetT => InterfaceType::FastEthernetT,
258            netdev::interface::types::InterfaceType::Isdn => InterfaceType::Isdn,
259            netdev::interface::types::InterfaceType::FastEthernetFx => {
260                InterfaceType::FastEthernetFx
261            }
262            netdev::interface::types::InterfaceType::Wireless80211 => InterfaceType::Wireless80211,
263            netdev::interface::types::InterfaceType::AsymmetricDsl => InterfaceType::AsymmetricDsl,
264            netdev::interface::types::InterfaceType::RateAdaptDsl => InterfaceType::RateAdaptDsl,
265            netdev::interface::types::InterfaceType::SymmetricDsl => InterfaceType::SymmetricDsl,
266            netdev::interface::types::InterfaceType::VeryHighSpeedDsl => {
267                InterfaceType::VeryHighSpeedDsl
268            }
269            netdev::interface::types::InterfaceType::IPOverAtm => InterfaceType::IPOverAtm,
270            netdev::interface::types::InterfaceType::GigabitEthernet => {
271                InterfaceType::GigabitEthernet
272            }
273            netdev::interface::types::InterfaceType::Tunnel => InterfaceType::Tunnel,
274            netdev::interface::types::InterfaceType::MultiRateSymmetricDsl => {
275                InterfaceType::MultiRateSymmetricDsl
276            }
277            netdev::interface::types::InterfaceType::HighPerformanceSerialBus => {
278                InterfaceType::HighPerformanceSerialBus
279            }
280            netdev::interface::types::InterfaceType::Wman => InterfaceType::Wman,
281            netdev::interface::types::InterfaceType::Wwan => InterfaceType::Wwan,
282            netdev::interface::types::InterfaceType::Wwanpp => InterfaceType::Wwanpp,
283            netdev::interface::types::InterfaceType::Wwanpp2 => InterfaceType::Wwanpp2,
284            netdev::interface::types::InterfaceType::Bridge => InterfaceType::Bridge,
285            netdev::interface::types::InterfaceType::Can => InterfaceType::Can,
286            netdev::interface::types::InterfaceType::PeerToPeerWireless => {
287                InterfaceType::PeerToPeerWireless
288            }
289            netdev::interface::types::InterfaceType::UnknownWithValue(v) => {
290                InterfaceType::UnknownWithValue(v)
291            }
292        }
293    }
294}
295
296impl TryFrom<u32> for InterfaceType {
297    type Error = ();
298
299    fn try_from(v: u32) -> Result<Self, Self::Error> {
300        Ok(InterfaceType::from(
301            netdev::interface::types::InterfaceType::try_from(v)?,
302        ))
303    }
304}
305
306/// Address information for a related network device.
307#[derive(Clone, Eq, PartialEq, Hash, Debug)]
308#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
309#[non_exhaustive]
310pub struct NetworkDevice {
311    /// Link-layer address of the related device.
312    pub mac_addr: MacAddr,
313    /// IPv4 addresses assigned to the device.
314    pub ipv4: Vec<Ipv4Addr>,
315    /// IPv6 addresses assigned to the device.
316    pub ipv6: Vec<Ipv6Addr>,
317}
318
319impl NetworkDevice {
320    pub fn new() -> NetworkDevice {
321        NetworkDevice {
322            mac_addr: MacAddr::zero(),
323            ipv4: Vec::new(),
324            ipv6: Vec::new(),
325        }
326    }
327}
328
329impl Default for NetworkDevice {
330    fn default() -> Self {
331        Self::new()
332    }
333}
334
335impl From<netdev::NetworkDevice> for NetworkDevice {
336    fn from(value: netdev::NetworkDevice) -> Self {
337        NetworkDevice {
338            mac_addr: value.mac_addr,
339            ipv4: value.ipv4,
340            ipv6: value.ipv6,
341        }
342    }
343}
344
345/// Interface traffic statistics at a given point in time.
346#[derive(Clone, Eq, PartialEq, Hash, Debug)]
347#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
348#[non_exhaustive]
349pub struct InterfaceStats {
350    /// Total received bytes.
351    pub rx_bytes: u64,
352    /// Total transmitted bytes.
353    pub tx_bytes: u64,
354    /// Time at which the counters were sampled.
355    pub timestamp: Option<SystemTime>,
356}
357
358impl From<netdev::stats::counters::InterfaceStats> for InterfaceStats {
359    fn from(value: netdev::stats::counters::InterfaceStats) -> Self {
360        InterfaceStats {
361            rx_bytes: value.rx_bytes,
362            tx_bytes: value.tx_bytes,
363            timestamp: value.timestamp,
364        }
365    }
366}
367
368/// A network interface.
369#[derive(Clone, Eq, PartialEq, Hash, Debug)]
370#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
371#[non_exhaustive]
372pub struct Interface {
373    /// Operating-system interface index.
374    pub index: u32,
375    /// Operating-system interface name.
376    pub name: String,
377    /// User-facing interface name when supplied by the platform.
378    pub friendly_name: Option<String>,
379    /// Platform-provided interface description.
380    pub description: Option<String>,
381    /// Classified interface hardware or transport type.
382    pub if_type: InterfaceType,
383    /// Link-layer address, when one is available.
384    pub mac_addr: Option<MacAddr>,
385    /// Assigned IPv4 networks.
386    pub ipv4: Vec<Ipv4Net>,
387    /// Assigned IPv6 networks.
388    pub ipv6: Vec<Ipv6Net>,
389    /// Scope identifiers corresponding to scoped IPv6 addresses.
390    pub ipv6_scope_ids: Vec<u32>,
391    /// Raw platform interface flags.
392    pub flags: u32,
393    /// Current operational state.
394    pub oper_state: OperState,
395    /// Reported transmit speed in bits per second.
396    pub transmit_speed: Option<u64>,
397    /// Reported receive speed in bits per second.
398    pub receive_speed: Option<u64>,
399    /// Most recently sampled traffic statistics.
400    pub stats: Option<InterfaceStats>,
401    #[cfg(feature = "gateway")]
402    /// Default gateway reachable through this interface.
403    pub gateway: Option<NetworkDevice>,
404    #[cfg(feature = "gateway")]
405    /// DNS servers associated with this interface.
406    pub dns_servers: Vec<IpAddr>,
407    /// Maximum transmission unit in bytes.
408    pub mtu: Option<u32>,
409    #[cfg(feature = "gateway")]
410    /// Whether this is the platform's default interface.
411    pub default: bool,
412}
413
414impl Interface {
415    #[cfg(feature = "gateway")]
416    #[allow(clippy::should_implement_trait)]
417    pub fn default() -> Result<Interface, InterfaceError> {
418        get_default_interface()
419    }
420
421    pub fn dummy() -> Interface {
422        Interface {
423            index: 0,
424            name: String::new(),
425            friendly_name: None,
426            description: None,
427            if_type: InterfaceType::Unknown,
428            mac_addr: None,
429            ipv4: Vec::new(),
430            ipv6: Vec::new(),
431            ipv6_scope_ids: Vec::new(),
432            flags: 0,
433            oper_state: OperState::Unknown,
434            transmit_speed: None,
435            receive_speed: None,
436            stats: None,
437            #[cfg(feature = "gateway")]
438            gateway: None,
439            #[cfg(feature = "gateway")]
440            dns_servers: Vec::new(),
441            mtu: None,
442            #[cfg(feature = "gateway")]
443            default: false,
444        }
445    }
446
447    /// Refresh all interface fields from the operating system.
448    ///
449    /// This performs a fresh system lookup and may be more expensive than
450    /// the accessor methods on `Interface`.
451    pub fn refresh(&mut self) -> io::Result<()> {
452        let refreshed = lookup_interface(&self.name, self.index).ok_or_else(|| {
453            io::Error::new(io::ErrorKind::NotFound, "interface could not be refreshed")
454        })?;
455        *self = refreshed.into();
456        Ok(())
457    }
458
459    pub fn is_up(&self) -> bool {
460        self.flags & IFF_UP != 0
461    }
462
463    pub fn is_loopback(&self) -> bool {
464        self.flags & IFF_LOOPBACK != 0
465    }
466
467    pub fn is_point_to_point(&self) -> bool {
468        self.flags & IFF_POINTOPOINT != 0
469    }
470
471    pub fn is_multicast(&self) -> bool {
472        self.flags & IFF_MULTICAST != 0
473    }
474
475    pub fn is_broadcast(&self) -> bool {
476        self.flags & IFF_BROADCAST != 0
477    }
478
479    pub fn is_tun(&self) -> bool {
480        self.is_up() && self.is_point_to_point() && !self.is_broadcast() && !self.is_loopback()
481    }
482
483    pub fn is_running(&self) -> bool {
484        #[cfg(unix)]
485        {
486            self.flags & IFF_RUNNING != 0
487        }
488        #[cfg(windows)]
489        {
490            self.is_up()
491        }
492    }
493
494    pub fn is_physical(&self) -> bool {
495        lookup_interface(&self.name, self.index)
496            .map(|iface| iface.is_physical())
497            .unwrap_or_else(|| {
498                self.is_up() && self.is_running() && !self.is_tun() && !self.is_loopback()
499            })
500    }
501
502    pub fn oper_state(&self) -> OperState {
503        self.oper_state
504    }
505
506    pub fn is_oper_up(&self) -> bool {
507        self.oper_state == OperState::Up
508    }
509
510    /// Refresh the operational state from the operating system.
511    ///
512    /// This may perform a fresh interface lookup.
513    pub fn refresh_oper_state(&mut self) -> io::Result<()> {
514        if let Some(iface) = lookup_interface(&self.name, self.index) {
515            self.oper_state = iface.oper_state.into();
516            return Ok(());
517        }
518        Err(io::Error::new(
519            io::ErrorKind::NotFound,
520            "interface operational state could not be refreshed",
521        ))
522    }
523
524    /// Refresh the operational state from the operating system.
525    ///
526    /// This may perform a fresh interface lookup.
527    pub fn update_oper_state(&mut self) {
528        let _ = self.refresh_oper_state();
529    }
530
531    /// Iterate IPv4 addresses without allocating a new vector.
532    pub fn ipv4_addr_iter(&self) -> impl Iterator<Item = Ipv4Addr> + '_ {
533        self.ipv4.iter().map(|net| net.addr())
534    }
535
536    pub fn ipv4_addrs(&self) -> Vec<Ipv4Addr> {
537        self.ipv4_addr_iter().collect()
538    }
539
540    /// Iterate IPv6 addresses without allocating a new vector.
541    pub fn ipv6_addr_iter(&self) -> impl Iterator<Item = Ipv6Addr> + '_ {
542        self.ipv6.iter().map(|net| net.addr())
543    }
544
545    pub fn ipv6_addrs(&self) -> Vec<Ipv6Addr> {
546        self.ipv6_addr_iter().collect()
547    }
548
549    /// Iterate IP addresses without allocating a new vector.
550    pub fn ip_addr_iter(&self) -> impl Iterator<Item = IpAddr> + '_ {
551        self.ipv4_addr_iter()
552            .map(IpAddr::V4)
553            .chain(self.ipv6_addr_iter().map(IpAddr::V6))
554    }
555
556    pub fn ip_addrs(&self) -> Vec<IpAddr> {
557        self.ip_addr_iter().collect()
558    }
559
560    pub fn has_ipv4(&self) -> bool {
561        !self.ipv4.is_empty()
562    }
563
564    pub fn has_ipv6(&self) -> bool {
565        !self.ipv6.is_empty()
566    }
567
568    pub fn has_global_ipv4(&self) -> bool {
569        self.ipv4_addrs().iter().any(is_global_ipv4)
570    }
571
572    pub fn has_global_ipv6(&self) -> bool {
573        self.ipv6_addrs().iter().any(is_global_ipv6)
574    }
575
576    pub fn has_global_ip(&self) -> bool {
577        self.ip_addrs().iter().any(is_global_ip)
578    }
579
580    pub fn global_ipv4_addrs(&self) -> Vec<Ipv4Addr> {
581        self.ipv4_addr_iter().filter(is_global_ipv4).collect()
582    }
583
584    pub fn global_ipv6_addrs(&self) -> Vec<Ipv6Addr> {
585        self.ipv6_addr_iter().filter(is_global_ipv6).collect()
586    }
587
588    pub fn global_ip_addrs(&self) -> Vec<IpAddr> {
589        self.ip_addr_iter().filter(is_global_ip).collect()
590    }
591
592    /// Refresh interface statistics from the operating system.
593    ///
594    /// This may perform a fresh interface lookup.
595    pub fn refresh_stats(&mut self) -> io::Result<()> {
596        if let Some(iface) = lookup_interface(&self.name, self.index) {
597            self.stats = iface.stats.map(Into::into);
598            return Ok(());
599        }
600        Err(io::Error::new(
601            io::ErrorKind::NotFound,
602            "interface statistics could not be refreshed",
603        ))
604    }
605
606    /// Refresh interface statistics from the operating system.
607    ///
608    /// This may perform a fresh interface lookup.
609    pub fn update_stats(&mut self) -> io::Result<()> {
610        self.refresh_stats()
611    }
612}
613
614impl From<netdev::Interface> for Interface {
615    fn from(value: netdev::Interface) -> Self {
616        Interface {
617            index: value.index,
618            name: value.name,
619            friendly_name: value.friendly_name,
620            description: value.description,
621            if_type: value.if_type.into(),
622            mac_addr: value.mac_addr,
623            ipv4: value.ipv4,
624            ipv6: value.ipv6,
625            ipv6_scope_ids: value.ipv6_scope_ids,
626            flags: value.flags,
627            oper_state: value.oper_state.into(),
628            transmit_speed: value.transmit_speed,
629            receive_speed: value.receive_speed,
630            stats: value.stats.map(Into::into),
631            #[cfg(feature = "gateway")]
632            gateway: value.gateway.map(Into::into),
633            #[cfg(feature = "gateway")]
634            dns_servers: value.dns_servers,
635            mtu: value.mtu,
636            #[cfg(feature = "gateway")]
637            default: value.default,
638        }
639    }
640}
641
642pub fn get_interfaces() -> Vec<Interface> {
643    netdev::get_interfaces()
644        .into_iter()
645        .map(Into::into)
646        .collect()
647}
648
649#[cfg(feature = "gateway")]
650pub fn get_default_interface() -> Result<Interface, InterfaceError> {
651    netdev::get_default_interface()
652        .map(Into::into)
653        .map_err(|message| InterfaceError::DefaultInterfaceUnavailable { message })
654}
655
656#[cfg(feature = "gateway")]
657pub fn get_default_gateway() -> Result<NetworkDevice, InterfaceError> {
658    netdev::get_default_gateway()
659        .map(Into::into)
660        .map_err(|message| InterfaceError::DefaultGatewayUnavailable { message })
661}
662
663fn lookup_interface(name: &str, index: u32) -> Option<netdev::Interface> {
664    netdev::get_interfaces()
665        .into_iter()
666        .find(|iface| iface.index == index || iface.name == name)
667}
668
669#[cfg(test)]
670mod tests {
671    use super::{InterfaceError, InterfaceType};
672
673    fn assert_error_contract<T: std::error::Error + Send + Sync + 'static>() {}
674
675    #[test]
676    fn interface_error_implements_public_error_contract() {
677        assert_error_contract::<InterfaceError>();
678    }
679
680    #[test]
681    fn interface_error_includes_operation_context() {
682        let error = InterfaceError::DefaultGatewayUnavailable {
683            message: "route table is empty".to_owned(),
684        };
685
686        assert_eq!(
687            error.to_string(),
688            "default gateway is unavailable: route table is empty"
689        );
690    }
691
692    #[test]
693    fn converts_wwan_interface_type() {
694        let interface_type = InterfaceType::from(netdev::interface::types::InterfaceType::Wwan);
695
696        assert_eq!(interface_type, InterfaceType::Wwan);
697        assert_eq!(interface_type.name(), "WWAN");
698    }
699}