reachability_core/
ping.rs1use async_trait::async_trait;
6use fuchsia_async::{self as fasync, TimeoutExt as _};
7use futures::{FutureExt as _, SinkExt as _, TryFutureExt as _, TryStreamExt as _};
8use net_types::ip::{Ipv4, Ipv6};
9use std::net::SocketAddr;
10
11const PING_MESSAGE: &str = "Hello from reachability monitor!";
12const SEQ_MIN: u16 = 1;
13const SEQ_MAX: u16 = 3;
14const TIMEOUT: fasync::MonotonicDuration = fasync::MonotonicDuration::from_seconds(1);
15
16#[derive(Debug, thiserror::Error)]
17pub enum PingError {
18 #[error("failed to create socket")]
19 CreateSocket(#[source] std::io::Error),
20 #[error("failed to bind socket to device {interface_name}")]
21 BindSocket {
22 interface_name: String,
23 #[source]
24 err: std::io::Error,
25 },
26 #[error("failed to send ping (seq={seq})")]
27 SendPing {
28 seq: u16,
29 #[source]
30 err: ping::PingError,
31 },
32 #[error("timed out sending ping (seq={seq})")]
33 SendPingTimeout { seq: u16 },
34 #[error("failed to receive ping")]
35 ReceivePing(#[source] ping::PingError),
36 #[error("ping reply stream ended unexpectedly")]
37 StreamEndedUnexpectedly,
38 #[error("received unexpected ping sequence number; got: {got}, want: {min}..={max}")]
39 UnexpectedSequenceNumber { got: u16, min: u16, max: u16 },
40 #[error("no ping reply received")]
41 NoReply,
42}
43
44impl PingError {
45 pub fn short_name(&self) -> String {
50 let (name, os_err) = match self {
51 Self::CreateSocket(err) => ("CreateSock", err.raw_os_error()),
52 Self::BindSocket { err, .. } => ("BindSock", err.raw_os_error()),
53 Self::SendPing { err, .. } => {
54 let os_err = match err {
55 ping::PingError::Send(io) => io.raw_os_error(),
56 ping::PingError::Recv(io) => io.raw_os_error(),
57 _ => None,
58 };
59 ("SendPing", os_err)
60 }
61 Self::SendPingTimeout { .. } => return "SendTimeout".to_string(),
62 Self::ReceivePing(err) => {
63 let os_err = match err {
64 ping::PingError::Send(io) => io.raw_os_error(),
65 ping::PingError::Recv(io) => io.raw_os_error(),
66 _ => None,
67 };
68 ("RecvPing", os_err)
69 }
70 Self::StreamEndedUnexpectedly => return "StreamEnded".to_string(),
71 Self::UnexpectedSequenceNumber { .. } => return "BadSeqNum".to_string(),
72 Self::NoReply => return "NoReply".to_string(),
73 };
74
75 if let Some(code) = os_err { format!("{name}_{code}") } else { name.to_string() }
76 }
77}
78
79pub(crate) fn ping_result_short_name(result: &Result<(), PingError>) -> String {
80 match result {
81 Ok(()) => "Success".to_string(),
82 Err(e) => format!("e_{}", e.short_name()),
83 }
84}
85
86async fn ping<I>(interface_name: &str, addr: I::SockAddr) -> Result<(), PingError>
87where
88 I: ping::FuchsiaIpExt,
89 I::SockAddr: std::fmt::Display + Copy,
90{
91 let socket = ping::new_icmp_socket::<I>().map_err(PingError::CreateSocket)?;
92 let () = socket
93 .bind_device(Some(interface_name.as_bytes()))
94 .map_err(|err| PingError::BindSocket { interface_name: interface_name.to_string(), err })?;
95 let (mut sink, mut stream) = ping::new_unicast_sink_and_stream::<
96 I,
97 _,
98 { PING_MESSAGE.len() + ping::ICMP_HEADER_LEN },
99 >(&socket, &addr, PING_MESSAGE.as_bytes());
100
101 for seq in SEQ_MIN..=SEQ_MAX {
102 let deadline = fasync::MonotonicInstant::after(TIMEOUT);
103 let () = sink
104 .send(seq)
105 .map_err(|err| PingError::SendPing { seq, err })
106 .on_timeout(deadline, || Err(PingError::SendPingTimeout { seq }))
107 .await?;
108 if match stream.try_next().map(Some).on_timeout(deadline, || None).await {
109 None => Ok(false),
110 Some(Err(err)) => Err(PingError::ReceivePing(err)),
111 Some(Ok(None)) => Err(PingError::StreamEndedUnexpectedly),
112 Some(Ok(Some(got))) if got >= SEQ_MIN && got <= seq => Ok(true),
113 Some(Ok(Some(got))) => {
114 Err(PingError::UnexpectedSequenceNumber { got, min: SEQ_MIN, max: seq })
115 }
116 }? {
117 return Ok(());
118 }
119 }
120 Err(PingError::NoReply)
121}
122
123#[async_trait]
125pub trait Ping {
126 async fn ping(&self, interface_name: &str, addr: SocketAddr) -> Result<(), PingError>;
128}
129
130pub struct Pinger;
131
132#[async_trait]
133impl Ping for Pinger {
134 async fn ping(&self, interface_name: &str, addr: SocketAddr) -> Result<(), PingError> {
135 match addr {
136 SocketAddr::V4(addr_v4) => ping::<Ipv4>(interface_name, addr_v4).await,
137 SocketAddr::V6(addr_v6) => ping::<Ipv6>(interface_name, addr_v6).await,
138 }
139 }
140}