Skip to main content

net_interfaces/
lib.rs

1//! Rust crate to handle information about network interfaces.
2
3use std::{ffi::{CStr, CString}, fmt::{Debug, Display}};
4
5pub use libc::IFNAMSIZ;
6
7type IfNameInner = [libc::c_char; IFNAMSIZ];
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
10pub struct IfName {
11    inner: IfNameInner,
12}
13
14impl IfName {
15    pub fn new<From: Into<Vec<u8>>>(name: From) -> std::io::Result<Self> {
16        let name = CString::new(name)
17            .map_err(|e| std::io::Error::other(e))?;
18
19        Self::from_c_str(name.as_ref())
20    }
21
22    pub fn from_c_str(name: &CStr) -> std::io::Result<Self> {
23        let name = name.to_bytes();
24        if name.len() >= IFNAMSIZ {
25            return Err(std::io::Error::other("Invalid interface name length"));
26        }
27
28        let mut ifname_buf: IfNameInner = [0; {IFNAMSIZ}];
29        for (i, c) in name.iter().enumerate() {
30            ifname_buf[i] = (*c) as libc::c_char;
31        }
32
33        Ok(Self {
34            inner: ifname_buf,
35        })
36    }
37
38    pub fn as_c_str<'a>(&'a self) -> &'a CStr {
39        unsafe { CStr::from_ptr(self.inner.as_ptr()) }
40    }
41}
42
43impl Display for IfName {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let string = format!("{:?}", self.as_c_str());
46        let str = string.trim_matches('"');
47        f.write_str(str)
48    }
49}
50
51impl Debug for IfName {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.write_str(&format!("IfName({})", self))
54    }
55}
56
57#[cfg(all(feature = "libc", not(feature = "rtnetlink")))]
58/// Converts an network interface index to the name.
59/// 
60/// This function just uses libc. May be a blocking call.
61pub fn index_to_name(index: InterfaceId) -> Result<IfName, std::io::Error> {
62    let index = if let Some(index) = index.inner() {
63        index
64    } else {
65        return Err(std::io::Error::new(
66            std::io::ErrorKind::InvalidInput,
67            "interface index is unspecified",
68        ));
69    };
70    let mut ifname_buf: IfNameInner = [0; {IFNAMSIZ}];
71    let ret = unsafe { libc::if_indextoname(index, ifname_buf.as_mut_ptr() as *mut libc::c_char) };
72    if ret.is_null() {
73        return Err(std::io::Error::last_os_error());
74    }
75
76    Ok(IfName { inner: ifname_buf })
77}
78
79#[cfg(feature = "rtnetlink")]
80/// Converts an network interface index to the name.
81/// 
82/// This function uses rtnetlink behind. This is a blocking call.
83pub fn index_to_name(index: InterfaceId) -> Result<IfName, std::io::Error> {
84    let index = if let Some(index) = index.inner() {
85        index
86    } else {
87        return Err(std::io::Error::new(
88            std::io::ErrorKind::InvalidInput,
89            "interface index is unspecified",
90        ));
91    };
92    let handle = ftth_rtnl::RtnlClient::new();
93    let link_handle = handle.link();
94    let interface = link_handle.interface_get(index as u32)?;
95    let name = IfName::new(interface.if_name)?;
96    Ok(name)
97}
98
99
100#[cfg(all(feature = "libc", not(feature = "rtnetlink")))]
101/// Converts a network interface name to the index.
102/// 
103/// This function just uses libc. May be a blocking call.
104pub fn name_to_index(name: IfName) -> Result<InterfaceId, std::io::Error> {
105    let index = unsafe { libc::if_nametoindex(name.inner.as_ptr() as *const libc::c_char) };
106    if index == 0 {
107        return Err(std::io::Error::last_os_error());
108    }
109    Ok(InterfaceId::new(Some(index)))
110}
111
112#[cfg(feature = "rtnetlink")]
113/// Converts a network interface name to the index.
114/// 
115/// This function uses rtnetlink behind. This is a blocking call.
116pub fn name_to_index(name: IfName) -> Result<InterfaceId, std::io::Error> {
117    let name = name.to_string();
118    let handle = ftth_rtnl::RtnlClient::new();
119    let link_handle = handle.link();
120    let interface = link_handle.interface_get_by_name(&name)?;
121    Ok(InterfaceId::new(Some(interface.if_id as libc::c_uint)))
122}
123
124#[derive(Clone, Copy, PartialEq, Eq, Hash)]
125pub struct InterfaceId {
126    if_index: libc::c_uint,
127}
128
129impl InterfaceId {
130    pub const UNSPECIFIED: Self = Self { if_index: 0 };
131
132    pub fn new(if_index: Option<libc::c_uint>) -> Self {
133        Self { if_index: if_index.unwrap_or(0) }
134    }
135
136    pub fn inner(&self) -> Option<libc::c_uint> {
137        if self.if_index == 0 {
138            None
139        } else {
140            Some(self.if_index)
141        }
142    }
143
144    pub fn is_unspecified(&self) -> bool {
145        self.if_index == 0
146    }
147}
148
149impl Debug for InterfaceId {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        if let Some(id) = self.inner() {
152            f.write_str(&format!("InterfaceId({id})"))
153        } else {
154            f.write_str(&format!("InterfaceId(UNSPECIFIED)"))
155        }
156    }
157}
158
159impl Display for InterfaceId {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        f.write_str(&format!("{}", self.if_index))
162    }
163}
164
165impl Into<libc::c_uint> for InterfaceId {
166    fn into(self) -> libc::c_uint {
167        self.if_index
168    }
169}
170
171impl From<libc::c_uint> for InterfaceId {
172    fn from(value: libc::c_uint) -> Self {
173        Self {
174            if_index: value,
175        }
176    }
177}
178
179/// Network interface with its index and name.
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
181pub struct Interface {
182    if_id: InterfaceId,
183    if_name: IfName,
184}
185
186impl Interface {
187    pub fn new(if_id: Option<libc::c_uint>, if_name: String) -> std::io::Result<Self> {
188        Ok(Self {
189            if_id: InterfaceId::new(if_id),
190            if_name: IfName::new(if_name)?,
191        })
192    }
193
194    /// Initiates the struct from the interface ID.
195    /// 
196    /// This is a blocking call.
197    pub fn from_id(if_id: InterfaceId) -> Option<Self> {
198        index_to_name(if_id).ok().map(|name| Self {
199            if_id,
200            if_name: name,
201        })
202    }
203
204    pub fn from_name(name: IfName) -> Option<Self> {
205        name_to_index(name).ok().map(|if_id| Self {
206            if_id,
207            if_name: name,
208        })
209    }
210}