surge_ping/
ping.rs

1use std::{
2    net::{IpAddr, SocketAddr},
3    time::{Duration, Instant},
4};
5
6use tokio::time::timeout;
7
8use crate::{
9    client::{AsyncSocket, ReplyMap},
10    error::{Result, SurgeError},
11    icmp::{icmpv4, icmpv6, IcmpPacket, PingIdentifier, PingSequence},
12    is_linux_icmp_socket,
13};
14
15/// A Ping struct represents the state of one particular ping instance.
16pub struct Pinger {
17    pub host: IpAddr,
18    pub ident: Option<PingIdentifier>,
19    timeout: Duration,
20    socket: AsyncSocket,
21    reply_map: ReplyMap,
22    last_sequence: Option<PingSequence>,
23}
24
25impl Drop for Pinger {
26    fn drop(&mut self) {
27        if let Some(sequence) = self.last_sequence.take() {
28            // Ensure no reply waiter is left hanging if this pinger is dropped while
29            // waiting for a reply.
30            self.reply_map.remove(self.host, self.ident, sequence);
31        }
32    }
33}
34
35impl Pinger {
36    pub(crate) fn new(
37        host: IpAddr,
38        ident_hint: PingIdentifier,
39        socket: AsyncSocket,
40        response_map: ReplyMap,
41    ) -> Pinger {
42        let ident = if is_linux_icmp_socket!(socket.get_type()) {
43            None
44        } else {
45            Some(ident_hint)
46        };
47
48        Pinger {
49            host,
50            ident,
51            timeout: Duration::from_secs(2),
52            socket,
53            reply_map: response_map,
54            last_sequence: None,
55        }
56    }
57
58    /// The timeout of each Ping, in seconds. (default: 2s)
59    pub fn timeout(&mut self, timeout: Duration) -> &mut Pinger {
60        self.timeout = timeout;
61        self
62    }
63
64    /// Send Ping request with sequence number.
65    pub async fn ping(
66        &mut self,
67        seq: PingSequence,
68        payload: &[u8],
69    ) -> Result<(IcmpPacket, Duration)> {
70        // Register to wait for a reply.
71        let reply_waiter = self.reply_map.new_waiter(self.host, self.ident, seq)?;
72
73        // Send actual packet
74        if let Err(e) = self.send_ping(seq, payload).await {
75            self.reply_map.remove(self.host, self.ident, seq);
76            return Err(e);
77        }
78
79        let send_time = Instant::now();
80        self.last_sequence = Some(seq);
81
82        // Wait for reply or timeout.
83        match timeout(self.timeout, reply_waiter).await {
84            Ok(Ok(reply)) => Ok((
85                reply.packet,
86                reply.timestamp.saturating_duration_since(send_time),
87            )),
88            Ok(Err(_err)) => Err(SurgeError::NetworkError),
89            Err(_) => {
90                self.reply_map.remove(self.host, self.ident, seq);
91                Err(SurgeError::Timeout { seq })
92            }
93        }
94    }
95
96    /// Send a ping packet (useful, when you don't need a reply).
97    pub async fn send_ping(&self, seq: PingSequence, payload: &[u8]) -> Result<()> {
98        // Create and send ping packet.
99        let mut packet = match self.host {
100            IpAddr::V4(_) => icmpv4::make_icmpv4_echo_packet(
101                self.ident.unwrap_or(PingIdentifier(0)),
102                seq,
103                self.socket.get_type(),
104                payload,
105            )?,
106            IpAddr::V6(_) => icmpv6::make_icmpv6_echo_packet(
107                self.ident.unwrap_or(PingIdentifier(0)),
108                seq,
109                payload,
110            )?,
111        };
112
113        self.socket
114            .send_to(&mut packet, &SocketAddr::new(self.host, 0))
115            .await?;
116
117        Ok(())
118    }
119}