netstack_testing_common/
ping.rs1use super::Result;
8
9use std::convert::TryFrom as _;
10
11use anyhow::Context as _;
12use futures::{FutureExt as _, StreamExt as _};
13use itertools::Itertools as _;
14use net_types::ip::{Ipv4, Ipv6};
15
16pub struct Node<'a> {
18 realm: &'a netemul::TestRealm<'a>,
20 local_interface_id: u64,
23 v4_addrs: Vec<net_types::ip::Ipv4Addr>,
25 v6_addrs: Vec<net_types::ip::Ipv6Addr>,
27}
28
29impl<'a> Node<'a> {
30 pub fn new(
32 realm: &'a netemul::TestRealm<'_>,
33 local_interface_id: u64,
34 v4_addrs: Vec<net_types::ip::Ipv4Addr>,
35 v6_addrs: Vec<net_types::ip::Ipv6Addr>,
36 ) -> Self {
37 Self { realm, local_interface_id, v4_addrs, v6_addrs }
38 }
39
40 pub fn id(&self) -> u64 {
42 self.local_interface_id
43 }
44
45 pub async fn new_with_wait_addr<
53 F: FnMut(
54 &[fidl_fuchsia_net_interfaces_ext::Address<
55 fidl_fuchsia_net_interfaces_ext::DefaultInterest,
56 >],
57 ) -> Option<(Vec<net_types::ip::Ipv4Addr>, Vec<net_types::ip::Ipv6Addr>)>,
58 >(
59 realm: &'a netemul::TestRealm<'_>,
60 interface: &'a netemul::TestInterface<'_>,
61 mut addr_predicate: F,
62 ) -> Result<Node<'a>> {
63 let mut state =
64 fidl_fuchsia_net_interfaces_ext::InterfaceState::<(), _>::Unknown(interface.id());
65 let (v4_addrs, v6_addrs) = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
66 realm.get_interface_event_stream()?,
67 &mut state,
68 |properties_and_state| addr_predicate(&properties_and_state.properties.addresses),
69 )
70 .await
71 .context("failed to wait for addresses")?;
72 Ok(Self::new(realm, interface.id(), v4_addrs, v6_addrs))
73 }
74
75 pub async fn new_with_v4_and_v6_link_local(
84 realm: &'a netemul::TestRealm<'_>,
85 interface: &'a netemul::TestInterface<'_>,
86 ) -> Result<Node<'a>> {
87 Self::new_with_wait_addr(realm, interface, |addresses| {
88 let (v4, v6) = addresses.into_iter().fold(
89 (None, None),
90 |(v4, v6),
91 &fidl_fuchsia_net_interfaces_ext::Address {
92 addr: fidl_fuchsia_net::Subnet { addr, prefix_len: _ },
93 valid_until: _,
94 preferred_lifetime_info: _,
95 assignment_state,
96 }| {
97 assert_eq!(
98 assignment_state,
99 fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned
100 );
101 match addr {
102 fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
103 addr,
104 }) => (Some(net_types::ip::Ipv4Addr::from(addr)), v6),
105 fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address {
106 addr,
107 }) => {
108 let v6_candidate = net_types::ip::Ipv6Addr::from_bytes(addr);
109 if v6_candidate.is_unicast_link_local() {
110 (v4, Some(v6_candidate))
111 } else {
112 (v4, v6)
113 }
114 }
115 }
116 },
117 );
118 match (v4, v6) {
119 (Some(v4), Some(v6)) => Some((vec![v4], vec![v6])),
120 _ => None,
121 }
122 })
123 .await
124 }
125
126 pub async fn ping_pairwise(
129 &self,
130 pingable: &[Node<'_>],
131 ) -> anyhow::Result<(), Vec<anyhow::Error>> {
132 let futs = pingable
134 .iter()
135 .chain(std::iter::once(self))
136 .tuple_combinations()
137 .map(
138 |(
139 Node {
140 realm,
141 local_interface_id: src_id,
142 v4_addrs: src_v4_addrs,
143 v6_addrs: src_v6_addrs,
144 },
145 Node {
146 realm: _,
147 local_interface_id: _,
148 v4_addrs: dst_v4_addrs,
149 v6_addrs: dst_v6_addrs,
150 },
151 )| {
152 const UNSPECIFIED_PORT: u16 = 0;
153 const PING_SEQ: u16 = 1;
154 let v4_futs = (!src_v4_addrs.is_empty()).then(|| {
155 dst_v4_addrs.iter().map(move |&addr| {
156 let dst_sockaddr = std::net::SocketAddrV4::new(
157 std::net::Ipv4Addr::from(addr.ipv4_bytes()),
158 UNSPECIFIED_PORT,
159 );
160 realm
161 .ping_once::<Ipv4>(dst_sockaddr, PING_SEQ)
162 .map(move |r| {
163 r.with_context(|| {
164 format!("failed to ping {} from {:?}", dst_sockaddr, realm)
165 })
166 })
167 .left_future()
168 })
169 });
170 let v6_futs = (!src_v6_addrs.is_empty()).then(|| {
171 dst_v6_addrs.iter().map(move |&addr| {
172 let dst_sockaddr = std::net::SocketAddrV6::new(
173 std::net::Ipv6Addr::from(addr.ipv6_bytes()),
174 UNSPECIFIED_PORT,
175 0,
176 if addr.is_unicast_link_local() {
177 u32::try_from(*src_id)
178 .expect("interface ID does not fit into u32")
179 } else {
180 0
181 },
182 );
183 realm
184 .ping_once::<Ipv6>(dst_sockaddr, PING_SEQ)
185 .map(move |r| {
186 r.with_context(|| {
187 format!("failed to ping {} from {:?}", dst_sockaddr, realm)
188 })
189 })
190 .right_future()
191 })
192 });
193 v4_futs.into_iter().flatten().chain(v6_futs.into_iter().flatten())
194 },
195 )
196 .flatten()
197 .collect::<futures::stream::FuturesUnordered<_>>();
198
199 let errors = futs
200 .filter_map(|r| {
201 futures::future::ready(match r {
202 Ok(()) => None,
203 Err(e) => Some(e),
204 })
205 })
206 .collect::<Vec<_>>()
207 .await;
208 if errors.is_empty() {
209 Ok(())
210 } else {
211 Err(errors)
212 }
213 }
214}