use serde::Serialize;
use std::time::Instant;
use super::DiagnosticResult;
#[derive(Debug, Clone, Serialize)]
pub struct GatewayInfo {
pub ip: String,
pub reachable: bool,
pub latency_ms: Option<f64>,
pub interface: Option<String>,
}
pub async fn check() -> (DiagnosticResult, Option<GatewayInfo>) {
let gateway_ip = match get_default_gateway().await {
Some(ip) => ip,
None => {
return (
DiagnosticResult::fail("Gateway", "No default gateway detected"),
None,
);
}
};
let (reachable, latency_ms) = ping_host(&gateway_ip).await;
let info = GatewayInfo {
ip: gateway_ip.clone(),
reachable,
latency_ms,
interface: None,
};
let result = if reachable {
let lat_str = latency_ms
.map(|l| format!("{:.0}ms", l))
.unwrap_or_else(|| "N/A".to_string());
DiagnosticResult::ok("Gateway", format!("Reachable ({})", lat_str))
} else {
DiagnosticResult::fail("Gateway", format!("Gateway {} unreachable", gateway_ip))
};
(result, Some(info))
}
async fn get_default_gateway() -> Option<String> {
tokio::task::spawn_blocking(|| {
default_net::get_default_gateway()
.ok()
.map(|gw| gw.ip_addr.to_string())
})
.await
.unwrap_or(None)
}
async fn ping_host(host: &str) -> (bool, Option<f64>) {
let start = Instant::now();
#[cfg(windows)]
let result = {
let mut cmd = tokio::process::Command::new("ping");
cmd.args(["-n", "1", "-w", "2000", host]);
super::util::run_with_timeout(cmd, super::util::SLOW).await
};
#[cfg(unix)]
let result = {
let mut cmd = tokio::process::Command::new("ping");
cmd.args(["-c", "1", "-W", "2", host]);
super::util::run_with_timeout(cmd, super::util::SLOW).await
};
match result {
Some(output) if output.status.success() => {
let elapsed = start.elapsed();
let text = String::from_utf8_lossy(&output.stdout);
let latency = parse_ping_latency(&text).unwrap_or(elapsed.as_secs_f64() * 1000.0);
(true, Some(latency))
}
_ => (false, None),
}
}
fn parse_ping_latency(output: &str) -> Option<f64> {
for line in output.lines() {
if let Some(pos) = line.find("time=") {
let after = &line[pos + 5..];
let num_str: String = after
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.' || *c == '<')
.filter(|c| c.is_ascii_digit() || *c == '.')
.collect();
if let Ok(ms) = num_str.parse::<f64>() {
return Some(ms);
}
}
if let Some(pos) = line.find("time<") {
let after = &line[pos + 5..];
let num_str: String = after
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect();
if let Ok(ms) = num_str.parse::<f64>() {
return Some(ms);
}
}
}
None
}