use std::net::IpAddr; use std::time::{Duration, Instant}; use surge_ping::{Client, Config, PingIdentifier, PingSequence, ICMP}; use tokio::time::timeout; #[derive(Debug)] pub struct PingResult { pub alive: bool, pub times: Vec, pub min: f64, pub max: f64, pub avg: f64, pub stddev: f64, pub packet_loss: f64, } pub async fn ping(host: &str, count: u32, timeout_ms: u64) -> Result { let addr: IpAddr = resolve_host(host).await?; let timeout_dur = Duration::from_millis(timeout_ms); let config = match addr { IpAddr::V4(_) => Config::default(), IpAddr::V6(_) => Config::builder().kind(ICMP::V6).build(), }; let client = Client::new(&config).map_err(|e| format!("Failed to create ping client: {e}"))?; let mut pinger = client.pinger(addr, PingIdentifier(rand_u16())).await; let mut times: Vec = Vec::with_capacity(count as usize); let mut alive_count: u32 = 0; for seq in 0..count { let payload = vec![0u8; 56]; let start = Instant::now(); match timeout(timeout_dur, pinger.ping(PingSequence(seq as u16), &payload)).await { Ok(Ok((_packet, rtt))) => { let ms = rtt.as_secs_f64() * 1000.0; times.push(ms); alive_count += 1; } Ok(Err(_)) => { times.push(f64::NAN); } Err(_) => { // timeout let _ = start; // suppress unused warning times.push(f64::NAN); } } } let valid: Vec = times.iter().copied().filter(|t| !t.is_nan()).collect(); let min = valid.iter().copied().fold(f64::INFINITY, f64::min); let max = valid.iter().copied().fold(f64::NEG_INFINITY, f64::max); let avg = if valid.is_empty() { f64::NAN } else { valid.iter().sum::() / valid.len() as f64 }; let stddev = if valid.is_empty() { f64::NAN } else { let variance = valid.iter().map(|v| (v - avg).powi(2)).sum::() / valid.len() as f64; variance.sqrt() }; let packet_loss = ((count - alive_count) as f64 / count as f64) * 100.0; Ok(PingResult { alive: alive_count > 0, times, min: if min.is_infinite() { f64::NAN } else { min }, max: if max.is_infinite() { f64::NAN } else { max }, avg, stddev, packet_loss, }) } async fn resolve_host(host: &str) -> Result { // Try parsing as IP first if let Ok(addr) = host.parse::() { return Ok(addr); } // DNS resolution let addrs = tokio::net::lookup_host(format!("{host}:0")) .await .map_err(|e| format!("DNS resolution failed for {host}: {e}"))?; for addr in addrs { return Ok(addr.ip()); } Err(format!("No addresses found for {host}")) } fn rand_u16() -> u16 { // Simple random using current time let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default(); (now.subsec_nanos() % 65536) as u16 }