use std::io::ErrorKind;
use std::net::{IpAddr, SocketAddr};
use std::time::{Duration, Instant};
use futures::future::join_all;
use tokio::net::TcpStream;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(2);
pub struct ReachResult {
pub ok: bool,
pub detail: String,
pub ms: u128,
}
enum PortOutcome {
Up, TimedOut, Error, }
async fn probe_port(addr: SocketAddr) -> PortOutcome {
match tokio::time::timeout(CONNECT_TIMEOUT, TcpStream::connect(addr)).await {
Ok(Ok(_)) => PortOutcome::Up,
Ok(Err(e)) if e.kind() == ErrorKind::ConnectionRefused => PortOutcome::Up,
Ok(Err(_)) => PortOutcome::Error,
Err(_) => PortOutcome::TimedOut,
}
}
pub async fn reachable(ip: IpAddr, ports: &[u16]) -> ReachResult {
let start = Instant::now();
let outcomes = join_all(ports.iter().map(|&p| probe_port(SocketAddr::new(ip, p)))).await;
let ms = start.elapsed().as_millis();
if outcomes.iter().any(|o| matches!(o, PortOutcome::Up)) {
ReachResult {
ok: true,
detail: "reachable".into(),
ms,
}
} else if outcomes.iter().all(|o| matches!(o, PortOutcome::TimedOut)) {
ReachResult {
ok: false,
detail: "unreachable or timed out".into(),
ms,
}
} else {
ReachResult {
ok: false,
detail: "unreachable (no route)".into(),
ms,
}
}
}