use libc::{
self, c_char, c_int, c_uint, if_data64, if_msghdr2, sysctl, CTL_NET, IFNAMSIZ, NET_RT_IFLIST2,
PF_ROUTE, RTM_IFINFO2,
};
use std::collections::{hash_map, HashMap};
use std::mem::{size_of, MaybeUninit};
use std::ptr::null_mut;
use crate::network::refresh_networks_addresses;
use crate::{IpNetwork, MacAddr, NetworkData};
#[repr(C)]
struct ifmibdata {
ifmd_name: [c_char; IFNAMSIZ],
ifmd_pcount: c_uint,
ifmd_flags: c_uint,
ifmd_snd_len: c_uint,
ifmd_snd_maxlen: c_uint,
ifmd_snd_drops: c_uint,
ifmd_filler: [c_uint; 4],
ifmd_data: if_data64,
}
pub const IFDATA_GENERAL: c_int = 1;
pub const IFMIB_IFDATA: c_int = 2;
pub const NETLINK_GENERIC: c_int = 0;
#[inline]
fn update_field(old_field: &mut u64, new_field: &mut u64, value: u64) {
*old_field = *new_field;
*new_field = value;
}
fn update_network_data(inner: &mut NetworkDataInner, data: &if_data64) {
update_field(&mut inner.old_out, &mut inner.current_out, data.ifi_obytes);
update_field(&mut inner.old_in, &mut inner.current_in, data.ifi_ibytes);
update_field(
&mut inner.old_packets_out,
&mut inner.packets_out,
data.ifi_opackets,
);
update_field(
&mut inner.old_packets_in,
&mut inner.packets_in,
data.ifi_ipackets,
);
update_field(
&mut inner.old_errors_in,
&mut inner.errors_in,
data.ifi_ierrors,
);
update_field(
&mut inner.old_errors_out,
&mut inner.errors_out,
data.ifi_oerrors,
);
}
pub(crate) struct NetworksInner {
pub(crate) interfaces: HashMap<String, NetworkData>,
}
impl NetworksInner {
pub(crate) fn new() -> Self {
Self {
interfaces: HashMap::new(),
}
}
pub(crate) fn list(&self) -> &HashMap<String, NetworkData> {
&self.interfaces
}
pub(crate) fn refresh(&mut self, remove_not_listed_interfaces: bool) {
self.update_networks();
if remove_not_listed_interfaces {
self.interfaces.retain(|_, i| {
if !i.inner.updated {
return false;
}
i.inner.updated = false;
true
});
}
refresh_networks_addresses(&mut self.interfaces);
}
#[allow(clippy::cast_ptr_alignment)]
#[allow(clippy::uninit_vec)]
fn update_networks(&mut self) {
let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0];
let mib2 = &mut [
CTL_NET,
libc::PF_LINK,
NETLINK_GENERIC,
IFMIB_IFDATA,
0,
IFDATA_GENERAL,
];
let mut len = 0;
unsafe {
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
null_mut(),
&mut len,
null_mut(),
0,
) < 0
{
return;
}
let mut buf = Vec::with_capacity(len);
buf.set_len(len);
if sysctl(
mib.as_mut_ptr(),
mib.len() as _,
buf.as_mut_ptr(),
&mut len,
null_mut(),
0,
) < 0
{
return;
}
let buf = buf.as_ptr() as *const c_char;
let lim = buf.add(len);
let mut next = buf;
while next < lim {
let ifm = next as *const libc::if_msghdr;
next = next.offset((*ifm).ifm_msglen as isize);
if (*ifm).ifm_type == RTM_IFINFO2 as u8 {
let mut name = vec![0u8; libc::IFNAMSIZ + 6];
let if2m: *const if_msghdr2 = ifm as *const if_msghdr2;
let pname =
libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _);
if pname.is_null() {
continue;
}
name.set_len(libc::strlen(pname));
let name = String::from_utf8_unchecked(name);
let mtu = (*if2m).ifm_data.ifi_mtu as u64;
let mut mib_data: MaybeUninit<ifmibdata> = MaybeUninit::uninit();
mib2[4] = (*if2m).ifm_index as _;
let ret = sysctl(
mib2.as_mut_ptr(),
mib2.len() as _,
mib_data.as_mut_ptr() as *mut _,
&mut size_of::<ifmibdata>(),
null_mut(),
0,
);
match self.interfaces.entry(name) {
hash_map::Entry::Occupied(mut e) => {
let interface = e.get_mut();
let interface = &mut interface.inner;
if ret < 0 {
sysinfo_debug!(
"Cannot get network interface data usage: sysctl failed: {ret}"
);
} else {
let data = mib_data.assume_init();
update_network_data(interface, &data.ifmd_data);
}
if interface.mtu != mtu {
interface.mtu = mtu
}
interface.updated = true;
}
hash_map::Entry::Vacant(e) => {
let current_in;
let current_out;
let packets_in;
let packets_out;
let errors_in;
let errors_out;
if ret < 0 {
sysinfo_debug!(
"Cannot get network interface data usage: sysctl failed: {ret}"
);
current_in = 0;
current_out = 0;
packets_in = 0;
packets_out = 0;
errors_in = 0;
errors_out = 0;
} else {
let data = mib_data.assume_init();
let data = data.ifmd_data;
current_in = data.ifi_ibytes;
current_out = data.ifi_obytes;
packets_in = data.ifi_ipackets;
packets_out = data.ifi_opackets;
errors_in = data.ifi_ierrors;
errors_out = data.ifi_oerrors;
}
e.insert(NetworkData {
inner: NetworkDataInner {
current_in,
old_in: current_in,
current_out,
old_out: current_out,
packets_in,
old_packets_in: packets_in,
packets_out,
old_packets_out: packets_out,
errors_in,
old_errors_in: errors_in,
errors_out,
old_errors_out: errors_out,
updated: true,
mac_addr: MacAddr::UNSPECIFIED,
ip_networks: vec![],
mtu,
},
});
}
}
}
}
}
}
}
#[derive(PartialEq, Eq)]
pub(crate) struct NetworkDataInner {
current_in: u64,
old_in: u64,
current_out: u64,
old_out: u64,
packets_in: u64,
old_packets_in: u64,
packets_out: u64,
old_packets_out: u64,
errors_in: u64,
old_errors_in: u64,
errors_out: u64,
old_errors_out: u64,
updated: bool,
pub(crate) mac_addr: MacAddr,
pub(crate) ip_networks: Vec<IpNetwork>,
mtu: u64,
}
impl NetworkDataInner {
pub(crate) fn received(&self) -> u64 {
self.current_in.saturating_sub(self.old_in)
}
pub(crate) fn total_received(&self) -> u64 {
self.current_in
}
pub(crate) fn transmitted(&self) -> u64 {
self.current_out.saturating_sub(self.old_out)
}
pub(crate) fn total_transmitted(&self) -> u64 {
self.current_out
}
pub(crate) fn packets_received(&self) -> u64 {
self.packets_in.saturating_sub(self.old_packets_in)
}
pub(crate) fn total_packets_received(&self) -> u64 {
self.packets_in
}
pub(crate) fn packets_transmitted(&self) -> u64 {
self.packets_out.saturating_sub(self.old_packets_out)
}
pub(crate) fn total_packets_transmitted(&self) -> u64 {
self.packets_out
}
pub(crate) fn errors_on_received(&self) -> u64 {
self.errors_in.saturating_sub(self.old_errors_in)
}
pub(crate) fn total_errors_on_received(&self) -> u64 {
self.errors_in
}
pub(crate) fn errors_on_transmitted(&self) -> u64 {
self.errors_out.saturating_sub(self.old_errors_out)
}
pub(crate) fn total_errors_on_transmitted(&self) -> u64 {
self.errors_out
}
pub(crate) fn mac_address(&self) -> MacAddr {
self.mac_addr
}
pub(crate) fn ip_networks(&self) -> &[IpNetwork] {
&self.ip_networks
}
pub(crate) fn mtu(&self) -> u64 {
self.mtu
}
}