use crate::models::Service;
use chrono::{DateTime, Utc};
use std::process::Command;
use tracing::trace;
pub struct NetworkMonitor;
impl NetworkMonitor {
pub fn get_active_connections(service: &Service) -> Option<usize> {
let output = Command::new("ss")
.args(&["-tn", &format!("sport = :{}", service.port)])
.output()
.ok()?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let count = stdout.lines().skip(1).count();
trace!("Service {} on port {} has {} active connections", service.name, service.port, count);
Some(count)
} else {
None
}
}
pub fn is_port_listening(port: u16) -> bool {
let output = Command::new("ss")
.args(&["-tln", &format!("sport = :{}", port)])
.output();
if let Ok(output) = output {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let has_listener = stdout.lines().skip(1).any(|line| {
line.contains(&format!(":{}", port)) && line.contains("LISTEN")
});
trace!("Port {} listening: {}", port, has_listener);
return has_listener;
}
}
false
}
pub fn get_process_stats(pid: u32) -> Option<ProcessNetStats> {
let tcp_path = format!("/proc/{}/net/tcp", pid);
let tcp6_path = format!("/proc/{}/net/tcp6", pid);
let mut established = 0;
let mut listen = 0;
if let Ok(content) = std::fs::read_to_string(&tcp_path) {
for line in content.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() > 3 {
match parts[3] {
"01" => established += 1, "0A" => listen += 1, _ => {}
}
}
}
}
if let Ok(content) = std::fs::read_to_string(&tcp6_path) {
for line in content.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() > 3 {
match parts[3] {
"01" => established += 1,
"0A" => listen += 1,
_ => {}
}
}
}
}
if established > 0 || listen > 0 {
Some(ProcessNetStats {
established_connections: established,
listening_sockets: listen,
})
} else {
None
}
}
pub fn get_traffic_stats(service: &Service) -> TrafficStats {
let active_connections = Self::get_active_connections(service).unwrap_or(0);
let is_active = active_connections > 0 && Self::is_port_listening(service.port);
let (bytes_in, bytes_out) = if let Some(pid) = service.pid {
Self::get_process_traffic_volume(pid).unwrap_or((0, 0))
} else {
(0, 0)
};
TrafficStats {
active_connections,
total_connections_today: 0, bytes_in,
bytes_out,
connections_per_minute: 0.0, last_connection_time: if is_active { Some(Utc::now()) } else { None },
is_active,
}
}
fn get_process_traffic_volume(_pid: u32) -> Option<(u64, u64)> {
None
}
}
#[derive(Debug, Clone)]
pub struct ProcessNetStats {
pub established_connections: usize,
pub listening_sockets: usize,
}
#[derive(Debug, Clone)]
pub struct TrafficStats {
pub active_connections: usize,
pub total_connections_today: u64,
pub bytes_in: u64,
pub bytes_out: u64,
pub connections_per_minute: f64,
pub last_connection_time: Option<DateTime<Utc>>,
pub is_active: bool, }
impl Default for TrafficStats {
fn default() -> Self {
Self {
active_connections: 0,
total_connections_today: 0,
bytes_in: 0,
bytes_out: 0,
connections_per_minute: 0.0,
last_connection_time: None,
is_active: false,
}
}
}