#![allow(unsafe_code)]
use std::ffi::CString;
use std::io;
const IFNAMSIZ: usize = 16;
#[derive(Debug, thiserror::Error)]
pub enum NetIfError {
#[error("interface name contains NUL byte")]
InvalidName,
#[error("interface name too long (max {} bytes)", IFNAMSIZ - 1)]
NameTooLong,
#[error("if_nametoindex failed for {name:?}: {source}")]
NotFound {
name: String,
#[source]
source: io::Error,
},
}
#[cfg(target_os = "linux")]
pub fn if_nametoindex(name: &str) -> Result<u32, NetIfError> {
if name.len() >= IFNAMSIZ {
return Err(NetIfError::NameTooLong);
}
let c_name = CString::new(name).map_err(|_| NetIfError::InvalidName)?;
let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
if idx == 0 {
let err = io::Error::last_os_error();
return Err(NetIfError::NotFound {
name: name.to_string(),
source: err,
});
}
Ok(idx)
}
#[cfg(not(target_os = "linux"))]
pub fn if_nametoindex(_name: &str) -> Result<u32, NetIfError> {
Err(NetIfError::NotFound {
name: _name.to_string(),
source: io::Error::new(io::ErrorKind::Unsupported, "non-Linux platform"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lo_ifindex() {
match if_nametoindex("lo") {
Ok(idx) => {
assert_eq!(idx, 1, "lo must have ifindex=1");
}
Err(NetIfError::NotFound { .. }) => {
}
Err(e) => panic!("unexpected error for lo: {:?}", e),
}
}
#[test]
fn test_nonexistent_iface() {
let result = if_nametoindex("zz_nx99");
assert!(matches!(result, Err(NetIfError::NotFound { .. })));
}
#[test]
fn test_name_too_long() {
let long_name = "a".repeat(IFNAMSIZ);
assert_eq!(long_name.len(), IFNAMSIZ);
let result = if_nametoindex(&long_name);
assert!(matches!(result, Err(NetIfError::NameTooLong)));
}
#[test]
fn test_name_with_nul() {
let result = if_nametoindex("eth\0");
assert!(matches!(result, Err(NetIfError::InvalidName)));
}
#[test]
fn test_boundary_name_length() {
let max_name = "a".repeat(IFNAMSIZ - 1);
let result = if_nametoindex(&max_name);
assert!(!matches!(result, Err(NetIfError::NameTooLong)));
}
}