blob: 6cff8681aa6f6b8538b918dfed68f6f5da577d2d [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.
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom as _;
use std::num::NonZeroU16;
use std::ops::DerefMut as _;
use std::sync::{Arc, Once};
use anyhow::{format_err, Context as _, Error};
use assert_matches::assert_matches;
use fidl_fuchsia_net as fidl_net;
use fidl_fuchsia_net_stack as fidl_net_stack;
use fidl_fuchsia_net_stack_ext::FidlReturn as _;
use fidl_fuchsia_netemul_network as net;
use fuchsia_async as fasync;
use futures::lock::Mutex;
use net_types::{
ip::{AddrSubnetEither, IpAddr, Ipv4Addr, Ipv6Addr, SubnetEither},
SpecifiedAddr,
};
use netstack3_core::{
context::EventContext,
get_all_ip_addr_subnets, get_ipv4_configuration, get_ipv6_configuration,
icmp::{BufferIcmpContext, IcmpConnId, IcmpContext, IcmpIpExt},
update_ipv4_configuration, update_ipv6_configuration, AddableEntryEither, BlanketCoreContext,
BufferUdpContext, Ctx, DeviceId, DeviceLayerEventDispatcher, EventDispatcher, IpExt,
IpSockCreationError, Ipv6DeviceConfiguration, StackStateBuilder, UdpBoundId, UdpContext,
};
use packet::{Buf, BufferMut, Serializer};
use packet_formats::icmp::{IcmpEchoReply, IcmpMessage, IcmpUnusedCode};
use crate::bindings::{
context::Lockable,
devices::{
CommonInfo, DeviceInfo, DeviceSpecificInfo, Devices, EthernetInfo, LoopbackInfo,
NetdeviceInfo,
},
socket::datagram::{IcmpEcho, SocketCollectionIpExt, Udp},
util::{ConversionContext as _, IntoFidl as _, TryFromFidlWithContext as _, TryIntoFidl as _},
BindingsContextImpl, BindingsDispatcher, DeviceStatusNotifier, LockableContext,
RequestStreamExt as _, DEFAULT_LOOPBACK_MTU,
};
/// log::Log implementation that uses stdout.
///
/// Useful when debugging tests.
struct Logger;
impl log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
true
}
fn log(&self, record: &log::Record<'_>) {
println!("[{}] ({}) {}", record.level(), record.module_path().unwrap_or(""), record.args())
}
fn flush(&self) {}
}
static LOGGER: Logger = Logger;
static LOGGER_ONCE: Once = Once::new();
/// Install a logger for tests.
pub(crate) fn set_logger_for_test() {
// log::set_logger will panic if called multiple times; using a Once makes
// set_logger_for_test idempotent
LOGGER_ONCE.call_once(|| {
log::set_logger(&LOGGER).unwrap();
log::set_max_level(log::LevelFilter::Trace);
})
}
/// A dispatcher that can be used for tests with the ability to optionally
/// intercept events to use as signals during testing.
///
/// `TestDispatcher` implements [`StackDispatcher`] and keeps an internal
/// [`BindingsDispatcherState`]. All the traits that are needed to have a
/// correct [`EventDispatcher`] are re-implemented by it so any events can be
/// short circuited into internal event watchers as opposed to routing into the
/// internal [`BindingsDispatcherState]`.
pub(crate) struct TestDispatcher {
disp: BindingsDispatcher,
/// A oneshot signal that is hit whenever changes to interface status occur
/// and it is set.
status_changed_signal: Option<futures::channel::oneshot::Sender<()>>,
}
impl Default for TestDispatcher {
fn default() -> TestDispatcher {
TestDispatcher { disp: BindingsDispatcher::default(), status_changed_signal: None }
}
}
impl TestDispatcher {
/// Shorthand method to get a [`DeviceInfo`] from the device's bindings
/// identifier.
fn get_device_info(&self, id: u64) -> Option<&DeviceInfo> {
AsRef::<Devices>::as_ref(self).get_device(id)
}
}
impl DeviceStatusNotifier for TestDispatcher {
fn device_status_changed(&mut self, id: u64) {
if let Some(s) = self.status_changed_signal.take() {
s.send(()).unwrap();
}
// we can always send that forward to the real dispatcher, no need to
// short-circuit it.
self.disp.device_status_changed(id);
}
}
impl<T> AsRef<T> for TestDispatcher
where
BindingsDispatcher: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.disp.as_ref()
}
}
impl<T> AsMut<T> for TestDispatcher
where
BindingsDispatcher: AsMut<T>,
{
fn as_mut(&mut self) -> &mut T {
self.disp.as_mut()
}
}
impl<I: SocketCollectionIpExt<Udp> + IcmpIpExt> UdpContext<I> for TestDispatcher {
fn receive_icmp_error(&mut self, id: UdpBoundId<I>, err: I::ErrorCode) {
UdpContext::receive_icmp_error(&mut self.disp, id, err)
}
}
impl<I: SocketCollectionIpExt<Udp> + IpExt, B: BufferMut> BufferUdpContext<I, B>
for TestDispatcher
{
fn receive_udp_from_conn(
&mut self,
conn: netstack3_core::UdpConnId<I>,
src_ip: I::Addr,
src_port: NonZeroU16,
body: B,
) {
self.disp.receive_udp_from_conn(conn, src_ip, src_port, body)
}
/// Receive a UDP packet for a listener.
fn receive_udp_from_listen(
&mut self,
listener: netstack3_core::UdpListenerId<I>,
src_ip: I::Addr,
dst_ip: I::Addr,
src_port: Option<NonZeroU16>,
body: B,
) {
self.disp.receive_udp_from_listen(listener, src_ip, dst_ip, src_port, body)
}
}
impl<B: BufferMut> DeviceLayerEventDispatcher<B> for TestDispatcher {
fn send_frame<S: Serializer<Buffer = B>>(
&mut self,
device: DeviceId,
frame: S,
) -> Result<(), S> {
self.disp.send_frame(device, frame)
}
}
impl<I: SocketCollectionIpExt<IcmpEcho> + IcmpIpExt> IcmpContext<I> for TestDispatcher {
fn receive_icmp_error(&mut self, conn: IcmpConnId<I>, seq_num: u16, err: I::ErrorCode) {
IcmpContext::<I>::receive_icmp_error(&mut self.disp, conn, seq_num, err)
}
fn close_icmp_connection(&mut self, conn: IcmpConnId<I>, err: IpSockCreationError) {
self.disp.close_icmp_connection(conn, err)
}
}
impl<I, B> BufferIcmpContext<I, B> for TestDispatcher
where
I: SocketCollectionIpExt<IcmpEcho> + IcmpIpExt,
B: BufferMut,
IcmpEchoReply: for<'a> IcmpMessage<I, &'a [u8], Code = IcmpUnusedCode>,
{
fn receive_icmp_echo_reply(
&mut self,
conn: IcmpConnId<I>,
src_ip: I::Addr,
dst_ip: I::Addr,
id: u16,
seq_num: u16,
data: B,
) {
self.disp.receive_icmp_echo_reply(conn, src_ip, dst_ip, id, seq_num, data)
}
}
impl<T: 'static + Send> EventContext<T> for TestDispatcher {
fn on_event(&mut self, _event: T) {}
}
#[derive(Clone)]
/// A netstack context for testing.
pub(crate) struct TestContext {
ctx: Arc<Mutex<Ctx<TestDispatcher, BindingsContextImpl>>>,
_interfaces_worker: Arc<super::interfaces_watcher::Worker>,
interfaces_sink: super::interfaces_watcher::WorkerInterfaceSink,
}
impl TestContext {
fn new(builder: StackStateBuilder) -> Self {
let (worker, _, interfaces_sink) = super::interfaces_watcher::Worker::new();
Self {
ctx: Arc::new(Mutex::new(Ctx::new(
builder.build(),
TestDispatcher::default(),
BindingsContextImpl::default(),
))),
_interfaces_worker: Arc::new(worker),
interfaces_sink,
}
}
}
impl super::InterfaceEventProducerFactory for TestContext {
fn create_interface_event_producer(
&self,
id: super::devices::BindingId,
properties: super::interfaces_watcher::InterfaceProperties,
) -> super::interfaces_watcher::InterfaceEventProducer {
self.interfaces_sink.add_interface(id, properties).expect("interfaces worker not running")
}
}
impl<'a> Lockable<'a, Ctx<TestDispatcher, BindingsContextImpl>> for TestContext {
type Guard = futures::lock::MutexGuard<'a, Ctx<TestDispatcher, BindingsContextImpl>>;
type Fut = futures::lock::MutexLockFuture<'a, Ctx<TestDispatcher, BindingsContextImpl>>;
fn lock(&'a self) -> Self::Fut {
self.ctx.lock()
}
}
impl LockableContext for TestContext {
type Dispatcher = TestDispatcher;
type Context = BindingsContextImpl;
}
/// A holder for a [`TestContext`].
/// `TestStack` is obtained from [`TestSetupBuilder`] and offers utility methods
/// to connect to the FIDL APIs served by [`TestContext`], as well as keeps
/// track of configured interfaces during the setup procedure.
pub(crate) struct TestStack {
ctx: TestContext,
endpoint_ids: HashMap<String, u64>,
}
struct InterfaceInfo {
admin_enabled: bool,
phy_up: bool,
addresses: Vec<fidl_net::Subnet>,
}
impl TestStack {
/// Connects to the `fuchsia.net.stack.Stack` service.
pub(crate) fn connect_stack(&self) -> Result<fidl_fuchsia_net_stack::StackProxy, Error> {
let (stack, rs) =
fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_net_stack::StackMarker>()?;
fasync::Task::spawn(rs.serve_with(|rs| {
crate::bindings::stack_fidl_worker::StackFidlWorker::serve(self.ctx.clone(), rs)
}))
.detach();
Ok(stack)
}
/// Connects to the `fuchsia.posix.socket.Provider` service.
pub(crate) fn connect_socket_provider(
&self,
) -> Result<fidl_fuchsia_posix_socket::ProviderProxy, Error> {
let (stack, rs) = fidl::endpoints::create_proxy_and_stream::<
fidl_fuchsia_posix_socket::ProviderMarker,
>()?;
fasync::Task::spawn(
rs.serve_with(|rs| crate::bindings::socket::serve(self.ctx.clone(), rs)),
)
.detach();
Ok(stack)
}
fn is_interface_link_up(info: &DeviceInfo) -> bool {
match info.info() {
DeviceSpecificInfo::Ethernet(EthernetInfo {
common_info: CommonInfo { admin_enabled: _, mtu: _, events: _, name: _ },
client: _,
mac: _,
features: _,
phy_up,
})
| DeviceSpecificInfo::Netdevice(NetdeviceInfo {
common_info: CommonInfo { admin_enabled: _, mtu: _, events: _, name: _ },
handler: _,
mac: _,
phy_up,
}) => *phy_up,
DeviceSpecificInfo::Loopback(LoopbackInfo {
common_info: CommonInfo { admin_enabled: _, mtu: _, events: _, name: _ },
}) => true,
}
}
/// Waits for interface with given `if_id` to come online.
pub(crate) async fn wait_for_interface_online(&mut self, if_id: u64) {
self.wait_for_interface_status(if_id, Self::is_interface_link_up).await;
}
/// Waits for interface with given `if_id` to go offline.
pub(crate) async fn wait_for_interface_offline(&mut self, if_id: u64) {
self.wait_for_interface_status(if_id, |info| !Self::is_interface_link_up(info)).await;
}
async fn wait_for_interface_status<F: Fn(&DeviceInfo) -> bool>(
&mut self,
if_id: u64,
check_status: F,
) {
loop {
let signal = {
let mut ctx = self.ctx.lock().await;
if check_status(
ctx.dispatcher
.get_device_info(if_id)
.expect("Wait for interface status on unknown device"),
) {
return;
}
let (sender, receiver) = futures::channel::oneshot::channel();
ctx.dispatcher.status_changed_signal = Some(sender);
receiver
};
let () = signal.await.expect("Stream ended before it was signalled");
}
}
/// Gets an installed interface identifier from the configuration endpoint
/// `index`.
pub(crate) fn get_endpoint_id(&self, index: usize) -> u64 {
self.get_named_endpoint_id(test_ep_name(index))
}
/// Gets an installed interface identifier from the configuration endpoint
/// `name`.
pub(crate) fn get_named_endpoint_id(&self, name: impl Into<String>) -> u64 {
*self.endpoint_ids.get(&name.into()).unwrap()
}
/// Creates a new `TestStack`.
pub(crate) fn new() -> Self {
// Create a new TestStack with Duplicate Address Detection disabled for
// tests.
//
// TODO(fxbug.dev/36238): Remove this code when an event is dispatched
// when Duplicate Address Detection finishes or when an IPv6 address has
// been assigned. Without such events, tests do not know how long to
// wait for the stack to be ready for events.
let mut builder = StackStateBuilder::default();
builder.device_builder().set_default_ipv6_config(Ipv6DeviceConfiguration {
dad_transmits: None,
max_router_solicitations: None,
slaac_config: Default::default(),
ip_config: Default::default(),
});
let ctx = TestContext::new(builder);
TestStack { ctx, endpoint_ids: HashMap::new() }
}
/// Helper function to invoke a closure that provides a locked
/// [`Ctx<TestDispatcher, BindingsContext>`] provided by this `TestStack`.
pub(crate) async fn with_ctx<
R,
F: FnOnce(&mut Ctx<TestDispatcher, BindingsContextImpl>) -> R,
>(
&mut self,
f: F,
) -> R {
let mut ctx = self.ctx.lock().await;
f(ctx.deref_mut())
}
/// Acquire a lock on this `TestStack`'s context.
pub(crate) async fn ctx(
&self,
) -> <TestContext as Lockable<'_, Ctx<TestDispatcher, BindingsContextImpl>>>::Guard {
self.ctx.lock().await
}
async fn get_interface_info(&self, id: u64) -> InterfaceInfo {
let ctx = self.ctx().await;
let device = ctx.dispatcher.get_device_info(id).expect("device");
let addresses = get_all_ip_addr_subnets(&ctx, device.core_id())
.map(|addr| addr.try_into_fidl().expect("convert to FIDL"))
.collect();
let (admin_enabled, phy_up) = assert_matches::assert_matches!(
device.info(),
DeviceSpecificInfo::Ethernet(EthernetInfo {
common_info: CommonInfo {
admin_enabled,
mtu: _,
events: _,
name: _,
},
client: _,
mac: _,
features: _,
phy_up
}) => (*admin_enabled, *phy_up));
InterfaceInfo { admin_enabled, phy_up, addresses }
}
}
/// A test setup that than contain multiple stack instances networked together.
pub(crate) struct TestSetup {
// Let connection to sandbox be made lazily, so a netemul sandbox is not
// created for tests that don't need it.
sandbox: Option<netemul::TestSandbox>,
// Keep around the handle to the virtual networks and endpoints we create to
// ensure they're not cleaned up before test execution is complete.
_network: Option<net::SetupHandleProxy>,
stacks: Vec<TestStack>,
}
impl TestSetup {
/// Gets the [`TestStack`] at index `i`.
pub(crate) fn get(&mut self, i: usize) -> &mut TestStack {
&mut self.stacks[i]
}
/// Acquires a lock on the [`TestContext`] at index `i`.
pub(crate) async fn ctx(
&mut self,
i: usize,
) -> <TestContext as Lockable<'_, Ctx<TestDispatcher, BindingsContextImpl>>>::Guard {
self.get(i).ctx.lock().await
}
async fn get_endpoint(
&mut self,
ep_name: &str,
) -> Result<fidl::endpoints::ClientEnd<fidl_fuchsia_hardware_ethernet::DeviceMarker>, Error>
{
let epm = self.sandbox().get_endpoint_manager()?;
let ep = match epm.get_endpoint(ep_name).await? {
Some(ep) => ep.into_proxy()?,
None => {
return Err(format_err!("Failed to retrieve endpoint {}", ep_name));
}
};
match ep.get_device().await? {
fidl_fuchsia_netemul_network::DeviceConnection::Ethernet(e) => Ok(e),
fidl_fuchsia_netemul_network::DeviceConnection::NetworkDevice(n) => {
todo!("(48853) Support NetworkDevice for integration tests. Got unexpected network device {:?}.", n)
}
}
}
/// Changes a named endpoint `ep_name` link status to `up`.
pub(crate) async fn set_endpoint_link_up(
&mut self,
ep_name: &str,
up: bool,
) -> Result<(), Error> {
let epm = self.sandbox().get_endpoint_manager()?;
if let Some(ep) = epm.get_endpoint(ep_name).await? {
ep.into_proxy()?.set_link_up(up).await?;
Ok(())
} else {
Err(format_err!("Failed to retrieve endpoint {}", ep_name))
}
}
/// Creates a new empty `TestSetup`.
fn new() -> Result<Self, Error> {
set_logger_for_test();
Ok(Self { sandbox: None, _network: None, stacks: Vec::new() })
}
fn sandbox(&mut self) -> &netemul::TestSandbox {
self.sandbox
.get_or_insert_with(|| netemul::TestSandbox::new().expect("create netemul sandbox"))
}
async fn configure_network(
&mut self,
ep_names: impl Iterator<Item = String>,
) -> Result<(), Error> {
let handle = self
.sandbox()
.setup_networks(vec![net::NetworkSetup {
name: "test_net".to_owned(),
config: net::NetworkConfig::EMPTY,
endpoints: ep_names.map(|name| new_endpoint_setup(name)).collect(),
}])
.await
.context("create network")?
.into_proxy();
self._network = Some(handle);
Ok(())
}
fn add_stack(&mut self, stack: TestStack) {
self.stacks.push(stack)
}
}
/// Helper function to retrieve the internal name of an endpoint specified only
/// by an index `i`.
pub(crate) fn test_ep_name(i: usize) -> String {
format!("test-ep{}", i)
}
fn new_endpoint_setup(name: String) -> net::EndpointSetup {
net::EndpointSetup { config: None, link_up: true, name }
}
/// A builder structure for [`TestSetup`].
pub(crate) struct TestSetupBuilder {
endpoints: Vec<String>,
stacks: Vec<StackSetupBuilder>,
}
impl TestSetupBuilder {
/// Creates an empty `SetupBuilder`.
pub(crate) fn new() -> Self {
Self { endpoints: Vec::new(), stacks: Vec::new() }
}
/// Adds an automatically-named endpoint to the setup builder. The automatic
/// names are taken using [`test_ep_name`] with index starting at 1.
///
/// Multiple calls to `add_endpoint` will result in the creation of multiple
/// endpoints with sequential indices.
pub(crate) fn add_endpoint(self) -> Self {
let id = self.endpoints.len() + 1;
self.add_named_endpoint(test_ep_name(id))
}
/// Ads an endpoint with a given `name`.
pub(crate) fn add_named_endpoint(mut self, name: impl Into<String>) -> Self {
self.endpoints.push(name.into());
self
}
/// Adds a stack to create upon building. Stack configuration is provided
/// by [`StackSetupBuilder`].
pub(crate) fn add_stack(mut self, stack: StackSetupBuilder) -> Self {
self.stacks.push(stack);
self
}
/// Adds an empty stack to create upon building. An empty stack contains
/// no endpoints.
pub(crate) fn add_empty_stack(mut self) -> Self {
self.stacks.push(StackSetupBuilder::new());
self
}
/// Attempts to build a [`TestSetup`] with the provided configuration.
pub(crate) async fn build(self) -> Result<TestSetup, Error> {
let mut setup = TestSetup::new()?;
if !self.endpoints.is_empty() {
let () = setup.configure_network(self.endpoints.into_iter()).await?;
}
// configure all the stacks:
for stack_cfg in self.stacks.into_iter() {
println!("Adding stack: {:?}", stack_cfg);
let mut stack = TestStack::new();
stack
.with_ctx(|ctx| {
let loopback = ctx
.state
.add_loopback_device(DEFAULT_LOOPBACK_MTU)
.expect("add loopback device");
update_ipv4_configuration(ctx, loopback, |config| {
config.ip_config.ip_enabled = true;
});
update_ipv6_configuration(ctx, loopback, |config| {
config.ip_config.ip_enabled = true;
});
})
.await;
for (ep_name, addr) in stack_cfg.endpoints.into_iter() {
// get the endpoint from the sandbox config:
let endpoint = setup.get_endpoint(&ep_name).await?;
let cli = stack.connect_stack()?;
let if_id = add_stack_endpoint(&cli, endpoint).await?;
// We'll ALWAYS await for the newly created interface to come up
// online before returning, so users of `TestSetupBuilder` can
// be 100% sure of the state once the setup is done.
stack.wait_for_interface_online(if_id).await;
if let Some(addr) = addr {
configure_endpoint_address(&cli, if_id, addr).await?;
}
assert_eq!(stack.endpoint_ids.insert(ep_name, if_id), None);
}
setup.add_stack(stack)
}
Ok(setup)
}
}
/// Shorthand function to create an IPv4 [`AddrSubnetEither`].
///
/// # Panics
///
/// May panic if `prefix` is longer than the number of bits in this type of IP
/// address (32 for IPv4), or if `ip` is not a unicast address in the resulting
/// subnet (see [`net_types::ip::IpAddress::is_unicast_in_subnet`]).
pub fn new_ipv4_addr_subnet(ip: [u8; 4], prefix: u8) -> AddrSubnetEither {
AddrSubnetEither::new(IpAddr::V4(Ipv4Addr::from(ip)), prefix).unwrap()
}
/// Shorthand function to create an IPv6 [`AddrSubnetEither`].
///
/// # Panics
///
/// May panic if `prefix` is longer than the number of bits in this type of IP
/// address (128 for IPv6), or if `ip` is not a unicast address in the resulting
/// subnet (see [`net_types::ip::IpAddress::is_unicast_in_subnet`]).
pub fn new_ipv6_addr_subnet(ip: [u8; 16], prefix: u8) -> AddrSubnetEither {
AddrSubnetEither::new(IpAddr::V6(Ipv6Addr::from(ip)), prefix).unwrap()
}
/// Helper struct to create stack configuration for [`TestSetupBuilder`].
#[derive(Debug)]
pub struct StackSetupBuilder {
endpoints: Vec<(String, Option<AddrSubnetEither>)>,
}
impl StackSetupBuilder {
/// Creates a new empty stack (no endpoints) configuration.
pub(crate) fn new() -> Self {
Self { endpoints: Vec::new() }
}
/// Adds endpoint number `index` with optional address configuration
/// `address` to the builder.
fn add_endpoint(self, index: usize, address: Option<AddrSubnetEither>) -> Self {
self.add_named_endpoint(test_ep_name(index), address)
}
/// Adds named endpoint `name` with optional address configuration `address`
/// to the builder.
pub(crate) fn add_named_endpoint(
mut self,
name: impl Into<String>,
address: Option<AddrSubnetEither>,
) -> Self {
self.endpoints.push((name.into(), address));
self
}
}
async fn add_stack_endpoint(
cli: &fidl_fuchsia_net_stack::StackProxy,
endpoint: fidl::endpoints::ClientEnd<fidl_fuchsia_hardware_ethernet::DeviceMarker>,
) -> Result<u64, Error> {
// add interface:
let if_id = cli
.add_ethernet_interface("fake_topo_path", endpoint)
.await
.squash_result()
.context("Add ethernet interface")?;
Ok(if_id)
}
async fn configure_endpoint_address(
cli: &fidl_fuchsia_net_stack::StackProxy,
if_id: u64,
addr: AddrSubnetEither,
) -> Result<(), Error> {
// add address:
let () = cli
.add_interface_address_deprecated(if_id, &mut addr.into_fidl())
.await
.squash_result()
.context("Add interface address")?;
// add route to ensure `addr` is valid, the result can be safely discarded
let _ =
AddrSubnetEither::try_from(addr).expect("Invalid test subnet configuration").addr_subnet();
let () = cli
.add_forwarding_entry(&mut fidl_fuchsia_net_stack::ForwardingEntry {
subnet: addr.addr_subnet().1.into_fidl(),
device_id: if_id,
next_hop: None,
metric: 0,
})
.await
.squash_result()
.context("Add forwarding entry")?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn test_add_remove_interface() {
let mut t = TestSetupBuilder::new().add_endpoint().add_empty_stack().build().await.unwrap();
let ep = t.get_endpoint("test-ep1").await.unwrap();
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let if_id = stack
.add_ethernet_interface("fake_topo_path", ep)
.await
.squash_result()
.expect("Add interface succeeds");
// remove the interface:
let () = stack.del_ethernet_interface(if_id).await.squash_result().expect("Remove interface");
// ensure the interface disappeared from records:
assert_matches!(test_stack.ctx.lock().await.dispatcher.get_device_info(if_id), None);
// if we try to remove it again, NotFound should be returned:
let res =
stack.del_ethernet_interface(if_id).await.unwrap().expect_err("Failed to remove twice");
assert_eq!(res, fidl_net_stack::Error::NotFound);
}
#[fasync::run_singlethreaded(test)]
async fn test_ethernet_link_up_down() {
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let ep_name = test_ep_name(1);
let test_stack = t.get(0);
let if_id = test_stack.get_endpoint_id(1);
let () = test_stack.wait_for_interface_online(if_id).await;
// Get the interface info to confirm status indicators are correct.
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.phy_up);
assert!(if_info.admin_enabled);
// Ensure that the device has been enabled in the core.
let core_id = {
let mut ctx = test_stack.ctx().await;
let core_id = ctx.dispatcher.get_device_info(if_id).unwrap().core_id();
check_ip_enabled(ctx.deref_mut(), core_id, true);
core_id
};
// Setting the link down should disable the interface and disable it from
// the core. The AdministrativeStatus should remain unchanged.
assert!(t.set_endpoint_link_up(&ep_name, false).await.is_ok());
let test_stack = t.get(0);
test_stack.wait_for_interface_offline(if_id).await;
// Get the interface info to confirm that it is disabled.
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.phy_up);
assert!(if_info.admin_enabled);
// Ensure that the device has been disabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, false);
// Setting the link down again should cause no effect on the device state,
// and should be handled gracefully.
assert!(t.set_endpoint_link_up(&ep_name, false).await.is_ok());
// Get the interface info to confirm that it is disabled.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.phy_up);
assert!(if_info.admin_enabled);
// Ensure that the device has been disabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, false);
// Setting the link up should reenable the interface and enable it in
// the core.
assert!(t.set_endpoint_link_up(&ep_name, true).await.is_ok());
t.get(0).wait_for_interface_online(if_id).await;
// Get the interface info to confirm that it is reenabled.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.phy_up);
assert!(if_info.admin_enabled);
// Ensure that the device has been enabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, true);
// Setting the link up again should cause no effect on the device state,
// and should be handled gracefully.
assert!(t.set_endpoint_link_up(&ep_name, true).await.is_ok());
// Get the interface info to confirm that there have been no changes.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.phy_up);
assert!(if_info.admin_enabled);
// Ensure that the device has been enabled in the core.
let core_id = t
.get(0)
.with_ctx(|ctx| {
let core_id = ctx.dispatcher.get_device_info(if_id).unwrap().core_id();
check_ip_enabled(ctx, core_id, true);
core_id
})
.await;
// call directly into core to prove that the device was correctly
// initialized (core will panic if we try to use the device and initialize
// hasn't been called)
netstack3_core::receive_frame(t.ctx(0).await.deref_mut(), core_id, Buf::new(&mut [], ..))
.expect("error receiving frame");
}
fn check_ip_enabled<D: EventDispatcher, C: BlanketCoreContext>(
ctx: &mut Ctx<D, C>,
core_id: DeviceId,
expected: bool,
) {
let ipv4_enabled = get_ipv4_configuration(ctx, core_id).ip_config.ip_enabled;
let ipv6_enabled = get_ipv6_configuration(ctx, core_id).ip_config.ip_enabled;
assert_eq!((ipv4_enabled, ipv6_enabled), (expected, expected));
}
#[fasync::run_singlethreaded(test)]
async fn test_disable_enable_interface() {
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let if_id = test_stack.get_endpoint_id(1);
// Get the interface info to confirm that it is enabled.
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.admin_enabled);
assert!(if_info.phy_up);
// Disable the interface and test again, physical_status should be
// unchanged.
let () = stack
.disable_interface_deprecated(if_id)
.await
.squash_result()
.expect("Disable interface succeeds");
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.admin_enabled);
assert!(if_info.phy_up);
// Ensure that the device has been disabled in the core.
let core_id = {
let mut ctx = test_stack.ctx().await;
let core_id = ctx.dispatcher.get_device_info(if_id).unwrap().core_id();
check_ip_enabled(ctx.deref_mut(), core_id, false);
core_id
};
// Enable the interface and test again.
let () = stack
.enable_interface_deprecated(if_id)
.await
.squash_result()
.expect("Enable interface succeeds");
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.admin_enabled);
assert!(if_info.phy_up);
// Ensure that the device has been enabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, true);
// Check that we get the correct error for a non-existing interface id.
assert_eq!(
stack.enable_interface_deprecated(12345).await.unwrap().unwrap_err(),
fidl_net_stack::Error::NotFound
);
// Check that we get the correct error for a non-existing interface id.
assert_eq!(
stack.disable_interface_deprecated(12345).await.unwrap().unwrap_err(),
fidl_net_stack::Error::NotFound
);
}
#[fasync::run_singlethreaded(test)]
async fn test_phy_admin_interface_state_interaction() {
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let ep_name = test_ep_name(1);
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let if_id = test_stack.get_endpoint_id(1);
t.get(0).wait_for_interface_online(if_id).await;
// Get the interface info to confirm that it is enabled.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.admin_enabled);
assert!(if_info.phy_up);
// Disable the interface and test again, physical_status should be
// unchanged.
let () = stack
.disable_interface_deprecated(if_id)
.await
.squash_result()
.expect("Disable interface succeeds");
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.admin_enabled);
assert!(if_info.phy_up);
// Ensure that the device has been disabled in the core.
let core_id = {
let mut ctx = test_stack.ctx().await;
let core_id = ctx.dispatcher.get_device_info(if_id).unwrap().core_id();
check_ip_enabled(ctx.deref_mut(), core_id, false);
core_id
};
// Setting the link down now that the interface is already down should only
// change the cached state. Both phy and admin should be down now.
assert!(t.set_endpoint_link_up(&ep_name, false).await.is_ok());
t.get(0).wait_for_interface_offline(if_id).await;
// Get the interface info to confirm that it is disabled.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.phy_up);
assert!(!if_info.admin_enabled);
// Ensure that the device is still disabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, false);
// Enable the interface and test again, only cached status should be changed
// and core state should still be disabled.
let () = stack
.enable_interface_deprecated(if_id)
.await
.squash_result()
.expect("Enable interface succeeds");
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.admin_enabled);
assert!(!if_info.phy_up);
// Ensure that the device is still disabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, false);
// Disable the interface and test again, both should be down now.
let () = stack
.disable_interface_deprecated(if_id)
.await
.squash_result()
.expect("Disable interface succeeds");
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.admin_enabled);
assert!(!if_info.phy_up);
// Ensure that the device is still disabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, false);
// Setting the link up should only affect cached state
assert!(t.set_endpoint_link_up(&ep_name, true).await.is_ok());
t.get(0).wait_for_interface_online(if_id).await;
// Get the interface info to confirm that it is reenabled.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.admin_enabled);
assert!(if_info.phy_up);
// Ensure that the device is still disabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, false);
// Finally, setting admin status up should update the cached state and
// re-add the device to the core.
let () = stack
.enable_interface_deprecated(if_id)
.await
.squash_result()
.expect("Enable interface succeeds");
// Get the interface info to confirm that it is reenabled.
let test_stack = t.get(0);
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.phy_up);
assert!(if_info.admin_enabled);
// Ensure that the device has been enabled in the core.
check_ip_enabled(test_stack.ctx().await.deref_mut(), core_id, true);
}
#[fasync::run_singlethreaded(test)]
async fn test_add_del_interface_address_deprecated() {
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let if_id = test_stack.get_endpoint_id(1);
for addr in [
new_ipv4_addr_subnet([192, 168, 0, 1], 24).into_fidl(),
new_ipv6_addr_subnet([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 64)
.into_fidl(),
]
.iter_mut()
{
// The first IP address added should succeed.
let () = stack
.add_interface_address_deprecated(if_id, addr)
.await
.squash_result()
.expect("Add interface address should succeed");
let if_info = test_stack.get_interface_info(if_id).await;
assert!(if_info.addresses.contains(&addr));
// Adding the same IP address again should fail with already exists.
let err = stack
.add_interface_address_deprecated(if_id, addr)
.await
.expect("Add interface address FIDL call should succeed")
.expect_err("Adding same address should fail");
assert_eq!(err, fidl_net_stack::Error::AlreadyExists);
// Deleting an IP address that exists should succeed.
let () = stack
.del_interface_address_deprecated(if_id, addr)
.await
.squash_result()
.expect("Delete interface address succeeds");
let if_info = test_stack.get_interface_info(if_id).await;
assert!(!if_info.addresses.contains(&addr));
// Deleting an IP address that doesn't exist should fail with not found.
let err = stack
.del_interface_address_deprecated(if_id, addr)
.await
.expect("Delete interface address FIDL call should succeed")
.expect_err("Deleting non-existent address should fail");
assert_eq!(err, fidl_net_stack::Error::NotFound);
}
}
#[fasync::run_singlethreaded(test)]
async fn test_add_device_routes() {
// create a stack and add a single endpoint to it so we have the interface
// id:
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let if_id = test_stack.get_endpoint_id(1);
let mut fwd_entry1 = fidl_net_stack::ForwardingEntry {
subnet: fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: [192, 168, 0, 0] }),
prefix_len: 24,
},
device_id: if_id,
next_hop: None,
metric: 0,
};
let mut fwd_entry2 = fidl_net_stack::ForwardingEntry {
subnet: fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: [10, 0, 0, 0] }),
prefix_len: 24,
},
device_id: if_id,
next_hop: None,
metric: 0,
};
let () = stack
.add_forwarding_entry(&mut fwd_entry1)
.await
.squash_result()
.expect("Add forwarding entry succeeds");
let () = stack
.add_forwarding_entry(&mut fwd_entry2)
.await
.squash_result()
.expect("Add forwarding entry succeeds");
// finally, check that bad routes will fail:
// a duplicate entry should fail with AlreadyExists:
let mut bad_entry = fidl_net_stack::ForwardingEntry {
subnet: fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: [192, 168, 0, 0] }),
prefix_len: 24,
},
device_id: if_id,
next_hop: None,
metric: 0,
};
assert_eq!(
stack.add_forwarding_entry(&mut bad_entry).await.unwrap().unwrap_err(),
fidl_net_stack::Error::AlreadyExists
);
// an entry with an invalid subnet should fail with Invalidargs:
let mut bad_entry = fidl_net_stack::ForwardingEntry {
subnet: fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: [10, 0, 0, 0] }),
prefix_len: 64,
},
device_id: if_id,
next_hop: None,
metric: 0,
};
assert_eq!(
stack.add_forwarding_entry(&mut bad_entry).await.unwrap().unwrap_err(),
fidl_net_stack::Error::InvalidArgs
);
// an entry with a bad devidce id should fail with NotFound:
let mut bad_entry = fidl_net_stack::ForwardingEntry {
subnet: fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: [10, 0, 0, 0] }),
prefix_len: 24,
},
device_id: 10,
next_hop: None,
metric: 0,
};
assert_eq!(
stack.add_forwarding_entry(&mut bad_entry).await.unwrap().unwrap_err(),
fidl_net_stack::Error::NotFound
);
}
#[fasync::run_singlethreaded(test)]
async fn test_list_del_routes() {
// create a stack and add a single endpoint to it so we have the interface
// id:
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let if_id = test_stack.get_endpoint_id(1);
let device = test_stack.ctx().await.dispatcher.get_core_id(if_id);
let route1_subnet_bytes = [192, 168, 0, 0];
let route1_subnet_prefix = 24;
let route1 = AddableEntryEither::new(
SubnetEither::new(Ipv4Addr::from(route1_subnet_bytes).into(), route1_subnet_prefix)
.unwrap(),
device,
None,
)
.unwrap();
let sub10 = SubnetEither::new(Ipv4Addr::from([10, 0, 0, 0]).into(), 24).unwrap();
let route2 = AddableEntryEither::new(sub10, device, None).unwrap();
let sub10_gateway = SpecifiedAddr::new(Ipv4Addr::from([10, 0, 0, 1])).map(Into::into);
let route3 = AddableEntryEither::new(sub10, None, sub10_gateway).unwrap();
let () = test_stack
.with_ctx(|ctx| {
// add a couple of routes directly into core:
netstack3_core::add_route(ctx, route1).unwrap();
netstack3_core::add_route(ctx, route2).unwrap();
netstack3_core::add_route(ctx, route3).unwrap();
})
.await;
let routes = stack.get_forwarding_table().await.expect("Can get forwarding table");
let route3_with_device = AddableEntryEither::new(sub10, device, sub10_gateway).unwrap();
assert_eq!(
test_stack
.with_ctx(|ctx| {
routes
.into_iter()
.map(|e| {
AddableEntryEither::try_from_fidl_with_ctx(&ctx.dispatcher, e).unwrap()
})
.collect::<HashSet<_>>()
})
.await,
HashSet::from([route1, route2, route3_with_device])
);
// delete route1:
let mut fwd_entry = fidl_net_stack::ForwardingEntry {
subnet: fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: route1_subnet_bytes }),
prefix_len: route1_subnet_prefix,
},
device_id: 0,
next_hop: None,
metric: 0,
};
let () = stack
.del_forwarding_entry(&mut fwd_entry)
.await
.squash_result()
.expect("can delete device forwarding entry");
// can't delete again:
assert_eq!(
stack.del_forwarding_entry(&mut fwd_entry).await.unwrap().unwrap_err(),
fidl_net_stack::Error::NotFound
);
// check that route was deleted (should've disappeared from core)
let routes = stack.get_forwarding_table().await.expect("Can get forwarding table");
assert_eq!(
test_stack
.with_ctx(|ctx| {
routes
.into_iter()
.map(|e| {
AddableEntryEither::try_from_fidl_with_ctx(&ctx.dispatcher, e).unwrap()
})
.collect::<HashSet<_>>()
})
.await,
HashSet::from([route2, route3_with_device])
);
}
#[fasync::run_singlethreaded(test)]
async fn test_add_remote_routes() {
let mut t = TestSetupBuilder::new()
.add_endpoint()
.add_stack(StackSetupBuilder::new().add_endpoint(1, None))
.build()
.await
.unwrap();
let test_stack = t.get(0);
let stack = test_stack.connect_stack().unwrap();
let device_id = test_stack.get_endpoint_id(1);
let subnet = fidl_net::Subnet {
addr: fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address { addr: [192, 168, 0, 0] }),
prefix_len: 24,
};
let mut fwd_entry = fidl_net_stack::ForwardingEntry {
subnet,
device_id: 0,
next_hop: Some(Box::new(fidl_net::IpAddress::Ipv4(fidl_net::Ipv4Address {
addr: [192, 168, 0, 1],
}))),
metric: 0,
};
// Cannot add gateway route without device set or on-link route to gateway.
assert_eq!(
stack.add_forwarding_entry(&mut fwd_entry).await.unwrap(),
Err(fidl_net_stack::Error::BadState)
);
let mut device_fwd_entry = fidl_net_stack::ForwardingEntry {
subnet: fwd_entry.subnet,
device_id,
next_hop: None,
metric: 0,
};
let () = stack
.add_forwarding_entry(&mut device_fwd_entry)
.await
.squash_result()
.expect("add device route");
let () =
stack.add_forwarding_entry(&mut fwd_entry).await.squash_result().expect("add device route");
// finally, check that bad routes will fail:
// a duplicate entry should fail with AlreadyExists:
assert_eq!(
stack.add_forwarding_entry(&mut fwd_entry).await.unwrap(),
Err(fidl_net_stack::Error::AlreadyExists)
);
}