reachability_core/
ping.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{anyhow, Context as _};
6use async_trait::async_trait;
7use fuchsia_async::{self as fasync, TimeoutExt as _};
8use futures::{FutureExt as _, SinkExt as _, TryFutureExt as _, TryStreamExt as _};
9use log::warn;
10use net_types::ip::{Ipv4, Ipv6};
11use std::net::SocketAddr;
12
13const PING_MESSAGE: &str = "Hello from reachability monitor!";
14const SEQ_MIN: u16 = 1;
15const SEQ_MAX: u16 = 3;
16const TIMEOUT: fasync::MonotonicDuration = fasync::MonotonicDuration::from_seconds(1);
17
18async fn ping<I>(interface_name: &str, addr: I::SockAddr) -> anyhow::Result<()>
19where
20    I: ping::FuchsiaIpExt,
21    I::SockAddr: std::fmt::Display + Copy,
22{
23    let socket = ping::new_icmp_socket::<I>().context("failed to create socket")?;
24    let () = socket
25        .bind_device(Some(interface_name.as_bytes()))
26        .with_context(|| format!("failed to bind socket to device {}", interface_name))?;
27    let (mut sink, mut stream) = ping::new_unicast_sink_and_stream::<
28        I,
29        _,
30        { PING_MESSAGE.len() + ping::ICMP_HEADER_LEN },
31    >(&socket, &addr, PING_MESSAGE.as_bytes());
32
33    for seq in SEQ_MIN..=SEQ_MAX {
34        let deadline = fasync::MonotonicInstant::after(TIMEOUT);
35        let () = sink
36            .send(seq)
37            .map_err(anyhow::Error::new)
38            .on_timeout(deadline, || Err(anyhow!("timed out")))
39            .await
40            .with_context(|| format!("failed to send ping (seq={})", seq))?;
41        if match stream.try_next().map(Some).on_timeout(deadline, || None).await {
42            None => Ok(false),
43            Some(Err(e)) => Err(anyhow!("failed to receive ping: {}", e)),
44            Some(Ok(None)) => Err(anyhow!("ping reply stream ended unexpectedly")),
45            Some(Ok(Some(got))) if got >= SEQ_MIN && got <= seq => Ok(true),
46            Some(Ok(Some(got))) => Err(anyhow!(
47                "received unexpected ping sequence number; got: {}, want: {}..={}",
48                got,
49                SEQ_MIN,
50                seq,
51            )),
52        }? {
53            return Ok(());
54        }
55    }
56    Err(anyhow!("no ping reply received"))
57}
58
59/// Trait that can send ICMP echo requests, and receive and validate replies.
60#[async_trait]
61pub trait Ping {
62    /// Returns true if the address is reachable, false otherwise.
63    async fn ping(&self, interface_name: &str, addr: SocketAddr) -> bool;
64}
65
66pub struct Pinger;
67
68#[async_trait]
69impl Ping for Pinger {
70    async fn ping(&self, interface_name: &str, addr: SocketAddr) -> bool {
71        let r = match addr {
72            SocketAddr::V4(addr_v4) => ping::<Ipv4>(interface_name, addr_v4).await,
73            SocketAddr::V6(addr_v6) => ping::<Ipv6>(interface_name, addr_v6).await,
74        };
75        match r {
76            Ok(()) => true,
77            Err(e) => {
78                warn!("error while pinging {}: {:?}", addr, e);
79                false
80            }
81        }
82    }
83}