// Take a look at the license at the top of the repository in the LICENSE file. use crate::{NetworkExt, NetworksExt, NetworksIter}; use std::collections::{hash_map, HashMap}; use winapi::shared::ifdef::{MediaConnectStateDisconnected, NET_LUID}; use winapi::shared::netioapi::{ FreeMibTable, GetIfEntry2, GetIfTable2, MIB_IF_ROW2, PMIB_IF_TABLE2, }; use winapi::shared::winerror::NO_ERROR; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $new_val; }}; } #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } impl Networks { pub(crate) fn new() -> Networks { Networks { interfaces: HashMap::new(), } } } impl NetworksExt for Networks { #[allow(clippy::needless_lifetimes)] fn iter<'a>(&'a self) -> NetworksIter<'a> { NetworksIter::new(self.interfaces.iter()) } fn refresh_networks_list(&mut self) { let mut table: PMIB_IF_TABLE2 = std::ptr::null_mut(); unsafe { if GetIfTable2(&mut table) != NO_ERROR { return; } for (_, data) in self.interfaces.iter_mut() { data.updated = false; } // In here, this is tricky: we have to filter out the software interfaces to only keep // the hardware ones. To do so, we first check the connection potential speed (if 0, not // interesting), then we check its state: if not open, not interesting either. And finally, // we count the members of a same group: if there is more than 1, then it's software level. let mut groups = HashMap::new(); let mut indexes = Vec::new(); let ptr = (*table).Table.as_ptr(); for i in 0..(*table).NumEntries { let ptr = &*ptr.offset(i as _); if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0) || ptr.MediaConnectState == MediaConnectStateDisconnected || ptr.PhysicalAddressLength == 0 { continue; } let id = vec![ ptr.InterfaceGuid.Data2, ptr.InterfaceGuid.Data3, ptr.InterfaceGuid.Data4[0] as _, ptr.InterfaceGuid.Data4[1] as _, ptr.InterfaceGuid.Data4[2] as _, ptr.InterfaceGuid.Data4[3] as _, ptr.InterfaceGuid.Data4[4] as _, ptr.InterfaceGuid.Data4[5] as _, ptr.InterfaceGuid.Data4[6] as _, ptr.InterfaceGuid.Data4[7] as _, ]; let entry = groups.entry(id.clone()).or_insert(0); *entry += 1; if *entry > 1 { continue; } indexes.push((i, id)); } for (i, id) in indexes { let ptr = &*ptr.offset(i as _); if *groups.get(&id).unwrap_or(&0) > 1 { continue; } let mut pos = 0; for x in ptr.Alias.iter() { if *x == 0 { break; } pos += 1; } let interface_name = match String::from_utf16(&ptr.Alias[..pos]) { Ok(s) => s, _ => continue, }; match self.interfaces.entry(interface_name) { hash_map::Entry::Occupied(mut e) => { let mut interface = e.get_mut(); old_and_new!(interface, current_out, old_out, ptr.OutOctets); old_and_new!(interface, current_in, old_in, ptr.InOctets); old_and_new!( interface, packets_in, old_packets_in, ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts) ); old_and_new!( interface, packets_out, old_packets_out, ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts) ); old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors); old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors); interface.updated = true; } hash_map::Entry::Vacant(e) => { let packets_in = ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts); let packets_out = ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts); e.insert(NetworkData { id: ptr.InterfaceLuid, current_out: ptr.OutOctets, old_out: ptr.OutOctets, current_in: ptr.InOctets, old_in: ptr.InOctets, packets_in, old_packets_in: packets_in, packets_out, old_packets_out: packets_out, errors_in: ptr.InErrors, old_errors_in: ptr.InErrors, errors_out: ptr.OutErrors, old_errors_out: ptr.OutErrors, updated: true, }); } } } FreeMibTable(table as _); } // Remove interfaces which are gone. self.interfaces.retain(|_, d| d.updated); } fn refresh(&mut self) { let entry = std::mem::MaybeUninit::::zeroed(); unsafe { let mut entry = entry.assume_init(); for (_, interface) in self.interfaces.iter_mut() { entry.InterfaceLuid = interface.id; entry.InterfaceIndex = 0; // to prevent the function to pick this one as index if GetIfEntry2(&mut entry) != NO_ERROR { continue; } old_and_new!(interface, current_out, old_out, entry.OutOctets); old_and_new!(interface, current_in, old_in, entry.InOctets); old_and_new!( interface, packets_in, old_packets_in, entry.InUcastPkts.saturating_add(entry.InNUcastPkts) ); old_and_new!( interface, packets_out, old_packets_out, entry.OutUcastPkts.saturating_add(entry.OutNUcastPkts) ); old_and_new!(interface, errors_in, old_errors_in, entry.InErrors); old_and_new!(interface, errors_out, old_errors_out, entry.OutErrors); } } } } #[doc = include_str!("../../md_doc/network_data.md")] pub struct NetworkData { id: NET_LUID, current_out: u64, old_out: u64, current_in: u64, old_in: 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, } impl NetworkExt for NetworkData { fn received(&self) -> u64 { self.current_in.saturating_sub(self.old_in) } fn total_received(&self) -> u64 { self.current_in } fn transmitted(&self) -> u64 { self.current_out.saturating_sub(self.old_out) } fn total_transmitted(&self) -> u64 { self.current_out } fn packets_received(&self) -> u64 { self.packets_in.saturating_sub(self.old_packets_in) } fn total_packets_received(&self) -> u64 { self.packets_in } fn packets_transmitted(&self) -> u64 { self.packets_out.saturating_sub(self.old_packets_out) } fn total_packets_transmitted(&self) -> u64 { self.packets_out } fn errors_on_received(&self) -> u64 { self.errors_in.saturating_sub(self.old_errors_in) } fn total_errors_on_received(&self) -> u64 { self.errors_in } fn errors_on_transmitted(&self) -> u64 { self.errors_out.saturating_sub(self.old_errors_out) } fn total_errors_on_transmitted(&self) -> u64 { self.errors_out } }