blob: 15d956ffe80a07924431318223054eef7c4959bf [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, convert::TryFrom as _, num::NonZeroU16};
use fuchsia_async as fasync;
use fuchsia_inspect::testing::TreeAssertion;
use fuchsia_zircon as zx;
use diagnostics_hierarchy::Property;
use itertools::Itertools as _;
use net_declare::{fidl_ip, fidl_mac, fidl_subnet};
use net_types::ip::Ip as _;
use netemul::Endpoint as _;
use netstack_testing_common::{
constants, get_inspect_data,
realms::{Netstack2, TestSandboxExt as _},
Result,
};
use netstack_testing_macros::variants_test;
use nonzero_ext::nonzero;
use packet::{ParsablePacket as _, Serializer as _};
use packet_formats::{
ethernet::{testutil::ETHERNET_HDR_LEN_NO_TAG, EtherType, EthernetFrameBuilder},
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: _ }| {
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 fuchsia_inspect::testing::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))
}
}
}
#[fasync::run_singlethreaded(test)]
async fn inspect_nic() {
// 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::<Netstack2, _>("inspect_nic")
.expect("failed to create realm");
const ETH_MAC: fidl_fuchsia_net::MacAddress = fidl_mac!("02:01:02:03:04:05");
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 eth = realm
.join_network_with(
&network,
"eth-ep",
netemul::Ethernet::make_config(max_frame_size, Some(ETH_MAC)),
Default::default(),
)
.await
.expect("failed to join network with ethernet endpoint");
eth.add_address_and_subnet_route(fidl_subnet!("192.168.0.1/24"))
.await
.expect("configure address");
let netdev = realm
.join_network_with(
&network,
"netdev-ep",
netemul::NetworkDevice::make_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, eth_props) =
fidl_fuchsia_net_interfaces_ext::wait_interface(
fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interfaces_state)
.expect("failed to create event stream"),
&mut HashMap::new(),
|if_map| {
let loopback =
if_map.values().find_map(|properties| 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 get_properties = |id| {
let properties = if_map.get(&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: _,
}| 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 {
Some(properties.clone())
} else {
None
}
};
Some((loopback, get_properties(netdev.id())?, get_properties(eth.id())?))
},
)
.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);
let eth_addrs = AddressMatcher::new(&eth_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(eth.id(), &mut BOB_IP.clone(), &mut BOB_MAC.clone())
.await
.expect("add_entry FIDL error")
.map_err(zx::Status::from_raw)
.expect("add_entry failed");
let data = get_inspect_data(&realm, "netstack", "NICs", "interfaces")
.await
.expect("get_inspect_data failed");
// Debug print the tree to make debugging easier in case of failures.
println!("Got inspect data: {:#?}", data);
use fuchsia_inspect::testing::{AnyProperty, NonZeroUintProperty};
fuchsia_inspect::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: {
UnreachableEntryLookups: 0u64,
},
MalformedL4RcvdPackets: 0u64,
UnknownL3ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
UnknownL4ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
},
"Network Endpoint Stats": {
ARP: contains {},
IPv4: contains {},
IPv6: contains {},
}
},
eth.id().to_string() => {
Name: eth_props.name,
Loopback: "false",
LinkOnline: "true",
AdminUp: "true",
Promiscuous: "false",
Up: "true",
MTU: u64::from(netemul::DEFAULT_MTU),
NICID: eth.id().to_string(),
Running: "true",
"DHCP enabled": "false",
LinkAddress: fidl_fuchsia_net_ext::MacAddress::from(ETH_MAC).to_string(),
// IPv4.
ProtocolAddress0: eth_addrs.clone(),
// Link-local IPv6.
ProtocolAddress1: eth_addrs.clone(),
Stats: {
DisabledRx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Tx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
TxPacketsDroppedNoBufferSpace: AnyProperty,
Rx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Neighbor: {
UnreachableEntryLookups: AnyProperty,
},
MalformedL4RcvdPackets: 0u64,
UnknownL3ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
UnknownL4ProtocolRcvdPacketCounts: {
Total: {
Count: "0"
},
},
},
"Ethernet Info": {
Filepath: "",
Topopath: "eth-ep",
Features: "Synthetic",
TxDrops: AnyProperty,
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",
// TODO(https://fxbug.dev/78847): Use NonZeroIntProperty once we are able to
// distinguish between signed and unsigned integers from the
// fuchsia.diagnostics FIDL. This is currently not possible because the inspect
// data is serialized into JSON then converted back, losing type information.
"Last updated": NonZeroUintProperty,
}
},
"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: {
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: {},
"Network Endpoint Stats": {
ARP: contains {},
IPv4: contains {},
IPv6: contains {},
}
}
});
let () = loopback_addrs.check().expect("loopback addresses match failed");
let () = eth_addrs.check().expect("ethernet addresses match failed");
let () = netdev_addrs.check().expect("netdev addresses match failed");
}
#[fasync::run_singlethreaded(test)]
async fn inspect_routing_table() {
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox
.create_netstack_realm::<Netstack2, _>("inspect_routing_table")
.expect("failed to create realm");
let stack = realm
.connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>()
.expect("failed to connect to fuchsia.net.stack/Stack");
// Capture the state of the routing table to verify the inspect data, and
// confirm that it's not empty.
let routing_table = stack.get_forwarding_table().await.expect("get_route_table FIDL error");
assert!(!routing_table.is_empty());
println!("Got routing table: {:#?}", routing_table);
use fuchsia_inspect::testing::{AnyProperty, TreeAssertion};
let mut routing_table_assertion = TreeAssertion::new("Routes", true);
for (i, route) in routing_table.into_iter().enumerate() {
let index = &i.to_string();
let fidl_fuchsia_net_stack_ext::ForwardingEntry { subnet, device_id, next_hop, metric } =
route.into();
let route_assertion = fuchsia_inspect::tree_assertion!(var index: {
"Destination": format!(
"{}",
subnet,
),
"Gateway": match next_hop {
Some(next_hop) => next_hop.to_string(),
None => String::new(),
},
"NIC": device_id.to_string(),
"Metric": metric.to_string(),
"MetricTracksInterface": AnyProperty,
"Dynamic": AnyProperty,
"Enabled": AnyProperty,
});
routing_table_assertion.add_child_assertion(route_assertion);
}
let data = get_inspect_data(&realm, "netstack", "Routes", "routes")
.await
.expect("get_inspect_data failed");
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 = nonzero!(1234u16);
const DHCP_CLIENT_PORT: NonZeroU16 = nonzero!(dhcp::protocol::CLIENT_PORT);
#[variants_test]
#[test_case(
"invalid_trans_proto",
vec![
PacketAttributes {
ip_proto: packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp),
port: DHCP_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: DHCP_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: DHCP_CLIENT_PORT,
}
]; "multiple_invalid_port_and_single_invalid_trans_proto")]
async fn inspect_dhcp<E: netemul::Endpoint>(
variants_test_name: &str,
test_case_name: &str,
inbound_packets: Vec<PacketAttributes>,
) {
// TODO(https://fxbug.dev/79556): 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 realm = sandbox
.create_netstack_realm::<Netstack2, _>(format!("{}-{}", variants_test_name, test_case_name))
.expect("failed to create realm");
// 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::<E, _>(&network, "ep1").await.expect("failed to join network");
eth.start_dhcp().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");
match datagram.dst_port().get() {
dhcp::protocol::SERVER_PORT => {
// Any DHCP message means the client is listening; we don't care
// about the contents.
let _: dhcp::protocol::Message =
dhcp::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(
net_types::ethernet::Mac::BROADCAST,
constants::eth::MAC_ADDR,
EtherType::Ipv4,
))
.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 != DHCP_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(
&realm,
"netstack",
std::iter::once(DISCARD_STATS_NAME)
.chain(path)
.into_iter()
.map(selectors::sanitize_string_for_selectors)
.rev()
.join("/"),
"interfaces",
)
.await
.expect("failed to get inspect data");
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.
#[fasync::run_singlethreaded(test)]
async fn inspect_stat_counters() {
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox
.create_netstack_realm::<Netstack2, _>("inspect_for_sampler")
.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 data = get_inspect_data(&realm, "netstack", r#"Networking\ Stat\ Counters"#, "counters")
.await
.expect("get_inspect_data failed");
// TODO(https://fxbug.dev/62447): change AnyProperty to AnyUintProperty when available.
use fuchsia_inspect::testing::AnyProperty;
fuchsia_inspect::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,
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,
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,
},
PacketsSent: {
Dropped: AnyProperty,
LeaveGroup: AnyProperty,
MembershipQuery: AnyProperty,
V1MembershipReport: AnyProperty,
V2MembershipReport: 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,
LinkLocalDestination: AnyProperty,
LinkLocalSource: AnyProperty,
PacketTooBig: AnyProperty,
Unrouteable: AnyProperty,
},
},
IPv6AddressConfig: {
DHCPv6ManagedAddressOnly: AnyProperty,
GlobalSLAACAndDHCPv6ManagedAddress: AnyProperty,
GlobalSLAACOnly: AnyProperty,
NoGlobalSLAACOrDHCPv6ManagedAddress: AnyProperty,
},
NICs: {
MalformedL4RcvdPackets: AnyProperty,
DisabledRx: {
Bytes: AnyProperty,
Packets: AnyProperty,
},
Neighbor: {
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,
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,
}
});
}
#[fasync::run_singlethreaded(test)]
async fn inspect_for_sampler() {
let sandbox = netemul::TestSandbox::new().expect("failed to create sandbox");
let realm = sandbox
.create_netstack_realm::<Netstack2, _>("inspect_for_sampler")
.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");
// We can pass any sample rate here. It is not used at all in this test.
const MINIMUM_SAMPLE_RATE_SEC: i64 = 60;
let sampler_config = sampler_config::SamplerConfig::from_directory(
MINIMUM_SAMPLE_RATE_SEC,
"/pkg/data/sampler-config",
)
.expect("SamplerConfig::from_directory 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::from_directory() should never return None for selectors",
)
.selector,
selectors => panic!("expected one selector but got {:#?}", selectors),
};
let fidl_fuchsia_diagnostics::Selector { component_selector, tree_selector, .. } = selector;
let fidl_fuchsia_diagnostics::ComponentSelector { moniker_segments, .. } =
component_selector.as_ref().expect("component_selector");
let component_moniker = match moniker_segments
.as_ref()
.expect("moniker_segments")
.last()
.expect("last moniker segment")
{
fidl_fuchsia_diagnostics::StringSelector::ExactMatch(segment) => segment,
selector => panic!("expected exact match selector but got {:#?}", 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(
&realm,
component_moniker,
format!("{}:{}", tree_selector, expected_key),
"counters",
)
.await
.expect("get_inspect_data failed");
let properties: Vec<_> = data
.property_iter()
.filter_map(|(_hierarchy_path, property_opt): (Vec<&String>, _)| 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);
}
}
}
}