Skip to main content

push_packet/
interface.rs

1//! Network interface primitives
2use nix::net::if_::{if_indextoname, if_nametoindex};
3use push_packet_common::FrameKind;
4
5use crate::error::Error;
6
7/// A network interface
8#[derive(Debug)]
9pub struct Interface {
10    name: String,
11    index: u32,
12}
13
14impl Interface {
15    /// Creates an interface from the name
16    ///
17    /// # Errors
18    /// Returns [`Error::InvalidInterfaceName`] if the name is invalid.
19    pub fn from_name(name: &str) -> Result<Self, Error> {
20        let index =
21            if_nametoindex(name).map_err(|_| Error::InvalidInterfaceName(name.to_string()))?;
22        let name = name.to_string();
23        Ok(Self { name, index })
24    }
25
26    /// Creates an interface from the index
27    ///
28    /// # Errors
29    /// Returns [`Error::InvalidInterfaceIndex`] if the index is invalid.
30    /// Returns [`Error::InvalidInterfaceName`] if the name is invalid.
31    ///
32    /// # Panics
33    /// Panics if the interface name is not a valid string
34    pub fn from_index(index: u32) -> Result<Self, Error> {
35        let name = if_indextoname(index)
36            .map_err(|_| Error::InvalidInterfaceIndex(index))?
37            .into_string()
38            .expect("Linux interface names are valid ASCII");
39        if name.is_empty() {
40            return Err(Error::InvalidInterfaceIndex(index));
41        }
42        Ok(Self { name, index })
43    }
44
45    /// Returns the interface name
46    #[must_use]
47    pub fn name(&self) -> &str {
48        &self.name
49    }
50
51    /// Returns the interface index
52    #[must_use]
53    pub fn index(&self) -> u32 {
54        self.index
55    }
56
57    /// Returns the interfaces [`FrameKind`]. This is needed because most interfaces receive
58    /// ethernet frames, while wireguard interfaces receive IP frames.
59    ///
60    /// # Errors
61    /// Returns [`Error::InvalidFrameKind`] if the [`FrameKind`] is not [`FrameKind::Eth`] or
62    /// [`FrameKind::Ip`].
63    pub fn frame_kind(&self) -> Result<FrameKind, Error> {
64        let value: u32 = std::fs::read_to_string(format!("/sys/class/net/{}/type", self.name))
65            .unwrap_or_default()
66            .trim()
67            .parse()
68            .map_err(|_e| Error::InvalidFrameKind(0))?;
69        match value {
70            1 | 772 => Ok(FrameKind::Eth),
71            65534 => Ok(FrameKind::Ip),
72            other => Err(Error::InvalidFrameKind(other)),
73        }
74    }
75}
76
77impl TryFrom<&String> for Interface {
78    type Error = Error;
79    fn try_from(value: &String) -> Result<Self, Self::Error> {
80        Interface::from_name(value)
81    }
82}
83
84impl TryFrom<&str> for Interface {
85    type Error = Error;
86    fn try_from(value: &str) -> Result<Self, Self::Error> {
87        Interface::from_name(value)
88    }
89}
90
91impl TryFrom<String> for Interface {
92    type Error = Error;
93    fn try_from(value: String) -> Result<Self, Self::Error> {
94        Interface::from_name(&value)
95    }
96}
97
98impl TryFrom<u32> for Interface {
99    type Error = Error;
100    fn try_from(value: u32) -> Result<Self, Self::Error> {
101        Interface::from_index(value)
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use crate::{error::Error, interface::Interface};
108
109    #[test]
110    fn interface_loads_loopback() {
111        assert!(Interface::from_name("lo").is_ok());
112    }
113
114    #[test]
115    fn interface_name_index_roundtrip() {
116        let interface = Interface::from_name("lo").unwrap();
117        let index = interface.index();
118        let interface = Interface::from_index(index).unwrap();
119        assert_eq!(interface.name(), "lo");
120    }
121
122    #[test]
123    fn interface_lo_is_eth() {
124        let interface = Interface::from_name("lo").unwrap();
125        assert!(matches!(
126            interface.frame_kind(),
127            Ok(push_packet_common::FrameKind::Eth)
128        ));
129    }
130
131    #[test]
132    fn try_interface_from_values() {
133        let _interface: Interface = "lo".try_into().unwrap();
134        let interface: Interface = "lo".to_string().try_into().unwrap();
135        let index = interface.index();
136        let interface: Interface = index.try_into().unwrap();
137        assert_eq!(interface.index(), index);
138    }
139
140    #[test]
141    fn invalid_interface_name_fails() {
142        let interface = Interface::from_name("invalidinterfacethatdoesntexist");
143        assert!(interface.is_err_and(|e| matches!(e, Error::InvalidInterfaceName(_))));
144    }
145
146    #[test]
147    fn invalid_interface_index_fails() {
148        let interface = Interface::from_index(u32::MAX);
149        println!("Got interface: {interface:?}");
150        assert!(matches!(interface, Err(Error::InvalidInterfaceIndex(_))));
151    }
152}