blob: 8bc87c25c7ecc3f87dc676efda50468d611649b9 [file] [log] [blame]
// Copyright 2019 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.
#![deny(missing_docs)]
//! Provides utilities for Netstack integration tests.
pub mod constants;
#[macro_use]
pub mod environments;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::fmt::Debug;
use fidl_fuchsia_hardware_ethertap as ethertap;
use fidl_fuchsia_net_interfaces as net_interfaces;
use fuchsia_async::{self as fasync, DurationExt as _, TimeoutExt as _};
use fuchsia_zircon as zx;
use anyhow::Context as _;
use futures::future::{FusedFuture, Future, FutureExt as _};
use futures::stream::{Stream, StreamExt, TryStreamExt};
use net_types::ethernet::Mac;
use net_types::ip as net_types_ip;
use packet::serialize::{InnerPacketBuilder, Serializer};
use packet_formats::ethernet::{EtherType, EthernetFrameBuilder};
use packet_formats::icmp::ndp::{self, options::NdpOption};
use packet_formats::icmp::{IcmpMessage, IcmpPacketBuilder, IcmpUnusedCode};
use packet_formats::ip::IpProto;
use packet_formats::ipv6::Ipv6PacketBuilder;
use zerocopy::ByteSlice;
/// An alias for `Result<T, anyhow::Error>`.
pub type Result<T = ()> = std::result::Result<T, anyhow::Error>;
/// Extra time to use when waiting for an async event to occur.
///
/// A large timeout to help prevent flakes.
pub const ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT: zx::Duration = zx::Duration::from_seconds(120);
/// Extra time to use when waiting for an async event to not occur.
///
/// Since a negative check is used to make sure an event did not happen, its okay to use a
/// smaller timeout compared to the positive case since execution stall in regards to the
/// monotonic clock will not affect the expected outcome.
pub const ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT: zx::Duration = zx::Duration::from_seconds(5);
/// The time to wait between two consecutive checks of an event.
pub const ASYNC_EVENT_CHECK_INTERVAL: zx::Duration = zx::Duration::from_seconds(1);
/// As per [RFC 4861] sections 4.1-4.5, NDP packets MUST have a hop limit of 255.
///
/// [RFC 4861]: https://tools.ietf.org/html/rfc4861
pub const NDP_MESSAGE_TTL: u8 = 255;
/// Returns `true` once the stream yields a `true`.
///
/// If the stream never yields `true` or never terminates, `try_any` may never resolve.
pub async fn try_any<S: Stream<Item = Result<bool>>>(stream: S) -> Result<bool> {
futures::pin_mut!(stream);
stream.try_filter(|v| futures::future::ready(*v)).next().await.unwrap_or(Ok(false))
}
/// Returns `true` if the stream only yields `true`.
///
/// If the stream never yields `false` or never terminates, `try_all` may never resolve.
pub async fn try_all<S: Stream<Item = Result<bool>>>(stream: S) -> Result<bool> {
futures::pin_mut!(stream);
stream.try_filter(|v| futures::future::ready(!*v)).next().await.unwrap_or(Ok(true))
}
/// A trait that provides an Ethertap compatible name.
pub trait EthertapName {
/// Returns an Ethertap compatible name.
fn ethertap_compatible_name(&self) -> Self;
}
impl<'a> EthertapName for &'a str {
fn ethertap_compatible_name(&self) -> &'a str {
let max_len =
usize::try_from(ethertap::MAX_NAME_LENGTH).expect("u32 could not fit into usize");
&self[self.len().checked_sub(max_len).unwrap_or(0)..self.len()]
}
}
/// Asynchronously sleeps for specified `secs` seconds.
pub async fn sleep(secs: i64) {
fasync::Timer::new(zx::Duration::from_seconds(secs).after_now()).await;
}
/// Writes an NDP message to the provided fake endpoint.
///
/// Given the source and destination MAC and IP addresses, NDP message and
/// options, the full NDP packet (including IPv6 and Ethernet headers) will be
/// transmitted to the fake endpoint's network.
pub async fn write_ndp_message<
B: ByteSlice + Debug,
M: IcmpMessage<net_types_ip::Ipv6, B, Code = IcmpUnusedCode> + Debug,
>(
src_mac: Mac,
dst_mac: Mac,
src_ip: net_types_ip::Ipv6Addr,
dst_ip: net_types_ip::Ipv6Addr,
message: M,
options: &[NdpOption<'_>],
ep: &netemul::TestFakeEndpoint<'_>,
) -> Result {
let ser = ndp::OptionsSerializer::<_>::new(options.iter())
.into_serializer()
.encapsulate(IcmpPacketBuilder::<_, B, _>::new(src_ip, dst_ip, IcmpUnusedCode, message))
.encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, NDP_MESSAGE_TTL, IpProto::Icmpv6))
.encapsulate(EthernetFrameBuilder::new(src_mac, dst_mac, EtherType::Ipv6))
.serialize_vec_outer()
.map_err(|e| anyhow::anyhow!("failed to serialize NDP packet: {:?}", e))?
.unwrap_b();
let () = ep.write(ser.as_ref()).await.context("failed to write to fake endpoint")?;
Ok(())
}
/// Waits for a non-loopback interface to come up with an ID not in `exclude_ids`.
///
/// Useful when waiting for an interface to be discovered and brought up by a
/// network manager.
///
/// Returns the interface's ID and name.
pub async fn wait_for_non_loopback_interface_up<
F: Unpin + FusedFuture + Future<Output = Result<fuchsia_component::client::ExitStatus>>,
>(
interface_state: &net_interfaces::StateProxy,
mut wait_for_netmgr: &mut F,
exclude_ids: Option<&HashSet<u32>>,
timeout: zx::Duration,
) -> Result<(u32, String)> {
let mut if_map = HashMap::new();
let wait_for_interface = fidl_fuchsia_net_interfaces_ext::wait_interface(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(interface_state)?,
&mut if_map,
|if_map| {
if_map.iter().find_map(|(id, properties)| {
let id = *id as u32;
if properties.device_class
!= Some(net_interfaces::DeviceClass::Loopback(net_interfaces::Empty {}))
&& properties.online?
&& exclude_ids.map_or(true, |ids| !ids.contains(&id))
{
Some((
id,
properties.name.clone().expect("failed to find loopback interface name"),
))
} else {
None
}
})
},
)
.on_timeout(timeout.after_now(), || {
Err(anyhow::anyhow!(
"timed out waiting for OnInterfaceseChanged event with a non-loopback interface"
))
})
.fuse();
fuchsia_async::pin_mut!(wait_for_interface);
futures::select! {
wait_for_interface_res = wait_for_interface => {
wait_for_interface_res
}
wait_for_netmgr_res = wait_for_netmgr => {
Err(anyhow::anyhow!("the network manager unexpectedly exited with exit status = {:?}", wait_for_netmgr_res?))
}
}
}