1use nix::net::if_::{if_indextoname, if_nametoindex};
3use push_packet_common::FrameKind;
4
5use crate::error::Error;
6
7#[derive(Debug)]
9pub struct Interface {
10 name: String,
11 index: u32,
12}
13
14impl Interface {
15 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 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 #[must_use]
47 pub fn name(&self) -> &str {
48 &self.name
49 }
50
51 #[must_use]
53 pub fn index(&self) -> u32 {
54 self.index
55 }
56
57 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}