blob: c6cf6cc600005cf97cfb66fe0005cb07f6b46282 [file] [log] [blame]
// Copyright 2020 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::collections::HashMap;
use fidl_fuchsia_net_ext::{IntoExt as _, NetTypesIpAddressExt};
use fidl_fuchsia_net_stack as fnet_stack;
use fidl_fuchsia_net_stack_ext::{exec_fidl, FidlReturn as _};
use fidl_fuchsia_netemul as fnetemul;
use fuchsia_async::{DurationExt as _, TimeoutExt as _};
use fuchsia_zircon as zx;
use anyhow::Context as _;
use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
use net_declare::{
fidl_ip_v4_with_prefix, fidl_ip_v6, fidl_ip_v6_with_prefix, fidl_mac, fidl_subnet, std_ip_v4,
std_ip_v6, std_socket_addr,
};
use netemul::RealmUdpSocket as _;
use netstack_testing_common::{
get_component_moniker,
realms::{
constants, KnownServiceProvider, Netstack, Netstack2, NetstackVersion, TestRealmExt as _,
TestSandboxExt as _,
},
ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT, ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT,
};
use netstack_testing_macros::variants_test;
use packet::{serialize::Serializer as _, ParsablePacket as _};
use packet_formats::{
error::ParseError,
ethernet::{EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck, EthernetIpExt as _},
icmp::{
IcmpEchoRequest, IcmpIpExt, IcmpMessage, IcmpPacket, IcmpPacketBuilder, IcmpParseArgs,
IcmpUnusedCode, MessageBody as _, OriginalPacket,
},
ip::IpPacketBuilder as _,
};
use test_case::test_case;
fn ipv4_with_prefix_to_subnet(
addr: fidl_fuchsia_net::Ipv4AddressWithPrefix,
) -> fidl_fuchsia_net::Subnet {
let fidl_fuchsia_net::Ipv4AddressWithPrefix { addr, prefix_len } = addr;
fidl_fuchsia_net::Subnet { addr: fidl_fuchsia_net::IpAddress::Ipv4(addr), prefix_len }
}
fn ipv6_with_prefix_to_subnet(
addr: fidl_fuchsia_net::Ipv6AddressWithPrefix,
) -> fidl_fuchsia_net::Subnet {
let fidl_fuchsia_net::Ipv6AddressWithPrefix { addr, prefix_len } = addr;
fidl_fuchsia_net::Subnet { addr: fidl_fuchsia_net::IpAddress::Ipv6(addr), prefix_len }
}
#[fuchsia_async::run_singlethreaded(test)]
async fn add_ethernet_device() {
let name = "add_ethernet_device";
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create realm");
let netstack = realm
.connect_to_protocol::<fidl_fuchsia_netstack::NetstackMarker>()
.expect("connect to protocol");
let device =
sandbox.create_endpoint::<netemul::Ethernet, _>(name).await.expect("create endpoint");
// We're testing add_ethernet_device (netstack.fidl), which
// does not have a network device entry point.
let eth = device.get_ethernet().await.expect("connet to ethernet device");
let id = netstack
.add_ethernet_device(
name,
&mut fidl_fuchsia_netstack::InterfaceConfig {
name: name[..fidl_fuchsia_net_interfaces::INTERFACE_NAME_LENGTH.into()].to_string(),
filepath: "/fake/filepath/for_test".to_string(),
metric: 0,
},
eth,
)
.await
.expect("add_ethernet_device FIDL error")
.map_err(fuchsia_zircon::Status::from_raw)
.expect("add_ethernet_device failed");
let interface_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let mut state = fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(id.into());
let (device_class, online) = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)
.expect("create event stream"),
&mut state,
|fidl_fuchsia_net_interfaces_ext::Properties {
id: _,
name: _,
device_class,
online,
addresses: _,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
}| Some((*device_class, *online)),
)
.await
.expect("observe interface addition");
assert_eq!(
device_class,
fidl_fuchsia_net_interfaces::DeviceClass::Device(
fidl_fuchsia_hardware_network::DeviceClass::Virtual
)
);
assert!(!online);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_no_duplicate_interface_names() {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox
.create_netstack_realm::<Netstack2, _>("no_duplicate_interface_names")
.expect("create realm");
let netstack = realm
.connect_to_protocol::<fidl_fuchsia_netstack::NetstackMarker>()
.expect("connect to protocol");
// Create one endpoint of each type so we can use all the APIs that add an
// interface. Note that fuchsia.net.stack/Stack.AddEthernetInterface does
// not support setting the interface name.
let eth_ep = sandbox
.create_endpoint::<netemul::Ethernet, _>("eth-ep")
.await
.expect("create ethernet endpoint");
const IFNAME: &'static str = "testif";
const TOPOPATH: &'static str = "/fake/topopath";
const FILEPATH: &'static str = "/fake/filepath";
// Add the first ep to the stack so it takes over the name.
let eth = eth_ep.get_ethernet().await.expect("connect to ethernet device");
let _id: u32 = netstack
.add_ethernet_device(
TOPOPATH,
&mut fidl_fuchsia_netstack::InterfaceConfig {
name: IFNAME.to_string(),
filepath: FILEPATH.to_string(),
metric: 0,
},
eth,
)
.await
.expect("add_ethernet_device FIDL error")
.map_err(fuchsia_zircon::Status::from_raw)
.expect("add_ethernet_device error");
// Now try to add again with the same parameters and expect an error.
let eth = eth_ep.get_ethernet().await.expect("connect to ethernet device");
let result = netstack
.add_ethernet_device(
TOPOPATH,
&mut fidl_fuchsia_netstack::InterfaceConfig {
name: IFNAME.to_string(),
filepath: FILEPATH.to_string(),
metric: 0,
},
eth,
)
.await
.expect("add_ethernet_device FIDL error")
.map_err(fuchsia_zircon::Status::from_raw);
assert_eq!(result, Err(fuchsia_zircon::Status::ALREADY_EXISTS));
}
// TODO(https://fxbug.dev/75553): Remove this test when fuchsia.net.interfaces is supported in N3
// and test_add_remove_interface can be parameterized on Netstack.
#[variants_test]
async fn add_ethernet_interface<N: Netstack>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let device =
sandbox.create_endpoint::<netemul::Ethernet, _>(name).await.expect("create endpoint");
let iface = device.into_interface_in_realm(&realm).await.expect("add device");
let id = iface.id();
let interfaces = fidl_fuchsia_net_interfaces_ext::existing(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create event stream"),
HashMap::new(),
)
.await
.expect("fetch existing interfaces");
let fidl_fuchsia_net_interfaces_ext::Properties {
id: _,
name: _,
device_class,
online,
addresses: _,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
} = interfaces.get(&id).expect("find added ethernet interface");
assert_eq!(
*device_class,
fidl_fuchsia_net_interfaces::DeviceClass::Device(
fidl_fuchsia_hardware_network::DeviceClass::Virtual
)
);
assert!(!online);
}
#[variants_test]
async fn add_del_interface_address_deprecated<N: Netstack>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
let stack =
realm.connect_to_protocol::<fnet_stack::StackMarker>().expect("connect to protocol");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let device =
sandbox.create_endpoint::<netemul::Ethernet, _>(name).await.expect("create endpoint");
let iface = device.into_interface_in_realm(&realm).await.expect("add device");
let id = iface.id();
// TODO(https://fxbug.dev/20989#c5): netstack3 doesn't allow addresses to be added while
// link is down.
let () = stack.enable_interface_deprecated(id).await.squash_result().expect("enable interface");
let () = iface.set_link_up(true).await.expect("bring device up");
// TODO(https://fxbug.dev/60923): N3 doesn't implement watcher events past idle.
loop {
let interfaces = fidl_fuchsia_net_interfaces_ext::existing(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create event stream"),
HashMap::new(),
)
.await
.expect("fetch existing interfaces");
let fidl_fuchsia_net_interfaces_ext::Properties {
id: _,
name: _,
device_class: _,
online,
addresses: _,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
} = interfaces.get(&id).expect("find added ethernet interface");
if *online {
break;
}
}
let interface_addr_with_prefix = fidl_ip_v4_with_prefix!("1.1.1.1/32");
let mut interface_subnet = ipv4_with_prefix_to_subnet(interface_addr_with_prefix);
let res = stack
.add_interface_address_deprecated(id, &mut interface_subnet)
.await
.expect("add_interface_address_deprecated");
assert_eq!(res, Ok(()));
// Should be an error the second time.
let res = stack
.add_interface_address_deprecated(id, &mut interface_subnet)
.await
.expect("add_interface_address_deprecated");
assert_eq!(res, Err(fnet_stack::Error::AlreadyExists));
let res = stack
.add_interface_address_deprecated(id + 1, &mut interface_subnet)
.await
.expect("add_interface_address_deprecated");
assert_eq!(res, Err(fnet_stack::Error::NotFound));
let error = stack
.add_interface_address_deprecated(
id,
&mut fidl_fuchsia_net::Subnet { prefix_len: 43, ..interface_subnet },
)
.await
.expect("add_interface_address_deprecated")
.unwrap_err();
assert_eq!(error, fnet_stack::Error::InvalidArgs);
let interface = fidl_fuchsia_net_interfaces_ext::existing(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("create event stream"),
fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(id),
)
.await
.expect("retrieve existing interface");
let interface_address = fidl_fuchsia_net::InterfaceAddress::Ipv4(interface_addr_with_prefix);
// We use contains here because netstack can generate link-local addresses
// that can't be predicted.
assert_matches::assert_matches!(
interface,
fidl_fuchsia_net_interfaces_ext::InterfaceState::Known(p)
if p.addresses.contains(&fidl_fuchsia_net_interfaces_ext::Address {
value: interface_address,
valid_until: zx::sys::ZX_TIME_INFINITE,
})
);
let res = stack
.del_interface_address_deprecated(id, &mut interface_subnet)
.await
.expect("del_interface_address_deprecated");
assert_eq!(res, Ok(()));
let interface = 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(id),
)
.await
.expect("retrieve existing interface");
// We use contains here because netstack can generate link-local addresses
// that can't be predicted.
assert_matches::assert_matches!(
interface,
fidl_fuchsia_net_interfaces_ext::InterfaceState::Known(p)
if !p.addresses.contains(&fidl_fuchsia_net_interfaces_ext::Address {
value: interface_address,
valid_until: zx::sys::ZX_TIME_INFINITE,
})
);
}
// Regression test which asserts that racing an address removal and interface
// removal doesn't cause a Netstack panic.
#[variants_test]
async fn remove_interface_and_address<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 stack =
realm.connect_to_protocol::<fnet_stack::StackMarker>().expect("connect to protocol");
// NB: A second channel is needed in order for the address removal
// requests and the interface removal request to be served concurrently.
let stack2 =
realm.connect_to_protocol::<fnet_stack::StackMarker>().expect("connect to protocol");
let mut addresses = (0..32)
.map(|i| fidl_fuchsia_net::Subnet {
addr: fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
addr: [1, 2, 3, i],
}),
prefix_len: 16,
})
.collect::<Vec<_>>();
for i in 0..8 {
let ep =
sandbox.create_endpoint::<E, _>(format!("ep{}", i)).await.expect("create endpoint");
let iface = ep.into_interface_in_realm(&realm).await.expect("add device");
futures::stream::iter(addresses.iter_mut())
.for_each_concurrent(None, |addr| {
stack
.add_interface_address_deprecated(iface.id(), addr)
.map(|r| r.expect("call add_interface_address"))
.map(|r| r.expect("add interface address"))
})
.await;
// Removing many addresses increases the chances that address removal
// will be handled concurrently with interface removal.
let remove_addr_fut =
futures::stream::iter(addresses.iter_mut()).for_each_concurrent(None, |addr| {
stack.del_interface_address_deprecated(iface.id(), addr).map(|r| {
match r.expect("call del_interface_address_deprecated") {
Ok(()) | Err(fnet_stack::Error::NotFound) => {}
Err(e) => panic!("delete interface address error: {:?}", e),
}
})
});
// NB: The async block is necessary because calls on FIDL proxy
// types make the request immediately and return a future which
// resolves when the response is returned. Without the async block,
// interface removal will be handled by Netstack immediately rather
// concurrently with address removal, which is not the desired
// behavior.
let remove_interface_fut = async {
stack2
.del_ethernet_interface(iface.id())
.await
.expect("call del_ethernet_interface")
.expect("delete interface")
};
futures::future::join(remove_addr_fut, remove_interface_fut).await;
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_log_packets() {
let name = "test_log_packets";
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
// Modify debug netstack args so that it does not log packets.
let (realm, stack_log) = {
let mut netstack =
fnetemul::ChildDef::from(&KnownServiceProvider::Netstack(Netstack2::VERSION));
let fnetemul::ChildDef { program_args, .. } = &mut netstack;
assert_eq!(
std::mem::replace(program_args, Some(vec!["--verbosity=debug".to_string()])),
None,
);
let realm = sandbox.create_realm(name, [netstack]).expect("create realm");
let netstack_proxy =
realm.connect_to_protocol::<fnet_stack::LogMarker>().expect("connect to netstack");
(realm, netstack_proxy)
};
let () = stack_log.set_log_packets(true).await.expect("enable packet logging");
let sock =
fuchsia_async::net::UdpSocket::bind_in_realm(&realm, std_socket_addr!("127.0.0.1:0"))
.await
.expect("create socket");
let addr = sock.local_addr().expect("get bound socket address");
const PAYLOAD: [u8; 4] = [1u8, 2, 3, 4];
let sent = sock.send_to(&PAYLOAD[..], addr).await.expect("send_to failed");
assert_eq!(sent, PAYLOAD.len());
let patterns = ["send", "recv"]
.iter()
.map(|t| format!("{} udp {} -> {} len:{}", t, addr, addr, PAYLOAD.len()))
.collect::<Vec<_>>();
let netstack_moniker = get_component_moniker(&realm, constants::netstack::COMPONENT_NAME)
.await
.expect("get netstack moniker");
let stream = diagnostics_reader::ArchiveReader::new()
.select_all_for_moniker(&netstack_moniker)
.snapshot_then_subscribe()
.expect("subscribe to snapshot");
let () = async_utils::fold::try_fold_while(stream, patterns, |mut patterns, data| {
let () = patterns
.retain(|pattern| !data.msg().map(|msg| msg.contains(pattern)).unwrap_or(false));
futures::future::ok(if patterns.is_empty() {
async_utils::fold::FoldWhile::Done(())
} else {
async_utils::fold::FoldWhile::Continue(patterns)
})
})
.await
.expect("observe expected patterns")
.short_circuited()
.unwrap_or_else(|patterns| {
panic!("log stream ended while still waiting for patterns {:?}", patterns)
});
}
const IPV4_LOOPBACK: fidl_fuchsia_net::Ipv4AddressWithPrefix =
fidl_ip_v4_with_prefix!("127.0.0.1/8");
const IPV6_LOOPBACK: fidl_fuchsia_net::Ipv6Address = fidl_ip_v6!("::1");
#[variants_test]
async fn add_remove_address_on_loopback<N: Netstack>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
let (loopback_id, addresses) = assert_matches::assert_matches!(
realm.loopback_properties().await,
Ok(Some(
fidl_fuchsia_net_interfaces_ext::Properties {
id,
online: true,
addresses,
..
},
)) => (id, addresses)
);
let addresses: Vec<_> = addresses
.into_iter()
.map(|fidl_fuchsia_net_interfaces_ext::Address { value, .. }| value)
.collect();
assert_eq!(
addresses[..],
[
fidl_fuchsia_net::InterfaceAddress::Ipv4(IPV4_LOOPBACK),
fidl_fuchsia_net::InterfaceAddress::Ipv6(IPV6_LOOPBACK),
]
);
let stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("connect to protocol");
let stack = &stack;
let del_addr = |mut addr| async move {
stack
.del_interface_address_deprecated(loopback_id, &mut addr)
.await
.expect("del_interface_address")
.expect("expected to remove address")
};
del_addr(ipv4_with_prefix_to_subnet(IPV4_LOOPBACK)).await;
del_addr(fidl_fuchsia_net::Subnet {
addr: fidl_fuchsia_net::IpAddress::Ipv6(IPV6_LOOPBACK),
prefix_len: 128, // Bogus value to appease legacy API.
})
.await;
const NEW_IPV4_ADDRESS: fidl_fuchsia_net::Ipv4AddressWithPrefix =
fidl_ip_v4_with_prefix!("1.1.1.1/24");
const NEW_IPV6_ADDRESS: fidl_fuchsia_net::Ipv6AddressWithPrefix =
fidl_ip_v6_with_prefix!("a::1/64");
let add_addr = |mut addr| async move {
stack
.add_interface_address_deprecated(loopback_id, &mut addr)
.await
.expect("add_interface_address")
.expect("expected to add address")
};
add_addr(ipv4_with_prefix_to_subnet(NEW_IPV4_ADDRESS)).await;
add_addr(ipv6_with_prefix_to_subnet(NEW_IPV6_ADDRESS)).await;
// Wait for the addresses to be set.
//
// On Netstack2, the DAD resolution event (which the interface watcher
// depends on to update its view of interface properties) is handled
// asynchronously w.r.t. the add address operation so the new IPv6 address
// may not be observed in the initial "Existing" event; it may be observed
// in a "Changed" event.
//
// This is not an issue on Netstack3 as we will observe the address in an
// "Existing" event right away as we do not perform DAD and Netstack3 is
// queried for all interface states on watcher creation (no events happen
// asynchronously).
//
// TODO(https://fxbug.dev/60923): Wait for changed event instead of creating
// a new watcher.
let interface_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let mut state = fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(loopback_id);
fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)
.expect("get interface event stream"),
&mut state,
|fidl_fuchsia_net_interfaces_ext::Properties {
id,
name: _,
device_class: _,
online: _,
addresses,
has_default_ipv4_route: _,
has_default_ipv6_route: _,
}| {
assert_eq!(loopback_id, *id, "Don't expect to see other interfaces");
let addresses: Vec<_> = addresses
.into_iter()
.map(|fidl_fuchsia_net_interfaces_ext::Address { value, valid_until: _ }| value)
.collect();
if addresses.len() < 2 {
println!("waiting for at least two addresses to be set; got {:?}", addresses);
None
} else {
assert_eq!(
addresses[..],
[
&fidl_fuchsia_net::InterfaceAddress::Ipv4(NEW_IPV4_ADDRESS),
&fidl_fuchsia_net::InterfaceAddress::Ipv6(NEW_IPV6_ADDRESS.addr),
]
);
Some(())
}
},
)
.await
.expect("new addresses should be observed");
}
#[variants_test]
async fn disable_interface_loopback<N: Netstack>(name: &str) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("create realm");
let stack =
realm.connect_to_protocol::<fnet_stack::StackMarker>().expect("connect to protocol");
let interface_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let stream = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)
.expect("get interface event stream");
futures::pin_mut!(stream);
let loopback_id = assert_matches::assert_matches!(
stream.try_next().await,
Ok(Some(fidl_fuchsia_net_interfaces::Event::Existing(
fidl_fuchsia_net_interfaces::Properties {
id: Some(id),
device_class:
Some(fidl_fuchsia_net_interfaces::DeviceClass::Loopback(
fidl_fuchsia_net_interfaces::Empty {},
)),
online: Some(true),
..
},
))) => id
);
let () = assert_matches::assert_matches!(
stream.try_next().await,
Ok(Some(fidl_fuchsia_net_interfaces::Event::Idle(
fidl_fuchsia_net_interfaces::Empty {},
))) => ()
);
match N::VERSION {
NetstackVersion::Netstack2 => {
let debug = realm
.connect_to_protocol::<fidl_fuchsia_net_debug::InterfacesMarker>()
.expect("connect to protocol");
let (control, server_end) =
fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
.expect("create proxy");
let () = debug.get_admin(loopback_id, server_end).expect("get admin");
let did_disable = control.disable().await.expect("send disable").expect("disable");
assert!(did_disable);
let () = assert_matches::assert_matches!(stream.try_next().await,
Ok(Some(fidl_fuchsia_net_interfaces::Event::Changed(
fidl_fuchsia_net_interfaces::Properties {
id: Some(id),
online: Some(false),
..
},
))) if id == loopback_id => ()
);
}
NetstackVersion::Netstack3 => {
// TODO(https://fxbug.dev/92767): Remove this when N3 implements Control.
let () =
exec_fidl!(stack.disable_interface_deprecated(loopback_id), "disable interface")
.unwrap();
// TODO(https://fxbug.dev/75553): Wait for changed event instead of
// creating a new watcher.
let stream = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)
.expect("get interface event stream");
futures::pin_mut!(stream);
let new_loopback_id = assert_matches::assert_matches!(
stream.try_next().await,
Ok(Some(fidl_fuchsia_net_interfaces::Event::Existing(
fidl_fuchsia_net_interfaces::Properties {
id: Some(id),
device_class:
Some(fidl_fuchsia_net_interfaces::DeviceClass::Loopback(
fidl_fuchsia_net_interfaces::Empty {},
)),
online: Some(false),
..
},
))) => id
);
assert_eq!(loopback_id, new_loopback_id);
}
NetstackVersion::ProdNetstack2 => panic!("unexpectedly got ProdNetstack2 variant"),
}
}
enum ForwardingConfiguration {
All,
Iface1Only(fidl_fuchsia_net::IpVersion),
Iface2Only(fidl_fuchsia_net::IpVersion),
}
struct ForwardingTestCase<I: IcmpIpExt> {
iface1_addr: fidl_fuchsia_net::Subnet,
iface2_addr: fidl_fuchsia_net::Subnet,
forwarding_config: Option<ForwardingConfiguration>,
src_ip: I::Addr,
dst_ip: I::Addr,
expect_forward: bool,
}
fn test_forwarding_v4(
forwarding_config: Option<ForwardingConfiguration>,
expect_forward: bool,
) -> ForwardingTestCase<net_types::ip::Ipv4> {
ForwardingTestCase {
iface1_addr: fidl_subnet!("192.168.1.1/24"),
iface2_addr: fidl_subnet!("192.168.2.1/24"),
forwarding_config,
// TODO(https://fxbug.dev/77901): Use `std_ip_v4!(..).into()`.
// TODO(https://fxbug.dev/77965): Use `net_declare` macros to create
// `net_types` addresses.
src_ip: net_types::ip::Ipv4Addr::new(std_ip_v4!("192.168.1.2").octets()),
dst_ip: net_types::ip::Ipv4Addr::new(std_ip_v4!("192.168.2.2").octets()),
expect_forward,
}
}
fn test_forwarding_v6(
forwarding_config: Option<ForwardingConfiguration>,
expect_forward: bool,
) -> ForwardingTestCase<net_types::ip::Ipv6> {
ForwardingTestCase {
iface1_addr: fidl_subnet!("a::1/64"),
iface2_addr: fidl_subnet!("b::1/64"),
forwarding_config,
// TODO(https://fxbug.dev/77901): Use `std_ip_v6!(..).into()`.
// TODO(https://fxbug.dev/77965): Use `net_declare` macros to create
// `net_types` addresses.
src_ip: net_types::ip::Ipv6Addr::from_bytes(std_ip_v6!("a::2").octets()),
dst_ip: net_types::ip::Ipv6Addr::from_bytes(std_ip_v6!("b::2").octets()),
expect_forward,
}
}
#[variants_test]
#[test_case(
"v4_none_forward_icmp_v4",
test_forwarding_v4(
None,
false,
); "v4_none_forward_icmp_v4")]
#[test_case(
"v4_all_forward_icmp_v4",
test_forwarding_v4(
Some(ForwardingConfiguration::All),
true,
); "v4_all_forward_icmp_v4")]
#[test_case(
"v4_iface1_forward_v4_icmp_v4",
test_forwarding_v4(
Some(ForwardingConfiguration::Iface1Only(fidl_fuchsia_net::IpVersion::V4)),
true,
); "v4_iface1_forward_v4_icmp_v4")]
#[test_case(
"v4_iface1_forward_v6_icmp_v4",
test_forwarding_v4(
Some(ForwardingConfiguration::Iface1Only(fidl_fuchsia_net::IpVersion::V6)),
false,
); "v4_iface1_forward_v6_icmp_v4")]
#[test_case(
"v4_iface2_forward_v4_icmp_v4",
test_forwarding_v4(
Some(ForwardingConfiguration::Iface2Only(fidl_fuchsia_net::IpVersion::V4)),
false,
); "v4_iface2_forward_v4_icmp_v4")]
#[test_case(
"v6_none_forward_icmp_v6",
test_forwarding_v6(
None,
false,
); "v6_none_forward_icmp_v6")]
#[test_case(
"v6_all_forward_icmp_v6",
test_forwarding_v6(
Some(ForwardingConfiguration::All),
true,
); "v6_all_forward_icmp_v6")]
#[test_case(
"v6_iface1_forward_v6_icmp_v6",
test_forwarding_v6(
Some(ForwardingConfiguration::Iface1Only(fidl_fuchsia_net::IpVersion::V6)),
true,
); "v6_iface1_forward_v6_icmp_v6")]
#[test_case(
"v6_iface1_forward_v4_icmp_v6",
test_forwarding_v6(
Some(ForwardingConfiguration::Iface1Only(fidl_fuchsia_net::IpVersion::V4)),
false,
); "v6_iface1_forward_v4_icmp_v6")]
#[test_case(
"v6_iface2_forward_v6_icmp_v6",
test_forwarding_v6(
Some(ForwardingConfiguration::Iface2Only(fidl_fuchsia_net::IpVersion::V6)),
false,
); "v6_iface2_forward_v6_icmp_v6")]
async fn test_forwarding<E: netemul::Endpoint, I: IcmpIpExt>(
test_name: &str,
sub_test_name: &str,
test_case: ForwardingTestCase<I>,
) where
IcmpEchoRequest:
for<'a> IcmpMessage<I, &'a [u8], Code = IcmpUnusedCode, Body = OriginalPacket<&'a [u8]>>,
I::Addr: NetTypesIpAddressExt,
{
const TTL: u8 = 64;
const ECHO_ID: u16 = 1;
const ECHO_SEQ: u16 = 2;
const MAC: fidl_fuchsia_net::MacAddress = fidl_mac!("02:0A:0B:0C:0D:0E");
let ForwardingTestCase {
iface1_addr,
iface2_addr,
forwarding_config,
src_ip,
dst_ip,
expect_forward,
} = test_case;
let name = format!("{}_{}", test_name, sub_test_name);
let name = name.as_str();
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let sandbox = &sandbox;
let realm = sandbox.create_netstack_realm::<Netstack2, _>(name).expect("create netstack realm");
let realm = &realm;
let net_ep_iface = |net_num: u8, addr: fidl_fuchsia_net::Subnet| async move {
let net = sandbox.create_network(format!("net{}", net_num)).await.expect("create network");
let fake_ep = net.create_fake_endpoint().expect("create fake endpoint");
let iface = realm
.join_network::<E, _>(
&net,
format!("iface{}", net_num),
&netemul::InterfaceConfig::StaticIp(addr),
)
.await
.expect("configure networking");
(net, fake_ep, iface)
};
let (_net1, fake_ep1, iface1) = net_ep_iface(1, iface1_addr).await;
let (_net2, fake_ep2, iface2) = net_ep_iface(2, iface2_addr).await;
if let Some(config) = forwarding_config {
let stack = realm
.connect_to_protocol::<fnet_stack::StackMarker>()
.expect("error connecting to stack");
match config {
ForwardingConfiguration::All => {
let () = stack
.enable_ip_forwarding()
.await
.expect("error enabling IP forwarding request");
}
ForwardingConfiguration::Iface1Only(ip_version) => {
let () = stack
.set_interface_ip_forwarding(iface1.id(), ip_version, true)
.await
.expect("set_interface_ip_forwarding FIDL error for iface1")
.expect("error enabling IP forwarding on iface1");
}
ForwardingConfiguration::Iface2Only(ip_version) => {
let () = stack
.set_interface_ip_forwarding(iface2.id(), ip_version, true)
.await
.expect("set_interface_ip_forwarding FIDL error for iface2")
.expect("error enabling IP forwarding on iface2");
}
}
}
let neighbor_controller = realm
.connect_to_protocol::<fidl_fuchsia_net_neighbor::ControllerMarker>()
.expect("connect to protocol");
let dst_ip_fidl: <I::Addr as NetTypesIpAddressExt>::Fidl = dst_ip.into_ext();
let () = neighbor_controller
.add_entry(iface2.id(), &mut dst_ip_fidl.into_ext(), &mut MAC.clone())
.await
.expect("add_entry FIDL error")
.expect("error adding static entry");
let mut icmp_body = [1, 2, 3, 4, 5, 6, 7, 8];
let ser = packet::Buf::new(&mut icmp_body, ..)
.encapsulate(IcmpPacketBuilder::<I, _, _>::new(
src_ip,
dst_ip,
IcmpUnusedCode,
IcmpEchoRequest::new(ECHO_ID, ECHO_SEQ),
))
.encapsulate(<I as packet_formats::ip::IpExt>::PacketBuilder::new(
src_ip,
dst_ip,
TTL,
I::ICMP_IP_PROTO,
))
.encapsulate(EthernetFrameBuilder::new(
net_types::ethernet::Mac::new([1, 2, 3, 4, 5, 6]),
net_types::ethernet::Mac::BROADCAST,
I::ETHER_TYPE,
))
.serialize_vec_outer()
.expect("serialize ICMP packet")
.unwrap_b();
let duration = if expect_forward {
ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT
} else {
ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT
};
let ((), forwarded) = futures::future::join(
fake_ep1.write(ser.as_ref()).map(|r| r.expect("write to fake endpoint #1")),
fake_ep2
.frame_stream()
.map(|r| r.expect("error getting OnData event"))
.filter_map(|(data, dropped)| {
assert_eq!(dropped, 0);
let mut data = &data[..];
let eth = EthernetFrame::parse(&mut data, EthernetFrameLengthCheck::NoCheck)
.expect("error parsing ethernet frame");
if eth.ethertype() != Some(I::ETHER_TYPE) {
// Ignore other IP packets.
return futures::future::ready(None);
}
let (mut payload, src_ip, dst_ip, proto, got_ttl) =
packet_formats::testutil::parse_ip_packet::<I>(&data)
.expect("error parsing IP packet");
if proto != I::ICMP_IP_PROTO {
// Ignore non-ICMP packets.
return futures::future::ready(None);
}
let icmp = match IcmpPacket::<I, _, IcmpEchoRequest>::parse(
&mut payload,
IcmpParseArgs::new(src_ip, dst_ip),
) {
Ok(o) => o,
Err(ParseError::NotExpected) => {
// Ignore non-echo request packets.
return futures::future::ready(None);
}
Err(e) => {
panic!("error parsing ICMP echo request packet: {}", e)
}
};
let echo_request = icmp.message();
assert_eq!(echo_request.id(), ECHO_ID);
assert_eq!(echo_request.seq(), ECHO_SEQ);
assert_eq!(icmp.body().bytes(), icmp_body);
assert_eq!(got_ttl, TTL - 1);
// Our packet was forwarded.
futures::future::ready(Some(true))
})
.next()
.map(|r| r.expect("stream unexpectedly ended"))
.on_timeout(duration.after_now(), || {
// The packet was not forwarded.
false
}),
)
.await;
assert_eq!(expect_forward, forwarded);
}