| // 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 std::borrow::Cow; |
| use std::collections::HashMap; |
| |
| use anyhow::Result; |
| use component_events::events::EventStream; |
| use derivative::Derivative; |
| use fidl_fuchsia_component as fcomponent; |
| use fidl_fuchsia_io as fio; |
| use fidl_fuchsia_net as fnet; |
| use fidl_fuchsia_net_dhcpv6 as fnet_dhcpv6; |
| use fidl_fuchsia_net_dhcpv6_ext as fnet_dhcpv6_ext; |
| use fidl_fuchsia_net_ext as fnet_ext; |
| use fidl_fuchsia_net_interfaces as fnet_interfaces; |
| use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext; |
| use fidl_fuchsia_net_root as fnet_root; |
| use fidl_fuchsia_net_test_realm as fntr; |
| use fidl_fuchsia_posix_socket as fposix_socket; |
| use futures::StreamExt as _; |
| use net_declare::{fidl_ip_v4, fidl_ip_v6, fidl_mac, fidl_socket_addr, fidl_subnet}; |
| use netstack_testing_common::interfaces::TestInterfaceExt as _; |
| use netstack_testing_common::packets; |
| use netstack_testing_common::realms::{KnownServiceProvider, Netstack3, TestSandboxExt as _}; |
| use netstack_testing_macros::netstack_test; |
| use packet::ParsablePacket as _; |
| use std::convert::TryInto as _; |
| use std::pin::pin; |
| use test_case::test_case; |
| |
| const INTERFACE1_MAC_ADDRESS: fnet::MacAddress = fidl_mac!("02:03:04:05:06:07"); |
| const INTERFACE2_MAC_ADDRESS: fnet::MacAddress = fidl_mac!("06:07:08:09:10:11"); |
| const INTERFACE1_NAME: &'static str = "iface1"; |
| const INTERFACE2_NAME: &'static str = "iface2"; |
| const EXPECTED_INTERFACE_NAME: &'static str = "added-interface"; |
| const IPV4_STUB_URL: &'static str = "#meta/unreliable-echo-v4.cm"; |
| const IPV6_STUB_URL: &'static str = "#meta/unreliable-echo-v6.cm"; |
| const TEST_STUB_MONIKER_REGEX: &'static str = ".*/stubs:test-stub$"; |
| |
| const DEFAULT_IPV4_TARGET_SUBNET: fnet::Subnet = fidl_subnet!("192.168.255.1/16"); |
| const DEFAULT_IPV6_TARGET_SUBNET: fnet::Subnet = fidl_subnet!("3080::2/64"); |
| const DEFAULT_IPV6_LINK_LOCAL_TARGET_SUBNET: fnet::Subnet = fidl_subnet!("fe80::1/64"); |
| const DEFAULT_IPV4_SOURCE_SUBNET: fnet::Subnet = fidl_subnet!("192.168.254.1/16"); |
| const DEFAULT_IPV6_SOURCE_SUBNET: fnet::Subnet = fidl_subnet!("3080::1/64"); |
| const DEFAULT_IPV6_LINK_LOCAL_SOURCE_ADDR: fnet::Ipv6Address = fidl_ip_v6!("fe80::2"); |
| const DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET: fnet::Subnet = fnet::Subnet { |
| addr: fnet::IpAddress::Ipv6(DEFAULT_IPV6_LINK_LOCAL_SOURCE_ADDR), |
| prefix_len: 64, |
| }; |
| |
| const DURATION_FIVE_MINUTES: zx::MonotonicDuration = zx::MonotonicDuration::from_minutes(5); |
| const MINIMUM_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(1); |
| const NO_WAIT_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(0); |
| const DEFAULT_PAYLOAD_LENGTH: u16 = 100; |
| const NON_EXISTENT_INTERFACE_NAME: &'static str = "non_existent"; |
| |
| const DEFAULT_IPV4_MULTICAST_ADDRESS: fnet::Ipv4Address = fidl_ip_v4!("224.1.2.3"); |
| const DEFAULT_IPV6_MULTICAST_ADDRESS: fnet::Ipv6Address = fidl_ip_v6!("ff02::3"); |
| const SOLICITED_NODE_MULTICAST_ADDRESS_PREFIX: net_types::ip::Subnet<net_types::ip::Ipv6Addr> = unsafe { |
| net_types::ip::Subnet::new_unchecked( |
| net_types::ip::Ipv6Addr::new([0xff02, 0, 0, 0, 0, 0x0001, 0xff00, 0]), |
| 104, |
| ) |
| }; |
| const DEFAULT_INTERFACE_ID: u64 = 77; |
| |
| /// Creates a `netemul::TestRealm` with a Netstack3 instance and the Network |
| /// Test Realm. |
| fn create_netstack_realm<'a>( |
| name: impl Into<Cow<'a, str>>, |
| sandbox: &'a netemul::TestSandbox, |
| ) -> Result<netemul::TestRealm<'a>> { |
| // NOTE: To simplify the tests and reduce the number of dependencies, netcfg |
| // is intentionally omitted from the `KnownServiceProvider` list below. |
| // Instead, it is expected that tests will manually register interfaces with |
| // the system's Netstack as needed. |
| sandbox.create_netstack_realm_with::<Netstack3, _, _>( |
| name, |
| &[KnownServiceProvider::NetworkTestRealm { require_outer_netstack: true }], |
| ) |
| } |
| |
| /// Verifies that an interface with `interface_name` exists and has the provided |
| /// `expected_online_status`. |
| /// |
| /// Note that this function will not return until the `expected_online_status` |
| /// is observed. |
| async fn wait_interface_online_status<'a>( |
| interface_name: &'a str, |
| expected_online_status: bool, |
| state_proxy: &'a fnet_interfaces::StateProxy, |
| ) { |
| let id = get_interface_id(interface_name, state_proxy).await.unwrap_or_else(|| { |
| panic!("failed to find interface with name {}", interface_name); |
| }); |
| fnet_interfaces_ext::wait_interface_with_id( |
| fnet_interfaces_ext::event_stream_from_state::<fnet_interfaces_ext::DefaultInterest>( |
| state_proxy, |
| Default::default(), |
| ) |
| .expect("watcher creation failed"), |
| &mut fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(id), |
| |properties_and_state| { |
| (expected_online_status == properties_and_state.properties.online).then(|| ()) |
| }, |
| ) |
| .await |
| .expect("wait for interface failed"); |
| } |
| |
| /// Verifies that an interface with `interface_name` does not exist. |
| async fn verify_interface_not_exist<'a>( |
| interface_name: &'a str, |
| state_proxy: &'a fnet_interfaces::StateProxy, |
| ) { |
| assert_eq!(get_interface_id(interface_name, state_proxy).await, None); |
| } |
| |
| /// Returns the id for the interface with `interface_name`. |
| /// |
| /// If the interface is not found then, None is returned. |
| async fn get_interface_id<'a>( |
| interface_name: &'a str, |
| state_proxy: &'a fnet_interfaces::StateProxy, |
| ) -> Option<u64> { |
| network_test_realm::get_interface_id(interface_name, state_proxy) |
| .await |
| .expect("failed to obtain interface id") |
| } |
| |
| /// Returns the id of the hermetic Netstack interface with `interface_name`. |
| /// |
| /// Panics if the interface could not be found. |
| async fn expect_hermetic_interface_id<'a>( |
| interface_name: &'a str, |
| realm: &netemul::TestRealm<'a>, |
| ) -> u64 { |
| let state_proxy = |
| connect_to_hermetic_network_realm_protocol::<fnet_interfaces::StateMarker>(realm).await; |
| get_interface_id(interface_name, &state_proxy).await.unwrap_or_else(|| { |
| panic!("failed to find hermetic Netstack interface with name {}", interface_name); |
| }) |
| } |
| |
| /// Connects to a protocol within the hermetic network realm. |
| async fn connect_to_hermetic_network_realm_protocol< |
| P: fidl::endpoints::DiscoverableProtocolMarker, |
| >( |
| realm: &netemul::TestRealm<'_>, |
| ) -> P::Proxy { |
| let directory_proxy = open_hermetic_network_realm_exposed_directory(realm).await; |
| fuchsia_component::client::connect_to_protocol_at_dir_root::<P>(&directory_proxy) |
| .unwrap_or_else(|e| { |
| panic!( |
| "failed to connect to hermetic network realm protocol {} with error: {:?}", |
| P::PROTOCOL_NAME, |
| e |
| ) |
| }) |
| } |
| |
| /// Opens the exposed directory that corresponds to the hermetic network realm. |
| /// |
| /// An error will be returned if the realm does not exist. |
| async fn open_hermetic_network_realm_exposed_directory( |
| realm: &netemul::TestRealm<'_>, |
| ) -> fio::DirectoryProxy { |
| let realm_proxy = realm |
| .connect_to_protocol::<fcomponent::RealmMarker>() |
| .expect("failed to connect to realm protocol"); |
| let (directory_proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>(); |
| let child_ref = network_test_realm::create_hermetic_network_realm_child_ref(); |
| realm_proxy |
| .open_exposed_dir(&child_ref, server_end) |
| .await |
| .expect("open_exposed_dir failed") |
| .expect("open_exposed_dir error"); |
| directory_proxy |
| } |
| |
| /// Returns true if the hermetic network realm exists. |
| async fn has_hermetic_network_realm(realm: &netemul::TestRealm<'_>) -> bool { |
| let realm_proxy = realm |
| .connect_to_protocol::<fcomponent::RealmMarker>() |
| .expect("failed to connect to realm protocol"); |
| network_test_realm::has_hermetic_network_realm(&realm_proxy) |
| .await |
| .expect("failed to check for hermetic network realm") |
| } |
| |
| async fn has_stub(realm: &netemul::TestRealm<'_>) -> bool { |
| let realm_proxy = |
| connect_to_hermetic_network_realm_protocol::<fcomponent::RealmMarker>(realm).await; |
| network_test_realm::has_stub(&realm_proxy).await.expect("failed to check for stub") |
| } |
| |
| /// Adds an enabled interface to the realm's Netstack with the provided |
| /// `mac_address` and `name`. |
| /// |
| /// Note that the Network Test Realm will only consider the interface to be |
| /// valid if it is also present in devfs. |
| async fn add_interface_to_netstack<'a>( |
| mac_address: fnet::MacAddress, |
| name: &'a str, |
| sandbox: &'a netemul::TestSandbox, |
| realm: &'a netemul::TestRealm<'a>, |
| ) -> netemul::TestInterface<'a> { |
| let endpoint = sandbox |
| .create_endpoint_with( |
| name, |
| netemul::new_endpoint_config(netemul::DEFAULT_MTU, Some(mac_address)), |
| ) |
| .await |
| .expect("failed to create endpoint"); |
| realm |
| .add_virtual_device(&endpoint, netemul::devfs_device_path(name).as_path()) |
| .await |
| .expect("failed to add virtual device"); |
| // Note that calling `install_endpoint` also enables the interface. |
| realm |
| .install_endpoint( |
| endpoint, |
| netemul::InterfaceConfig { name: Some(name.into()), ..Default::default() }, |
| ) |
| .await |
| .expect("failed to install endpoint") |
| } |
| |
| /// Adds the address from the specified `subnet` to the hermetic Netstack |
| /// interface that has the provided `interface_name`. |
| /// |
| /// A forwarding entry is also added for the relevant interface and the provided |
| /// `subnet`. |
| async fn add_address_to_hermetic_interface( |
| interface_name: &str, |
| subnet: fnet::Subnet, |
| realm: &netemul::TestRealm<'_>, |
| ) { |
| let state_proxy = |
| connect_to_hermetic_network_realm_protocol::<fnet_interfaces::StateMarker>(realm).await; |
| let id = get_interface_id(interface_name, &state_proxy).await.unwrap_or_else(|| { |
| panic!("failed to find interface with name {}", interface_name); |
| }); |
| let interfaces_proxy = |
| connect_to_hermetic_network_realm_protocol::<fnet_root::InterfacesMarker>(realm).await; |
| let (control, server_end) = |
| fnet_interfaces_ext::admin::Control::create_endpoints().expect("create_endpoints failed"); |
| interfaces_proxy.get_admin(id, server_end).expect("get_admin failed"); |
| |
| let address_state_provider = netstack_testing_common::interfaces::add_address_wait_assigned( |
| &control, |
| subnet, |
| fidl_fuchsia_net_interfaces_admin::AddressParameters { |
| add_subnet_route: Some(true), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("add_address_wait_assigned failed"); |
| |
| // Allow the address to live beyond the `address_state_provider` handle. |
| address_state_provider.detach().expect("detatch failed"); |
| } |
| |
| /// Adds an interface to the hermetic Netstack with `interface_name` and |
| /// `mac_address`. |
| /// |
| /// The added interface is assigned a static IP address based on `subnet`. |
| /// Additionally, the interface joins the provided `network`. |
| async fn join_network_with_hermetic_netstack<'a>( |
| realm: &'a netemul::TestRealm<'a>, |
| network: &'a netemul::TestNetwork<'a>, |
| network_test_realm: &'a fntr::ControllerProxy, |
| interface_name: &'a str, |
| mac_address: fnet::MacAddress, |
| subnet: fnet::Subnet, |
| ) -> netemul::TestInterface<'a> { |
| let interface = realm |
| .join_network_with( |
| &network, |
| interface_name, |
| netemul::new_endpoint_config(netemul::DEFAULT_MTU, Some(mac_address)), |
| Default::default(), |
| ) |
| .await |
| .expect("join_network failed"); |
| realm |
| .add_virtual_device( |
| interface.endpoint(), |
| netemul::devfs_device_path(interface_name).as_path(), |
| ) |
| .await |
| .expect("failed to add the device"); |
| |
| network_test_realm |
| .add_interface(&mac_address, interface_name, /* wait_any_ip_address= */ false) |
| .await |
| .expect("add_interface failed") |
| .expect("add_interface error"); |
| |
| add_address_to_hermetic_interface(interface_name, subnet, realm).await; |
| interface |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn start_hermetic_network_realm(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| assert!(has_hermetic_network_realm(&realm).await); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn start_hermetic_network_realm_replaces_existing_realm( |
| name: &str, |
| sub_name: &str, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE1_MAC_ADDRESS, INTERFACE1_NAME, &sandbox, &realm).await; |
| |
| network_test_realm |
| .add_interface( |
| &INTERFACE1_MAC_ADDRESS, |
| EXPECTED_INTERFACE_NAME, |
| /* wait_any_ip_address= */ false, |
| ) |
| .await |
| .expect("add_interface failed") |
| .expect("add_interface error"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let hermetic_network_state_proxy = |
| connect_to_hermetic_network_realm_protocol::<fnet_interfaces::StateMarker>(&realm).await; |
| |
| // The Netstack in the replaced hermetic network realm should not have the |
| // previously attached interface. |
| verify_interface_not_exist(EXPECTED_INTERFACE_NAME, &hermetic_network_state_proxy).await; |
| |
| assert!(has_hermetic_network_realm(&realm).await); |
| } |
| |
| #[netstack_test] |
| #[test_case("no_wait_any_ip_address_netstack2", false, fntr::Netstack::V2)] |
| #[test_case("wait_any_ip_address_netstack2", true, fntr::Netstack::V2)] |
| #[test_case("no_wait_any_ip_address_netstack3", false, fntr::Netstack::V3)] |
| #[test_case("wait_any_ip_address_netstack3", true, fntr::Netstack::V3)] |
| async fn add_interface( |
| name: &str, |
| sub_name: &str, |
| wait_any_ip_address: bool, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE1_MAC_ADDRESS, INTERFACE1_NAME, &sandbox, &realm).await; |
| |
| let _interface: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE2_MAC_ADDRESS, INTERFACE2_NAME, &sandbox, &realm).await; |
| |
| let hermetic_network_state_proxy = |
| connect_to_hermetic_network_realm_protocol::<fnet_interfaces::StateMarker>(&realm).await; |
| |
| network_test_realm |
| .add_interface(&INTERFACE1_MAC_ADDRESS, EXPECTED_INTERFACE_NAME, wait_any_ip_address) |
| .await |
| .expect("add_interface failed") |
| .expect("add_interface error"); |
| |
| if wait_any_ip_address { |
| let ifaces_state = fnet_interfaces_ext::existing( |
| fnet_interfaces_ext::event_stream_from_state::<fnet_interfaces_ext::DefaultInterest>( |
| &hermetic_network_state_proxy, |
| Default::default(), |
| ) |
| .expect("failed getting interfaces event stream"), |
| HashMap::<u64, fnet_interfaces_ext::PropertiesAndState<(), _>>::new(), |
| ) |
| .await |
| .expect("failed getting existing interfaces state"); |
| |
| let iface = ifaces_state |
| .values() |
| .find(|iface| iface.properties.name == EXPECTED_INTERFACE_NAME) |
| .unwrap_or_else(|| panic!("no interface with name {}", EXPECTED_INTERFACE_NAME)); |
| |
| assert!( |
| !iface.properties.addresses.is_empty(), |
| "interface {:?} has no IP addresses; expected to have waited for autoconfigured IP \ |
| address", |
| iface |
| ); |
| } |
| |
| // An interface with a name of `EXPECTED_INTERFACE_NAME` should be enabled and |
| // present in the hermetic Netstack. |
| wait_interface_online_status( |
| EXPECTED_INTERFACE_NAME, |
| true, /* expected_online_status */ |
| &hermetic_network_state_proxy, |
| ) |
| .await; |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn add_interface_already_exists(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface1: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE1_MAC_ADDRESS, INTERFACE1_NAME, &sandbox, &realm).await; |
| |
| let _interface2: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE2_MAC_ADDRESS, INTERFACE2_NAME, &sandbox, &realm).await; |
| |
| network_test_realm |
| .add_interface( |
| &INTERFACE1_MAC_ADDRESS, |
| INTERFACE1_NAME, |
| /* wait_any_ip_address= */ false, |
| ) |
| .await |
| .expect("add_interface failed") |
| .expect("add_interface error"); |
| |
| assert_eq!( |
| network_test_realm |
| .add_interface( |
| &INTERFACE2_MAC_ADDRESS, |
| INTERFACE1_NAME, |
| /* wait_any_ip_address= */ false |
| ) |
| .await |
| .expect("add_interface failed"), |
| Err(fntr::Error::AlreadyExists) |
| ); |
| } |
| |
| // Tests the case where the MAC address provided to `Controller.AddInterface` |
| // does not match any of the interfaces on the system. |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn add_interface_with_no_matching_interface( |
| name: &str, |
| sub_name: &str, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE1_MAC_ADDRESS, INTERFACE1_NAME, &sandbox, &realm).await; |
| |
| // `non_matching_mac_address` doesn't match any of the MAC addresses for |
| // interfaces owned by the system's Netstack. |
| let non_matching_mac_address = fidl_mac!("aa:bb:cc:dd:ee:ff"); |
| assert_eq!( |
| network_test_realm |
| .add_interface( |
| &non_matching_mac_address, |
| EXPECTED_INTERFACE_NAME, |
| /* wait_any_ip_address= */ false |
| ) |
| .await |
| .expect("failed to add interface to hermetic netstack"), |
| Err(fntr::Error::InterfaceNotFound) |
| ); |
| } |
| |
| // Tests the case where the MAC address provided to `Controller.AddInterface` |
| // matches an interface in devfs, but not in the system Netstack. |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn add_interface_with_no_matching_interface_in_netstack( |
| name: &str, |
| sub_name: &str, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| // The Network Test Realm requires that the matching interface be present in |
| // both the system's Netstack and devfs. In this case, it is only present in |
| // devfs. |
| assert_eq!( |
| network_test_realm |
| .add_interface( |
| &INTERFACE1_MAC_ADDRESS, |
| EXPECTED_INTERFACE_NAME, |
| /* wait_any_ip_address= */ false |
| ) |
| .await |
| .expect("failed to add interface to hermetic netstack"), |
| Err(fntr::Error::InterfaceNotFound) |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn stop_hermetic_network_realm(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface: netemul::TestInterface<'_> = |
| add_interface_to_netstack(INTERFACE1_MAC_ADDRESS, INTERFACE1_NAME, &sandbox, &realm).await; |
| |
| network_test_realm |
| .add_interface( |
| &INTERFACE1_MAC_ADDRESS, |
| EXPECTED_INTERFACE_NAME, |
| /* wait_any_ip_address= */ false, |
| ) |
| .await |
| .expect("add_interface failed") |
| .expect("add_interface error"); |
| |
| network_test_realm |
| .stop_hermetic_network_realm() |
| .await |
| .expect("stop_hermetic_network_realm failed") |
| .expect("stop_hermetic_network_realm error"); |
| |
| assert!(!has_hermetic_network_realm(&realm).await); |
| } |
| |
| #[fuchsia::test] |
| async fn stop_hermetic_network_realm_with_no_existing_realm() { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = |
| create_netstack_realm("stop_hermetic_network_realm_with_no_existing_realm", &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| assert_eq!( |
| network_test_realm |
| .stop_hermetic_network_realm() |
| .await |
| .expect("failed to stop hermetic network realm"), |
| Err(fntr::Error::HermeticNetworkRealmNotRunning), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn start_stub(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| network_test_realm |
| .start_stub(IPV4_STUB_URL) |
| .await |
| .expect("start_stub failed") |
| .expect("start_stub error"); |
| |
| assert!(has_stub(&realm).await); |
| } |
| |
| #[netstack_test] |
| #[test_case("ipv4_netstack2", fposix_socket::Domain::Ipv4, fntr::Netstack::V2)] |
| #[test_case("ipv6_netstack2", fposix_socket::Domain::Ipv6, fntr::Netstack::V2)] |
| #[test_case("ipv4_netstack3", fposix_socket::Domain::Ipv4, fntr::Netstack::V3)] |
| #[test_case("ipv6_netstack3", fposix_socket::Domain::Ipv6, fntr::Netstack::V3)] |
| async fn poll_udp( |
| name: &str, |
| sub_name: &str, |
| domain: fposix_socket::Domain, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| network_test_realm |
| .start_stub(match domain { |
| fposix_socket::Domain::Ipv4 => IPV4_STUB_URL, |
| fposix_socket::Domain::Ipv6 => IPV6_STUB_URL, |
| }) |
| .await |
| .expect("start_stub failed") |
| .expect("start_stub error"); |
| |
| assert!(has_stub(&realm).await, "expected has_stub(&realm) to be true"); |
| |
| let payload = "hello".as_bytes(); |
| let poll_addr = fnet_ext::SocketAddress(match domain { |
| fposix_socket::Domain::Ipv4 => unreliable_echo::socket_addr_v4(), |
| fposix_socket::Domain::Ipv6 => unreliable_echo::socket_addr_v6(), |
| }) |
| .into(); |
| let response = network_test_realm |
| .poll_udp(&poll_addr, payload, zx::MonotonicDuration::from_millis(10).into_nanos(), 6000) |
| .await |
| .expect("poll_udp FIDL error") |
| .expect("poll_udp error"); |
| assert_eq!(response, payload); |
| |
| network_test_realm.stop_stub().await.expect("stop_stub failed").expect("stop_stub error"); |
| |
| assert!(!has_stub(&realm).await, "expected has_stub(&realm) to be false"); |
| |
| let response = network_test_realm |
| .poll_udp(&poll_addr, payload, zx::MonotonicDuration::from_millis(10).into_nanos(), 100) |
| .await |
| .expect("poll_udp FIDL error"); |
| assert_eq!(response, Err(fntr::Error::TimeoutExceeded)); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn poll_udp_unreachable(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| // 203.0.113.0/24 is reserved for documentation only, so no address in this subnet |
| // should be routable/reachable. See https://www.rfc-editor.org/rfc/rfc5737.html. |
| let poll_addr = fidl_socket_addr!("203.0.113.1:10000"); |
| |
| let response = network_test_realm |
| .poll_udp( |
| &poll_addr, |
| "test payload".as_bytes(), |
| zx::MonotonicDuration::from_millis(10).into_nanos(), |
| 100, |
| ) |
| .await |
| .expect("poll_udp FIDL error"); |
| assert_eq!(response, Err(fntr::Error::AddressUnreachable)); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn start_stub_with_existing_stub(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let mut event_stream = EventStream::open_at_path("/events/started_stopped") |
| .await |
| .expect("failed to subscribe to EventStream"); |
| |
| network_test_realm |
| .start_stub(IPV4_STUB_URL) |
| .await |
| .expect("start_stub failed") |
| .expect("start_stub error"); |
| |
| let event_matcher = |
| component_events::matcher::EventMatcher::ok().moniker_regex(TEST_STUB_MONIKER_REGEX); |
| |
| let component_events::events::StartedPayload {} = event_matcher |
| .clone() |
| .wait::<component_events::events::Started>(&mut event_stream) |
| .await |
| .expect("initial test-stub observe start event failed") |
| .result() |
| .expect("initial test-stub observe start event error"); |
| |
| network_test_realm |
| .start_stub(IPV4_STUB_URL) |
| .await |
| .expect("start_stub replace failed") |
| .expect("start_stub replace error"); |
| |
| // Verify that the previously running stub was replaced. That is, check that |
| // the stub was stopped and then started. |
| let stopped_event = event_matcher |
| .clone() |
| .wait::<component_events::events::Stopped>(&mut event_stream) |
| .await |
| .expect("test-stub observe stop event failed"); |
| |
| // Note that stopped_event.result below borrows from `stopped_event`. As a |
| // result it needs to be in a different statement. |
| let component_events::events::StoppedPayload { status, .. } = |
| stopped_event.result().expect("test-stub observe stop event error"); |
| let raw_status = match *status { |
| component_events::events::ExitStatus::Clean => { |
| // The component is expected to have a nonzero exit status due to it having been killed |
| // rather than being allowed to exit normally. |
| panic!("got ExitStatus::Clean, expected Crash") |
| } |
| component_events::events::ExitStatus::Crash(i) => i, |
| }; |
| let component_error = |
| match raw_status.try_into().ok().and_then(fcomponent::Error::from_primitive) { |
| None => panic!( |
| "expected {:?} to match a fuchsia.component.Error value", |
| zx::Status::err_from_raw(raw_status) |
| ), |
| Some(e) => e, |
| }; |
| assert_eq!(component_error, fcomponent::Error::InstanceDied); |
| |
| let component_events::events::StartedPayload {} = event_matcher |
| .clone() |
| .wait::<component_events::events::Started>(&mut event_stream) |
| .await |
| .expect("replacement test-stub observe start event failed") |
| .result() |
| .expect("replacement test-stub observe start event error"); |
| |
| assert!(has_stub(&realm).await); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn start_stub_with_non_existent_component( |
| name: &str, |
| sub_name: &str, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| assert_eq!( |
| network_test_realm |
| .start_stub("#meta/non-existent-stub.cm") |
| .await |
| .expect("failed to call start_stub"), |
| Err(fntr::Error::ComponentNotFound), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn start_stub_with_malformed_component_url( |
| name: &str, |
| sub_name: &str, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| assert_eq!( |
| network_test_realm |
| .start_stub("malformed-component-url") |
| .await |
| .expect("failed to call start_stub"), |
| Err(fntr::Error::InvalidArguments), |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn start_stub_with_no_hermetic_network_realm() { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm("start_stub_with_no_hermetic_network_realm", &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| assert_eq!( |
| network_test_realm.start_stub(IPV4_STUB_URL).await.expect("failed to call start_stub"), |
| Err(fntr::Error::HermeticNetworkRealmNotRunning), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn stop_stub(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| network_test_realm |
| .start_stub(IPV4_STUB_URL) |
| .await |
| .expect("start_stub failed") |
| .expect("start_stub error"); |
| |
| network_test_realm.stop_stub().await.expect("stop_stub failed").expect("stop_stub error"); |
| |
| assert!(!has_stub(&realm).await); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn stop_stub_with_no_running_stub(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| assert_eq!( |
| network_test_realm.stop_stub().await.expect("failed to call stop_stub"), |
| Err(fntr::Error::StubNotRunning), |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn stop_stub_with_no_hermetic_network_realm() { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm("stop_stub_with_no_hermetic_network_realm", &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| assert_eq!( |
| network_test_realm.stop_stub().await.expect("failed to call stop_stub"), |
| Err(fntr::Error::HermeticNetworkRealmNotRunning), |
| ); |
| } |
| |
| /// Defaultable configuration options for ping tests. |
| #[derive(Debug, Derivative)] |
| #[derivative(Default)] |
| struct PingOptions { |
| interface_name: Option<String>, |
| #[derivative(Default(value = "DEFAULT_PAYLOAD_LENGTH"))] |
| payload_length: u16, |
| #[derivative(Default(value = "DURATION_FIVE_MINUTES"))] |
| timeout: zx::MonotonicDuration, |
| disable_target_interface: bool, |
| } |
| |
| /// Address configuration for ping tests. |
| struct PingAddressConfig { |
| source_subnet: fnet::Subnet, |
| target_subnet: fnet::Subnet, |
| } |
| |
| const IPV4_ADDRESS_CONFIG: PingAddressConfig = PingAddressConfig { |
| source_subnet: DEFAULT_IPV4_SOURCE_SUBNET, |
| target_subnet: DEFAULT_IPV4_TARGET_SUBNET, |
| }; |
| const IPV6_ADDRESS_CONFIG: PingAddressConfig = PingAddressConfig { |
| source_subnet: DEFAULT_IPV6_SOURCE_SUBNET, |
| target_subnet: DEFAULT_IPV6_TARGET_SUBNET, |
| }; |
| const IPV6_LINK_LOCAL_ADDRESS_CONFIG: PingAddressConfig = PingAddressConfig { |
| source_subnet: DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| target_subnet: DEFAULT_IPV6_LINK_LOCAL_TARGET_SUBNET, |
| }; |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions::default(), |
| fntr::Netstack::V2, |
| Ok(()); |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv4_bind_to_existing_interface_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Ok(()); |
| "ipv4 bind to existing interface netstack2")] |
| #[test_case( |
| "ipv4_bind_to_non_existent_interface_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(NON_EXISTENT_INTERFACE_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Err(fntr::Error::InterfaceNotFound); |
| "ipv4 bind to non existent interface netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| IPV6_ADDRESS_CONFIG, |
| PingOptions::default(), |
| fntr::Netstack::V2, |
| Ok(()); |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv6_bind_to_existing_interface_netstack2", |
| IPV6_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Ok(()); |
| "ipv6 bind to existing interface netstack2")] |
| #[test_case( |
| "ipv6_link_local_bind_to_existing_interface_netstack2", |
| IPV6_LINK_LOCAL_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Ok(()); |
| "ipv6 link local bind to existing interface netstack2")] |
| #[test_case( |
| "ipv6_link_local_with_no_interface_specified_netstack2", |
| IPV6_LINK_LOCAL_ADDRESS_CONFIG, |
| PingOptions::default(), |
| fntr::Netstack::V2, |
| Err(fntr::Error::InvalidArguments); |
| "ipv6 link local with no interface specified netstack2")] |
| #[test_case( |
| "ipv6_bind_to_non_existent_interface_netstack2", |
| IPV6_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(NON_EXISTENT_INTERFACE_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Err(fntr::Error::InterfaceNotFound); |
| "ipv6 bind to non existent interface netstack2")] |
| #[test_case( |
| "timeout_exceeded_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| // Attempting to ping a target interface that is disabled forces a timeout. |
| PingOptions { |
| disable_target_interface: true, |
| timeout: MINIMUM_TIMEOUT, ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Err(fntr::Error::TimeoutExceeded); |
| "timeout exceeded netstack2")] |
| #[test_case( |
| "no_timeout_with_disabled_target_interface_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { |
| disable_target_interface: true, |
| timeout: NO_WAIT_TIMEOUT, |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| // Since no timeout is defined, this ping should succeed. |
| Ok(()); |
| "no timeout with disabled target interface netstack2")] |
| #[test_case( |
| "no_timeout_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { timeout: NO_WAIT_TIMEOUT, ..PingOptions::default() }, |
| fntr::Netstack::V2, |
| Ok(()); |
| "no timeout netstack2")] |
| #[test_case( |
| "host_unreachable_netstack2", |
| PingAddressConfig { |
| target_subnet: fidl_subnet!("192.167.1.1/16"), |
| ..IPV4_ADDRESS_CONFIG |
| }, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V2, |
| Err(fntr::Error::PingFailed); |
| "host unreachable netstack2")] |
| #[test_case( |
| "oversized_payload_length_netstack2", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { payload_length: u16::MAX, ..PingOptions::default() }, |
| fntr::Netstack::V2, |
| Err(fntr::Error::PingFailed); |
| "oversized payload length netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions::default(), |
| fntr::Netstack::V3, |
| Ok(()); |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv4_bind_to_existing_interface_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Ok(()); |
| "ipv4 bind to existing interface netstack3")] |
| #[test_case( |
| "ipv4_bind_to_non_existent_interface_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(NON_EXISTENT_INTERFACE_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Err(fntr::Error::InterfaceNotFound); |
| "ipv4 bind to non existent interface netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| IPV6_ADDRESS_CONFIG, |
| PingOptions::default(), |
| fntr::Netstack::V3, |
| Ok(()); |
| "ipv6 netstack3")] |
| #[test_case( |
| "ipv6_bind_to_existing_interface_netstack3", |
| IPV6_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Ok(()); |
| "ipv6 bind to existing interface netstack3")] |
| #[test_case( |
| "ipv6_link_local_bind_to_existing_interface_netstack3", |
| IPV6_LINK_LOCAL_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| // TODO(https://fxbug.dev/42083514): Fix and use the default timeout. |
| timeout: zx::MonotonicDuration::from_seconds(5), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Ok(()); |
| "ipv6 link local bind to existing interface netstack3")] |
| #[test_case( |
| "ipv6_link_local_with_no_interface_specified_netstack3", |
| IPV6_LINK_LOCAL_ADDRESS_CONFIG, |
| PingOptions::default(), |
| fntr::Netstack::V3, |
| Err(fntr::Error::InvalidArguments); |
| "ipv6 link local with no interface specified netstack3")] |
| #[test_case( |
| "ipv6_bind_to_non_existent_interface_netstack3", |
| IPV6_ADDRESS_CONFIG, |
| PingOptions { |
| interface_name: Some(NON_EXISTENT_INTERFACE_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Err(fntr::Error::InterfaceNotFound); |
| "ipv6 bind to non existent interface netstack3")] |
| #[test_case( |
| "timeout_exceeded_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| // Attempting to ping a target interface that is disabled forces a timeout. |
| PingOptions { |
| disable_target_interface: true, |
| timeout: MINIMUM_TIMEOUT, ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Err(fntr::Error::TimeoutExceeded); |
| "timeout exceeded netstack3")] |
| #[test_case( |
| "no_timeout_with_disabled_target_interface_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { |
| disable_target_interface: true, |
| timeout: NO_WAIT_TIMEOUT, |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| // Since no timeout is defined, this ping should succeed. |
| Ok(()); |
| "no timeout with disabled target interface netstack3")] |
| #[test_case( |
| "no_timeout_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { timeout: NO_WAIT_TIMEOUT, ..PingOptions::default() }, |
| fntr::Netstack::V3, |
| Ok(()); |
| "no timeout netstack3")] |
| #[test_case( |
| "host_unreachable_netstack3", |
| PingAddressConfig { |
| target_subnet: fidl_subnet!("192.167.1.1/16"), |
| ..IPV4_ADDRESS_CONFIG |
| }, |
| PingOptions { |
| interface_name: Some(INTERFACE1_NAME.to_string()), |
| ..PingOptions::default() |
| }, |
| fntr::Netstack::V3, |
| Err(fntr::Error::PingFailed); |
| "host unreachable netstack3")] |
| #[test_case( |
| "oversized_payload_length_netstack3", |
| IPV4_ADDRESS_CONFIG, |
| PingOptions { payload_length: u16::MAX, ..PingOptions::default() }, |
| fntr::Netstack::V3, |
| Err(fntr::Error::PingFailed); |
| "oversized payload length netstack3")] |
| async fn ping( |
| name: &str, |
| case_name: &str, |
| address_config: PingAddressConfig, |
| options: PingOptions, |
| netstack: fntr::Netstack, |
| expected_result: Result<(), fntr::Error>, |
| ) { |
| let PingAddressConfig { source_subnet, target_subnet } = address_config; |
| let PingOptions { interface_name, payload_length, timeout, disable_target_interface } = options; |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| // Create a realm that contains a system Netstack and the Network Test |
| // Realm. |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| |
| // Create another Netstack realm that will be pinged by the hermetic |
| // Netstack. |
| let target_realm = sandbox |
| .create_netstack_realm::<Netstack3, _>(format!("{}_{}_target", name, case_name)) |
| .expect("failed to create target netstack realm"); |
| |
| let target_ep = target_realm |
| .join_network_with_if_config( |
| &network, |
| INTERFACE2_NAME, |
| netemul::InterfaceConfig { name: Some(INTERFACE2_NAME.into()), ..Default::default() }, |
| ) |
| .await |
| .expect("join_network failed for target_realm"); |
| target_ep.add_address_and_subnet_route(target_subnet).await.expect("configure address"); |
| target_ep.apply_nud_flake_workaround().await.expect("nud flake workaround"); |
| |
| if disable_target_interface { |
| // Disable the target interface and wait for it to achieve the disabled |
| // state. |
| let did_disable = |
| target_ep.control().disable().await.expect("send disable").expect("disable interface"); |
| assert!(did_disable); |
| let state_proxy = target_realm |
| .connect_to_protocol::<fnet_interfaces::StateMarker>() |
| .expect("failed to connect to state"); |
| wait_interface_online_status( |
| INTERFACE2_NAME, |
| false, /* expected_online_status */ |
| &state_proxy, |
| ) |
| .await; |
| } |
| |
| let system_ep = realm |
| .join_network_with( |
| &network, |
| INTERFACE1_NAME, |
| netemul::new_endpoint_config(netemul::DEFAULT_MTU, Some(INTERFACE1_MAC_ADDRESS)), |
| Default::default(), |
| ) |
| .await |
| .expect("join_network failed for base realm"); |
| system_ep.apply_nud_flake_workaround().await.expect("nud flake workaround"); |
| realm |
| .add_virtual_device( |
| system_ep.endpoint(), |
| netemul::devfs_device_path(INTERFACE1_NAME).as_path(), |
| ) |
| .await |
| .expect("failed to add virtual device"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| network_test_realm |
| .add_interface( |
| &INTERFACE1_MAC_ADDRESS, |
| INTERFACE1_NAME, |
| /* wait_any_ip_address= */ false, |
| ) |
| .await |
| .expect("add_interface failed") |
| .expect("add_interface error"); |
| |
| add_address_to_hermetic_interface(INTERFACE1_NAME, source_subnet, &realm).await; |
| |
| assert_eq!( |
| network_test_realm |
| .ping( |
| &target_subnet.addr, |
| payload_length, |
| interface_name.as_deref(), |
| timeout.into_nanos(), |
| ) |
| .await |
| .expect("ping failed"), |
| expected_result |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn ping_with_no_hermetic_network_realm() { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm("ping_with_no_hermetic_network_realm", &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| let target_ip = DEFAULT_IPV4_TARGET_SUBNET.addr; |
| assert_eq!( |
| network_test_realm |
| .ping(&target_ip, DEFAULT_PAYLOAD_LENGTH, None, NO_WAIT_TIMEOUT.into_nanos()) |
| .await |
| .expect("ping failed"), |
| Err(fntr::Error::HermeticNetworkRealmNotRunning), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("netstack2", fntr::Netstack::V2)] |
| #[test_case("netstack3", fntr::Netstack::V3)] |
| async fn ping_with_no_added_interface(name: &str, sub_name: &str, netstack: fntr::Netstack) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let target_ip = DEFAULT_IPV4_TARGET_SUBNET.addr; |
| assert_eq!( |
| network_test_realm |
| .ping(&target_ip, DEFAULT_PAYLOAD_LENGTH, None, NO_WAIT_TIMEOUT.into_nanos()) |
| .await |
| .expect("ping failed"), |
| Err(fntr::Error::PingFailed), |
| ); |
| } |
| |
| #[derive(Debug, PartialEq)] |
| enum MulticastEvent { |
| Joined(fnet::IpAddress), |
| Left(fnet::IpAddress), |
| } |
| |
| /// Extracts Ipv4 `MulticastEvent`s from the provided `data`. |
| fn extract_v4_multicast_event(data: &[u8]) -> Vec<MulticastEvent> { |
| let (mut payload, _src_ip, _dst_ip, proto, _ttl) = |
| packet_formats::testutil::parse_ip_packet::<net_types::ip::Ipv4>(&data) |
| .expect("error parsing IPv4 packet"); |
| |
| if proto != packet_formats::ip::Ipv4Proto::Igmp { |
| // Ignore non-IGMP packets. |
| return Vec::new(); |
| } |
| |
| let igmp_packet = packet_formats::igmp::messages::IgmpPacket::parse(&mut payload, ()) |
| .expect("failed to parse IGMP packet"); |
| |
| match igmp_packet { |
| packet_formats::igmp::messages::IgmpPacket::MembershipReportV2(message) => { |
| vec![MulticastEvent::Joined(fnet::IpAddress::Ipv4(fnet::Ipv4Address { |
| addr: message.group_addr().ipv4_bytes(), |
| }))] |
| } |
| packet_formats::igmp::messages::IgmpPacket::LeaveGroup(message) => { |
| vec![MulticastEvent::Left(fnet::IpAddress::Ipv4(fnet::Ipv4Address { |
| addr: message.group_addr().ipv4_bytes(), |
| }))] |
| } |
| packet_formats::igmp::messages::IgmpPacket::MembershipReportV3(message) => message |
| .body() |
| .iter() |
| .filter_map(|record| { |
| use packet_formats::igmp::messages::IgmpGroupRecordType; |
| |
| assert_eq!(record.sources(), &[], "Do not expect source filtering"); |
| |
| let hdr = record.header(); |
| let action = match hdr.record_type().expect("record type") { |
| IgmpGroupRecordType::ModeIsExclude |
| | IgmpGroupRecordType::ChangeToExcludeMode => MulticastEvent::Joined, |
| IgmpGroupRecordType::ModeIsInclude |
| | IgmpGroupRecordType::ChangeToIncludeMode => MulticastEvent::Left, |
| IgmpGroupRecordType::AllowNewSources | IgmpGroupRecordType::BlockOldSources => { |
| return None; |
| } |
| }; |
| |
| Some(action(fnet::IpAddress::Ipv4(fnet::Ipv4Address { |
| addr: hdr.multicast_addr().ipv4_bytes(), |
| }))) |
| }) |
| .collect(), |
| packet_formats::igmp::messages::IgmpPacket::MembershipReportV1(_) |
| | packet_formats::igmp::messages::IgmpPacket::MembershipQueryV2(_) |
| | packet_formats::igmp::messages::IgmpPacket::MembershipQueryV3(_) => { |
| panic!("unexpected IgmpPacket format: {:?}", igmp_packet) |
| } |
| } |
| } |
| |
| /// Extracts Ipv6 `MulticastEvent`s from the provided `data`. |
| fn extract_v6_multicast_event(data: &[u8]) -> Vec<MulticastEvent> { |
| let (mut payload, src_ip, dst_ip, proto, _ttl) = |
| packet_formats::testutil::parse_ip_packet::<net_types::ip::Ipv6>(&data) |
| .expect("error parsing IPv6 packet"); |
| |
| if proto != packet_formats::ip::Ipv6Proto::Icmpv6 { |
| // Ignore non-ICMPv6 packets. |
| return Vec::new(); |
| } |
| |
| let icmp_packet = packet_formats::icmp::Icmpv6Packet::parse( |
| &mut payload, |
| packet_formats::icmp::IcmpParseArgs::new(src_ip, dst_ip), |
| ) |
| .expect("error parsing ICMPv6 packet"); |
| |
| let mld_packet = match icmp_packet { |
| packet_formats::icmp::Icmpv6Packet::Mld(mld) => mld, |
| packet_formats::icmp::Icmpv6Packet::DestUnreachable(_) |
| | packet_formats::icmp::Icmpv6Packet::EchoReply(_) |
| | packet_formats::icmp::Icmpv6Packet::EchoRequest(_) |
| | packet_formats::icmp::Icmpv6Packet::Ndp(_) |
| | packet_formats::icmp::Icmpv6Packet::PacketTooBig(_) |
| | packet_formats::icmp::Icmpv6Packet::ParameterProblem(_) |
| | packet_formats::icmp::Icmpv6Packet::TimeExceeded(_) => return Vec::new(), |
| }; |
| |
| match mld_packet { |
| packet_formats::icmp::mld::MldPacket::MulticastListenerReport(packet) => { |
| (!SOLICITED_NODE_MULTICAST_ADDRESS_PREFIX.contains(&packet.body().group_addr)) |
| .then(|| { |
| MulticastEvent::Joined(fnet::IpAddress::Ipv6(fnet::Ipv6Address { |
| addr: packet.body().group_addr.ipv6_bytes(), |
| })) |
| }) |
| .into_iter() |
| .collect() |
| } |
| packet_formats::icmp::mld::MldPacket::MulticastListenerDone(packet) => { |
| (!SOLICITED_NODE_MULTICAST_ADDRESS_PREFIX.contains(&packet.body().group_addr)) |
| .then(|| { |
| MulticastEvent::Left(fnet::IpAddress::Ipv6(fnet::Ipv6Address { |
| addr: packet.body().group_addr.ipv6_bytes(), |
| })) |
| }) |
| .into_iter() |
| .collect() |
| } |
| packet_formats::icmp::mld::MldPacket::MulticastListenerQuery(_) => Vec::new(), |
| packet_formats::icmp::mld::MldPacket::MulticastListenerQueryV2(_) => Vec::new(), |
| packet_formats::icmp::mld::MldPacket::MulticastListenerReportV2(packet) => packet |
| .body() |
| .iter_multicast_records() |
| .filter_map(|record| { |
| use packet_formats::icmp::mld::Mldv2MulticastRecordType; |
| |
| assert_eq!(record.sources(), &[], "Do not expect source filtering"); |
| |
| let hdr = record.header(); |
| let action = match hdr.record_type().expect("record type") { |
| Mldv2MulticastRecordType::ModeIsExclude |
| | Mldv2MulticastRecordType::ChangeToExcludeMode => MulticastEvent::Joined, |
| Mldv2MulticastRecordType::ModeIsInclude |
| | Mldv2MulticastRecordType::ChangeToIncludeMode => MulticastEvent::Left, |
| Mldv2MulticastRecordType::AllowNewSources |
| | Mldv2MulticastRecordType::BlockOldSources => return None, |
| }; |
| |
| Some(action(fnet::IpAddress::Ipv6(fnet::Ipv6Address { |
| addr: hdr.multicast_addr().ipv6_bytes(), |
| }))) |
| }) |
| .collect(), |
| } |
| } |
| |
| /// Verifies that the `expected_event` occurred on the `fake_endpoint`. |
| async fn expect_multicast_event( |
| fake_endpoint: &netemul::TestFakeEndpoint<'_>, |
| expected_event: MulticastEvent, |
| ) { |
| let expected_event = &expected_event; |
| let stream = fake_endpoint |
| .frame_stream() |
| .map(|r| r.expect("error getting OnData event")) |
| .filter_map(|(data, _dropped)| async move { |
| let mut data = &data[..]; |
| let eth = packet_formats::ethernet::EthernetFrame::parse( |
| &mut data, |
| // Do not check the frame length as the size of IGMP reports may |
| // be less than the minimum ethernet frame length and our |
| // virtual (netemul) interface does not pad runt ethernet frames |
| // before transmission. |
| packet_formats::ethernet::EthernetFrameLengthCheck::NoCheck, |
| ) |
| .expect("failed to parse ethernet frame"); |
| |
| let events = match eth.ethertype().expect("ethertype missing from ethernet frame") { |
| packet_formats::ethernet::EtherType::Ipv4 => extract_v4_multicast_event(data), |
| packet_formats::ethernet::EtherType::Ipv6 => extract_v6_multicast_event(data), |
| packet_formats::ethernet::EtherType::Arp |
| | packet_formats::ethernet::EtherType::Other(_) => return None, |
| }; |
| |
| // The same event may be emitted multiple times. As a result, we |
| // must wait for the expected event. |
| events.contains(expected_event).then(|| ()) |
| }); |
| let mut stream = pin!(stream); |
| stream.next().await.expect("failed to find expected multicast event"); |
| } |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| fntr::Netstack::V2, |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| fntr::Netstack::V3, |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack2", |
| fntr::Netstack::V2, |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv6_netstack3", |
| fntr::Netstack::V3, |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET; |
| "ipv6 netstack3")] |
| async fn join_multicast_group( |
| name: &str, |
| case_name: &str, |
| netstack: fntr::Netstack, |
| multicast_address: fnet::IpAddress, |
| subnet: fnet::Subnet, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| let fake_ep = network.create_fake_endpoint().expect("failed to create fake endpoint"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| network_test_realm |
| .join_multicast_group( |
| &multicast_address, |
| expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await, |
| ) |
| .await |
| .expect("join_multicast_group failed") |
| .expect("join_multicast_group error"); |
| |
| expect_multicast_event(&fake_ep, MulticastEvent::Joined(multicast_address)).await; |
| } |
| |
| // Tests that the persisted multicast socket is cleared when the hermetic |
| // network realm is stopped. |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| fnet::IpAddress::Ipv4(fidl_ip_v4!("224.1.2.4")), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| fnet::IpAddress::Ipv6(fidl_ip_v6!("ff02::4")), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| fnet::IpAddress::Ipv4(fidl_ip_v4!("224.1.2.4")), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| fnet::IpAddress::Ipv6(fidl_ip_v6!("ff02::4")), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv6 netstack3")] |
| async fn join_multicast_group_after_stop( |
| name: &str, |
| case_name: &str, |
| multicast_address: fnet::IpAddress, |
| second_multicast_address: fnet::IpAddress, |
| subnet: fnet::Subnet, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| let fake_ep = network.create_fake_endpoint().expect("failed to create fake endpoint"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| network_test_realm |
| .join_multicast_group( |
| &multicast_address, |
| expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await, |
| ) |
| .await |
| .expect("join_multicast_group failed") |
| .expect("join_multicast_group error"); |
| |
| network_test_realm |
| .stop_hermetic_network_realm() |
| .await |
| .expect("stop_hermetic_network_realm failed") |
| .expect("stop_hermetic_network_realm error"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(fntr::Netstack::V2) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE2_NAME, |
| INTERFACE2_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| network_test_realm |
| .join_multicast_group( |
| &second_multicast_address, |
| expect_hermetic_interface_id(INTERFACE2_NAME, &realm).await, |
| ) |
| .await |
| .expect("join_multicast_group failed") |
| .expect("join_multicast_group error"); |
| |
| expect_multicast_event(&fake_ep, MulticastEvent::Joined(second_multicast_address)).await; |
| } |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv6 netstack3")] |
| async fn leave_multicast_group( |
| name: &str, |
| case_name: &str, |
| multicast_address: fnet::IpAddress, |
| subnet: fnet::Subnet, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| let fake_ep = network.create_fake_endpoint().expect("failed to create fake endpoint"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| let id = expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await; |
| |
| network_test_realm |
| .join_multicast_group(&multicast_address, id) |
| .await |
| .expect("join_multicast_group failed") |
| .expect("join_multicast_group error"); |
| |
| network_test_realm |
| .leave_multicast_group(&multicast_address, id) |
| .await |
| .expect("leave_multicast_group failed") |
| .expect("leave_multicast_group error"); |
| |
| expect_multicast_event(&fake_ep, MulticastEvent::Left(multicast_address)).await; |
| } |
| |
| #[fuchsia::test] |
| async fn join_multicast_group_with_no_hermetic_network_realm() { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = |
| create_netstack_realm("join_multicast_group_with_no_hermetic_network_realm", &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| assert_eq!( |
| network_test_realm |
| .join_multicast_group( |
| &fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_INTERFACE_ID |
| ) |
| .await |
| .expect("join_multicast_group failed"), |
| Err(fntr::Error::HermeticNetworkRealmNotRunning), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("v2", fntr::Netstack::V2)] |
| #[test_case("v3", fntr::Netstack::V3)] |
| async fn join_multicast_group_with_non_existent_interface( |
| name: &str, |
| case_name: &str, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| // TODO(https://fxbug.dev/42074274): Resolve error code discrepancy for `IP_ADD_MEMBERSHIP` |
| // (called under the hood by `join_multicast_group` below). |
| let expected_err = match netstack { |
| fntr::Netstack::V2 => fntr::Error::InvalidArguments, |
| fntr::Netstack::V3 => fntr::Error::Internal, |
| }; |
| assert_eq!( |
| network_test_realm |
| .join_multicast_group( |
| &fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| // This interface id does not exist. As a result, an error |
| // should be returned. |
| DEFAULT_INTERFACE_ID |
| ) |
| .await |
| .expect("join_multicast_group failed"), |
| Err(expected_err), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv6 netstack3")] |
| async fn join_multicast_group_with_non_multicast_address( |
| name: &str, |
| case_name: &str, |
| subnet: fnet::Subnet, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| // `address` is not within the multicast address range. Therefore, an error |
| // should be returned. |
| let address = subnet.addr; |
| assert_eq!( |
| network_test_realm |
| .join_multicast_group( |
| &address, |
| expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await |
| ) |
| .await |
| .expect("join_multicast_group failed"), |
| Err(fntr::Error::InvalidArguments), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv6 netstack3")] |
| async fn join_same_multicast_group_multiple_times( |
| name: &str, |
| case_name: &str, |
| multicast_address: fnet::IpAddress, |
| subnet: fnet::Subnet, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| let id = expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await; |
| network_test_realm |
| .join_multicast_group(&multicast_address, id) |
| .await |
| .expect("join_multicast_group failed") |
| .expect("join_multicast_group error"); |
| |
| // Verify that the error is propagated whenever the same multicast group is |
| // joined multiple times. |
| assert_eq!( |
| network_test_realm |
| .join_multicast_group(&multicast_address, id) |
| .await |
| .expect("duplicate join_multicast_group failed"), |
| Err(fntr::Error::AddressInUse) |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn leave_multicast_group_with_no_hermetic_network_realm() { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let realm = |
| create_netstack_realm("leave_multicast_group_with_no_hermetic_network_realm", &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| assert_eq!( |
| network_test_realm |
| .leave_multicast_group( |
| &fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_INTERFACE_ID |
| ) |
| .await |
| .expect("leave_multicast_group failed"), |
| Err(fntr::Error::HermeticNetworkRealmNotRunning), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv6 netstack3")] |
| async fn leave_multicast_group_with_non_multicast_address( |
| name: &str, |
| case_name: &str, |
| subnet: fnet::Subnet, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| // `address` is not within the multicast address range. Therefore, an error |
| // should be returned. |
| let address = subnet.addr; |
| assert_eq!( |
| network_test_realm |
| .leave_multicast_group( |
| &address, |
| expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await |
| ) |
| .await |
| .expect("leave_multicast_group failed"), |
| Err(fntr::Error::InvalidArguments), |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case( |
| "ipv4_netstack2", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv4 netstack2")] |
| #[test_case( |
| "ipv6_netstack2", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V2; |
| "ipv6 netstack2")] |
| #[test_case( |
| "ipv4_netstack3", |
| fnet::IpAddress::Ipv4(DEFAULT_IPV4_MULTICAST_ADDRESS), |
| DEFAULT_IPV4_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv4 netstack3")] |
| #[test_case( |
| "ipv6_netstack3", |
| fnet::IpAddress::Ipv6(DEFAULT_IPV6_MULTICAST_ADDRESS), |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| fntr::Netstack::V3; |
| "ipv6 netstack3")] |
| async fn leave_unjoined_multicast_group( |
| name: &str, |
| case_name: &str, |
| multicast_address: fnet::IpAddress, |
| subnet: fnet::Subnet, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let realm = create_netstack_realm(format!("{}_{}", name, case_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let _interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| subnet, |
| ) |
| .await; |
| |
| // The multicast group must be joined before it can be left. |
| assert_eq!( |
| network_test_realm |
| .leave_multicast_group( |
| &multicast_address, |
| expect_hermetic_interface_id(INTERFACE1_NAME, &realm).await |
| ) |
| .await |
| .expect("leave_multicast_group failed"), |
| Err(fntr::Error::AddressNotAvailable) |
| ); |
| } |
| |
| #[netstack_test] |
| #[test_case("na_ns2", true, true, false, fntr::Netstack::V2; "na netstack2")] |
| #[test_case("pd_ns2", false, true, false, fntr::Netstack::V2; "pd netstack2")] |
| #[test_case("na_pd_ns2", true, true, false, fntr::Netstack::V2; "na pd netstack2")] |
| #[test_case("stateless_ns2", false, false, true, fntr::Netstack::V2; "stateless netstack2")] |
| #[test_case("na_ns3", true, true, false, fntr::Netstack::V3; "na netstack3")] |
| #[test_case("pd_ns3", false, true, false, fntr::Netstack::V3; "pd netstack3")] |
| #[test_case("na_pd_ns3", true, true, false, fntr::Netstack::V3; "na pd netstack3")] |
| #[test_case("stateless_ns3", false, false, true, fntr::Netstack::V3; "stateless netstack3")] |
| async fn start_dhcpv6_client( |
| name: &str, |
| sub_name: &str, |
| request_non_temporary_address: bool, |
| request_prefix_delegation: bool, |
| request_dns_servers: bool, |
| netstack: fntr::Netstack, |
| ) { |
| let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox"); |
| let network = sandbox.create_network("network").await.expect("failed to create network"); |
| let fake_ep = network.create_fake_endpoint().expect("failed to create fake endpoint"); |
| let realm = create_netstack_realm(format!("{}_{}", name, sub_name), &sandbox) |
| .expect("failed to create netstack realm"); |
| |
| let network_test_realm = realm |
| .connect_to_protocol::<fntr::ControllerMarker>() |
| .expect("failed to connect to network test realm controller"); |
| |
| network_test_realm |
| .start_hermetic_network_realm(netstack) |
| .await |
| .expect("start_hermetic_network_realm failed") |
| .expect("start_hermetic_network_realm error"); |
| |
| let interface = join_network_with_hermetic_netstack( |
| &realm, |
| &network, |
| &network_test_realm, |
| INTERFACE1_NAME, |
| INTERFACE1_MAC_ADDRESS, |
| DEFAULT_IPV6_LINK_LOCAL_SOURCE_SUBNET, |
| ) |
| .await; |
| |
| network_test_realm |
| .start_dhcpv6_client( |
| &fnet_dhcpv6_ext::NewClientParams { |
| interface_id: interface.id(), |
| address: fnet::Ipv6SocketAddress { |
| address: DEFAULT_IPV6_LINK_LOCAL_SOURCE_ADDR, |
| port: fnet_dhcpv6::DEFAULT_CLIENT_PORT, |
| zone_index: interface.id(), |
| }, |
| config: fnet_dhcpv6_ext::ClientConfig { |
| information_config: fnet_dhcpv6_ext::InformationConfig { |
| dns_servers: request_dns_servers, |
| }, |
| non_temporary_address_config: fnet_dhcpv6_ext::AddressConfig { |
| address_count: if request_non_temporary_address { 1 } else { 0 }, |
| preferred_addresses: None, |
| }, |
| prefix_delegation_config: request_prefix_delegation |
| .then_some(fnet_dhcpv6::PrefixDelegationConfig::Empty(fnet_dhcpv6::Empty)), |
| }, |
| duid: (request_non_temporary_address || request_prefix_delegation) |
| .then(|| fnet_dhcpv6::Duid::Uuid(uuid::Uuid::new_v4().into_bytes())), |
| } |
| .into(), |
| ) |
| .await |
| .expect("FIDL error") |
| .expect("start DHCPv6 client"); |
| |
| let stateful = request_non_temporary_address || request_prefix_delegation; |
| let want_msg_type = if stateful { |
| packet_formats_dhcp::v6::MessageType::Solicit |
| } else { |
| packet_formats_dhcp::v6::MessageType::InformationRequest |
| }; |
| fake_ep |
| .frame_stream() |
| .filter_map(|r| { |
| let (data, _dropped) = r.expect("frame stream error"); |
| futures::future::ready(packets::parse_dhcpv6(data.as_slice()).map(|msg| { |
| assert_eq!(msg.msg_type(), want_msg_type); |
| if stateful { |
| let found_opts = |
| msg.options().fold((false, false), |(found_na, found_pd), opt| match opt { |
| packet_formats_dhcp::v6::ParsedDhcpOption::Iana(_) => (true, found_pd), |
| packet_formats_dhcp::v6::ParsedDhcpOption::IaPd(_) => (found_na, true), |
| _ => (found_na, found_pd), |
| }); |
| assert_eq!( |
| found_opts, |
| (request_non_temporary_address, request_prefix_delegation) |
| ); |
| } |
| })) |
| }) |
| .next() |
| .await |
| .expect("frame stream terminated unexpectedly"); |
| } |
| |
| // TODO(https://fxbug.dev/42059019): Test stopping all DHCPv6 clients. Currently |
| // blocked on address assignment, otherwise there's no observable effect of |
| // stopping the clients. |