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