// Take a look at the license at the top of the repository in the LICENSE file. use std::fs::File; use std::io::Read; use std::path::Path; use crate::{NetworkExt, NetworksExt, NetworksIter}; use std::collections::{hash_map, HashMap}; #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $name; }}; ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{ let _tmp = $path; $ty_.$old = $ty_.$name; $ty_.$name = _tmp; }}; } #[allow(clippy::ptr_arg)] fn read>(parent: P, path: &str, data: &mut Vec) -> u64 { if let Ok(mut f) = File::open(parent.as_ref().join(path)) { if let Ok(size) = f.read(data) { let mut i = 0; let mut ret = 0; while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' { ret *= 10; ret += (data[i] - b'0') as u64; i += 1; } return ret; } } 0 } impl Networks { pub(crate) fn new() -> Self { Networks { interfaces: HashMap::new(), } } } fn refresh_networks_list_from_sysfs( interfaces: &mut HashMap, sysfs_net: &Path, ) { if let Ok(dir) = std::fs::read_dir(sysfs_net) { let mut data = vec![0; 30]; for stats in interfaces.values_mut() { stats.updated = false; } for entry in dir.flatten() { let parent = &entry.path().join("statistics"); let entry = match entry.file_name().into_string() { Ok(entry) => entry, Err(_) => continue, }; let rx_bytes = read(parent, "rx_bytes", &mut data); let tx_bytes = read(parent, "tx_bytes", &mut data); let rx_packets = read(parent, "rx_packets", &mut data); let tx_packets = read(parent, "tx_packets", &mut data); let rx_errors = read(parent, "rx_errors", &mut data); let tx_errors = read(parent, "tx_errors", &mut data); // let rx_compressed = read(parent, "rx_compressed", &mut data); // let tx_compressed = read(parent, "tx_compressed", &mut data); match interfaces.entry(entry) { hash_map::Entry::Occupied(mut e) => { let mut interface = e.get_mut(); old_and_new!(interface, rx_bytes, old_rx_bytes); old_and_new!(interface, tx_bytes, old_tx_bytes); old_and_new!(interface, rx_packets, old_rx_packets); old_and_new!(interface, tx_packets, old_tx_packets); old_and_new!(interface, rx_errors, old_rx_errors); old_and_new!(interface, tx_errors, old_tx_errors); // old_and_new!(e, rx_compressed, old_rx_compressed); // old_and_new!(e, tx_compressed, old_tx_compressed); interface.updated = true; } hash_map::Entry::Vacant(e) => { e.insert(NetworkData { rx_bytes, old_rx_bytes: rx_bytes, tx_bytes, old_tx_bytes: tx_bytes, rx_packets, old_rx_packets: rx_packets, tx_packets, old_tx_packets: tx_packets, rx_errors, old_rx_errors: rx_errors, tx_errors, old_tx_errors: tx_errors, // rx_compressed, // old_rx_compressed: rx_compressed, // tx_compressed, // old_tx_compressed: tx_compressed, updated: true, }); } }; } // Remove interfaces which are gone. interfaces.retain(|_, d| d.updated); } } impl NetworksExt for Networks { fn iter(&self) -> NetworksIter { NetworksIter::new(self.interfaces.iter()) } fn refresh(&mut self) { let mut v = vec![0; 30]; for (interface_name, data) in self.interfaces.iter_mut() { data.update(interface_name, &mut v); } } fn refresh_networks_list(&mut self) { refresh_networks_list_from_sysfs(&mut self.interfaces, Path::new("/sys/class/net/")); } } #[doc = include_str!("../../md_doc/network_data.md")] pub struct NetworkData { /// Total number of bytes received over interface. rx_bytes: u64, old_rx_bytes: u64, /// Total number of bytes transmitted over interface. tx_bytes: u64, old_tx_bytes: u64, /// Total number of packets received. rx_packets: u64, old_rx_packets: u64, /// Total number of packets transmitted. tx_packets: u64, old_tx_packets: u64, /// Shows the total number of packets received with error. This includes /// too-long-frames errors, ring-buffer overflow errors, CRC errors, /// frame alignment errors, fifo overruns, and missed packets. rx_errors: u64, old_rx_errors: u64, /// similar to `rx_errors` tx_errors: u64, old_tx_errors: u64, // /// Indicates the number of compressed packets received by this // /// network device. This value might only be relevant for interfaces // /// that support packet compression (e.g: PPP). // rx_compressed: usize, // old_rx_compressed: usize, // /// Indicates the number of transmitted compressed packets. Note // /// this might only be relevant for devices that support // /// compression (e.g: PPP). // tx_compressed: usize, // old_tx_compressed: usize, /// Whether or not the above data has been updated during refresh updated: bool, } impl NetworkData { fn update(&mut self, path: &str, data: &mut Vec) { let path = &Path::new("/sys/class/net/").join(path).join("statistics"); old_and_new!(self, rx_bytes, old_rx_bytes, read(path, "rx_bytes", data)); old_and_new!(self, tx_bytes, old_tx_bytes, read(path, "tx_bytes", data)); old_and_new!( self, rx_packets, old_rx_packets, read(path, "rx_packets", data) ); old_and_new!( self, tx_packets, old_tx_packets, read(path, "tx_packets", data) ); old_and_new!( self, rx_errors, old_rx_errors, read(path, "rx_errors", data) ); old_and_new!( self, tx_errors, old_tx_errors, read(path, "tx_errors", data) ); // old_and_new!( // self, // rx_compressed, // old_rx_compressed, // read(path, "rx_compressed", data) // ); // old_and_new!( // self, // tx_compressed, // old_tx_compressed, // read(path, "tx_compressed", data) // ); } } impl NetworkExt for NetworkData { fn received(&self) -> u64 { self.rx_bytes.saturating_sub(self.old_rx_bytes) } fn total_received(&self) -> u64 { self.rx_bytes } fn transmitted(&self) -> u64 { self.tx_bytes.saturating_sub(self.old_tx_bytes) } fn total_transmitted(&self) -> u64 { self.tx_bytes } fn packets_received(&self) -> u64 { self.rx_packets.saturating_sub(self.old_rx_packets) } fn total_packets_received(&self) -> u64 { self.rx_packets } fn packets_transmitted(&self) -> u64 { self.tx_packets.saturating_sub(self.old_tx_packets) } fn total_packets_transmitted(&self) -> u64 { self.tx_packets } fn errors_on_received(&self) -> u64 { self.rx_errors.saturating_sub(self.old_rx_errors) } fn total_errors_on_received(&self) -> u64 { self.rx_errors } fn errors_on_transmitted(&self) -> u64 { self.tx_errors.saturating_sub(self.old_tx_errors) } fn total_errors_on_transmitted(&self) -> u64 { self.tx_errors } } #[cfg(test)] mod test { use super::refresh_networks_list_from_sysfs; use std::collections::HashMap; use std::fs; #[test] fn refresh_networks_list_add_interface() { let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); fs::create_dir(sys_net_dir.path().join("itf1")).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf1"]); fs::create_dir(sys_net_dir.path().join("itf2")).expect("failed to create subdirectory"); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); let mut itf_names: Vec = interfaces.keys().map(|n| n.to_owned()).collect(); itf_names.sort(); assert_eq!(itf_names, ["itf1", "itf2"]); } #[test] fn refresh_networks_list_remove_interface() { let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); let itf1_dir = sys_net_dir.path().join("itf1"); let itf2_dir = sys_net_dir.path().join("itf2"); fs::create_dir(&itf1_dir).expect("failed to create subdirectory"); fs::create_dir(&itf2_dir).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); let mut itf_names: Vec = interfaces.keys().map(|n| n.to_owned()).collect(); itf_names.sort(); assert_eq!(itf_names, ["itf1", "itf2"]); fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory"); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf2"]); } }