use crate::config::RingProfile;
use crate::error::Error;
#[derive(Debug, Clone)]
pub struct InterfaceInfo {
pub name: String,
pub index: u32,
pub mtu: u32,
pub speed: u32,
pub driver: String,
pub num_queues: u32,
pub carrier: bool,
pub flags: u32,
}
pub fn interface_info(name: &str) -> Result<InterfaceInfo, Error> {
let index = crate::afpacket::socket::resolve_interface(name)? as u32;
let mtu = read_sysfs_u32(name, "mtu").unwrap_or_else(|| {
tracing::debug!(iface = name, "no MTU in sysfs; defaulting to 1500");
1500
});
let speed = read_sysfs_u32(name, "speed").unwrap_or(0);
let carrier = read_sysfs_u32(name, "carrier").unwrap_or(0) == 1;
let flags = read_sysfs_u32(name, "flags").unwrap_or(0);
let driver = read_sysfs_link_basename(name, "device/driver").unwrap_or_default();
let num_queues = count_sysfs_queues(name).unwrap_or(1);
Ok(InterfaceInfo {
name: name.to_string(),
index,
mtu,
speed,
driver,
num_queues,
carrier,
flags,
})
}
impl InterfaceInfo {
pub fn suggest_profile(&self) -> RingProfile {
if self.mtu > 1500 {
RingProfile::JumboFrames
} else if self.speed >= 10_000 {
RingProfile::HighThroughput
} else if self.speed >= 1_000 {
RingProfile::Default
} else {
RingProfile::LowMemory
}
}
pub fn suggest_fanout_threads(&self) -> usize {
let queues = self.num_queues as usize;
let cpus = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
queues.min(cpus).max(1)
}
}
fn read_sysfs_u32(iface: &str, attr: &str) -> Option<u32> {
std::fs::read_to_string(format!("/sys/class/net/{iface}/{attr}"))
.ok()?
.trim()
.parse()
.ok()
}
fn read_sysfs_link_basename(iface: &str, path: &str) -> Option<String> {
let target = std::fs::read_link(format!("/sys/class/net/{iface}/{path}")).ok()?;
target.file_name()?.to_str().map(String::from)
}
fn count_sysfs_queues(iface: &str) -> Option<u32> {
let entries = std::fs::read_dir(format!("/sys/class/net/{iface}/queues")).ok()?;
let count = entries
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_str().is_some_and(|n| n.starts_with("rx-")))
.count();
Some(count.max(1) as u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn loopback_info() {
let info = interface_info("lo").unwrap();
assert_eq!(info.name, "lo");
assert!(info.index > 0);
assert!(info.mtu > 0);
assert_eq!(info.speed, 0);
}
#[test]
fn nonexistent_interface() {
let err = interface_info("nonexistent_xyz_42").unwrap_err();
assert!(matches!(err, Error::InterfaceNotFound(_)));
}
#[test]
fn suggest_profile_jumbo() {
let info = InterfaceInfo {
name: "test".into(),
index: 1,
mtu: 9000,
speed: 10_000,
driver: String::new(),
num_queues: 4,
carrier: true,
flags: 0,
};
assert_eq!(info.suggest_profile(), RingProfile::JumboFrames);
}
#[test]
fn suggest_profile_high_throughput() {
let info = InterfaceInfo {
name: "test".into(),
index: 1,
mtu: 1500,
speed: 25_000,
driver: String::new(),
num_queues: 8,
carrier: true,
flags: 0,
};
assert_eq!(info.suggest_profile(), RingProfile::HighThroughput);
}
#[test]
fn suggest_fanout_threads_capped() {
let info = InterfaceInfo {
name: "test".into(),
index: 1,
mtu: 1500,
speed: 1000,
driver: String::new(),
num_queues: 1000, carrier: true,
flags: 0,
};
let threads = info.suggest_fanout_threads();
assert!(threads >= 1);
assert!(threads <= 1000); }
}