1use std::{
2 collections::HashMap,
3 mem::MaybeUninit,
4 net::{IpAddr, SocketAddr},
5 sync::Arc,
6 time::{Duration, Instant},
7};
8
9use parking_lot::Mutex;
10use tokio::task;
11use tokio::time::timeout;
12
13use crate::error::{Error, Result};
14use crate::icmp::{EchoReply, EchoRequest};
15use crate::unix::AsyncSocket;
16
17type Token = (u16, u16);
18
19#[derive(Debug, Clone)]
20struct Cache {
21 inner: Arc<Mutex<HashMap<Token, Instant>>>,
22}
23
24impl Cache {
25 fn new() -> Cache {
26 Cache {
27 inner: Arc::new(Mutex::new(HashMap::new())),
28 }
29 }
30
31 fn insert(&self, ident: u16, seq_cnt: u16, time: Instant) {
32 self.inner.lock().insert((ident, seq_cnt), time);
33 }
34
35 fn remove(&self, ident: u16, seq_cnt: u16) -> Option<Instant> {
36 self.inner.lock().remove(&(ident, seq_cnt))
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct Pinger {
43 host: IpAddr,
44 ident: u16,
45 size: usize,
46 timeout: Duration,
47 socket: AsyncSocket,
48 cache: Cache,
49}
50
51impl Pinger {
52 pub fn new(host: IpAddr) -> Result<Pinger> {
54 Ok(Pinger {
55 host,
56 ident: 1,
57 size: 56,
58 timeout: Duration::from_secs(2),
59 socket: AsyncSocket::new(host)?,
60 cache: Cache::new(),
61 })
62 }
63
64 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
74 pub fn bind_device(&mut self, interface: Option<&[u8]>) -> Result<&mut Pinger> {
75 self.socket.bind_device(interface)?;
76 Ok(self)
77 }
78
79 pub fn ident(&mut self, val: u16) -> &mut Pinger {
81 self.ident = val;
82 self
83 }
84
85 pub fn size(&mut self, size: usize) -> &mut Pinger {
87 self.size = size;
88 self
89 }
90
91 pub fn timeout(&mut self, timeout: Duration) -> &mut Pinger {
93 self.timeout = timeout;
94 self
95 }
96
97 async fn recv_reply(&self, seq_cnt: u16) -> Result<(EchoReply, Duration)> {
98 let mut buffer = [MaybeUninit::new(0); 2048];
99 loop {
100 let size = self.socket.recv(&mut buffer).await?;
101 let buf = unsafe { assume_init(&buffer[..size]) };
102 match EchoReply::decode(self.host, buf) {
103 Ok(reply) => {
104 if reply.identifier == self.ident && reply.sequence == seq_cnt {
106 if let Some(ins) = self.cache.remove(self.ident, seq_cnt) {
107 return Ok((reply, Instant::now() - ins));
108 }
109 }
110 continue;
111 }
112 Err(Error::NotEchoReply) => continue,
113 Err(Error::NotV6EchoReply) => continue,
114 Err(Error::OtherICMP) => continue,
115 Err(e) => {
116 return Err(e);
117 }
118 }
119 }
120 }
121
122 pub async fn ping(&self, seq_cnt: u16) -> Result<(EchoReply, Duration)> {
124 let sender = self.socket.clone();
125 let mut packet = EchoRequest::new(self.host, self.ident, seq_cnt, self.size).encode()?;
126 let sock_addr = SocketAddr::new(self.host, 0);
127 let ident = self.ident;
128 let cache = self.cache.clone();
129 task::spawn(async move {
130 let _size = sender
131 .send_to(&mut packet, &sock_addr.into())
132 .await
133 .expect("socket send packet error");
134 cache.insert(ident, seq_cnt, Instant::now());
135 });
136
137 match timeout(self.timeout, self.recv_reply(seq_cnt))
138 .await
139 .map_err(|err| {
140 self.cache.remove(ident, seq_cnt);
141 err
142 }) {
143 Ok(rez) => rez,
144 Err(_) => Err(Error::Timeout),
145 }
146 }
147}
148
149unsafe fn assume_init(buf: &[MaybeUninit<u8>]) -> &[u8] {
152 &*(buf as *const [MaybeUninit<u8>] as *const [u8])
153}