use async_icmp::{
message::{
decode::DecodedIcmpMsg,
echo::{parse_echo_reply, EchoSeq, IcmpEchoRequest},
IcmpV4MsgType, IcmpV6MsgType,
},
socket::{SocketConfig, SocketPair},
IpVersion,
};
use clap::Parser as _;
use log::{info, warn};
use owo_colors::OwoColorize;
use std::{collections, net, sync::Arc, time};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("INFO"))
.format_timestamp_millis()
.init();
let cli = Cli::parse();
let count = 1000_u16;
info!("Count = {count}");
for pause_time in [
time::Duration::from_millis(10),
time::Duration::from_millis(5),
time::Duration::from_millis(2),
time::Duration::from_millis(1),
time::Duration::from_micros(500),
time::Duration::from_micros(200),
time::Duration::from_micros(100),
time::Duration::from_micros(50),
time::Duration::from_micros(20),
time::Duration::from_micros(10),
time::Duration::from_micros(1),
] {
info!("Pause time: {}", humantime::Duration::from(pause_time));
let socket_pair = Arc::new(SocketPair::new(
SocketConfig::default(),
SocketConfig::default(),
)?);
let ip_version = cli.dest.into();
let reply_msg_type = match ip_version {
IpVersion::V4 => IcmpV4MsgType::EchoReply as u8,
IpVersion::V6 => IcmpV6MsgType::EchoReply as u8,
};
let orig_id = socket_pair
.platform_echo_id(ip_version)
.unwrap_or_else(rand::random);
let orig_data = rand::random::<[u8; 32]>().to_vec();
let mut req = IcmpEchoRequest::from_fields(orig_id, EchoSeq::from_be(0), &orig_data);
let rx_socket = socket_pair.clone();
let receiver = tokio::spawn(async move {
let mut received = collections::HashSet::new();
let mut buf = vec![0; 1_000];
while received.len() < count.into() {
match tokio::time::timeout(
time::Duration::from_secs(1),
rx_socket.recv_either(ip_version, &mut buf),
)
.await
{
Ok(res) => match res {
Ok((msg, _range)) => DecodedIcmpMsg::decode(msg)
.ok()
.iter()
.filter(|decoded| {
decoded.msg_type() == reply_msg_type && decoded.msg_code() == 0
})
.filter_map(|decoded| parse_echo_reply(decoded.body()))
.filter(|(id, _seq, data)| id == &orig_id && data == &orig_data)
.for_each(|(_id, seq, _data)| {
received.insert(seq);
}),
Err(e) => {
warn!("Read error: {e}");
return received;
}
},
Err(_) => {
warn!("Read timeout");
return received;
}
};
}
received
});
for seq in (0..count).map(EchoSeq::from_be) {
req.set_seq(seq);
socket_pair.send_to_either(&mut req, cli.dest).await?;
tokio::time::sleep(pause_time).await;
}
let received_seqs = receiver.await?;
let loss = usize::from(count) - received_seqs.len();
info!(
"Loss: {}",
if loss > 0 {
format!(
"{} ({:.2})%",
loss,
(1.0 - received_seqs.len() as f64 / (count as f64)) * 100.0
)
.yellow()
.to_string()
} else {
"0".green().to_string()
}
);
}
Ok(())
}
#[derive(clap::Parser)]
struct Cli {
dest: net::IpAddr,
}