#[cfg(not(target_os = "windows"))]
mod icmp;
mod platform;
pub use platform::IcmpEchoRequestor;
use std::net::IpAddr;
use std::time::Duration;
pub const PING_DEFAULT_TTL: u8 = 128;
pub const PING_DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
pub const PING_DEFAULT_REQUEST_DATA_LENGTH: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IcmpEchoStatus {
Success,
TimedOut,
Unreachable,
Unknown,
}
impl IcmpEchoStatus {
pub fn ok(self) -> Result<(), String> {
match self {
Self::Success => Ok(()),
Self::TimedOut => Err("Timed out".to_string()),
Self::Unreachable => Err("Destination unreachable".to_string()),
Self::Unknown => Err("Unknown error".to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IcmpEchoReply {
destination: IpAddr,
status: IcmpEchoStatus,
round_trip_time: Duration,
}
impl IcmpEchoReply {
pub fn new(destination: IpAddr, status: IcmpEchoStatus, round_trip_time: Duration) -> Self {
Self {
destination,
status,
round_trip_time,
}
}
pub fn destination(&self) -> IpAddr {
self.destination
}
pub fn status(&self) -> IcmpEchoStatus {
self.status
}
pub fn round_trip_time(&self) -> Duration {
self.round_trip_time
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn ping_localhost_v4() -> std::io::Result<()> {
let pinger = IcmpEchoRequestor::new("127.0.0.1".parse().unwrap(), None, None, None)?;
let reply = pinger.send().await?;
assert_eq!(reply.destination(), "127.0.0.1".parse::<IpAddr>().unwrap());
println!("IPv4 ping result: {reply:?}");
Ok(())
}
#[tokio::test]
async fn ping_localhost_v6() -> std::io::Result<()> {
let pinger = IcmpEchoRequestor::new("::1".parse().unwrap(), None, None, None)?;
let reply = pinger.send().await?;
assert_eq!(reply.destination(), "::1".parse::<IpAddr>().unwrap());
println!("IPv6 ping result: {reply:?}");
Ok(())
}
#[tokio::test]
async fn test_thread_safety() -> std::io::Result<()> {
let pinger = IcmpEchoRequestor::new("127.0.0.1".parse().unwrap(), None, None, None)?;
let pinger_clone = pinger.clone();
let handle = tokio::spawn(async move { pinger_clone.send().await });
let reply = handle.await.unwrap()?;
assert_eq!(reply.destination(), "127.0.0.1".parse::<IpAddr>().unwrap());
Ok(())
}
#[test]
fn test_send_sync_traits() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<IcmpEchoRequestor>();
assert_sync::<IcmpEchoRequestor>();
assert_send::<IcmpEchoReply>();
assert_sync::<IcmpEchoReply>();
assert_send::<IcmpEchoStatus>();
assert_sync::<IcmpEchoStatus>();
}
#[tokio::test]
async fn test_concurrent_pings() -> std::io::Result<()> {
let pinger = IcmpEchoRequestor::new("127.0.0.1".parse().unwrap(), None, None, None)?;
let mut handles = Vec::new();
for _ in 0..5 {
let pinger_clone = pinger.clone();
let handle = tokio::spawn(async move { pinger_clone.send().await });
handles.push(handle);
}
for handle in handles {
let reply = handle.await.unwrap()?;
assert_eq!(reply.destination(), "127.0.0.1".parse::<IpAddr>().unwrap());
}
Ok(())
}
#[tokio::test]
async fn test_multiple_requestors_independent_routers() -> std::io::Result<()> {
let pinger1 = IcmpEchoRequestor::new("127.0.0.1".parse().unwrap(), None, None, None)?;
let pinger2 = IcmpEchoRequestor::new("::1".parse().unwrap(), None, None, None)?;
let reply1 = pinger1.send().await?;
let reply2 = pinger2.send().await?;
assert_eq!(reply1.destination(), "127.0.0.1".parse::<IpAddr>().unwrap());
assert_eq!(reply2.destination(), "::1".parse::<IpAddr>().unwrap());
Ok(())
}
}