1use std::net::IpAddr;
8use std::time::Duration;
9
10use async_trait::async_trait;
11use surge_ping::{Client, Config, ICMP, PingIdentifier, PingSequence};
12use thiserror::Error;
13
14#[cfg(windows)]
18const ICMP_PAYLOAD: &[u8] = b"\"Hello!\" from ftnet ping.......\0";
19#[cfg(not(windows))]
20const ICMP_PAYLOAD: &[u8] = b"\"Hello!\" from Futu ping.";
21
22#[derive(Debug, Error)]
23pub enum IcmpProbeError {
24 #[error("failed to create ICMP client: {0}")]
25 Client(String),
26 #[error("ICMP echo failed: {0}")]
27 Echo(String),
28}
29
30#[async_trait]
31pub trait IcmpProbe: Send + Sync + std::fmt::Debug {
32 async fn probe(&self, target: IpAddr, timeout: Duration) -> Result<Duration, IcmpProbeError>;
33}
34
35#[derive(Debug, Default)]
42pub struct SurgeIcmpProbe;
43
44#[async_trait]
45impl IcmpProbe for SurgeIcmpProbe {
46 async fn probe(&self, target: IpAddr, timeout: Duration) -> Result<Duration, IcmpProbeError> {
47 let kind = match target {
48 IpAddr::V4(_) => ICMP::V4,
49 IpAddr::V6(_) => ICMP::V6,
50 };
51 let config = Config::builder().kind(kind).build();
52 let client =
53 Client::new(&config).map_err(|error| IcmpProbeError::Client(error.to_string()))?;
54 let mut pinger = client.pinger(target, PingIdentifier(0)).await;
55 pinger.timeout(timeout);
56 let (_, round_trip) = pinger
57 .ping(PingSequence(0), ICMP_PAYLOAD)
58 .await
59 .map_err(|error| IcmpProbeError::Echo(error.to_string()))?;
60 Ok(round_trip)
61 }
62}