netstack_testing_common/ping.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Ping utilities.
use super::Result;
use std::convert::TryFrom as _;
use anyhow::Context as _;
use futures::{FutureExt as _, StreamExt as _};
use itertools::Itertools as _;
use net_types::ip::{Ipv4, Ipv6};
/// A realm and associated data as a helper for issuing pings in tests.
pub struct Node<'a> {
/// The test realm of this node.
realm: &'a netemul::TestRealm<'a>,
/// Local interface ID (used as scope ID when the destination IPv6 address
/// is link-local).
local_interface_id: u64,
/// Local IPv4 addresses.
v4_addrs: Vec<net_types::ip::Ipv4Addr>,
/// Local IPv6 addresses.
v6_addrs: Vec<net_types::ip::Ipv6Addr>,
}
impl<'a> Node<'a> {
/// Constructs a new [`Node`].
pub fn new(
realm: &'a netemul::TestRealm<'_>,
local_interface_id: u64,
v4_addrs: Vec<net_types::ip::Ipv4Addr>,
v6_addrs: Vec<net_types::ip::Ipv6Addr>,
) -> Self {
Self { realm, local_interface_id, v4_addrs, v6_addrs }
}
/// Returns the local interface ID.
pub fn id(&self) -> u64 {
self.local_interface_id
}
/// Create a new [`Node`], waiting for addresses to satisfy the provided
/// predicate.
///
/// Addresses changes on `interface` will be observed via a watcher, and
/// `addr_predicate` will be called with the addresses until the returned
/// value is `Some`, and the vector of addresses will be used as the local
/// addresses for this `Node`.
pub async fn new_with_wait_addr<
F: FnMut(
&[fidl_fuchsia_net_interfaces_ext::Address<
fidl_fuchsia_net_interfaces_ext::DefaultInterest,
>],
) -> Option<(Vec<net_types::ip::Ipv4Addr>, Vec<net_types::ip::Ipv6Addr>)>,
>(
realm: &'a netemul::TestRealm<'_>,
interface: &'a netemul::TestInterface<'_>,
mut addr_predicate: F,
) -> Result<Node<'a>> {
let mut state =
fidl_fuchsia_net_interfaces_ext::InterfaceState::<(), _>::Unknown(interface.id());
let (v4_addrs, v6_addrs) = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
realm.get_interface_event_stream()?,
&mut state,
|properties_and_state| addr_predicate(&properties_and_state.properties.addresses),
)
.await
.context("failed to wait for addresses")?;
Ok(Self::new(realm, interface.id(), v4_addrs, v6_addrs))
}
/// Create a new [`Node`] with one IPv4 address and one IPv6 link-local
/// address.
///
/// Note that if there are multiple addresses of either kind assigned to
/// the interface, the specific address chosen is potentially
/// non-deterministic. Callers that care about specific addresses being
/// included rather than any IPv4 address and any IPv6 link-local address
/// should prefer [`Self::new_with_wait_addr`] instead.
pub async fn new_with_v4_and_v6_link_local(
realm: &'a netemul::TestRealm<'_>,
interface: &'a netemul::TestInterface<'_>,
) -> Result<Node<'a>> {
Self::new_with_wait_addr(realm, interface, |addresses| {
let (v4, v6) = addresses.into_iter().fold(
(None, None),
|(v4, v6),
&fidl_fuchsia_net_interfaces_ext::Address {
addr: fidl_fuchsia_net::Subnet { addr, prefix_len: _ },
valid_until: _,
preferred_lifetime_info: _,
assignment_state,
}| {
assert_eq!(
assignment_state,
fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned
);
match addr {
fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
addr,
}) => (Some(net_types::ip::Ipv4Addr::from(addr)), v6),
fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address {
addr,
}) => {
let v6_candidate = net_types::ip::Ipv6Addr::from_bytes(addr);
if v6_candidate.is_unicast_link_local() {
(v4, Some(v6_candidate))
} else {
(v4, v6)
}
}
}
},
);
match (v4, v6) {
(Some(v4), Some(v6)) => Some((vec![v4], vec![v6])),
_ => None,
}
})
.await
}
/// Returns `Ok(())` iff every possible pair in the union of `self` and
/// `pingable` is able to ping each other.
pub async fn ping_pairwise(
&self,
pingable: &[Node<'_>],
) -> anyhow::Result<(), Vec<anyhow::Error>> {
// NB: The interface ID of the sender is used as the scope_id for IPv6.
let futs = pingable
.iter()
.chain(std::iter::once(self))
.tuple_combinations()
.map(
|(
Node {
realm,
local_interface_id: src_id,
v4_addrs: src_v4_addrs,
v6_addrs: src_v6_addrs,
},
Node {
realm: _,
local_interface_id: _,
v4_addrs: dst_v4_addrs,
v6_addrs: dst_v6_addrs,
},
)| {
const UNSPECIFIED_PORT: u16 = 0;
const PING_SEQ: u16 = 1;
let v4_futs = (!src_v4_addrs.is_empty()).then(|| {
dst_v4_addrs.iter().map(move |&addr| {
let dst_sockaddr = std::net::SocketAddrV4::new(
std::net::Ipv4Addr::from(addr.ipv4_bytes()),
UNSPECIFIED_PORT,
);
realm
.ping_once::<Ipv4>(dst_sockaddr, PING_SEQ)
.map(move |r| {
r.with_context(|| {
format!("failed to ping {} from {:?}", dst_sockaddr, realm)
})
})
.left_future()
})
});
let v6_futs = (!src_v6_addrs.is_empty()).then(|| {
dst_v6_addrs.iter().map(move |&addr| {
let dst_sockaddr = std::net::SocketAddrV6::new(
std::net::Ipv6Addr::from(addr.ipv6_bytes()),
UNSPECIFIED_PORT,
0,
if addr.is_unicast_link_local() {
u32::try_from(*src_id)
.expect("interface ID does not fit into u32")
} else {
0
},
);
realm
.ping_once::<Ipv6>(dst_sockaddr, PING_SEQ)
.map(move |r| {
r.with_context(|| {
format!("failed to ping {} from {:?}", dst_sockaddr, realm)
})
})
.right_future()
})
});
v4_futs.into_iter().flatten().chain(v6_futs.into_iter().flatten())
},
)
.flatten()
.collect::<futures::stream::FuturesUnordered<_>>();
let errors = futs
.filter_map(|r| {
futures::future::ready(match r {
Ok(()) => None,
Err(e) => Some(e),
})
})
.collect::<Vec<_>>()
.await;
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}