blob: 51c51839695efc83938f8527bcd178a169dd0ac1 [file] [log] [blame]
// 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.
#![cfg(test)]
use fidl_fuchsia_net_interfaces_admin as finterfaces_admin;
use fidl_fuchsia_net_stack_ext::FidlReturn as _;
use fuchsia_async::TimeoutExt as _;
use futures::{FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _};
use net_declare::{fidl_ip, fidl_mac, fidl_subnet, std_ip_v6, std_socket_addr};
use net_types::ip::IpAddress as _;
use netemul::RealmUdpSocket as _;
use netstack_testing_common::{
interfaces,
realms::{Netstack, Netstack2, NetstackVersion, TestRealmExt as _, TestSandboxExt as _},
};
use netstack_testing_macros::variants_test;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto as _;
use test_case::test_case;
fn create_tun_device() -> (
fidl_fuchsia_net_tun::DeviceProxy,
fidl::endpoints::ClientEnd<fidl_fuchsia_hardware_network::DeviceMarker>,
) {
let tun_ctl =
fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_net_tun::ControlMarker>()
.expect("connect to protocol");
let (tun_dev, tun_dev_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::DeviceMarker>()
.expect("create proxy");
let () = tun_ctl
.create_device(fidl_fuchsia_net_tun::DeviceConfig::EMPTY, tun_dev_server_end)
.expect("create tun device");
let (netdevice_client_end, netdevice_server_end) =
fidl::endpoints::create_endpoints::<fidl_fuchsia_hardware_network::DeviceMarker>()
.expect("create endpoints");
let () = tun_dev.get_device(netdevice_server_end).expect("get device");
(tun_dev, netdevice_client_end)
}
#[variants_test]
async fn address_deprecation<E: netemul::Endpoint>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let device = sandbox.create_endpoint::<E, _>(name).await.expect("create endpoint");
let interface = device.into_interface_in_realm(&realm).await.expect("add endpoint to Netstack");
assert!(interface.control().enable().await.expect("send enable").expect("enable"));
let () = interface.set_link_up(true).await.expect("bring device up");
const ADDR1: std::net::Ipv6Addr = std_ip_v6!("abcd::1");
const ADDR2: std::net::Ipv6Addr = std_ip_v6!("abcd::2");
// Cannot be const because `std::net::SocketAddrV6:new` isn't const.
let sock_addr = std_socket_addr!("[abcd::3]:12345");
// Note that the absence of the preferred_lifetime_info field implies infinite
// preferred lifetime.
const PREFERRED_PROPERTIES: fidl_fuchsia_net_interfaces_admin::AddressProperties =
fidl_fuchsia_net_interfaces_admin::AddressProperties::EMPTY;
const DEPRECATED_PROPERTIES: fidl_fuchsia_net_interfaces_admin::AddressProperties =
fidl_fuchsia_net_interfaces_admin::AddressProperties {
preferred_lifetime_info: Some(
fidl_fuchsia_net_interfaces_admin::PreferredLifetimeInfo::Deprecated(
fidl_fuchsia_net_interfaces_admin::Empty,
),
),
..fidl_fuchsia_net_interfaces_admin::AddressProperties::EMPTY
};
let addr1_state_provider = interfaces::add_subnet_address_and_route_wait_assigned(
&interface,
fidl_fuchsia_net::Subnet {
addr: fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address {
addr: ADDR1.octets(),
}),
prefix_len: 16,
},
// Note that an empty AddressParameters means that the address has
// infinite preferred lifetime.
fidl_fuchsia_net_interfaces_admin::AddressParameters {
initial_properties: Some(PREFERRED_PROPERTIES.clone()),
..fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY
},
)
.await
.expect("failed to add preferred address");
let addr2_state_provider = interfaces::add_address_wait_assigned(
interface.control(),
fidl_fuchsia_net::Subnet {
addr: fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address {
addr: ADDR2.octets(),
}),
prefix_len: (ADDR2.octets().len() * 8).try_into().unwrap(),
},
fidl_fuchsia_net_interfaces_admin::AddressParameters {
initial_properties: Some(DEPRECATED_PROPERTIES.clone()),
..fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY
},
)
.await
.expect("failed to add deprecated address");
let get_source_addr = || async {
let sock = realm
.datagram_socket(
fidl_fuchsia_posix_socket::Domain::Ipv6,
fidl_fuchsia_posix_socket::DatagramSocketProtocol::Udp,
)
.await
.expect("failed to create UDP socket");
sock.connect(&socket2::SockAddr::from(sock_addr)).expect("failed to connect with socket");
*sock
.local_addr()
.expect("failed to get socket local addr")
.as_socket_ipv6()
.expect("socket local addr not IPv6")
.ip()
};
assert_eq!(get_source_addr().await, ADDR1);
addr1_state_provider
.update_address_properties(DEPRECATED_PROPERTIES)
.await
.expect("FIDL error deprecating address");
addr2_state_provider
.update_address_properties(PREFERRED_PROPERTIES)
.await
.expect("FIDL error setting address to preferred");
assert_eq!(get_source_addr().await, ADDR2);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn add_address_errors() {
let name = "interfaces_admin_add_address_errors";
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let fidl_fuchsia_net_interfaces_ext::Properties {
id: loopback_id,
addresses,
name: _,
device_class: _,
online: _,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
} = realm
.loopback_properties()
.await
.expect("failed to get loopback properties")
.expect("loopback not found");
let control = realm
.interface_control(loopback_id)
.expect("failed to get loopback interface control client proxy");
const VALID_ADDRESS_PARAMETERS: fidl_fuchsia_net_interfaces_admin::AddressParameters =
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY;
// Removing non-existent address.
{
let mut address = fidl_subnet!("1.1.1.1/32");
let did_remove = control
.remove_address(&mut address)
.await
.expect("FIDL error calling fuchsia.net.interfaces.admin/Control.RemoveAddress")
.expect("RemoveAddress failed");
assert!(!did_remove);
}
let (control, v4_addr, v6_addr) = futures::stream::iter(addresses).fold((control, None, None), |(control, v4, v6), fidl_fuchsia_net_interfaces_ext::Address {
addr,
valid_until: _,
}| {
let (v4, v6) = {
let fidl_fuchsia_net::Subnet { addr, prefix_len } = addr;
match addr {
fidl_fuchsia_net::IpAddress::Ipv4(addr) => {
let nt_addr = net_types::ip::Ipv4Addr::new(addr.addr);
assert!(nt_addr.is_loopback(), "{} is not a loopback address", nt_addr);
let addr = fidl_fuchsia_net::Ipv4AddressWithPrefix {
addr,
prefix_len,
};
assert_eq!(v4, None, "v4 address already present, found {:?}", addr);
(Some(addr), v6)
}
fidl_fuchsia_net::IpAddress::Ipv6(addr) => {
let nt_addr = net_types::ip::Ipv6Addr::from_bytes(addr.addr);
assert!(nt_addr.is_loopback(), "{} is not a loopback address", nt_addr);
assert_eq!(v6, None, "v6 address already present, found {:?}", addr);
let addr = fidl_fuchsia_net::Ipv6AddressWithPrefix {
addr,
prefix_len,
};
(v4, Some(addr))
}
}
};
async move {
assert_matches::assert_matches!(
interfaces::add_address_wait_assigned(&control, addr.clone(), VALID_ADDRESS_PARAMETERS).await,
Err(fidl_fuchsia_net_interfaces_ext::admin::AddressStateProviderError::AddressRemoved(
fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::AlreadyAssigned
)));
(control, v4, v6)
}
}).await;
assert_ne!(v4_addr, None, "expected v4 address");
assert_ne!(v6_addr, None, "expected v6 address");
// Adding an invalid address returns error.
{
// NB: fidl_subnet! doesn't allow invalid prefix lengths.
let invalid_address =
fidl_fuchsia_net::Subnet { addr: fidl_ip!("1.1.1.1"), prefix_len: 33 };
assert_matches::assert_matches!(
interfaces::add_address_wait_assigned(
&control,
invalid_address,
VALID_ADDRESS_PARAMETERS
)
.await,
Err(fidl_fuchsia_net_interfaces_ext::admin::AddressStateProviderError::AddressRemoved(
fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::Invalid
))
);
}
}
#[variants_test]
async fn add_address_removal<E: netemul::Endpoint>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("new sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("connect to protocol");
let device = sandbox.create_endpoint::<E, _>(name).await.expect("create endpoint");
let interface = device.into_interface_in_realm(&realm).await.expect("add endpoint to Netstack");
let id = interface.id();
let did_enable = interface.control().enable().await.expect("send enable").expect("enable");
assert!(did_enable);
let () = interface.set_link_up(true).await.expect("bring device up");
let debug_control = realm
.connect_to_protocol::<fidl_fuchsia_net_debug::InterfacesMarker>()
.expect(<fidl_fuchsia_net_debug::InterfacesMarker as fidl::endpoints::DiscoverableProtocolMarker>::PROTOCOL_NAME);
let (control, server) = fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.expect("create Control proxy");
let () = debug_control.get_admin(id, server).expect("get admin");
const VALID_ADDRESS_PARAMETERS: fidl_fuchsia_net_interfaces_admin::AddressParameters =
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY;
// Adding a valid address and observing the address removal.
{
let mut address = fidl_subnet!("3.3.3.3/32");
let address_state_provider =
interfaces::add_address_wait_assigned(&control, address, VALID_ADDRESS_PARAMETERS)
.await
.expect("add address failed unexpectedly");
let did_remove = control
.remove_address(&mut address)
.await
.expect("FIDL error calling Control.RemoveAddress")
.expect("error calling Control.RemoveAddress");
assert!(did_remove);
let fidl_fuchsia_net_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
error: reason,
} = address_state_provider
.take_event_stream()
.try_next()
.await
.expect("read AddressStateProvider event")
.expect("AddressStateProvider event stream ended unexpectedly");
assert_eq!(reason, fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::UserRemoved);
}
// Adding a valid address and removing the interface.
{
let address = fidl_subnet!("4.4.4.4/32");
let address_state_provider =
interfaces::add_address_wait_assigned(&control, address, VALID_ADDRESS_PARAMETERS)
.await
.expect("add address failed unexpectedly");
let () = stack
.del_ethernet_interface(id)
.await
.squash_result()
.expect("delete ethernet interface");
let fidl_fuchsia_net_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
error: reason,
} = address_state_provider
.take_event_stream()
.try_next()
.await
.expect("read AddressStateProvider event")
.expect("AddressStateProvider event stream ended unexpectedly");
assert_eq!(
reason,
fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::InterfaceRemoved
);
assert_matches::assert_matches!(
control.wait_termination().await,
fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::User
)
);
}
}
// Add an address while the interface is offline, bring the interface online and ensure that the
// assignment state is set correctly.
#[variants_test]
async fn add_address_offline<E: netemul::Endpoint>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("new sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let device = sandbox.create_endpoint::<E, _>(name).await.expect("create endpoint");
let interface = device.into_interface_in_realm(&realm).await.expect("add endpoint to Netstack");
let id = interface.id();
let debug_control = realm
.connect_to_protocol::<fidl_fuchsia_net_debug::InterfacesMarker>()
.expect(<fidl_fuchsia_net_debug::InterfacesMarker as fidl::endpoints::DiscoverableProtocolMarker>::PROTOCOL_NAME);
let (control, server) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::ControlMarker>()
.expect("create Control proxy");
let () = debug_control.get_admin(id, server).expect("get admin");
const VALID_ADDRESS_PARAMETERS: fidl_fuchsia_net_interfaces_admin::AddressParameters =
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY;
// Adding a valid address and observing the address removal.
let mut address = fidl_subnet!("5.5.5.5/32");
let (address_state_provider, server) = fidl::endpoints::create_proxy::<
fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker,
>()
.expect("create AddressStateProvider proxy");
let () = control
.add_address(&mut address, VALID_ADDRESS_PARAMETERS, server)
.expect("Control.AddAddress FIDL error");
let state_stream = fidl_fuchsia_net_interfaces_ext::admin::assignment_state_stream(
address_state_provider.clone(),
);
futures::pin_mut!(state_stream);
let () = fidl_fuchsia_net_interfaces_ext::admin::wait_assignment_state(
&mut state_stream,
fidl_fuchsia_net_interfaces_admin::AddressAssignmentState::Unavailable,
)
.await
.expect("wait for UNAVAILABLE address assignment state");
let did_enable = interface.control().enable().await.expect("send enable").expect("enable");
assert!(did_enable);
let () = interface.set_link_up(true).await.expect("bring device up");
let () = fidl_fuchsia_net_interfaces_ext::admin::wait_assignment_state(
&mut state_stream,
fidl_fuchsia_net_interfaces_admin::AddressAssignmentState::Assigned,
)
.await
.expect("wait for ASSIGNED address assignment state");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn add_address_success() {
let name = "interfaces_admin_add_address_success";
let sandbox = netemul::TestSandbox::new().expect("new sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let interface_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect(<fidl_fuchsia_net_interfaces::StateMarker as fidl::endpoints::DiscoverableProtocolMarker>::PROTOCOL_NAME);
let interfaces = fidl_fuchsia_net_interfaces_ext::existing(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)
.expect("create watcher event stream"),
HashMap::new(),
)
.await
.expect("initial");
assert_eq!(interfaces.len(), 1);
let id = interfaces
.keys()
.next()
.expect("interface properties map unexpectedly does not include loopback");
let debug_control = realm
.connect_to_protocol::<fidl_fuchsia_net_debug::InterfacesMarker>()
.expect(<fidl_fuchsia_net_debug::InterfacesMarker as fidl::endpoints::DiscoverableProtocolMarker>::PROTOCOL_NAME);
let stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("connect to protocol");
let (control, server) = fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.expect("create Control proxy");
let () = debug_control.get_admin(*id, server).expect("get admin");
const VALID_ADDRESS_PARAMETERS: fidl_fuchsia_net_interfaces_admin::AddressParameters =
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY;
// Adding a valid address succeeds.
{
let subnet = fidl_subnet!("1.1.1.1/32");
let address_state_provider =
interfaces::add_address_wait_assigned(&control, subnet, VALID_ADDRESS_PARAMETERS)
.await
.expect("add address failed unexpectedly");
// Ensure that no route to the subnet was added as a result of adding the address.
assert!(stack
.get_forwarding_table()
.await
.expect("FIDL error calling fuchsia.net.stack/Stack.GetForwardingTable")
.into_iter()
.all(|r| r.subnet != subnet));
let (watcher, server_endpoint) =
::fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces::WatcherMarker>()
.expect("create watcher proxy endpoints");
let () = interface_state
.get_watcher(fidl_fuchsia_net_interfaces::WatcherOptions::EMPTY, server_endpoint)
.expect("error calling fuchsia.net.interfaces/State.GetWatcher");
let mut properties = fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(*id);
let () = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
fidl_fuchsia_net_interfaces_ext::event_stream(watcher.clone()),
&mut properties,
|fidl_fuchsia_net_interfaces_ext::Properties {
id: _,
name: _,
device_class: _,
online: _,
addresses,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
}| {
addresses
.iter()
.any(|&fidl_fuchsia_net_interfaces_ext::Address { addr, valid_until: _ }| {
addr == subnet
})
.then(|| ())
},
)
.await
.expect("wait for address presence");
// Explicitly drop the AddressStateProvider channel to cause address deletion.
std::mem::drop(address_state_provider);
let () = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
fidl_fuchsia_net_interfaces_ext::event_stream(watcher.clone()),
&mut properties,
|fidl_fuchsia_net_interfaces_ext::Properties {
id: _,
name: _,
device_class: _,
online: _,
addresses,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
}| {
addresses
.iter()
.all(|&fidl_fuchsia_net_interfaces_ext::Address { addr, valid_until: _ }| {
addr != subnet
})
.then(|| ())
},
)
.await
.expect("wait for address absence");
}
// Adding a valid address and detaching does not cause the address to be removed.
{
let subnet = fidl_subnet!("2.2.2.2/32");
let address_state_provider =
interfaces::add_address_wait_assigned(&control, subnet, VALID_ADDRESS_PARAMETERS)
.await
.expect("add address failed unexpectedly");
let () = address_state_provider
.detach()
.expect("FIDL error calling fuchsia.net.interfaces.admin/Control.Detach");
std::mem::drop(address_state_provider);
let mut properties = fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(*id);
let () = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)
.expect("create interface event stream"),
&mut properties,
|fidl_fuchsia_net_interfaces_ext::Properties {
id: _,
name: _,
device_class: _,
online: _,
addresses,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
}| {
addresses
.iter()
.all(|&fidl_fuchsia_net_interfaces_ext::Address { addr, valid_until: _ }| {
addr != subnet
})
.then(|| ())
},
)
.map_ok(|()| panic!("address deleted after detaching and closing channel"))
.on_timeout(fuchsia_async::Time::after(fuchsia_zircon::Duration::from_millis(100)), || {
Ok(())
})
.await
.expect("wait for address to not be removed");
}
}
#[variants_test]
async fn device_control_create_interface<N: Netstack>(name: &str) {
// NB: interface names are limited to fuchsia.net.interfaces/INTERFACE_NAME_LENGTH.
const IF_NAME: &'static str = "ctrl_create_if";
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
let endpoint =
sandbox.create_endpoint::<netemul::NetworkDevice, _>(name).await.expect("create endpoint");
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let (device, mut port_id) = endpoint.get_netdevice().await.expect("get netdevice");
let (device_control, device_control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::DeviceControlMarker>()
.expect("create proxy");
let () = installer.install_device(device, device_control_server_end).expect("install device");
let (control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints().expect("create proxy");
let () = device_control
.create_interface(
&mut port_id,
control_server_end,
fidl_fuchsia_net_interfaces_admin::Options {
name: Some(IF_NAME.to_string()),
metric: None,
..fidl_fuchsia_net_interfaces_admin::Options::EMPTY
},
)
.expect("create interface");
let iface_id = control.get_id().await.expect("get id");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let interface_state = fidl_fuchsia_net_interfaces_ext::existing(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create watcher event stream"),
fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(iface_id),
)
.await
.expect("get interface state");
let properties = match interface_state {
fidl_fuchsia_net_interfaces_ext::InterfaceState::Known(properties) => properties,
fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(id) => {
panic!("failed to retrieve new interface with id {}", id)
}
};
assert_eq!(
properties,
fidl_fuchsia_net_interfaces_ext::Properties {
id: iface_id,
name: IF_NAME.to_string(),
device_class: fidl_fuchsia_net_interfaces::DeviceClass::Device(
fidl_fuchsia_hardware_network::DeviceClass::Virtual
),
online: false,
// We haven't enabled the interface, it mustn't have any addresses assigned
// to it yet.
addresses: vec![],
has_default_ipv4_route: false,
has_default_ipv6_route: false
}
);
}
// Tests that when a DeviceControl instance is dropped, all interfaces created
// from it are dropped as well.
#[variants_test]
#[test_case(false; "no_detach")]
#[test_case(true; "detach")]
async fn device_control_owns_interfaces_lifetimes<N: Netstack>(name: &str, detach: bool) {
if detach && N::VERSION == NetstackVersion::Netstack3 {
// TODO(https://fxbug.dev/100867): Run this test when we support
// detaching.
return;
}
let detach_str = if detach { "detach" } else { "no_detach" };
let name = format!("{name}_{detach_str}");
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
// Create tun interfaces directly to attach ports to different interfaces.
let (tun_dev, netdevice_client_end) = create_tun_device();
let (device_control, device_control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::DeviceControlMarker>()
.expect("create proxy");
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let () = installer
.install_device(netdevice_client_end, device_control_server_end)
.expect("install device");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let watcher = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create event stream")
.map(|r| r.expect("watcher error"))
.fuse();
futures::pin_mut!(watcher);
// Consume the watcher until we see the idle event.
let existing = fidl_fuchsia_net_interfaces_ext::existing(
watcher.by_ref().map(Result::<_, fidl::Error>::Ok),
HashMap::new(),
)
.await
.expect("existing");
// Only loopback should exist.
assert_eq!(existing.len(), 1, "unexpected interfaces in existing: {:?}", existing);
const PORT_COUNT: u8 = 5;
let mut interfaces = HashSet::new();
let mut ports_detached_stream = futures::stream::FuturesUnordered::new();
let mut control_proxies = Vec::new();
// NB: For loop here is much more friendly to lifetimes than a closure
// chain.
for index in 1..=PORT_COUNT {
let (iface_id, port, control) = async {
let (port, port_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::PortMarker>()
.expect("create proxy");
let () = tun_dev
.add_port(
fidl_fuchsia_net_tun::DevicePortConfig {
base: Some(fidl_fuchsia_net_tun::BasePortConfig {
id: Some(index),
rx_types: Some(vec![
fidl_fuchsia_hardware_network::FrameType::Ethernet,
]),
tx_types: Some(vec![fidl_fuchsia_hardware_network::FrameTypeSupport {
type_: fidl_fuchsia_hardware_network::FrameType::Ethernet,
features: fidl_fuchsia_hardware_network::FRAME_FEATURES_RAW,
supported_flags: fidl_fuchsia_hardware_network::TxFlags::empty(),
}]),
mtu: Some(netemul::DEFAULT_MTU.into()),
..fidl_fuchsia_net_tun::BasePortConfig::EMPTY
}),
mac: Some(fidl_mac!("02:03:04:05:06:07")),
..fidl_fuchsia_net_tun::DevicePortConfig::EMPTY
},
port_server_end,
)
.expect("add port");
let mut port_id = {
let (device_port, server) =
fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>()
.expect("create endpoints");
let () = port.get_port(server).expect("get port");
device_port.get_info().await.expect("get info").id.expect("missing port id")
};
let (control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.expect("create proxy");
let () = device_control
.create_interface(
&mut port_id,
control_server_end,
fidl_fuchsia_net_interfaces_admin::Options::EMPTY,
)
.expect("create interface");
let iface_id = control.get_id().await.expect("get id");
// Observe interface creation in watcher.
let event = watcher.select_next_some().await;
assert_matches::assert_matches!(
event,
fidl_fuchsia_net_interfaces::Event::Added(
fidl_fuchsia_net_interfaces::Properties { id: Some(id), .. }
) if id == iface_id
);
(iface_id, port, control)
}
.await;
assert!(
interfaces.insert(iface_id),
"unexpected duplicate interface iface_id: {}, interfaces={:?}",
iface_id,
interfaces
);
// Enable the interface and wait for port to be attached.
assert!(control.enable().await.expect("calling enable").expect("enable failed"));
let mut port_has_session_stream = futures::stream::unfold(port, |port| {
port.watch_state().map(move |state| {
let fidl_fuchsia_net_tun::InternalState { mac: _, has_session, .. } =
state.expect("calling watch_state");
Some((has_session.expect("has_session missing from table"), port))
})
});
loop {
if port_has_session_stream.next().await.expect("port stream ended unexpectedly") {
break;
}
}
let port_detached = port_has_session_stream
.filter_map(move |has_session| {
futures::future::ready((!has_session).then(move || index))
})
.into_future()
.map(|(i, _stream)| i.expect("port stream ended unexpectedly"));
let () = ports_detached_stream.push(port_detached);
let () = control_proxies.push(control);
}
let mut control_wait_termination_stream = control_proxies
.into_iter()
.map(|control| control.wait_termination())
.collect::<futures::stream::FuturesUnordered<_>>();
if detach {
// Drop detached device_control and ensure none of the futures resolve.
let () = device_control.detach().expect("detach");
std::mem::drop(device_control);
let watcher_fut = watcher.next().map(|e| panic!("unexpected watcher event {:?}", e));
let ports_fut = ports_detached_stream
.next()
.map(|item| panic!("session detached from port unexpectedly {:?}", item));
let control_closed_fut = control_wait_termination_stream
.next()
.map(|termination| panic!("unexpected control termination event {:?}", termination));
let ((), (), ()) = futures::future::join3(watcher_fut, ports_fut, control_closed_fut)
.on_timeout(
fuchsia_async::Time::after(
netstack_testing_common::ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT,
),
|| ((), (), ()),
)
.await;
} else {
// Drop device_control and wait for futures to resolve.
std::mem::drop(device_control);
let interfaces_removed_fut = async_utils::fold::fold_while(
watcher,
interfaces,
|mut interfaces, event| match event {
fidl_fuchsia_net_interfaces::Event::Removed(id) => {
assert!(interfaces.remove(&id));
futures::future::ready(if interfaces.is_empty() {
async_utils::fold::FoldWhile::Done(())
} else {
async_utils::fold::FoldWhile::Continue(interfaces)
})
}
event => panic!("unexpected event {:?}", event),
},
)
.map(|fold_result| fold_result.short_circuited().expect("watcher ended"));
let ports_are_detached_fut =
ports_detached_stream.map(|_port_index: u8| ()).collect::<()>();
let control_closed_fut = control_wait_termination_stream.for_each(|termination| {
assert_matches::assert_matches!(
termination,
fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortClosed
)
);
futures::future::ready(())
});
let ((), (), ()) = futures::future::join3(
interfaces_removed_fut,
ports_are_detached_fut,
control_closed_fut,
)
.await;
}
}
#[variants_test]
#[test_case(
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::DuplicateName;
"DuplicateName"
)]
#[test_case(
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortAlreadyBound;
"PortAlreadyBound"
)]
#[test_case(fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::BadPort; "BadPort")]
#[test_case(fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortClosed; "PortClosed")]
#[test_case(fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::User; "User")]
async fn control_terminal_events<N: Netstack>(
name: &str,
reason: fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason,
) {
let name = format!("{}_{:?}", name, reason);
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(&name).expect("create realm");
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let (tun_dev, device) = create_tun_device();
const BASE_PORT_ID: u8 = 13;
let base_port_config = fidl_fuchsia_net_tun::BasePortConfig {
id: Some(BASE_PORT_ID),
rx_types: Some(vec![fidl_fuchsia_hardware_network::FrameType::Ethernet]),
tx_types: Some(vec![fidl_fuchsia_hardware_network::FrameTypeSupport {
type_: fidl_fuchsia_hardware_network::FrameType::Ethernet,
features: fidl_fuchsia_hardware_network::FRAME_FEATURES_RAW,
supported_flags: fidl_fuchsia_hardware_network::TxFlags::empty(),
}]),
mtu: Some(netemul::DEFAULT_MTU.into()),
..fidl_fuchsia_net_tun::BasePortConfig::EMPTY
};
let create_port = |config: fidl_fuchsia_net_tun::BasePortConfig| {
let (port, port_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::PortMarker>()
.expect("create proxy");
let () = tun_dev
.add_port(
fidl_fuchsia_net_tun::DevicePortConfig {
base: Some(config),
mac: Some(fidl_mac!("02:aa:bb:cc:dd:ee")),
..fidl_fuchsia_net_tun::DevicePortConfig::EMPTY
},
port_server_end,
)
.expect("add port");
async move {
// Interact with port to make sure it's installed.
let () = port.set_online(false).await.expect("calling set_online");
let (device_port, server) =
fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>()
.expect("create endpoints");
let () = port.get_port(server).expect("get port");
let id = device_port.get_info().await.expect("get info").id.expect("missing port id");
(port, id)
}
};
let (device_control, device_control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::DeviceControlMarker>()
.expect("create proxy");
let () = installer.install_device(device, device_control_server_end).expect("install device");
let create_interface = |mut port_id, options| {
let (control, control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::ControlMarker>()
.expect("create proxy");
let () = device_control
.create_interface(&mut port_id, control_server_end, options)
.expect("create interface");
control
};
enum KeepResource {
Control(fidl_fuchsia_net_interfaces_ext::admin::Control),
Port(fidl_fuchsia_net_tun::PortProxy),
}
let (control, _keep_alive): (_, Vec<KeepResource>) = match reason {
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortAlreadyBound => {
let (port, port_id) = create_port(base_port_config).await;
let control1 = {
let control =
fidl_fuchsia_net_interfaces_ext::admin::Control::new(create_interface(
port_id.clone(),
fidl_fuchsia_net_interfaces_admin::Options::EMPTY,
));
// Verify that interface was created.
let _: u64 = control.get_id().await.expect("get id");
control
};
// Create a new interface with the same port identifier.
let control2 =
create_interface(port_id, fidl_fuchsia_net_interfaces_admin::Options::EMPTY);
(control2, vec![KeepResource::Control(control1), KeepResource::Port(port)])
}
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::DuplicateName => {
if N::VERSION == NetstackVersion::Netstack3 {
// TODO(https://fxbug.dev/84516): Keep track of names properly
// in NS3 and reject duplicate interface names.
return;
}
let (port1, port1_id) = create_port(base_port_config.clone()).await;
let if_name = "test_same_name";
let control1 = {
let control =
fidl_fuchsia_net_interfaces_ext::admin::Control::new(create_interface(
port1_id,
fidl_fuchsia_net_interfaces_admin::Options {
name: Some(if_name.to_string()),
..fidl_fuchsia_net_interfaces_admin::Options::EMPTY
},
));
// Verify that interface was created.
let _: u64 = control.get_id().await.expect("get id");
control
};
// Create a new interface with the same name.
let (port2, port2_id) = create_port(fidl_fuchsia_net_tun::BasePortConfig {
id: Some(BASE_PORT_ID + 1),
..base_port_config
})
.await;
let control2 = create_interface(
port2_id,
fidl_fuchsia_net_interfaces_admin::Options {
name: Some(if_name.to_string()),
..fidl_fuchsia_net_interfaces_admin::Options::EMPTY
},
);
(
control2,
vec![
KeepResource::Control(control1),
KeepResource::Port(port1),
KeepResource::Port(port2),
],
)
}
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::BadPort => {
let (port, port_id) = create_port(fidl_fuchsia_net_tun::BasePortConfig {
// netdevice/client.go only accepts IP devices that support both
// IPv4 and IPv6.
rx_types: Some(vec![fidl_fuchsia_hardware_network::FrameType::Ipv4]),
..base_port_config
})
.await;
let control =
create_interface(port_id, fidl_fuchsia_net_interfaces_admin::Options::EMPTY);
(control, vec![KeepResource::Port(port)])
}
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortClosed => {
// Port closed is equivalent to port doesn't exist.
let control = create_interface(
fidl_fuchsia_hardware_network::PortId { base: BASE_PORT_ID, salt: 0 },
fidl_fuchsia_net_interfaces_admin::Options::EMPTY,
);
(control, vec![])
}
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::User => {
if N::VERSION == NetstackVersion::Netstack3 {
// TODO(https://fxbug.dev/88797): Update this test to observe
// epitaphs on fuchsia.net.debug once Netstack3 supports it.
// It's a bad idea to test this API in terms of the deprecated
// one, and not worth it implementing this machinery in NS3.
return;
}
let (port, port_id) = create_port(base_port_config).await;
let control =
create_interface(port_id, fidl_fuchsia_net_interfaces_admin::Options::EMPTY);
let interface_id = control.get_id().await.expect("get id");
// Remove the interface using legacy API.
let stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("connect to protocol");
let () = stack
.del_ethernet_interface(interface_id)
.await
.expect("calling del_ethernet_interface")
.expect("del_ethernet_interface failed");
(control, vec![KeepResource::Port(port)])
}
unknown_reason => panic!("unknown reason {:?}", unknown_reason),
};
// Observe a terminal event and channel closure.
let got_reason = control
.take_event_stream()
.map_ok(|fidl_fuchsia_net_interfaces_admin::ControlEvent::OnInterfaceRemoved { reason }| {
reason
})
.try_collect::<Vec<_>>()
.await
.expect("waiting for terminal event");
assert_eq!(got_reason, [reason]);
}
// Test that destroying a device causes device control instance to close.
#[variants_test]
async fn device_control_closes_on_device_close<N: Netstack>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
let endpoint =
sandbox.create_endpoint::<netemul::NetworkDevice, _>(name).await.expect("create endpoint");
// Create a watcher, we'll use it to ensure the Netstack didn't crash.
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let watcher = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create watcher");
futures::pin_mut!(watcher);
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let (device, mut port_id) = endpoint.get_netdevice().await.expect("get netdevice");
let (device_control, device_control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::DeviceControlMarker>()
.expect("create proxy");
let () = installer.install_device(device, device_control_server_end).expect("install device");
// Create an interface and get its identifier to ensure the device is
// installed.
let (control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints().expect("create proxy");
let () = device_control
.create_interface(
&mut port_id,
control_server_end,
fidl_fuchsia_net_interfaces_admin::Options::EMPTY,
)
.expect("create interface");
let _iface_id: u64 = control.get_id().await.expect("get id");
// Drop the device and observe the control channel closing because the
// device was destroyed.
std::mem::drop(endpoint);
assert_matches::assert_matches!(device_control.take_event_stream().next().await, None);
// The channel could've been closed by a Netstack crash, consume from the
// watcher to ensure that's not the case.
let _: fidl_fuchsia_net_interfaces::Event =
watcher.try_next().await.expect("watcher error").expect("watcher ended uexpectedly");
}
// TODO(https://fxbug.dev/48853): Enable in netstack3 once netdevice support is
// fully in.
// Tests that interfaces created through installer have a valid datapath.
#[fuchsia_async::run_singlethreaded(test)]
async fn installer_creates_datapath() {
const SUBNET: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.0/24");
const ALICE: &'static str = "alice";
const ALICE_IP: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.1/24");
const BOB: &'static str = "bob";
const BOB_IP: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.2/24");
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let network = sandbox.create_network("net").await.expect("create network");
struct RealmInfo<'a> {
realm: netemul::TestRealm<'a>,
endpoint: netemul::TestEndpoint<'a>,
device_control: fidl_fuchsia_net_interfaces_admin::DeviceControlProxy,
control: fidl_fuchsia_net_interfaces_ext::admin::Control,
address_state_provider: fidl_fuchsia_net_interfaces_admin::AddressStateProviderProxy,
}
let realms_stream =
futures::stream::iter([(ALICE, ALICE_IP), (BOB, BOB_IP)]).then(|(name, addr)| {
let sandbox = &sandbox;
let network = &network;
async move {
let realm =
sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let endpoint = network
.create_endpoint::<netemul::NetworkDevice, _>(name)
.await
.expect("create endpoint");
let () = endpoint.set_link_up(true).await.expect("set link up");
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let (device, mut port_id) = endpoint.get_netdevice().await.expect("get netdevice");
let (device_control, device_control_server_end) = fidl::endpoints::create_proxy::<
fidl_fuchsia_net_interfaces_admin::DeviceControlMarker,
>()
.expect("create proxy");
let () = installer
.install_device(device, device_control_server_end)
.expect("install device");
let (control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.expect("create proxy");
let () = device_control
.create_interface(
&mut port_id,
control_server_end,
fidl_fuchsia_net_interfaces_admin::Options {
name: Some(name.to_string()),
metric: None,
..fidl_fuchsia_net_interfaces_admin::Options::EMPTY
},
)
.expect("create interface");
let iface_id = control.get_id().await.expect("get id");
let did_enable =
control.enable().await.expect("calling enable").expect("enable failed");
assert!(did_enable);
let address_state_provider = interfaces::add_address_wait_assigned(
&control,
addr,
fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY,
)
.await
.expect("add address");
// Adding addresses through Control does not add the subnet
// routes.
let stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("connect to protocol");
let () = stack
.add_forwarding_entry(&mut fidl_fuchsia_net_stack::ForwardingEntry {
subnet: SUBNET,
device_id: iface_id,
next_hop: None,
metric: 0,
})
.await
.expect("send add route")
.expect("add route");
RealmInfo { realm, endpoint, device_control, control, address_state_provider }
}
});
futures::pin_mut!(realms_stream);
// Can't drop any of the fields of RealmInfo to maintain objects alive.
let RealmInfo {
realm: alice_realm,
endpoint: _alice_endpoint,
device_control: _alice_device_control,
control: _alice_control,
address_state_provider: _alice_asp,
} = realms_stream.next().await.expect("create alice realm");
let RealmInfo {
realm: bob_realm,
endpoint: _bob_endpoint,
device_control: _bob_device_control,
control: _bob_control,
address_state_provider: _bob_asp,
} = realms_stream.next().await.expect("create bob realm");
let fidl_fuchsia_net_ext::IpAddress(std_addr) = BOB_IP.addr.into();
let bob_addr = std::net::SocketAddr::new(std_addr, 8080);
let alice_sock =
fuchsia_async::net::UdpSocket::bind_in_realm(&alice_realm, std_socket_addr!("0.0.0.0:0"))
.await
.expect("bind alice sock");
let bob_sock = fuchsia_async::net::UdpSocket::bind_in_realm(&bob_realm, bob_addr)
.await
.expect("bind bob sock");
const PAYLOAD: &'static str = "hello bob";
let payload_bytes = PAYLOAD.as_bytes();
assert_eq!(
alice_sock.send_to(payload_bytes, bob_addr).await.expect("sendto"),
payload_bytes.len()
);
let mut buff = [0; PAYLOAD.len() + 1];
let (read, from) = bob_sock.recv_from(&mut buff[..]).await.expect("recvfrom");
{
let fidl_fuchsia_net_ext::IpAddress(std_addr) = ALICE_IP.addr.into();
assert_eq!(from.ip(), std_addr);
}
assert_eq!(read, payload_bytes.len());
assert_eq!(&buff[..read], payload_bytes);
}
// TODO(https://fxbug.dev/48853): Enable in netstack3 once we can observe link state.
#[fuchsia_async::run_singlethreaded(test)]
async fn control_enable_disable() {
let name = "control_enable_disable";
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let endpoint =
sandbox.create_endpoint::<netemul::NetworkDevice, _>(name).await.expect("create endpoint");
let () = endpoint.set_link_up(true).await.expect("set link up");
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let (device, mut port_id) = endpoint.get_netdevice().await.expect("get netdevice");
let (device_control, device_control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::DeviceControlMarker>()
.expect("create proxy");
let () = installer.install_device(device, device_control_server_end).expect("install device");
let (control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints().expect("create proxy");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let watcher = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create event stream")
.map(|r| r.expect("watcher error"))
.fuse();
futures::pin_mut!(watcher);
// Consume the watcher until we see the idle event.
let existing = fidl_fuchsia_net_interfaces_ext::existing(
watcher.by_ref().map(Result::<_, fidl::Error>::Ok),
HashMap::new(),
)
.await
.expect("existing");
// Only loopback should exist.
assert_eq!(existing.len(), 1, "unexpected interfaces in existing: {:?}", existing);
let () = device_control
.create_interface(
&mut port_id,
control_server_end,
fidl_fuchsia_net_interfaces_admin::Options::EMPTY,
)
.expect("create interface");
let iface_id = control.get_id().await.expect("get id");
// Expect the added event.
let event = watcher.select_next_some().await;
assert_matches::assert_matches!(event,
fidl_fuchsia_net_interfaces::Event::Added(
fidl_fuchsia_net_interfaces::Properties {
id: Some(id), online: Some(online), ..
},
) if id == iface_id && !online
);
// Starts disabled, it's a no-op.
let did_disable = control.disable().await.expect("calling disable").expect("disable failed");
assert!(!did_disable);
// Enable and observe online.
let did_enable = control.enable().await.expect("calling enable").expect("enable failed");
assert!(did_enable);
let () = watcher
.by_ref()
.filter_map(|event| match event {
fidl_fuchsia_net_interfaces::Event::Changed(
fidl_fuchsia_net_interfaces::Properties { id: Some(id), online, .. },
) if id == iface_id => {
futures::future::ready(online.and_then(|online| online.then(|| ())))
}
event => panic!("unexpected event {:?}", event),
})
.select_next_some()
.await;
// Enable again should be no-op.
let did_enable = control.enable().await.expect("calling enable").expect("enable failed");
assert!(!did_enable);
// Disable again, expect offline.
let did_disable = control.disable().await.expect("calling disable").expect("disable failed");
assert!(did_disable);
let () = watcher
.filter_map(|event| match event {
fidl_fuchsia_net_interfaces::Event::Changed(
fidl_fuchsia_net_interfaces::Properties { id: Some(id), online, .. },
) if id == iface_id => {
futures::future::ready(online.and_then(|online| (!online).then(|| ())))
}
event => panic!("unexpected event {:?}", event),
})
.select_next_some()
.await;
}
#[variants_test]
#[test_case(false; "no_detach")]
#[test_case(true; "detach")]
async fn control_owns_interface_lifetime<N: Netstack>(name: &str, detach: bool) {
if detach && N::VERSION == NetstackVersion::Netstack3 {
// TODO(https://fxbug.dev/100867): Enable in Netstack3 once detaching is
// supported.
return;
}
let detach_str = if detach { "detach" } else { "no_detach" };
let name = format!("{}_{}", name, detach_str);
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(&name).expect("create realm");
let endpoint =
sandbox.create_endpoint::<netemul::NetworkDevice, _>(&name).await.expect("create endpoint");
let installer = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>()
.expect("connect to protocol");
let (device, mut port_id) = endpoint.get_netdevice().await.expect("get netdevice");
let (device_control, device_control_server_end) =
fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::DeviceControlMarker>()
.expect("create proxy");
let () = installer.install_device(device, device_control_server_end).expect("install device");
let (control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints().expect("create proxy");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let watcher = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create event stream")
.map(|r| r.expect("watcher error"))
.fuse();
futures::pin_mut!(watcher);
// Consume the watcher until we see the idle event.
let existing = fidl_fuchsia_net_interfaces_ext::existing(
watcher.by_ref().map(Result::<_, fidl::Error>::Ok),
HashMap::new(),
)
.await
.expect("existing");
// Only loopback should exist.
assert_eq!(existing.len(), 1, "unexpected interfaces in existing: {:?}", existing);
let () = device_control
.create_interface(
&mut port_id,
control_server_end,
fidl_fuchsia_net_interfaces_admin::Options::EMPTY,
)
.expect("create interface");
let iface_id = control.get_id().await.expect("get id");
// Expect the added event.
let event = watcher.select_next_some().await;
assert_matches::assert_matches!(event,
fidl_fuchsia_net_interfaces::Event::Added(
fidl_fuchsia_net_interfaces::Properties {
id: Some(id), ..
},
) if id == iface_id
);
let debug_control = if N::VERSION == NetstackVersion::Netstack3 {
// TODO(https://fxbug.dev/88797): Observe termination through the debug
// handle once we support it. For now, just check that the interface is
// removed on detach
None
} else {
let debug = realm
.connect_to_protocol::<fidl_fuchsia_net_debug::InterfacesMarker>()
.expect("connect to protocol");
let (debug_control, control_server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.expect("create proxy");
let () = debug.get_admin(iface_id, control_server_end).expect("get admin");
let same_iface_id = debug_control.get_id().await.expect("get id");
assert_eq!(same_iface_id, iface_id);
Some(debug_control)
};
if detach {
let () = control.detach().expect("detach");
// Drop control and expect the interface to NOT be removed.
std::mem::drop(control);
let watcher_fut =
watcher.select_next_some().map(|event| panic!("unexpected event {:?}", event));
let debug_control_fut = if let Some(debug_control) = debug_control {
debug_control
.wait_termination()
.map(|event| panic!("unexpected termination {:?}", event))
.left_future()
} else {
futures::future::pending().right_future()
};
let ((), ()) = futures::future::join(watcher_fut, debug_control_fut)
.on_timeout(
fuchsia_async::Time::after(
netstack_testing_common::ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT,
),
|| ((), ()),
)
.await;
} else {
// Drop control and expect the interface to be removed.
std::mem::drop(control);
let event = watcher.select_next_some().await;
assert_matches::assert_matches!(event,
fidl_fuchsia_net_interfaces::Event::Removed(id) if id == iface_id
);
if let Some(debug_control) = debug_control {
// The debug control channel is a weak ref, it didn't prevent destruction,
// but is closed now.
assert_matches::assert_matches!(
debug_control.wait_termination().await,
fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(
fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::User
)
);
}
}
}
#[variants_test]
async fn get_set_forwarding<E: netemul::Endpoint>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create netstack realm");
let net = sandbox.create_network("net").await.expect("create network");
let iface1 = realm.join_network::<E, _>(&net, "iface1").await.expect("create iface1");
let iface2 = realm.join_network::<E, _>(&net, "iface2").await.expect("create iface1");
#[derive(Debug, PartialEq)]
struct IpForwarding {
v4: Option<bool>,
v6: Option<bool>,
}
async fn get_ip_forwarding(iface: &netemul::TestInterface<'_>) -> IpForwarding {
let finterfaces_admin::Configuration { ipv4: ipv4_config, ipv6: ipv6_config, .. } = iface
.control()
.get_configuration()
.await
.expect("get_configuration FIDL error")
.expect("error getting configuration");
let finterfaces_admin::Ipv4Configuration { forwarding: v4, .. } =
ipv4_config.expect("IPv4 configuration should be populated");
let finterfaces_admin::Ipv6Configuration { forwarding: v6, .. } =
ipv6_config.expect("IPv6 configuration should be populated");
IpForwarding { v4, v6 }
}
// Initially, interfaces have IP forwarding disabled.
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(false), v6: Some(false) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
/// Sets the forwarding configuration and checks the configuration before
/// the update was applied.
async fn set_ip_forwarding(
iface: &netemul::TestInterface<'_>,
enable: IpForwarding,
expected_previous: IpForwarding,
) {
let config_with_ip_forwarding_set =
|IpForwarding { v4, v6 }| finterfaces_admin::Configuration {
ipv4: Some(finterfaces_admin::Ipv4Configuration {
forwarding: v4,
..finterfaces_admin::Ipv4Configuration::EMPTY
}),
ipv6: Some(finterfaces_admin::Ipv6Configuration {
forwarding: v6,
..finterfaces_admin::Ipv6Configuration::EMPTY
}),
..finterfaces_admin::Configuration::EMPTY
};
let configuration = iface
.control()
.set_configuration(config_with_ip_forwarding_set(enable))
.await
.expect("set_configuration FIDL error")
.expect("error setting configuration");
assert_eq!(configuration, config_with_ip_forwarding_set(expected_previous))
}
// Set nothing.
set_ip_forwarding(
&iface1,
IpForwarding { v4: None, v6: None },
IpForwarding { v4: None, v6: None },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(false), v6: Some(false) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
// Should do nothing since the interface's IP forwarding is already
// disabled.
set_ip_forwarding(
&iface1,
IpForwarding { v4: Some(false), v6: Some(false) },
IpForwarding { v4: Some(false), v6: Some(false) },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(false), v6: Some(false) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
// Enabling an interface's IP forwarding should not affect another
// interface/protocol.
set_ip_forwarding(
&iface1,
IpForwarding { v4: Some(true), v6: None },
IpForwarding { v4: Some(false), v6: None },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(true), v6: Some(false) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
set_ip_forwarding(
&iface1,
IpForwarding { v4: None, v6: Some(true) },
IpForwarding { v4: None, v6: Some(false) },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(true), v6: Some(true) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
// Enabling IP forwarding again should be a no-op.
set_ip_forwarding(
&iface1,
IpForwarding { v4: Some(true), v6: Some(true) },
IpForwarding { v4: Some(true), v6: Some(true) },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(true), v6: Some(true) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
// Enabling an interface's IP forwarding should not affect another
// interface.
set_ip_forwarding(
&iface2,
IpForwarding { v4: Some(true), v6: Some(true) },
IpForwarding { v4: Some(false), v6: Some(false) },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(true), v6: Some(true) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(true), v6: Some(true) });
// Disabling an interface's IP forwarding should not affect another
// interface/protocol.
set_ip_forwarding(
&iface2,
IpForwarding { v4: Some(false), v6: Some(true) },
IpForwarding { v4: Some(true), v6: Some(true) },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(true), v6: Some(true) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(true) });
// Disabling IP forwarding again should be a no-op if already disabled.
set_ip_forwarding(
&iface2,
IpForwarding { v4: Some(false), v6: Some(false) },
IpForwarding { v4: Some(false), v6: Some(true) },
)
.await;
assert_eq!(get_ip_forwarding(&iface1).await, IpForwarding { v4: Some(true), v6: Some(true) });
assert_eq!(get_ip_forwarding(&iface2).await, IpForwarding { v4: Some(false), v6: Some(false) });
}