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