net-interfaces 0.1.0

Lib crate to get information about network interfaces.
Documentation
//! Rust crate to handle information about network interfaces.

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")))]
/// Converts an network interface index to the name.
/// 
/// This function just uses libc. May be a blocking call.
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")]
/// Converts an network interface index to the name.
/// 
/// This function uses rtnetlink behind. This is a blocking call.
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")))]
/// Converts a network interface name to the index.
/// 
/// This function just uses libc. May be a blocking call.
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")]
/// Converts a network interface name to the index.
/// 
/// This function uses rtnetlink behind. This is a blocking call.
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,
        }
    }
}

/// Network interface with its index and name.
#[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)?,
        })
    }

    /// Initiates the struct from the interface ID.
    /// 
    /// This is a blocking call.
    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,
        })
    }
}