use crate::models::Service;
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
}
}
}
#[derive(Debug, Clone)]
pub struct ProcessNetStats {
pub established_connections: usize,
pub listening_sockets: usize,
}