use std::alloc;
use std::ffi::CStr;
use std::mem;
use std::os::fd::AsRawFd;
use std::os::fd::FromRawFd;
use std::os::fd::OwnedFd;
use std::ptr;
use std::str;
use nix::errno::Errno;
use nix::libc;
use nix::sys::socket::socket;
use nix::sys::socket::AddressFamily;
use nix::sys::socket::SockFlag;
use nix::sys::socket::SockType;
use crate::errors::EthtoolError;
use crate::ethtool_sys;
const ETH_GSTATS_LEN: usize = 8;
fn if_name_bytes(if_name: &str) -> [libc::c_char; libc::IF_NAMESIZE] {
let mut it = if_name.as_bytes().iter().copied();
[0; libc::IF_NAMESIZE].map(|_| it.next().unwrap_or(0) as libc::c_char)
}
fn ioctl(
fd: &OwnedFd,
if_name: [libc::c_char; libc::IF_NAMESIZE],
data: *mut libc::c_char,
) -> Result<(), Errno> {
let ifr = libc::ifreq {
ifr_name: if_name,
ifr_ifru: libc::__c_anonymous_ifr_ifru { ifru_data: data },
};
let exit_code = unsafe { libc::ioctl(fd.as_raw_fd(), nix::libc::SIOCETHTOOL, &ifr) };
if exit_code != 0 {
return Err(Errno::from_i32(exit_code));
}
Ok(())
}
fn parse_names(data: &[u8]) -> Result<Vec<String>, EthtoolError> {
let names = data
.chunks(ethtool_sys::ETH_GSTRING_LEN as usize)
.map(|chunk| {
let c_str = CStr::from_bytes_until_nul(chunk);
match c_str {
Ok(c_str) => Ok(c_str.to_string_lossy().into_owned()),
Err(err) => Err(EthtoolError::ParseError(err.to_string())),
}
})
.collect::<Result<Vec<String>, EthtoolError>>()?;
Ok(names)
}
fn parse_values(data: &[u64], length: usize) -> Result<Vec<u64>, EthtoolError> {
let values = data.iter().take(length).copied().collect::<Vec<u64>>();
Ok(values)
}
struct StringSetInfo {
layout: alloc::Layout,
ptr: *mut ethtool_sys::ethtool_sset_info,
}
impl StringSetInfo {
fn new() -> Result<Self, EthtoolError> {
let layout = alloc::Layout::from_size_align(
mem::size_of::<ethtool_sys::ethtool_sset_info>(),
mem::align_of::<ethtool_sys::ethtool_sset_info>(),
)
.map_err(EthtoolError::CStructInitError)?;
let sset_info_ptr = unsafe { alloc::alloc(layout) } as *mut ethtool_sys::ethtool_sset_info;
unsafe {
let cmd_ptr = ptr::addr_of_mut!((*sset_info_ptr).cmd);
let reserved_ptr = ptr::addr_of_mut!((*sset_info_ptr).reserved);
let sset_mask_ptr = ptr::addr_of_mut!((*sset_info_ptr).sset_mask);
let data_ptr = ptr::addr_of_mut!((*sset_info_ptr).data);
cmd_ptr.write(ethtool_sys::ETHTOOL_GSSET_INFO);
reserved_ptr.write(1u32);
sset_mask_ptr.write((1 << ethtool_sys::ethtool_stringset_ETH_SS_STATS) as u64);
data_ptr.write_bytes(0u8, mem::size_of::<u32>());
}
Ok(StringSetInfo {
layout,
ptr: sset_info_ptr,
})
}
fn data(&self) -> Result<usize, EthtoolError> {
unsafe {
let value = self.ptr.as_ref().ok_or(EthtoolError::CStructReadError())?;
Ok(value.data.as_ptr().read() as usize)
}
}
}
impl Drop for StringSetInfo {
fn drop(&mut self) {
unsafe {
alloc::dealloc(self.ptr as *mut u8, self.layout);
}
}
}
struct GStrings {
length: usize,
layout: alloc::Layout,
ptr: *mut ethtool_sys::ethtool_gstrings,
}
impl GStrings {
fn new(length: usize) -> Result<Self, EthtoolError> {
let data_length = length * ethtool_sys::ETH_GSTRING_LEN as usize;
let layout = alloc::Layout::from_size_align(
mem::size_of::<ethtool_sys::ethtool_gstrings>() + data_length * mem::size_of::<u8>(),
mem::align_of::<ethtool_sys::ethtool_gstrings>(),
)
.map_err(EthtoolError::CStructInitError)?;
let gstrings_ptr = unsafe { alloc::alloc(layout) } as *mut ethtool_sys::ethtool_gstrings;
if gstrings_ptr.is_null() {
return Err(EthtoolError::AllocationFailure());
}
unsafe {
let cmd_ptr = ptr::addr_of_mut!((*gstrings_ptr).cmd);
let ss_ptr = ptr::addr_of_mut!((*gstrings_ptr).string_set);
let data_ptr = ptr::addr_of_mut!((*gstrings_ptr).data);
cmd_ptr.write(ethtool_sys::ETHTOOL_GSTRINGS);
ss_ptr.write(ethtool_sys::ethtool_stringset_ETH_SS_STATS);
data_ptr.write_bytes(0u8, data_length);
}
Ok(GStrings {
length,
layout,
ptr: gstrings_ptr,
})
}
fn data(&self) -> Result<&[u8], EthtoolError> {
unsafe {
Ok(std::slice::from_raw_parts(
(*self.ptr).data.as_ptr(),
self.length * ethtool_sys::ETH_GSTRING_LEN as usize,
))
}
}
}
impl Drop for GStrings {
fn drop(&mut self) {
unsafe {
alloc::dealloc(self.ptr as *mut u8, self.layout);
}
}
}
struct GStats {
length: usize,
layout: alloc::Layout,
ptr: *mut ethtool_sys::ethtool_stats,
}
impl GStats {
fn new(length: usize) -> Result<Self, EthtoolError> {
let data_length = length * ETH_GSTATS_LEN;
let layout = alloc::Layout::from_size_align(
mem::size_of::<ethtool_sys::ethtool_stats>() + data_length * mem::size_of::<u64>(),
mem::align_of::<ethtool_sys::ethtool_stats>(),
)
.map_err(EthtoolError::CStructInitError)?;
let stats_ptr = unsafe { alloc::alloc(layout) } as *mut ethtool_sys::ethtool_stats;
if stats_ptr.is_null() {
return Err(EthtoolError::AllocationFailure());
}
unsafe {
let cmd_ptr = ptr::addr_of_mut!((*stats_ptr).cmd);
let data_ptr = ptr::addr_of_mut!((*stats_ptr).data);
cmd_ptr.write(ethtool_sys::ETHTOOL_GSTATS);
let data_bytes = data_length * mem::size_of::<u64>();
data_ptr.write_bytes(0u8, data_bytes);
}
Ok(GStats {
length,
layout,
ptr: stats_ptr,
})
}
fn data(&self) -> Result<&[u64], EthtoolError> {
unsafe {
Ok(std::slice::from_raw_parts(
(*self.ptr).data.as_ptr(),
self.length * ETH_GSTATS_LEN,
))
}
}
}
impl Drop for GStats {
fn drop(&mut self) {
unsafe {
alloc::dealloc(self.ptr as *mut u8, self.layout);
}
}
}
pub trait EthtoolReadable {
fn new(if_name: &str) -> Result<Self, EthtoolError>
where
Self: Sized;
fn stats(&self) -> Result<Vec<(String, u64)>, EthtoolError>;
}
pub struct Ethtool {
sock_fd: OwnedFd,
if_name: [libc::c_char; libc::IF_NAMESIZE],
}
impl Ethtool {
fn gsset_info(&self) -> Result<usize, EthtoolError> {
let sset_info = StringSetInfo::new()?;
let data = sset_info.ptr as *mut libc::c_char;
match ioctl(&self.sock_fd, self.if_name, data) {
Ok(_) => Ok(sset_info.data()?),
Err(errno) => Err(EthtoolError::SocketError(errno)),
}
}
fn gstrings(&self, length: usize) -> Result<Vec<String>, EthtoolError> {
let gstrings = GStrings::new(length)?;
let data = gstrings.ptr as *mut libc::c_char;
match ioctl(&self.sock_fd, self.if_name, data) {
Ok(_) => parse_names(gstrings.data()?),
Err(errno) => Err(EthtoolError::GStringsReadError(errno)),
}
}
fn gstats(&self, features: &[String]) -> Result<Vec<u64>, EthtoolError> {
let length = features.len();
let gstats = GStats::new(length)?;
let data = gstats.ptr as *mut libc::c_char;
match ioctl(&self.sock_fd, self.if_name, data) {
Ok(_) => parse_values(gstats.data()?, length),
Err(errno) => Err(EthtoolError::GStatsReadError(errno)),
}
}
}
impl EthtoolReadable for Ethtool {
fn new(if_name: &str) -> Result<Self, EthtoolError> {
match socket(
AddressFamily::Inet,
SockType::Datagram,
SockFlag::empty(),
None,
) {
Ok(fd) => Ok(Ethtool {
sock_fd: unsafe { OwnedFd::from_raw_fd(fd) },
if_name: if_name_bytes(if_name),
}),
Err(errno) => Err(EthtoolError::SocketError(errno)),
}
}
fn stats(&self) -> Result<Vec<(String, u64)>, EthtoolError> {
let length = self.gsset_info()?;
let features = self.gstrings(length)?;
let values = self.gstats(&features)?;
let final_stats = features.into_iter().zip(values).collect();
Ok(final_stats)
}
}