Files
watcheragent/WatcherAgent/src/hardware/network.rs
2025-10-01 12:11:34 +02:00

236 lines
7.3 KiB
Rust

use std::error::Error;
use std::result::Result;
use std::time::Instant;
/// # Network Hardware Module
///
/// This module provides network information collection for WatcherAgent, including interface enumeration and bandwidth statistics.
///
/// ## Responsibilities
/// - **Interface Detection:** Lists all network interfaces.
/// - **Bandwidth Monitoring:** Tracks receive/transmit rates using a rolling monitor.
/// - **Error Handling:** Graceful fallback if metrics are unavailable.
///
/// ## Units
/// - `rx_rate`, `tx_rate`: Network bandwidth in **bytes per second (B/s)**
///
/// Network statistics for the host system.
///
/// # Fields
/// - `interfaces`: List of network interface names (strings)
/// - `rx_rate`: Receive bandwidth in **bytes per second (B/s)**
/// - `tx_rate`: Transmit bandwidth in **bytes per second (B/s)**
#[derive(Debug)]
pub struct NetworkInfo {
pub interfaces: Option<Vec<String>>,
pub rx_rate: Option<f64>,
pub tx_rate: Option<f64>,
}
/// Rolling monitor for network bandwidth statistics.
///
/// # Fields
/// - `prev_rx`: Previous received bytes
/// - `prev_tx`: Previous transmitted bytes
/// - `last_update`: Timestamp of last update
#[derive(Debug)]
pub struct NetworkMonitor {
prev_rx: u64,
prev_tx: u64,
last_update: Instant,
}
impl Default for NetworkMonitor {
fn default() -> Self {
Self::new()
}
}
impl NetworkMonitor {
/// Creates a new `NetworkMonitor` for bandwidth tracking.
pub fn new() -> Self {
Self {
prev_rx: 0,
prev_tx: 0,
last_update: Instant::now(),
}
}
/// Updates the network usage statistics and returns current rx/tx rates.
///
/// # Returns
/// * `Result<(f64, f64), Box<dyn Error>>` - Tuple of (rx_rate, tx_rate) in bytes per second.
pub fn update_usage(&mut self) -> Result<(f64, f64), Box<dyn Error>> {
let (current_rx, current_tx) = get_network_bytes()?;
let elapsed = self.last_update.elapsed().as_secs_f64();
self.last_update = Instant::now();
let rx_rate = if current_rx >= self.prev_rx {
(current_rx - self.prev_rx) as f64 / elapsed
} else {
0.0
};
let tx_rate = if current_tx >= self.prev_tx {
(current_tx - self.prev_tx) as f64 / elapsed
} else {
0.0
};
self.prev_rx = current_rx;
self.prev_tx = current_tx;
Ok((rx_rate, tx_rate))
}
}
/// Collects network information (interfaces, rx/tx rates) using a monitor.
///
/// # Arguments
/// * `monitor` - Mutable reference to a `NetworkMonitor`
///
/// # Returns
/// * `Result<NetworkInfo, Box<dyn Error>>` - Network statistics or error if unavailable.
pub async fn get_network_info(monitor: &mut NetworkMonitor) -> Result<NetworkInfo, Box<dyn Error>> {
let (rx_rate, tx_rate) = monitor.update_usage()?;
Ok(NetworkInfo {
interfaces: Some(get_network_interfaces()),
rx_rate: Some(rx_rate),
tx_rate: Some(tx_rate),
})
}
fn get_network_bytes() -> Result<(u64, u64), Box<dyn Error>> {
#[cfg(target_os = "windows")]
{
use std::ptr::null_mut;
use winapi::shared::ifmib::MIB_IFTABLE;
use winapi::um::iphlpapi::GetIfTable;
unsafe {
// Erste Abfrage zur Bestimmung der benötigten Puffergröße
let mut buffer_size = 0u32;
if GetIfTable(null_mut(), &mut buffer_size, 0)
!= winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER
{
return Err(
anyhow::anyhow!("Failed to get buffer size for network interfaces").into(),
);
}
// Puffer allozieren
let mut buffer = vec![0u8; buffer_size as usize];
let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE;
// Tatsächliche Daten abrufen
if GetIfTable(if_table, &mut buffer_size, 0) != 0 {
return Err(anyhow::anyhow!("Failed to get network interface table").into());
}
// Daten auswerten
let mut rx_total = 0u64;
let mut tx_total = 0u64;
for i in 0..(*if_table).dwNumEntries {
let row = &*((*if_table).table.as_ptr().offset(i as isize));
rx_total += row.dwInOctets as u64;
tx_total += row.dwOutOctets as u64;
}
if rx_total == 0 && tx_total == 0 {
Err(anyhow::anyhow!("No network data available").into())
} else {
Ok((rx_total, tx_total))
}
}
}
#[cfg(target_os = "linux")]
{
use std::fs;
let mut rx_total = 0u64;
let mut tx_total = 0u64;
if let Ok(dir) = fs::read_dir("/sys/class/net") {
for entry in dir.flatten() {
let iface = entry.file_name();
let iface_name = iface.to_string_lossy();
// Ignoriere virtuelle Interfaces
if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") {
if let (Ok(rx), Ok(tx)) = (
fs::read_to_string(entry.path().join("statistics/rx_bytes")),
fs::read_to_string(entry.path().join("statistics/tx_bytes")),
) {
rx_total += rx.trim().parse::<u64>().unwrap_or(0);
tx_total += tx.trim().parse::<u64>().unwrap_or(0);
}
}
}
}
if rx_total == 0 && tx_total == 0 {
return Err(anyhow::anyhow!("No network data available").into());
} else {
return Ok((rx_total, tx_total));
}
}
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
Err("No network data available for this OS".into())
}
fn get_network_interfaces() -> Vec<String> {
#[cfg(target_os = "windows")]
{
use std::ffi::CStr;
use std::ptr::null_mut;
use winapi::shared::ifmib::MIB_IFTABLE;
use winapi::um::iphlpapi::GetIfTable;
unsafe {
let mut buffer_size = 0u32;
if GetIfTable(null_mut(), &mut buffer_size, 0)
!= winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER
{
return vec![];
}
let mut buffer = vec![0u8; buffer_size as usize];
let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE;
if GetIfTable(if_table, &mut buffer_size, 0) != 0 {
return vec![];
}
(0..(*if_table).dwNumEntries)
.map(|i| {
let row = &*((*if_table).table.as_ptr().offset(i as isize));
let descr = CStr::from_ptr(row.bDescr.as_ptr() as *const i8)
.to_string_lossy()
.into_owned();
descr.trim().to_string()
})
.collect()
}
}
#[cfg(target_os = "linux")]
{
use std::fs;
let mut interfaces = vec![];
if let Ok(dir) = fs::read_dir("/sys/class/net") {
for entry in dir.flatten() {
let iface = entry.file_name();
interfaces.push(iface.to_string_lossy().to_string());
}
}
interfaces
}
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
vec![]
}