reachability_core/
ping.rsuse anyhow::{anyhow, Context as _};
use async_trait::async_trait;
use fuchsia_async::{self as fasync, TimeoutExt as _};
use futures::{FutureExt as _, SinkExt as _, TryFutureExt as _, TryStreamExt as _};
use net_types::ip::{Ipv4, Ipv6};
use std::net::SocketAddr;
use tracing::warn;
const PING_MESSAGE: &str = "Hello from reachability monitor!";
const SEQ_MIN: u16 = 1;
const SEQ_MAX: u16 = 3;
const TIMEOUT: fasync::MonotonicDuration = fasync::MonotonicDuration::from_seconds(1);
async fn ping<I>(interface_name: &str, addr: I::SockAddr) -> anyhow::Result<()>
where
I: ping::FuchsiaIpExt,
I::SockAddr: std::fmt::Display + Copy,
{
let socket = ping::new_icmp_socket::<I>().context("failed to create socket")?;
let () = socket
.bind_device(Some(interface_name.as_bytes()))
.with_context(|| format!("failed to bind socket to device {}", interface_name))?;
let (mut sink, mut stream) = ping::new_unicast_sink_and_stream::<
I,
_,
{ PING_MESSAGE.len() + ping::ICMP_HEADER_LEN },
>(&socket, &addr, PING_MESSAGE.as_bytes());
for seq in SEQ_MIN..=SEQ_MAX {
let deadline = fasync::MonotonicInstant::after(TIMEOUT);
let () = sink
.send(seq)
.map_err(anyhow::Error::new)
.on_timeout(deadline, || Err(anyhow!("timed out")))
.await
.with_context(|| format!("failed to send ping (seq={})", seq))?;
if match stream.try_next().map(Some).on_timeout(deadline, || None).await {
None => Ok(false),
Some(Err(e)) => Err(anyhow!("failed to receive ping: {}", e)),
Some(Ok(None)) => Err(anyhow!("ping reply stream ended unexpectedly")),
Some(Ok(Some(got))) if got >= SEQ_MIN && got <= seq => Ok(true),
Some(Ok(Some(got))) => Err(anyhow!(
"received unexpected ping sequence number; got: {}, want: {}..={}",
got,
SEQ_MIN,
seq,
)),
}? {
return Ok(());
}
}
Err(anyhow!("no ping reply received"))
}
#[async_trait]
pub trait Ping {
async fn ping(&self, interface_name: &str, addr: SocketAddr) -> bool;
}
pub struct Pinger;
#[async_trait]
impl Ping for Pinger {
async fn ping(&self, interface_name: &str, addr: SocketAddr) -> bool {
let r = match addr {
SocketAddr::V4(addr_v4) => ping::<Ipv4>(interface_name, addr_v4).await,
SocketAddr::V6(addr_v6) => ping::<Ipv6>(interface_name, addr_v6).await,
};
match r {
Ok(()) => true,
Err(e) => {
warn!("error while pinging {}: {:?}", addr, e);
false
}
}
}
}