use std::{ffi::{CStr, CString}, fmt::{Debug, Display}};
pub use libc::IFNAMSIZ;
type IfNameInner = [libc::c_char; IFNAMSIZ];
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct IfName {
inner: IfNameInner,
}
impl IfName {
pub fn new<From: Into<Vec<u8>>>(name: From) -> std::io::Result<Self> {
let name = CString::new(name)
.map_err(|e| std::io::Error::other(e))?;
Self::from_c_str(name.as_ref())
}
pub fn from_c_str(name: &CStr) -> std::io::Result<Self> {
let name = name.to_bytes();
if name.len() >= IFNAMSIZ {
return Err(std::io::Error::other("Invalid interface name length"));
}
let mut ifname_buf: IfNameInner = [0; {IFNAMSIZ}];
for (i, c) in name.iter().enumerate() {
ifname_buf[i] = (*c) as libc::c_char;
}
Ok(Self {
inner: ifname_buf,
})
}
pub fn as_c_str<'a>(&'a self) -> &'a CStr {
unsafe { CStr::from_ptr(self.inner.as_ptr()) }
}
}
impl Display for IfName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string = format!("{:?}", self.as_c_str());
let str = string.trim_matches('"');
f.write_str(str)
}
}
impl Debug for IfName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("IfName({})", self))
}
}
#[cfg(all(feature = "libc", not(feature = "rtnetlink")))]
pub fn index_to_name(index: InterfaceId) -> Result<IfName, std::io::Error> {
let index = if let Some(index) = index.inner() {
index
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"interface index is unspecified",
));
};
let mut ifname_buf: IfNameInner = [0; {IFNAMSIZ}];
let ret = unsafe { libc::if_indextoname(index, ifname_buf.as_mut_ptr() as *mut libc::c_char) };
if ret.is_null() {
return Err(std::io::Error::last_os_error());
}
Ok(IfName { inner: ifname_buf })
}
#[cfg(feature = "rtnetlink")]
pub fn index_to_name(index: InterfaceId) -> Result<IfName, std::io::Error> {
let index = if let Some(index) = index.inner() {
index
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"interface index is unspecified",
));
};
let handle = ftth_rtnl::RtnlClient::new();
let link_handle = handle.link();
let interface = link_handle.interface_get(index as u32)?;
let name = IfName::new(interface.if_name)?;
Ok(name)
}
#[cfg(all(feature = "libc", not(feature = "rtnetlink")))]
pub fn name_to_index(name: IfName) -> Result<InterfaceId, std::io::Error> {
let index = unsafe { libc::if_nametoindex(name.inner.as_ptr() as *const libc::c_char) };
if index == 0 {
return Err(std::io::Error::last_os_error());
}
Ok(InterfaceId::new(Some(index)))
}
#[cfg(feature = "rtnetlink")]
pub fn name_to_index(name: IfName) -> Result<InterfaceId, std::io::Error> {
let name = name.to_string();
let handle = ftth_rtnl::RtnlClient::new();
let link_handle = handle.link();
let interface = link_handle.interface_get_by_name(&name)?;
Ok(InterfaceId::new(Some(interface.if_id as libc::c_uint)))
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InterfaceId {
if_index: libc::c_uint,
}
impl InterfaceId {
pub const UNSPECIFIED: Self = Self { if_index: 0 };
pub fn new(if_index: Option<libc::c_uint>) -> Self {
Self { if_index: if_index.unwrap_or(0) }
}
pub fn inner(&self) -> Option<libc::c_uint> {
if self.if_index == 0 {
None
} else {
Some(self.if_index)
}
}
pub fn is_unspecified(&self) -> bool {
self.if_index == 0
}
}
impl Debug for InterfaceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(id) = self.inner() {
f.write_str(&format!("InterfaceId({id})"))
} else {
f.write_str(&format!("InterfaceId(UNSPECIFIED)"))
}
}
}
impl Display for InterfaceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{}", self.if_index))
}
}
impl Into<libc::c_uint> for InterfaceId {
fn into(self) -> libc::c_uint {
self.if_index
}
}
impl From<libc::c_uint> for InterfaceId {
fn from(value: libc::c_uint) -> Self {
Self {
if_index: value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Interface {
if_id: InterfaceId,
if_name: IfName,
}
impl Interface {
pub fn new(if_id: Option<libc::c_uint>, if_name: String) -> std::io::Result<Self> {
Ok(Self {
if_id: InterfaceId::new(if_id),
if_name: IfName::new(if_name)?,
})
}
pub fn from_id(if_id: InterfaceId) -> Option<Self> {
index_to_name(if_id).ok().map(|name| Self {
if_id,
if_name: name,
})
}
pub fn from_name(name: IfName) -> Option<Self> {
name_to_index(name).ok().map(|if_id| Self {
if_id,
if_name: name,
})
}
}