feat(smartnetwork): add Rust-powered network diagnostics bridge and IP intelligence lookups
This commit is contained in:
101
rust/crates/rustnetwork/src/ping.rs
Normal file
101
rust/crates/rustnetwork/src/ping.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
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<f64>,
|
||||
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<PingResult, String> {
|
||||
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<f64> = 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<f64> = 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::<f64>() / valid.len() as f64
|
||||
};
|
||||
let stddev = if valid.is_empty() {
|
||||
f64::NAN
|
||||
} else {
|
||||
let variance = valid.iter().map(|v| (v - avg).powi(2)).sum::<f64>() / 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<IpAddr, String> {
|
||||
// Try parsing as IP first
|
||||
if let Ok(addr) = host.parse::<IpAddr>() {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user