use async_icmp::{
message::echo::{EchoId, EchoSeq},
ping::PingMultiplexer,
socket::SocketConfig,
};
use log::{debug, warn};
use rand::seq::SliceRandom;
use std::{collections, net, time};
#[cfg(target_os = "linux")]
macro_rules! platform_param {
($linux_val:expr, $macos_val:expr) => {
$linux_val
};
}
#[cfg(target_os = "macos")]
macro_rules! platform_param {
($linux_val:expr, $macos_val:expr) => {
$macos_val
};
}
#[tokio::test]
async fn single_session_ipv4_ping() -> anyhow::Result<()> {
multi_ping_test(&[(net::Ipv4Addr::LOCALHOST.into(), 1000)]).await
}
#[tokio::test]
async fn single_session_ipv6_ping() -> anyhow::Result<()> {
multi_ping_test(&[(net::Ipv6Addr::LOCALHOST.into(), platform_param!(1000, 100))]).await
}
#[tokio::test]
async fn multi_session_ipv4_ping() -> anyhow::Result<()> {
multi_ping_test(
&[(net::Ipv4Addr::LOCALHOST.into(), platform_param!(50, 5)); platform_param!(50, 5)],
)
.await
}
#[tokio::test]
async fn multi_session_ipv6_ping() -> anyhow::Result<()> {
multi_ping_test(
&[(net::Ipv6Addr::LOCALHOST.into(), platform_param!(50, 5)); platform_param!(50, 5)],
)
.await
}
#[tokio::test]
async fn multi_session_multi_protocol_ping() -> anyhow::Result<()> {
multi_ping_test(
&[
[(net::Ipv4Addr::LOCALHOST.into(), platform_param!(25, 3)); platform_param!(50, 5)]
.as_slice(),
[(net::Ipv6Addr::LOCALHOST.into(), platform_param!(25, 3)); platform_param!(50, 5)]
.as_slice(),
]
.concat(),
)
.await
}
fn local_port_or_random_id(multiplexer: &PingMultiplexer, ip: net::IpAddr) -> EchoId {
multiplexer
.platform_echo_id(ip.into())
.unwrap_or_else(rand::random)
}
async fn multi_ping_test(targets: &[(net::IpAddr, u16)]) -> anyhow::Result<()> {
let _ = env_logger::builder()
.is_test(true)
.format_timestamp_millis()
.try_init();
let multiplexer = PingMultiplexer::new(SocketConfig::default(), SocketConfig::default())?;
let mut rng = rand::thread_rng();
let (rate_limiter_tx, rate_limiter_rx) = flume::bounded(1);
let rate_limiter_handle = tokio::spawn({
async move {
loop {
if rate_limiter_tx.send_async(()).await.is_err() {
debug!("Rate limiter stopping");
break;
}
tokio::time::sleep(time::Duration::from_millis(1)).await;
}
}
});
let mut send_sessions = vec![];
let mut recv_tasks = tokio::task::JoinSet::new();
for (ip, num_pings) in targets.iter().cloned() {
let id = local_port_or_random_id(&multiplexer, ip);
let data = rand::random::<[u8; 32]>().to_vec();
let (handle, mut rx) = multiplexer.add_session(ip, id, data.clone()).await?;
let hash_key = (id, data);
let recv_hash_key = hash_key.clone();
recv_tasks.spawn(async move {
let mut recv_timestamps = vec![];
while recv_timestamps.len() < num_pings.into() {
if let Some(recv_ts) = rx.recv().await {
recv_timestamps.push(recv_ts);
debug!("len now {}", recv_timestamps.len());
} else {
warn!("Recv session closed?");
break;
}
}
(recv_hash_key, recv_timestamps)
});
send_sessions.push((handle, num_pings, hash_key));
}
send_sessions.shuffle(&mut rng);
let send_handles = send_sessions.iter().cloned().fold(
tokio::task::JoinSet::<Result<_, anyhow::Error>>::new(),
|mut set, (handle, num_pings, hash_key)| {
let multiplexer = multiplexer.clone();
let rate_limiter_clone = rate_limiter_rx.clone();
set.spawn(async move {
let mut sent_timestamps = collections::HashMap::new();
for seq in (0..num_pings).map(EchoSeq::from_be) {
rate_limiter_clone.recv_async().await?;
let sent_at = multiplexer.send_ping(handle, seq).await?;
sent_timestamps.insert(seq, sent_at);
}
Ok((hash_key, sent_timestamps))
});
set
},
);
drop(rate_limiter_rx);
let send_tasks_data = send_handles
.join_all()
.await
.into_iter()
.collect::<Result<collections::HashMap<_, _>, _>>()?;
let mut recv_tasks_data =
tokio::time::timeout(time::Duration::from_secs(5), recv_tasks.join_all())
.await?
.into_iter()
.collect::<collections::HashMap<_, _>>();
for (handle, _, _) in send_sessions {
multiplexer.close_session(handle).await?;
}
for (key, sent_at_by_seq) in send_tasks_data {
let recv_ts_vec = recv_tasks_data.remove(&key).unwrap();
assert_eq!(sent_at_by_seq.len(), recv_ts_vec.len());
let recv_at_by_seq = recv_ts_vec
.into_iter()
.map(|ts| (ts.seq, ts.received_at))
.collect::<collections::HashMap<_, _>>();
assert_eq!(sent_at_by_seq.len(), recv_at_by_seq.len());
for (seq, sent_at) in sent_at_by_seq.into_iter() {
let recv_at = recv_at_by_seq.get(&seq).unwrap();
assert!(*recv_at >= sent_at);
}
}
multiplexer.shutdown().await;
rate_limiter_handle.await?;
Ok(())
}