use std::net::IpAddr;
use std::time::{Duration, Instant};
use socket2::Socket;
use crate::errors::{Error, io_error};
use crate::socket::{Config, FamilyState, MIN_SOCKET_TIMEOUT, check_payload, recv_from};
const DEFAULT_PAYLOAD_SIZE: usize = 48;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SocketType {
RAW,
DGRAM,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Request {
pub target: IpAddr,
pub ttl: Option<u8>,
pub seq: Option<u16>,
pub payload: Vec<u8>,
}
impl Request {
pub fn new(target: IpAddr) -> Self {
Request {
target,
ttl: None,
seq: None,
payload: vec![0; DEFAULT_PAYLOAD_SIZE],
}
}
pub fn ttl(mut self, ttl: u8) -> Self {
self.ttl = Some(ttl);
self
}
pub fn seq(mut self, seq: u16) -> Self {
self.seq = Some(seq);
self
}
pub fn payload(mut self, payload: Vec<u8>) -> Self {
self.payload = payload;
self
}
}
impl From<IpAddr> for Request {
fn from(target: IpAddr) -> Self {
Request::new(target)
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Reply {
pub source: IpAddr,
pub seq: u16,
pub rtt: Duration,
pub ttl: Option<u8>,
pub payload: Vec<u8>,
}
#[derive(Clone, Debug, Default)]
pub struct PingerBuilder {
pub(crate) config: Config,
}
impl PingerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn socket_type(mut self, socket_type: SocketType) -> Self {
self.config.socket_type = Some(socket_type);
self
}
pub fn ident(mut self, ident: u16) -> Self {
self.config.ident = Some(ident);
self
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn bind_device(mut self, device: impl Into<String>) -> Self {
self.config.bind_device = Some(device.into());
self
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mark(mut self, mark: u32) -> Self {
self.config.mark = Some(mark);
self
}
pub fn build(self) -> Result<Pinger, Error> {
Ok(Pinger::from_config(self.config))
}
#[cfg(feature = "tokio")]
pub fn build_tokio(self) -> Result<crate::tokio::Pinger, Error> {
Ok(crate::tokio::Pinger::from_config(self.config))
}
}
#[derive(Debug)]
pub struct Pinger {
config: Config,
v4: Option<(Socket, FamilyState)>,
v6: Option<(Socket, FamilyState)>,
}
impl Default for Pinger {
fn default() -> Self {
Self::new()
}
}
impl Pinger {
pub fn new() -> Self {
Self::from_config(Config::default())
}
pub fn builder() -> PingerBuilder {
PingerBuilder::new()
}
pub(crate) fn from_config(config: Config) -> Self {
Pinger {
config,
v4: None,
v6: None,
}
}
pub(crate) fn ident(&self, v6: bool) -> Option<u16> {
let slot = if v6 { &self.v6 } else { &self.v4 };
slot.as_ref().map(|(_, state)| state.ident)
}
pub 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 => slot.insert(self.config.open(v6)?),
};
let outgoing = state.prepare(socket, &request)?;
socket
.set_write_timeout(Some(timeout.max(MIN_SOCKET_TIMEOUT)))
.map_err(io_error)?;
let started_at = Instant::now();
socket
.send_to(&outgoing.packet, &outgoing.dest)
.map_err(io_error)?;
loop {
let remaining = timeout
.checked_sub(started_at.elapsed())
.filter(|remaining| !remaining.is_zero())
.ok_or(Error::Timeout)?;
socket
.set_read_timeout(Some(remaining.max(MIN_SOCKET_TIMEOUT)))
.map_err(io_error)?;
let (n, source) = match recv_from(socket, &mut state.buffer) {
Ok(received) => received,
#[cfg(windows)]
Err(error) if error.kind() == std::io::ErrorKind::ConnectionReset => continue,
Err(error) => return Err(io_error(error)),
};
let rtt = started_at.elapsed();
if let Some(reply) = state.match_reply(&outgoing, &state.buffer[..n], &source, rtt) {
return Ok(reply);
}
}
}
}
pub fn ping(request: impl Into<Request>, timeout: Duration) -> Result<Reply, Error> {
Pinger::new().ping(request, timeout)
}