use std::time::Duration;
#[cfg(unix)]
use std::time::Instant;
#[cfg(unix)]
use ::tokio::io::unix::AsyncFd;
#[cfg(unix)]
use socket2::Socket;
use crate::errors::Error;
#[cfg(unix)]
use crate::errors::io_error;
use crate::pinger::{Reply, Request};
use crate::socket::Config;
#[cfg(unix)]
use crate::socket::{FamilyState, check_payload, recv_from};
#[derive(Debug)]
pub struct Pinger {
config: Config,
#[cfg(unix)]
v4: Option<(AsyncFd<Socket>, FamilyState)>,
#[cfg(unix)]
v6: Option<(AsyncFd<Socket>, FamilyState)>,
#[cfg(not(unix))]
inner: Option<crate::Pinger>,
}
impl Default for Pinger {
fn default() -> Self {
Self::new()
}
}
impl Pinger {
pub fn new() -> Self {
Self::from_config(Config::default())
}
pub(crate) fn from_config(config: Config) -> Self {
Pinger {
config,
#[cfg(unix)]
v4: None,
#[cfg(unix)]
v6: None,
#[cfg(not(unix))]
inner: None,
}
}
pub(crate) fn ident(&self, v6: bool) -> Option<u16> {
#[cfg(unix)]
{
let slot = if v6 { &self.v6 } else { &self.v4 };
slot.as_ref().map(|(_, state)| state.ident)
}
#[cfg(not(unix))]
self.inner.as_ref()?.ident(v6)
}
#[cfg(unix)]
pub async fn ping(
&mut self,
request: impl Into<Request>,
timeout: Duration,
) -> Result<Reply, Error> {
let request = request.into();
check_payload(&request)?;
let v6 = request.target.is_ipv6();
let slot = if v6 { &mut self.v6 } else { &mut self.v4 };
let (socket, state) = match slot {
Some(family) => family,
None => {
let (socket, state) = self.config.open(v6)?;
socket.set_nonblocking(true)?;
slot.insert((AsyncFd::new(socket)?, state))
}
};
let outgoing = state.prepare(socket.get_ref(), &request)?;
let started_at = Instant::now();
let exchange = async {
loop {
let mut ready = socket.writable().await.map_err(io_error)?;
match ready
.try_io(|inner| inner.get_ref().send_to(&outgoing.packet, &outgoing.dest))
{
Ok(result) => {
result.map_err(io_error)?;
break;
}
Err(_would_block) => continue,
}
}
loop {
let mut ready = socket.readable().await.map_err(io_error)?;
let received = ready.try_io(|inner| recv_from(inner.get_ref(), &mut state.buffer));
let (n, source) = match received {
Ok(result) => result.map_err(io_error)?,
Err(_would_block) => continue,
};
let rtt = started_at.elapsed();
if let Some(reply) = state.match_reply(&outgoing, &state.buffer[..n], &source, rtt)
{
return Ok(reply);
}
}
};
::tokio::time::timeout(timeout, exchange)
.await
.map_err(|_| Error::Timeout)?
}
#[cfg(not(unix))]
pub async fn ping(
&mut self,
request: impl Into<Request>,
timeout: Duration,
) -> Result<Reply, Error> {
let request = request.into();
let mut inner = self
.inner
.take()
.unwrap_or_else(|| crate::Pinger::from_config(self.config.clone()));
let (inner, result) = ::tokio::task::spawn_blocking(move || {
let result = inner.ping(request, timeout);
(inner, result)
})
.await
.map_err(|_| Error::InternalError)?;
self.inner = Some(inner);
result
}
}
pub async fn ping(request: impl Into<Request>, timeout: Duration) -> Result<Reply, Error> {
Pinger::new().ping(request, timeout).await
}