blob: df3d85491f1d61a007a3e5751d4f533a48c307c4 [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)]
// Needed for invocations of the `assert_data_tree` macro.
#![recursion_limit = "256"]
use std::{collections::HashMap, convert::TryFrom as _, num::NonZeroU16};
use diagnostics_assertions::{
assert_data_tree, tree_assertion, AnyProperty, NonZeroIntProperty, PropertyAssertion,
TreeAssertion,
};
use fidl_fuchsia_io as fio;
use fidl_fuchsia_posix_socket as fposix_socket;
use fidl_fuchsia_posix_socket_packet as fposix_socket_packet;
use fidl_fuchsia_posix_socket_raw as fposix_socket_raw;
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use const_unwrap::const_unwrap_option;
use diagnostics_hierarchy::{DiagnosticsHierarchy, Property};
use itertools::Itertools as _;
use net_declare::{fidl_ip, fidl_mac, fidl_subnet};
use net_types::ip::Ip as _;
use netemul::InStack;
use netstack_testing_common::{
constants,
realms::{KnownServiceProvider, Netstack, TestSandboxExt as _},
Result,
};
use netstack_testing_macros::netstack_test;
use packet::{ParsablePacket as _, Serializer as _};
use packet_formats::{
ethernet::{
testutil::ETHERNET_HDR_LEN_NO_TAG, EtherType, EthernetFrameBuilder,
ETHERNET_MIN_BODY_LEN_NO_TAG,
},
ipv4::{Ipv4Header as _, Ipv4PacketBuilder},
udp::{UdpPacketBuilder, UdpParseArgs},
};
use test_case::test_case;
/// A helper type to provide address verification in inspect NIC data.
///
/// Address matcher implements `PropertyAssertion` in a stateful manner. It
/// expects all addresses in its internal set to be consumed as part of property
/// matching.
#[derive(Clone)]
struct AddressMatcher {
set: std::rc::Rc<std::cell::RefCell<std::collections::HashSet<String>>>,
}
impl AddressMatcher {
/// Creates an `AddressMatcher` from interface properties.
fn new(props: &fidl_fuchsia_net_interfaces_ext::Properties) -> Self {
let set = props
.addresses
.iter()
.map(
|&fidl_fuchsia_net_interfaces_ext::Address {
addr: subnet,
valid_until: _,
assignment_state,
}| {
assert_eq!(
assignment_state,
fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned
);
let fidl_fuchsia_net::Subnet { addr, prefix_len: _ } = subnet;
let prefix = match addr {
fidl_fuchsia_net::IpAddress::Ipv4(_) => "ipv4",
fidl_fuchsia_net::IpAddress::Ipv6(_) => "ipv6",
};
format!("[{}] {}", prefix, fidl_fuchsia_net_ext::Subnet::from(subnet))
},
)
.collect::<std::collections::HashSet<_>>();
Self { set: std::rc::Rc::new(std::cell::RefCell::new(set)) }
}
/// Checks that the internal set has been entirely consumed.
///
/// Empties the internal set on return. Subsequent calls to check will
/// always succeed.
fn check(&self) -> Result<()> {
let set = self.set.replace(Default::default());
if set.is_empty() {
Ok(())
} else {
Err(anyhow::anyhow!("unseen addresses left in set: {:?}", set))
}
}
}
impl std::ops::Drop for AddressMatcher {
fn drop(&mut self) {
// Always check for left over addresses on drop. Prevents the caller
// from forgetting to do so.
let () = self.check().expect("AddressMatcher was not emptied");
}
}
impl PropertyAssertion for AddressMatcher {
fn run(&self, actual: &Property<String>) -> Result<()> {
let actual = actual.string().ok_or_else(|| {
anyhow::anyhow!("invalid property {:#?} for AddressMatcher, want String", actual)
})?;
if self.set.borrow_mut().remove(actual) {
Ok(())
} else {
Err(anyhow::anyhow!("{} not in expected address set", actual))
}
}
}
#[netstack_test]
async fn inspect_nic(name: &str) {
type N = netstack_testing_common::realms::Netstack2;
// The number of IPv6 addresses that the stack will assign to an interface.
const EXPECTED_NUM_IPV6_ADDRESSES: usize = 1;
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let network = sandbox.create_network("net").await.expect("failed to create network");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("failed to create realm");
const NETDEV_MAC: fidl_fuchsia_net::MacAddress = fidl_mac!("02:0A:0B:0C:0D:0E");
let max_frame_size = netemul::DEFAULT_MTU
+ u16::try_from(ETHERNET_HDR_LEN_NO_TAG)
.expect("should fit ethernet header length in a u16");
let netdev = realm
.join_network_with(
&network,
"netdev-ep",
netemul::new_endpoint_config(max_frame_size, Some(NETDEV_MAC)),
Default::default(),
)
.await
.expect("failed to join network with netdevice endpoint");
netdev
.add_address_and_subnet_route(fidl_subnet!("192.168.0.2/24"))
.await
.expect("configure address");
let interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("failed to connect to fuchsia.net.interfaces/State");
// Wait for the world to stabilize and capture the state to verify inspect
// data.
let (loopback_props, netdev_props) = fidl_fuchsia_net_interfaces_ext::wait_interface(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(
&interfaces_state,
fidl_fuchsia_net_interfaces_ext::IncludedAddresses::OnlyAssigned,
)
.expect("failed to create event stream"),
&mut HashMap::<u64, fidl_fuchsia_net_interfaces_ext::PropertiesAndState<()>>::new(),
|if_map| {
let loopback = if_map.values().find_map(
|fidl_fuchsia_net_interfaces_ext::PropertiesAndState { properties, state: _ }| {
match properties.device_class {
fidl_fuchsia_net_interfaces::DeviceClass::Loopback(
fidl_fuchsia_net_interfaces::Empty {},
) => Some(properties.clone()),
fidl_fuchsia_net_interfaces::DeviceClass::Device(_) => None,
}
},
)?;
// Endpoint is up, has assigned IPv4 and at least the expected number of
// IPv6 addresses.
let netdev_properties = {
let fidl_fuchsia_net_interfaces_ext::PropertiesAndState { properties, state: _ } =
if_map.get(&netdev.id())?;
let fidl_fuchsia_net_interfaces_ext::Properties { online, addresses, .. } =
properties;
if !online {
return None;
}
let (v4_count, v6_count) = addresses.iter().fold(
(0, 0),
|(v4_count, v6_count),
fidl_fuchsia_net_interfaces_ext::Address {
addr: fidl_fuchsia_net::Subnet { addr, prefix_len: _ },
valid_until: _,
assignment_state,
}| {
assert_eq!(
*assignment_state,
fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned
);
match addr {
fidl_fuchsia_net::IpAddress::Ipv4(_) => (v4_count + 1, v6_count),
fidl_fuchsia_net::IpAddress::Ipv6(_) => (v4_count, v6_count + 1),
}
},
);
if v4_count > 0 && v6_count >= EXPECTED_NUM_IPV6_ADDRESSES {
properties.clone()
} else {
return None;
}
};
Some((loopback, netdev_properties))
},
)
.await
.expect("failed to wait for interfaces up and addresses configured");
let loopback_addrs = AddressMatcher::new(&loopback_props);
let netdev_addrs = AddressMatcher::new(&netdev_props);
// Populate the neighbor table so we can verify inspection of its entries.
const BOB_IP: fidl_fuchsia_net::IpAddress = fidl_ip!("192.168.0.1");
const BOB_MAC: fidl_fuchsia_net::MacAddress = fidl_mac!("02:0A:0B:0C:0D:0E");
let () = realm
.connect_to_protocol::<fidl_fuchsia_net_neighbor::ControllerMarker>()
.expect("failed to connect to Controller")
.add_entry(netdev.id(), &BOB_IP, &BOB_MAC)
.await
.expect("add_entry FIDL error")
.map_err(zx::Status::from_raw)
.expect("add_entry failed");
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("failed to open diagnostics dir");
let data = get_inspect_data(&diagnostics_dir, "NICs", "interfaces").await;
// Debug print the tree to make debugging easier in case of failures.
println!("Got inspect data: {:#?}", data);
assert_data_tree!(data, NICs: {
loopback_props.id.to_string() => {
Name: loopback_props.name,
Loopback: "true",
LinkOnline: "true",
AdminUp: "true",
Promiscuous: "false",
Up: "true",
MTU: 65522u64,
NICID: loopback_props.id.to_string(),
Running: "true",
"DHCP enabled": "false",
LinkAddress: "00:00:00:00:00:00",
ProtocolAddress0: loopback_addrs.clone(),
ProtocolAddress1: loopback_addrs.clone(),
Stats: {
DisabledRx: {
Bytes: 0u64,
Packets: 0u64,
},
Tx: {
Bytes: 0u64,
Packets: 0u64,
},
TxPacketsDroppedNoBufferSpace: 0u64,
Rx: {
Bytes: 0u64,
Packets: 0u64,
},
Neighbor: {
DroppedConfirmationForNoninitiatedNeighbor: 0u64,
DroppedInvalidLinkAddressConfirmations: 0u64,
UnreachableEntryLookups: 0u64,
},
MalformedL4RcvdPackets: 0u64,
UnknownL3ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
UnknownL4ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
},
"Network Endpoint Stats": {
ARP: contains {},
IPv4: contains {},
IPv6: contains {},
}
},
netdev.id().to_string() => {
Name: netdev_props.name,
Loopback: "false",
LinkOnline: "true",
AdminUp: "true",
Promiscuous: "false",
Up: "true",
MTU: u64::from(netemul::DEFAULT_MTU),
NICID: netdev.id().to_string(),
Running: "true",
"DHCP enabled": "false",
LinkAddress: fidl_fuchsia_net_ext::MacAddress::from(NETDEV_MAC).to_string(),
// IPv4.
ProtocolAddress0: netdev_addrs.clone(),
// Link-local IPv6.
ProtocolAddress1: netdev_addrs.clone(),
Stats: {
DisabledRx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Tx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
TxPacketsDroppedNoBufferSpace: AnyProperty,
Rx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Neighbor: {
DroppedConfirmationForNoninitiatedNeighbor: AnyProperty,
DroppedInvalidLinkAddressConfirmations: AnyProperty,
UnreachableEntryLookups: AnyProperty,
},
MalformedL4RcvdPackets: 0u64,
UnknownL3ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
UnknownL4ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
},
"Network Device Info": {
TxDrops: AnyProperty,
Class: "Virtual",
RxReads: contains {},
RxWrites: contains {},
TxReads: contains {},
TxWrites: contains {}
},
Neighbors: {
fidl_fuchsia_net_ext::IpAddress::from(BOB_IP).to_string() => {
"Link address": fidl_fuchsia_net_ext::MacAddress::from(BOB_MAC).to_string(),
State: "Static",
"Last updated": NonZeroIntProperty,
}
},
"Network Endpoint Stats": {
ARP: contains {},
IPv4: contains {},
IPv6: contains {},
}
}
});
let () = loopback_addrs.check().expect("loopback addresses match failed");
let () = netdev_addrs.check().expect("netdev addresses match failed");
}
#[netstack_test]
async fn inspect_routing_table(name: &str) {
type N = netstack_testing_common::realms::Netstack2;
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("failed to create realm");
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("diagnostics dir exists");
// NB: The Netstack component won't be started until we attempt to access
// one of its FIDL services. Connect to fuchsia.net.interfaces/State to
// start it.
let _interfaces_state = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("failed to connect to fuchsia.net.interfaces/State");
// NB: Hardcode the expected routes rather than look them up via FIDL
// (fuchsia.net.routes). They are "keyed" in the inspect data by their
// insertion order, but the `fuchsia.net.routes` API do not guarantee
// listing routes in any particular order.
let mut routing_table_assertion = TreeAssertion::new("Routes", true);
routing_table_assertion.add_child_assertion(tree_assertion!("0": {
"Destination": "127.0.0.0/8",
"Gateway": "",
"NIC": "1",
"Metric": "100",
"MetricTracksInterface": "true",
"Dynamic": AnyProperty,
"Enabled": AnyProperty,
}));
routing_table_assertion.add_child_assertion(tree_assertion!("1": {
"Destination": "::1/128",
"Gateway": "",
"NIC": "1",
"Metric": "100",
"MetricTracksInterface": "true",
"Dynamic": AnyProperty,
"Enabled": AnyProperty,
}));
routing_table_assertion.add_child_assertion(tree_assertion!("2": {
"Destination": "255.255.255.255/32",
"Gateway": "",
"NIC": "1",
"Metric": "99999",
"MetricTracksInterface": "false",
"Dynamic": AnyProperty,
"Enabled": AnyProperty,
}));
let data = get_inspect_data(&diagnostics_dir, "Routes", "routes").await;
let () = routing_table_assertion
.run(&data)
.unwrap_or_else(|e| panic!("tree assertion fails: {}, inspect data is: {:#?}", e, data));
}
struct PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto,
port: NonZeroU16,
}
const INVALID_PORT: NonZeroU16 = const_unwrap_option(NonZeroU16::new(1234));
#[netstack_test]
#[test_case(
"invalid_trans_proto",
vec![
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp),
port: dhcpv4::protocol::CLIENT_PORT,
}
]; "invalid_trans_proto")]
#[test_case(
"invalid_port",
vec![
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
port: INVALID_PORT,
}
]; "invalid_port")]
#[test_case(
"valid",
vec![
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
port: dhcpv4::protocol::CLIENT_PORT,
}
]; "valid")]
#[test_case(
"multiple_invalid_port_and_single_invalid_trans_proto",
vec![
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
port: INVALID_PORT,
},
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
port: INVALID_PORT,
},
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp),
port: dhcpv4::protocol::CLIENT_PORT,
}
]; "multiple_invalid_port_and_single_invalid_trans_proto")]
async fn inspect_dhcp(
netstack_test_name: &str,
test_case_name: &str,
inbound_packets: Vec<PacketAttributes>,
) {
type N = netstack_testing_common::realms::Netstack2;
// TODO(https://fxbug.dev/42159805): Extend this test to cover the stat tracking frames discarded
// due to an invalid PacketType.
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let network = sandbox.create_network("net").await.expect("failed to create network");
let name = format!("{netstack_test_name}-{test_case_name}");
let realm = sandbox.create_netstack_realm::<N, _>(&name).expect("failed to create realm");
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("diagnostics dir exists");
// Create the fake endpoint before installing an endpoint in the netstack to ensure
// that we receive all DHCP messages sent by the client.
let fake_ep = network.create_fake_endpoint().expect("failed to create fake endpoint");
let eth = realm.join_network(&network, "ep1").await.expect("failed to join network");
eth.start_dhcp::<InStack>().await.expect("failed to start DHCP");
// Wait for a DHCP message here to ensure that the client is ready to receive
// incoming packets.
loop {
let (buf, _dropped_frames): (Vec<u8>, u64) =
fake_ep.read().await.expect("failed to read from endpoint");
let mut buf = &buf[..];
let frame = packet_formats::ethernet::EthernetFrame::parse(
&mut buf,
packet_formats::ethernet::EthernetFrameLengthCheck::NoCheck,
)
.expect("failed to parse ethernet frame");
match frame.ethertype().expect("failed to parse frame ethertype") {
packet_formats::ethernet::EtherType::Ipv4 => {
let mut frame_body = frame.body();
let packet = packet_formats::ipv4::Ipv4Packet::parse(&mut frame_body, ())
.expect("failed to parse IPv4 packet");
match packet.proto() {
packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp) => {
let mut packet_body = packet.body();
let datagram = packet_formats::udp::UdpPacket::parse(
&mut packet_body,
UdpParseArgs::new(packet.src_ip(), packet.dst_ip()),
)
.expect("failed to parse UDP datagram");
const DHCPV4_SERVER_PORT: u16 = dhcpv4::protocol::SERVER_PORT.get();
match datagram.dst_port().get() {
DHCPV4_SERVER_PORT => {
// Any DHCP message means the client is listening; we don't care
// about the contents.
let _: dhcpv4::protocol::Message =
dhcpv4::protocol::Message::from_buffer(datagram.body())
.expect("failed to parse DHCP message");
break;
}
port => println!(
"received non-DHCP UDP datagram with destination port: {:?}",
port
),
}
}
proto => println!(
"received non-UDP IPv4 packet with transport protocol: {:?}",
proto
),
}
}
ethertype => println!("received non-IPv4 frame with ethertype: {:?}", ethertype),
}
}
const SRC_IP: net_types::ip::Ipv4Addr = net_types::ip::Ipv4::UNSPECIFIED_ADDRESS;
const DST_IP: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr> =
net_types::ip::Ipv4::LIMITED_BROADCAST_ADDRESS;
for PacketAttributes { ip_proto, port } in &inbound_packets {
let ser = packet::Buf::new(&mut [], ..)
.encapsulate(UdpPacketBuilder::new(SRC_IP, *DST_IP, None, *port))
.encapsulate(Ipv4PacketBuilder::new(SRC_IP, DST_IP, /* ttl */ 1, *ip_proto))
.encapsulate(EthernetFrameBuilder::new(
constants::eth::MAC_ADDR, /* src_mac */
net_types::ethernet::Mac::BROADCAST, /* dst_mac */
EtherType::Ipv4,
ETHERNET_MIN_BODY_LEN_NO_TAG,
))
.serialize_vec_outer()
.expect("failed to serialize UDP packet")
.unwrap_b();
let () = fake_ep.write(ser.as_ref()).await.expect("failed to write to endpoint");
}
const DISCARD_STATS_NAME: &str = "PacketDiscardStats";
const INVALID_PORT_STAT_NAME: &str = "InvalidPort";
const INVALID_TRANS_PROTO_STAT_NAME: &str = "InvalidTransProto";
const INVALID_PACKET_TYPE_STAT_NAME: &str = "InvalidPacketType";
const COUNTER_PROPERTY_NAME: &str = "Count";
const TOTAL_COUNTER_NAME: &str = "Total";
let path = ["Stats", "DHCP Info", &eth.id().to_string(), "NICs"];
let mut invalid_ports = HashMap::<NonZeroU16, u64>::new();
let mut invalid_trans_protos = HashMap::<u8, u64>::new();
for PacketAttributes { port, ip_proto } in inbound_packets {
if ip_proto != packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp) {
let _: &mut u64 =
invalid_trans_protos.entry(ip_proto.into()).and_modify(|v| *v += 1).or_insert(1);
} else if port != dhcpv4::protocol::CLIENT_PORT {
let _: &mut u64 = invalid_ports.entry(port.into()).and_modify(|v| *v += 1).or_insert(1);
}
}
let mut invalid_port_assertion = TreeAssertion::new(INVALID_PORT_STAT_NAME, true);
let mut invalid_trans_proto_assertion = TreeAssertion::new(INVALID_TRANS_PROTO_STAT_NAME, true);
let mut invalid_packet_type_assertion = TreeAssertion::new(INVALID_PACKET_TYPE_STAT_NAME, true);
let mut total_packet_type_assertion = TreeAssertion::new(TOTAL_COUNTER_NAME, true);
let () = total_packet_type_assertion
.add_property_assertion(COUNTER_PROPERTY_NAME, Box::new(0.to_string()));
let () = invalid_packet_type_assertion.add_child_assertion(total_packet_type_assertion);
let mut total_port = 0;
for (port, count) in invalid_ports {
let mut port_assertion = TreeAssertion::new(&port.to_string(), true);
let () = port_assertion
.add_property_assertion(COUNTER_PROPERTY_NAME, Box::new(count.to_string()));
total_port += count;
let () = invalid_port_assertion.add_child_assertion(port_assertion);
}
let mut total_port_assertion = TreeAssertion::new(TOTAL_COUNTER_NAME, true);
let () = total_port_assertion
.add_property_assertion(COUNTER_PROPERTY_NAME, Box::new(total_port.to_string()));
let () = invalid_port_assertion.add_child_assertion(total_port_assertion);
let mut total_trans_proto = 0;
for (proto, count) in invalid_trans_protos {
let mut trans_proto_assertion = TreeAssertion::new(&proto.to_string(), true);
let () = trans_proto_assertion
.add_property_assertion(COUNTER_PROPERTY_NAME, Box::new(count.to_string()));
total_trans_proto += count;
let () = invalid_trans_proto_assertion.add_child_assertion(trans_proto_assertion);
}
let mut total_trans_proto_assertion = TreeAssertion::new(TOTAL_COUNTER_NAME, true);
let () = total_trans_proto_assertion
.add_property_assertion(COUNTER_PROPERTY_NAME, Box::new(total_trans_proto.to_string()));
let () = invalid_trans_proto_assertion.add_child_assertion(total_trans_proto_assertion);
let mut discard_stats_assertion = TreeAssertion::new(DISCARD_STATS_NAME, true);
let () = discard_stats_assertion.add_child_assertion(invalid_port_assertion);
let () = discard_stats_assertion.add_child_assertion(invalid_trans_proto_assertion);
let () = discard_stats_assertion.add_child_assertion(invalid_packet_type_assertion);
let tree_assertion = path.iter().fold(discard_stats_assertion, |acc, name| {
let mut assertion = TreeAssertion::new(name, true);
let () = assertion.add_child_assertion(acc);
assertion
});
loop {
let data = get_inspect_data(
&diagnostics_dir,
std::iter::once(DISCARD_STATS_NAME)
.chain(path)
.into_iter()
.map(selectors::sanitize_string_for_selectors)
.rev()
.join("/"),
"interfaces",
)
.await;
match tree_assertion.run(&data) {
Ok(()) => break,
Err(err) => {
println!("Got mismatched inspect data with err: {:?}", err);
}
}
let () = fasync::Timer::new(std::time::Duration::from_millis(100)).await;
}
}
// This test verifies exactly which stat counters are exported through
// inspect. If any counter is added or deleted, the inline list of the
// counters below should be updated accordingly.
//
// Note that many of the counters are implemented in gVisor. They are
// automatically exported from netstack via reflection. This test
// serves as a change detector to acknowledge any possible additions
// or deletions when importing code from upstream.
#[netstack_test]
async fn inspect_stat_counters(name: &str) {
type N = netstack_testing_common::realms::Netstack2;
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("failed to create realm");
// Connect to netstack service to spawn a netstack instance.
let _stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("failed to connect to fuchsia.net.stack/Stack");
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("diagnostics dir exists");
let data =
get_inspect_data(&diagnostics_dir, r#"Networking\ Stat\ Counters"#, "counters").await;
// TODO(https://fxbug.dev/42140843): change AnyProperty to AnyUintProperty when available.
assert_data_tree!(data, "Networking Stat Counters": {
DroppedPackets: AnyProperty,
SocketCount: AnyProperty,
SocketsCreated: AnyProperty,
SocketsDestroyed: AnyProperty,
ARP: {
DisabledPacketsReceived: AnyProperty,
MalformedPacketsReceived: AnyProperty,
OutgoingRepliesDropped: AnyProperty,
OutgoingRepliesSent: AnyProperty,
OutgoingRequestBadLocalAddressErrors: AnyProperty,
OutgoingRequestInterfaceHasNoLocalAddressErrors: AnyProperty,
OutgoingRequestsDropped: AnyProperty,
OutgoingRequestsSent: AnyProperty,
PacketsReceived: AnyProperty,
RepliesReceived: AnyProperty,
RequestsReceived: AnyProperty,
RequestsReceivedUnknownTargetAddress: AnyProperty,
},
DHCPv6: {
ManagedAddress: AnyProperty,
NoConfiguration: AnyProperty,
OtherConfiguration: AnyProperty,
},
ICMP: {
V4: {
PacketsReceived: {
DstUnreachable: AnyProperty,
EchoReply: AnyProperty,
EchoRequest: AnyProperty,
InfoReply: AnyProperty,
InfoRequest: AnyProperty,
Invalid: AnyProperty,
ParamProblem: AnyProperty,
Redirect: AnyProperty,
SrcQuench: AnyProperty,
TimeExceeded: AnyProperty,
Timestamp: AnyProperty,
TimestampReply: AnyProperty,
},
PacketsSent: {
Dropped: AnyProperty,
DstUnreachable: AnyProperty,
EchoReply: AnyProperty,
EchoRequest: AnyProperty,
InfoReply: AnyProperty,
InfoRequest: AnyProperty,
ParamProblem: AnyProperty,
RateLimited: AnyProperty,
Redirect: AnyProperty,
SrcQuench: AnyProperty,
TimeExceeded: AnyProperty,
Timestamp: AnyProperty,
TimestampReply: AnyProperty,
},
},
V6: {
PacketsReceived: {
DstUnreachable: AnyProperty,
EchoReply: AnyProperty,
EchoRequest: AnyProperty,
Invalid: AnyProperty,
MulticastListenerDone: AnyProperty,
MulticastListenerQuery: AnyProperty,
MulticastListenerReport: AnyProperty,
MulticastListenerReportV2: AnyProperty,
NeighborAdvert: AnyProperty,
NeighborSolicit: AnyProperty,
PacketTooBig: AnyProperty,
ParamProblem: AnyProperty,
RedirectMsg: AnyProperty,
RouterAdvert: AnyProperty,
RouterOnlyPacketsDroppedByHost: AnyProperty,
RouterSolicit: AnyProperty,
TimeExceeded: AnyProperty,
Unrecognized: AnyProperty,
},
PacketsSent: {
Dropped: AnyProperty,
DstUnreachable: AnyProperty,
EchoReply: AnyProperty,
EchoRequest: AnyProperty,
MulticastListenerDone: AnyProperty,
MulticastListenerQuery: AnyProperty,
MulticastListenerReport: AnyProperty,
MulticastListenerReportV2: AnyProperty,
NeighborAdvert: AnyProperty,
NeighborSolicit: AnyProperty,
PacketTooBig: AnyProperty,
ParamProblem: AnyProperty,
RateLimited: AnyProperty,
RedirectMsg: AnyProperty,
RouterAdvert: AnyProperty,
RouterSolicit: AnyProperty,
TimeExceeded: AnyProperty,
},
},
},
IGMP: {
PacketsReceived: {
ChecksumErrors: AnyProperty,
Invalid: AnyProperty,
LeaveGroup: AnyProperty,
MembershipQuery: AnyProperty,
Unrecognized: AnyProperty,
V1MembershipReport: AnyProperty,
V2MembershipReport: AnyProperty,
V3MembershipReport: AnyProperty,
},
PacketsSent: {
Dropped: AnyProperty,
LeaveGroup: AnyProperty,
MembershipQuery: AnyProperty,
V1MembershipReport: AnyProperty,
V2MembershipReport: AnyProperty,
V3MembershipReport: AnyProperty,
},
},
IP: {
DisabledPacketsReceived: AnyProperty,
IPTablesForwardDropped: AnyProperty,
IPTablesInputDropped: AnyProperty,
IPTablesOutputDropped: AnyProperty,
IPTablesPostroutingDropped: AnyProperty,
IPTablesPreroutingDropped: AnyProperty,
InvalidDestinationAddressesReceived: AnyProperty,
InvalidSourceAddressesReceived: AnyProperty,
MalformedFragmentsReceived: AnyProperty,
MalformedPacketsReceived: AnyProperty,
OptionRecordRouteReceived: AnyProperty,
OptionRouterAlertReceived: AnyProperty,
OptionTimestampReceived: AnyProperty,
OptionUnknownReceived: AnyProperty,
OutgoingPacketErrors: AnyProperty,
PacketsDelivered: AnyProperty,
PacketsReceived: AnyProperty,
PacketsSent: AnyProperty,
ValidPacketsReceived: AnyProperty,
Forwarding: {
Errors: AnyProperty,
ExhaustedTTL: AnyProperty,
ExtensionHeaderProblem: AnyProperty,
HostUnreachable: AnyProperty,
InitializingSource: AnyProperty,
LinkLocalDestination: AnyProperty,
LinkLocalSource: AnyProperty,
NoMulticastPendingQueueBufferSpace: AnyProperty,
OutgoingDeviceNoBufferSpace: AnyProperty,
PacketTooBig: AnyProperty,
UnexpectedMulticastInputInterface: AnyProperty,
UnknownOutputEndpoint: AnyProperty,
Unrouteable: AnyProperty,
},
},
IPv6AddressConfig: {
DHCPv6ManagedAddressOnly: AnyProperty,
GlobalSLAACAndDHCPv6ManagedAddress: AnyProperty,
GlobalSLAACOnly: AnyProperty,
NoGlobalSLAACOrDHCPv6ManagedAddress: AnyProperty,
},
NICs: {
MalformedL4RcvdPackets: AnyProperty,
DisabledRx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Neighbor: {
DroppedConfirmationForNoninitiatedNeighbor: AnyProperty,
DroppedInvalidLinkAddressConfirmations: AnyProperty,
UnreachableEntryLookups: AnyProperty,
},
Rx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Tx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
TxPacketsDroppedNoBufferSpace: AnyProperty,
UnknownL3ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
UnknownL4ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
},
TCP: {
ActiveConnectionOpenings: AnyProperty,
ChecksumErrors: AnyProperty,
CurrentConnected: AnyProperty,
CurrentEstablished: AnyProperty,
EstablishedClosed: AnyProperty,
EstablishedResets: AnyProperty,
EstablishedTimedout: AnyProperty,
FailedConnectionAttempts: AnyProperty,
FailedPortReservations: AnyProperty,
FastRecovery: AnyProperty,
FastRetransmit: AnyProperty,
ForwardMaxInFlightDrop: AnyProperty,
InvalidSegmentsReceived: AnyProperty,
ListenOverflowAckDrop: AnyProperty,
ListenOverflowInvalidSynCookieRcvd: AnyProperty,
ListenOverflowSynCookieRcvd: AnyProperty,
ListenOverflowSynCookieSent: AnyProperty,
ListenOverflowSynDrop: AnyProperty,
PassiveConnectionOpenings: AnyProperty,
ResetsReceived: AnyProperty,
ResetsSent: AnyProperty,
Retransmits: AnyProperty,
SACKRecovery: AnyProperty,
SegmentSendErrors: AnyProperty,
SegmentsAckedWithDSACK: AnyProperty,
SegmentsSent: AnyProperty,
SlowStartRetransmits: AnyProperty,
SpuriousRTORecovery: AnyProperty,
SpuriousRecovery: AnyProperty,
TLPRecovery: AnyProperty,
Timeouts: AnyProperty,
ValidSegmentsReceived: AnyProperty,
},
UDP: {
ChecksumErrors: AnyProperty,
MalformedPacketsReceived: AnyProperty,
PacketSendErrors: AnyProperty,
PacketsReceived: AnyProperty,
PacketsSent: AnyProperty,
ReceiveBufferErrors: AnyProperty,
UnknownPortErrors: AnyProperty,
},
MaxSocketOptionStats: {
// Socket options common to all sockets.
SetReuseAddress: 0u64,
GetReuseAddress: 0u64,
GetError: 0u64,
SetBroadcast: 0u64,
GetBroadcast: 0u64,
SetSendBuffer: 0u64,
GetSendBuffer: 0u64,
SetReceiveBuffer: 0u64,
GetReceiveBuffer: 0u64,
SetKeepAlive: 0u64,
GetKeepAlive: 0u64,
SetOutOfBandInline: 0u64,
GetOutOfBandInline: 0u64,
SetNoCheck: 0u64,
GetNoCheck: 0u64,
SetLinger: 0u64,
GetLinger: 0u64,
SetReusePort: 0u64,
GetReusePort: 0u64,
GetAcceptConn: 0u64,
SetBindToDevice: 0u64,
GetBindToDevice: 0u64,
SetTimestamp: 0u64,
GetTimestamp: 0u64,
// Socket options defined on network sockets.
SetIpTypeOfService: 0u64,
GetIpTypeOfService: 0u64,
SetIpTtl: 0u64,
GetIpTtl: 0u64,
SetIpPacketInfo: 0u64,
GetIpPacketInfo: 0u64,
SetIpReceiveTypeOfService: 0u64,
GetIpReceiveTypeOfService: 0u64,
SetIpReceiveTtl: 0u64,
GetIpReceiveTtl: 0u64,
SetIpMulticastInterface: 0u64,
GetIpMulticastInterface: 0u64,
SetIpMulticastTtl: 0u64,
GetIpMulticastTtl: 0u64,
SetIpMulticastLoopback: 0u64,
GetIpMulticastLoopback: 0u64,
SetIpv6MulticastInterface: 0u64,
GetIpv6MulticastInterface: 0u64,
SetIpv6UnicastHops: 0u64,
GetIpv6UnicastHops: 0u64,
SetIpv6ReceiveHopLimit: 0u64,
GetIpv6ReceiveHopLimit: 0u64,
SetIpv6MulticastHops: 0u64,
GetIpv6MulticastHops: 0u64,
SetIpv6MulticastLoopback: 0u64,
GetIpv6MulticastLoopback: 0u64,
AddIpMembership: 0u64,
DropIpMembership: 0u64,
AddIpv6Membership: 0u64,
DropIpv6Membership: 0u64,
SetIpv6Only: 0u64,
GetIpv6Only: 0u64,
SetIpv6ReceiveTrafficClass: 0u64,
GetIpv6ReceiveTrafficClass: 0u64,
SetIpv6TrafficClass: 0u64,
GetIpv6TrafficClass: 0u64,
SetIpv6ReceivePacketInfo: 0u64,
GetIpv6ReceivePacketInfo: 0u64,
// Socket options defined on stream sockets.
SetTcpNoDelay: 0u64,
GetTcpNoDelay: 0u64,
SetTcpMaxSegment: 0u64,
GetTcpMaxSegment: 0u64,
SetTcpCork: 0u64,
GetTcpCork: 0u64,
SetTcpKeepAliveIdle: 0u64,
GetTcpKeepAliveIdle: 0u64,
SetTcpKeepAliveInterval: 0u64,
GetTcpKeepAliveInterval: 0u64,
SetTcpKeepAliveCount: 0u64,
GetTcpKeepAliveCount: 0u64,
SetTcpSynCount: 0u64,
GetTcpSynCount: 0u64,
SetTcpLinger: 0u64,
GetTcpLinger: 0u64,
SetTcpDeferAccept: 0u64,
GetTcpDeferAccept: 0u64,
SetTcpWindowClamp: 0u64,
GetTcpWindowClamp: 0u64,
GetTcpInfo: 0u64,
SetTcpQuickAck: 0u64,
GetTcpQuickAck: 0u64,
SetTcpCongestion: 0u64,
GetTcpCongestion: 0u64,
SetTcpUserTimeout: 0u64,
GetTcpUserTimeout: 0u64,
// Socket options defined on raw sockets.
SetIpHeaderIncluded: 0u64,
GetIpHeaderIncluded: 0u64,
SetIcmpv6Filter: 0u64,
GetIcmpv6Filter: 0u64,
SetIpv6Checksum: 0u64,
GetIpv6Checksum: 0u64,
}
});
}
#[netstack_test]
async fn inspect_socket_stats(name: &str) {
type N = netstack_testing_common::realms::Netstack2;
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("failed to create realm");
let _tcp_socket = realm
.stream_socket(fposix_socket::Domain::Ipv4, fposix_socket::StreamSocketProtocol::Tcp)
.await
.expect("create TCP socket");
let _udp_socket = realm
.datagram_socket(fposix_socket::Domain::Ipv4, fposix_socket::DatagramSocketProtocol::Udp)
.await
.expect("create UDP socket");
let _raw_socket = realm
.raw_socket(
fposix_socket::Domain::Ipv4,
fposix_socket_raw::ProtocolAssociation::Unassociated(fposix_socket_raw::Empty),
)
.await
.expect("create raw socket");
let _packet_socket = realm
.packet_socket(fposix_socket_packet::Kind::Network)
.await
.expect("create packet socket");
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("diagnostics dir exists");
let mut data = get_inspect_data(&diagnostics_dir, r#"Socket\ Info"#, "sockets").await;
// Debug print the tree to make debugging easier in case of failures.
println!("Got inspect data: {:#?}", data);
// TODO(https://fxbug.dev/324494938): We are getting the property ChecksumErrors duplicated in
// one case. Remove it before asserting as assert_data_tree expects it to be present only once.
let receive_errors = data.get_child_by_path_mut(&["1", "Stats", "ReceiveErrors"]).unwrap();
receive_errors.properties.sort_by(|a, b| a.key().cmp(b.key()));
receive_errors.properties.dedup_by_key(|p| p.key().clone());
assert_data_tree!(data, "Socket Info": contains {
"1": {
BindAddress: "",
BindNICID: "0",
LocalAddress: "0.0.0.0:0",
NetworkProtocol: "IPv4",
RegisterNICID: "0",
RemoteAddress: "0.0.0.0:0",
State: "INITIAL",
TransportProtocol: "TCP",
Stats: {
FailedConnectionAttempts: 0u64,
SegmentsReceived: 0u64,
SegmentsSent: 0u64,
ReadErrors: {
InvalidEndpointState: 0u64,
NotConnected: 0u64,
ReadClosed: 0u64,
},
ReceiveErrors: {
ChecksumErrors: 0u64,
ClosedReceiver: 0u64,
ListenOverflowAckDrop: 0u64,
ListenOverflowSynDrop: 0u64,
MalformedPacketsReceived: 0u64,
ReceiveBufferOverflow: 0u64,
SegmentQueueDropped: 0u64,
WantZeroRcvWindow: 0u64,
ZeroRcvWindowState: 0u64,
},
SendErrors: {
FastRetransmit: 0u64,
NoRoute: 0u64,
Retransmits: 0u64,
SegmentSendToNetworkFailed: 0u64,
SendToNetworkFailed: 0u64,
SynSendToNetworkFailed: 0u64,
Timeouts: 0u64,
},
WriteErrors: {
InvalidArgs: 0u64,
InvalidEndpointState: 0u64,
WriteClosed: 0u64,
},
},
"Socket Option Stats": {
AddIpMembership: 0u64,
AddIpv6Membership: 0u64,
DropIpMembership: 0u64,
DropIpv6Membership: 0u64,
GetAcceptConn: 0u64,
GetBindToDevice: 0u64,
GetBroadcast: 0u64,
GetError: 0u64,
GetIpMulticastInterface: 0u64,
GetIpMulticastLoopback: 0u64,
GetIpMulticastTtl: 0u64,
GetIpPacketInfo: 0u64,
GetIpReceiveTtl: 0u64,
GetIpReceiveTypeOfService: 0u64,
GetIpTtl: 0u64,
GetIpTypeOfService: 0u64,
GetIpv6MulticastHops: 0u64,
GetIpv6MulticastInterface: 0u64,
GetIpv6MulticastLoopback: 0u64,
GetIpv6Only: 0u64,
GetIpv6ReceiveHopLimit: 0u64,
GetIpv6ReceivePacketInfo: 0u64,
GetIpv6ReceiveTrafficClass: 0u64,
GetIpv6TrafficClass: 0u64,
GetIpv6UnicastHops: 0u64,
GetKeepAlive: 0u64,
GetLinger: 0u64,
GetNoCheck: 0u64,
GetOutOfBandInline: 0u64,
GetReceiveBuffer: 0u64,
GetReuseAddress: 0u64,
GetReusePort: 0u64,
GetSendBuffer: 0u64,
GetTcpCongestion: 0u64,
GetTcpCork: 0u64,
GetTcpDeferAccept: 0u64,
GetTcpInfo: 0u64,
GetTcpKeepAliveCount: 0u64,
GetTcpKeepAliveIdle: 0u64,
GetTcpKeepAliveInterval: 0u64,
GetTcpLinger: 0u64,
GetTcpMaxSegment: 0u64,
GetTcpNoDelay: 0u64,
GetTcpQuickAck: 0u64,
GetTcpSynCount: 0u64,
GetTcpUserTimeout: 0u64,
GetTcpWindowClamp: 0u64,
GetTimestamp: 0u64,
SetBindToDevice: 0u64,
SetBroadcast: 0u64,
SetIpMulticastInterface: 0u64,
SetIpMulticastLoopback: 0u64,
SetIpMulticastTtl: 0u64,
SetIpPacketInfo: 0u64,
SetIpReceiveTtl: 0u64,
SetIpReceiveTypeOfService: 0u64,
SetIpTtl: 0u64,
SetIpTypeOfService: 0u64,
SetIpv6MulticastHops: 0u64,
SetIpv6MulticastInterface: 0u64,
SetIpv6MulticastLoopback: 0u64,
SetIpv6Only: 0u64,
SetIpv6ReceiveHopLimit: 0u64,
SetIpv6ReceivePacketInfo: 0u64,
SetIpv6ReceiveTrafficClass: 0u64,
SetIpv6TrafficClass: 0u64,
SetIpv6UnicastHops: 0u64,
SetKeepAlive: 0u64,
SetLinger: 0u64,
SetNoCheck: 0u64,
SetOutOfBandInline: 0u64,
SetReceiveBuffer: 0u64,
SetReuseAddress: 0u64,
SetReusePort: 0u64,
SetSendBuffer: 0u64,
SetTcpCongestion: 0u64,
SetTcpCork: 0u64,
SetTcpDeferAccept: 0u64,
SetTcpKeepAliveCount: 0u64,
SetTcpKeepAliveIdle: 0u64,
SetTcpKeepAliveInterval: 0u64,
SetTcpLinger: 0u64,
SetTcpMaxSegment: 0u64,
SetTcpNoDelay: 0u64,
SetTcpQuickAck: 0u64,
SetTcpSynCount: 0u64,
SetTcpUserTimeout: 0u64,
SetTcpWindowClamp: 0u64,
SetTimestamp: 0u64,
},
},
"2": {
BindAddress: "",
BindNICID: "0",
LocalAddress: "0.0.0.0:0",
NetworkProtocol: "IPv4",
RegisterNICID: "0",
RemoteAddress: "0.0.0.0:0",
State: "INITIAL",
TransportProtocol: "UDP",
Stats: {
PacketsReceived: 0u64,
PacketsSent: 0u64,
ReadErrors: {
InvalidEndpointState: 0u64,
NotConnected: 0u64,
ReadClosed: 0u64,
},
ReceiveErrors: {
ChecksumErrors: 0u64,
ClosedReceiver: 0u64,
MalformedPacketsReceived: 0u64,
ReceiveBufferOverflow: 0u64,
},
SendErrors: {
NoRoute: 0u64,
SendToNetworkFailed: 0u64,
},
WriteErrors: {
InvalidArgs: 0u64,
InvalidEndpointState: 0u64,
WriteClosed: 0u64,
},
},
"Socket Option Stats": {
AddIpMembership: 0u64,
AddIpv6Membership: 0u64,
DropIpMembership: 0u64,
DropIpv6Membership: 0u64,
GetAcceptConn: 0u64,
GetBindToDevice: 0u64,
GetBroadcast: 0u64,
GetError: 0u64,
GetIpMulticastInterface: 0u64,
GetIpMulticastLoopback: 0u64,
GetIpMulticastTtl: 0u64,
GetIpPacketInfo: 0u64,
GetIpReceiveTtl: 0u64,
GetIpReceiveTypeOfService: 0u64,
GetIpTtl: 0u64,
GetIpTypeOfService: 0u64,
GetIpv6MulticastHops: 0u64,
GetIpv6MulticastInterface: 0u64,
GetIpv6MulticastLoopback: 0u64,
GetIpv6Only: 0u64,
GetIpv6ReceiveHopLimit: 0u64,
GetIpv6ReceivePacketInfo: 0u64,
GetIpv6ReceiveTrafficClass: 0u64,
GetIpv6TrafficClass: 0u64,
GetIpv6UnicastHops: 0u64,
GetKeepAlive: 0u64,
GetLinger: 0u64,
GetNoCheck: 0u64,
GetOutOfBandInline: 0u64,
GetReceiveBuffer: 0u64,
GetReuseAddress: 0u64,
GetReusePort: 0u64,
GetSendBuffer: 0u64,
GetTimestamp: 0u64,
SetBindToDevice: 0u64,
SetBroadcast: 0u64,
SetIpMulticastInterface: 0u64,
SetIpMulticastLoopback: 0u64,
SetIpMulticastTtl: 0u64,
SetIpPacketInfo: 0u64,
SetIpReceiveTtl: 0u64,
SetIpReceiveTypeOfService: 0u64,
SetIpTtl: 0u64,
SetIpTypeOfService: 0u64,
SetIpv6MulticastHops: 0u64,
SetIpv6MulticastInterface: 0u64,
SetIpv6MulticastLoopback: 0u64,
SetIpv6Only: 0u64,
SetIpv6ReceiveHopLimit: 0u64,
SetIpv6ReceivePacketInfo: 0u64,
SetIpv6ReceiveTrafficClass: 0u64,
SetIpv6TrafficClass: 0u64,
SetIpv6UnicastHops: 0u64,
SetKeepAlive: 0u64,
SetLinger: 0u64,
SetNoCheck: 0u64,
SetOutOfBandInline: 0u64,
SetReceiveBuffer: 0u64,
SetReuseAddress: 0u64,
SetReusePort: 0u64,
SetSendBuffer: 0u64,
SetTimestamp: 0u64,
},
},
"3": {
BindAddress: "",
BindNICID: "0",
LocalAddress: "0.0.0.0:0",
NetworkProtocol: "IPv4",
RegisterNICID: "0",
RemoteAddress: "0.0.0.0:0",
State: "",
TransportProtocol: "UNKNOWN",
Stats: {
PacketsReceived: 0u64,
PacketsSent: 0u64,
ReadErrors: {
InvalidEndpointState: 0u64,
NotConnected: 0u64,
ReadClosed: 0u64,
},
ReceiveErrors: {
ChecksumErrors: 0u64,
ClosedReceiver: 0u64,
MalformedPacketsReceived: 0u64,
ReceiveBufferOverflow: 0u64,
},
SendErrors: {
NoRoute: 0u64,
SendToNetworkFailed: 0u64,
},
WriteErrors: {
InvalidArgs: 0u64,
InvalidEndpointState: 0u64,
WriteClosed: 0u64,
},
},
"Socket Option Stats": {
AddIpMembership: 0u64,
AddIpv6Membership: 0u64,
DropIpMembership: 0u64,
DropIpv6Membership: 0u64,
GetAcceptConn: 0u64,
GetBindToDevice: 0u64,
GetBroadcast: 0u64,
GetError: 0u64,
GetIcmpv6Filter: 0u64,
GetIpHeaderIncluded: 0u64,
GetIpMulticastInterface: 0u64,
GetIpMulticastLoopback: 0u64,
GetIpMulticastTtl: 0u64,
GetIpPacketInfo: 0u64,
GetIpReceiveTtl: 0u64,
GetIpReceiveTypeOfService: 0u64,
GetIpTtl: 0u64,
GetIpTypeOfService: 0u64,
GetIpv6Checksum: 0u64,
GetIpv6MulticastHops: 0u64,
GetIpv6MulticastInterface: 0u64,
GetIpv6MulticastLoopback: 0u64,
GetIpv6Only: 0u64,
GetIpv6ReceiveHopLimit: 0u64,
GetIpv6ReceivePacketInfo: 0u64,
GetIpv6ReceiveTrafficClass: 0u64,
GetIpv6TrafficClass: 0u64,
GetIpv6UnicastHops: 0u64,
GetKeepAlive: 0u64,
GetLinger: 0u64,
GetNoCheck: 0u64,
GetOutOfBandInline: 0u64,
GetReceiveBuffer: 0u64,
GetReuseAddress: 0u64,
GetReusePort: 0u64,
GetSendBuffer: 0u64,
GetTimestamp: 0u64,
SetBindToDevice: 0u64,
SetBroadcast: 0u64,
SetIcmpv6Filter: 0u64,
SetIpHeaderIncluded: 0u64,
SetIpMulticastInterface: 0u64,
SetIpMulticastLoopback: 0u64,
SetIpMulticastTtl: 0u64,
SetIpPacketInfo: 0u64,
SetIpReceiveTtl: 0u64,
SetIpReceiveTypeOfService: 0u64,
SetIpTtl: 0u64,
SetIpTypeOfService: 0u64,
SetIpv6Checksum: 0u64,
SetIpv6MulticastHops: 0u64,
SetIpv6MulticastInterface: 0u64,
SetIpv6MulticastLoopback: 0u64,
SetIpv6Only: 0u64,
SetIpv6ReceiveHopLimit: 0u64,
SetIpv6ReceivePacketInfo: 0u64,
SetIpv6ReceiveTrafficClass: 0u64,
SetIpv6TrafficClass: 0u64,
SetIpv6UnicastHops: 0u64,
SetKeepAlive: 0u64,
SetLinger: 0u64,
SetNoCheck: 0u64,
SetOutOfBandInline: 0u64,
SetReceiveBuffer: 0u64,
SetReuseAddress: 0u64,
SetReusePort: 0u64,
SetSendBuffer: 0u64,
SetTimestamp: 0u64,
},
},
"4": {
BindAddress: "",
BindNICID: "0",
LocalAddress: "<nil>:0",
NetworkProtocol: "UNKNOWN",
RegisterNICID: "0",
RemoteAddress: "<nil>:0",
State: "",
TransportProtocol: "UNKNOWN",
Stats: {
PacketsReceived: 0u64,
PacketsSent: 0u64,
ReadErrors: {
InvalidEndpointState: 0u64,
NotConnected: 0u64,
ReadClosed: 0u64,
},
ReceiveErrors: {
ChecksumErrors: 0u64,
ClosedReceiver: 0u64,
MalformedPacketsReceived: 0u64,
ReceiveBufferOverflow: 0u64,
},
SendErrors: {
NoRoute: 0u64,
SendToNetworkFailed: 0u64,
},
WriteErrors: {
InvalidArgs: 0u64,
InvalidEndpointState: 0u64,
WriteClosed: 0u64,
},
},
"Socket Option Stats": {
AddIpMembership: 0u64,
AddIpv6Membership: 0u64,
DropIpMembership: 0u64,
DropIpv6Membership: 0u64,
GetAcceptConn: 0u64,
GetBindToDevice: 0u64,
GetBroadcast: 0u64,
GetError: 0u64,
GetIpMulticastInterface: 0u64,
GetIpMulticastLoopback: 0u64,
GetIpMulticastTtl: 0u64,
GetIpPacketInfo: 0u64,
GetIpReceiveTtl: 0u64,
GetIpReceiveTypeOfService: 0u64,
GetIpTtl: 0u64,
GetIpTypeOfService: 0u64,
GetIpv6MulticastHops: 0u64,
GetIpv6MulticastInterface: 0u64,
GetIpv6MulticastLoopback: 0u64,
GetIpv6Only: 0u64,
GetIpv6ReceiveHopLimit: 0u64,
GetIpv6ReceivePacketInfo: 0u64,
GetIpv6ReceiveTrafficClass: 0u64,
GetIpv6TrafficClass: 0u64,
GetIpv6UnicastHops: 0u64,
GetKeepAlive: 0u64,
GetLinger: 0u64,
GetNoCheck: 0u64,
GetOutOfBandInline: 0u64,
GetReceiveBuffer: 0u64,
GetReuseAddress: 0u64,
GetReusePort: 0u64,
GetSendBuffer: 0u64,
GetTimestamp: 0u64,
SetBindToDevice: 0u64,
SetBroadcast: 0u64,
SetIpMulticastInterface: 0u64,
SetIpMulticastLoopback: 0u64,
SetIpMulticastTtl: 0u64,
SetIpPacketInfo: 0u64,
SetIpReceiveTtl: 0u64,
SetIpReceiveTypeOfService: 0u64,
SetIpTtl: 0u64,
SetIpTypeOfService: 0u64,
SetIpv6MulticastHops: 0u64,
SetIpv6MulticastInterface: 0u64,
SetIpv6MulticastLoopback: 0u64,
SetIpv6Only: 0u64,
SetIpv6ReceiveHopLimit: 0u64,
SetIpv6ReceivePacketInfo: 0u64,
SetIpv6ReceiveTrafficClass: 0u64,
SetIpv6TrafficClass: 0u64,
SetIpv6UnicastHops: 0u64,
SetKeepAlive: 0u64,
SetLinger: 0u64,
SetNoCheck: 0u64,
SetOutOfBandInline: 0u64,
SetReceiveBuffer: 0u64,
SetReuseAddress: 0u64,
SetReusePort: 0u64,
SetSendBuffer: 0u64,
SetTimestamp: 0u64,
},
},
});
}
#[netstack_test]
async fn inspect_for_sampler(name: &str) {
type N = netstack_testing_common::realms::Netstack2;
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox.create_netstack_realm::<N, _>(name).expect("failed to create realm");
// Connect to netstack service to spawn a netstack instance.
let _stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("failed to connect to fuchsia.net.stack/Stack");
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("diagnostics dir exists");
let sampler_config = sampler_config::SamplerConfigBuilder::default()
.sampler_dir("/pkg/data/sampler-config")
.load()
.expect("SamplerConfig load failed");
let project_config = match &sampler_config.project_configs[..] {
[project_config] => project_config,
project_configs => panic!("expected one project_config but got {:#?}", project_configs),
};
for metric_config in &project_config.metrics {
let selector = match &metric_config.selectors[..] {
[selector] => {
&selector
.as_ref()
.expect("SamplerConfig load should never return None for selectors")
.selector
}
selectors => panic!("expected one selector but got {:#?}", selectors),
};
let fidl_fuchsia_diagnostics::Selector { tree_selector, .. } = selector;
let (tree_selector, expected_key) = match tree_selector.as_ref().expect("tree_selector") {
fidl_fuchsia_diagnostics::TreeSelector::PropertySelector(
fidl_fuchsia_diagnostics::PropertySelector { node_path, target_properties },
) => {
let tree_selector = node_path
.iter()
.map(|selector| match selector {
fidl_fuchsia_diagnostics::StringSelector::ExactMatch(segment) => {
selectors::sanitize_string_for_selectors(segment)
}
selector => panic!("expected exact match selector but got {:#?}", selector),
})
.join("/");
let expected_key = match target_properties {
fidl_fuchsia_diagnostics::StringSelector::ExactMatch(segment) => segment,
selector => panic!("expected exact match selector but got {:#?}", selector),
};
(tree_selector, expected_key)
}
selector => panic!("expected property selector but got {:#?}", selector),
};
let data = get_inspect_data(
&diagnostics_dir,
&format!("{tree_selector}:{expected_key}"),
"counters",
)
.await;
let properties: Vec<_> = data
.property_iter()
.filter_map(|(_hierarchy_path, property_opt): (Vec<&str>, _)| property_opt)
.collect();
match &properties[..] {
[Property::Uint(key, _)] => {
if key != expected_key {
panic!(
"wrong key {:#?} found (expected {:#?}) for selector {:#?}",
key, expected_key, selector
);
}
}
[] => {
panic!("no properties found for selector {:#?}", selector)
}
properties => {
panic!("wrong properties {:#?} found for selector {:#?}", properties, selector);
}
}
}
}
const CONFIG_DATA_EMPTY: &str = "/pkg/netstack/empty.json";
const CONFIG_DATA_SPECIFIED_PROCS: &str = "/pkg/netstack/specified_procs.json";
const CONFIG_DATA_NONEXISTENT: &str = "/pkg/netstack/idontexist.json";
#[netstack_test]
#[test_case(
true, "DEBUG", "1m0s", false, false, CONFIG_DATA_EMPTY, None;
"default debug config"
)]
#[test_case(
false, "INFO", "2m0s", true, true, CONFIG_DATA_SPECIFIED_PROCS, Some(2);
"non-default config"
)]
#[test_case(
false, "INFO", "2m0s", true, true, CONFIG_DATA_NONEXISTENT, None;
"config-data file is nonexistent"
)]
async fn inspect_config(
name: &str,
log_packets: bool,
verbosity: &str,
socket_stats_sampling_interval: &str,
no_opaque_iids: bool,
fast_udp: bool,
config_data: &str,
expect_max_procs_set: Option<usize>,
) {
let sandbox = netemul::TestSandbox::new().expect("create sandbox");
let realm = {
let mut netstack = fidl_fuchsia_netemul::ChildDef::from(&KnownServiceProvider::Netstack(
netstack_testing_common::realms::Netstack2::VERSION,
));
let fidl_fuchsia_netemul::ChildDef { program_args, .. } = &mut netstack;
*program_args = Some(vec![
format!("--log-packets={log_packets}"),
format!("--verbosity={verbosity}"),
format!("--socket-stats-sampling-interval={socket_stats_sampling_interval}"),
format!("--no-opaque-iids={no_opaque_iids}"),
format!("--fast-udp={fast_udp}"),
format!("--config-data={config_data}"),
]);
sandbox.create_realm(name, [netstack]).expect("create realm")
};
// Connect to a protocol exposed by netstack so that it starts up.
let _ = realm
.connect_to_protocol::<fidl_fuchsia_net_interfaces::StateMarker>()
.expect("connect to protocol");
let mut inspect_assertion = tree_assertion!("Runtime Configuration Flags": {
"log-packets": format!("{log_packets}"),
"verbosity": verbosity.to_string(),
"socket-stats-sampling-interval": socket_stats_sampling_interval.to_string(),
"no-opaque-iids": format!("{no_opaque_iids}"),
"fast-udp": format!("{fast_udp}"),
});
let mut config_data_assertion = tree_assertion!("config-data": {
file: config_data.to_string(),
"num-cpu": AnyProperty,
});
if let Some(max_procs) = expect_max_procs_set {
config_data_assertion
.add_property_assertion("max-procs", Box::new(format!("{}", max_procs)));
}
inspect_assertion.add_child_assertion(config_data_assertion);
let diagnostics_dir =
realm.open_diagnostics_directory("netstack").expect("diagnostics dir exists");
let data =
get_inspect_data(&diagnostics_dir, r#"Runtime\ Configuration\ Flags"#, "configuration")
.await;
inspect_assertion
.run(&data)
.unwrap_or_else(|e| panic!("unexpected inspect data: {:?}; got {:#?}", e, data));
}
async fn get_inspect_data(
diagnostics_dir: &fio::DirectoryProxy,
tree_selector: impl AsRef<str>,
subdir: &str,
) -> DiagnosticsHierarchy {
let tree_selector = tree_selector.as_ref();
let selector =
selectors::parse_selector::<selectors::VerboseError>(&format!("netstack:{tree_selector}"))
.expect("parse selector");
netstack_testing_common::get_deprecated_netstack2_inspect_data(
diagnostics_dir,
subdir,
[selector],
)
.await
}