Skip to main content

netstack_testing_common/
nud.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Useful NUD functions for tests.
6
7use anyhow::Context;
8use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
9use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
10use futures::StreamExt as _;
11use net_types::SpecifiedAddress as _;
12
13/// Frame metadata of interest to neighbor tests.
14#[derive(Debug, Eq, PartialEq)]
15pub enum FrameMetadata {
16    /// An ARP request or NDP Neighbor Solicitation target address.
17    NeighborSolicitation(fidl_fuchsia_net::IpAddress),
18    /// A UDP datagram destined to the address.
19    Udp(fidl_fuchsia_net::IpAddress),
20    /// Any other successfully parsed frame.
21    Other,
22}
23
24/// Helper function to extract specific frame metadata from a raw Ethernet
25/// frame.
26///
27/// Returns `Err` if the frame can't be parsed or `Ok(FrameMetadata)` with any
28/// interesting metadata of interest to neighbor tests.
29fn extract_frame_metadata(data: Vec<u8>) -> crate::Result<FrameMetadata> {
30    use packet::ParsablePacket;
31    use packet_formats::arp::{ArpOp, ArpPacket};
32    use packet_formats::ethernet::{EtherType, EthernetFrame, EthernetFrameLengthCheck};
33    use packet_formats::icmp::ndp::NdpPacket;
34    use packet_formats::icmp::{IcmpParseArgs, Icmpv6Packet};
35    use packet_formats::ip::{IpPacket, IpProto, Ipv6Proto};
36    use packet_formats::ipv4::Ipv4Packet;
37    use packet_formats::ipv6::Ipv6Packet;
38
39    let mut bv = &data[..];
40    let ethernet = EthernetFrame::parse(&mut bv, EthernetFrameLengthCheck::NoCheck)
41        .context("failed to parse Ethernet frame")?;
42    match ethernet
43        .ethertype()
44        .ok_or_else(|| anyhow::anyhow!("missing ethertype in Ethernet frame"))?
45    {
46        EtherType::Ipv4 => {
47            let ipv4 = Ipv4Packet::parse(&mut bv, ()).context("failed to parse IPv4 packet")?;
48            if ipv4.proto() != IpProto::Udp.into() {
49                return Ok(FrameMetadata::Other);
50            }
51            Ok(FrameMetadata::Udp(fidl_fuchsia_net::IpAddress::Ipv4(
52                fidl_fuchsia_net::Ipv4Address { addr: ipv4.dst_ip().ipv4_bytes() },
53            )))
54        }
55        EtherType::Arp => {
56            let arp = ArpPacket::<_, net_types::ethernet::Mac, net_types::ip::Ipv4Addr>::parse(
57                &mut bv,
58                (),
59            )
60            .context("failed to parse ARP packet")?;
61            match arp.operation() {
62                ArpOp::Request => Ok(FrameMetadata::NeighborSolicitation(
63                    fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
64                        addr: arp.target_protocol_address().ipv4_bytes(),
65                    }),
66                )),
67                ArpOp::Response => Ok(FrameMetadata::Other),
68                ArpOp::Other(other) => Err(anyhow::anyhow!("unrecognized ARP operation {}", other)),
69            }
70        }
71        EtherType::Ipv6 => {
72            let ipv6 = Ipv6Packet::parse(&mut bv, ()).context("failed to parse IPv6 packet")?;
73            match ipv6.proto() {
74                Ipv6Proto::Icmpv6 => {
75                    // NB: filtering out packets with an unspecified source address will
76                    // filter out DAD-related solicitations.
77                    if !ipv6.src_ip().is_specified() {
78                        return Ok(FrameMetadata::Other);
79                    }
80                    let parse_args = IcmpParseArgs::new(ipv6.src_ip(), ipv6.dst_ip());
81                    match Icmpv6Packet::parse(&mut bv, parse_args)
82                        .context("failed to parse ICMP packet")?
83                    {
84                        Icmpv6Packet::Ndp(NdpPacket::NeighborSolicitation(solicit)) => {
85                            Ok(FrameMetadata::NeighborSolicitation(
86                                fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address {
87                                    addr: solicit.message().target_address().ipv6_bytes(),
88                                }),
89                            ))
90                        }
91                        _ => Ok(FrameMetadata::Other),
92                    }
93                }
94                Ipv6Proto::Proto(IpProto::Udp) => {
95                    Ok(FrameMetadata::Udp(fidl_fuchsia_net::IpAddress::Ipv6(
96                        fidl_fuchsia_net::Ipv6Address { addr: ipv6.dst_ip().ipv6_bytes() },
97                    )))
98                }
99                _ => Ok(FrameMetadata::Other),
100            }
101        }
102        EtherType::Other(other) => {
103            Err(anyhow::anyhow!("unrecognized ethertype in Ethernet frame {}", other))
104        }
105    }
106}
107
108/// Creates a fake endpoint that extracts [`FrameMetadata`] from exchanged
109/// frames in `network`.
110pub fn create_metadata_stream<'a>(
111    ep: &'a netemul::TestFakeEndpoint<'a>,
112) -> impl futures::Stream<Item = crate::Result<FrameMetadata>> + 'a {
113    ep.frame_stream().map(|r| {
114        let (data, dropped) = r.context("fake_ep FIDL error")?;
115        if dropped != 0 {
116            Err(anyhow::anyhow!("dropped {} frames on fake endpoint", dropped))
117        } else {
118            extract_frame_metadata(data)
119        }
120    })
121}
122
123/// Works around CQ timing flakes due to NUD failures.
124///
125/// Many tests can have flakes reduced by applying this workaround. Typically
126/// tests that have more than 1 netstack and use pings or sockets between stacks
127/// can observe spurious NUD failures due to infra timing woes. That can be
128/// worked around by setting the number of NUD probes to a very high value. Any
129/// test that is not directly verifying neighbor behavior can use this
130/// workaround to get rid of flakes.
131pub async fn apply_nud_flake_workaround(
132    control: &fnet_interfaces_ext::admin::Control,
133) -> crate::Result {
134    control
135        .set_configuration(&fnet_interfaces_admin::Configuration {
136            ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
137                arp: Some(fnet_interfaces_admin::ArpConfiguration {
138                    nud: Some(fnet_interfaces_admin::NudConfiguration {
139                        max_multicast_solicitations: Some(u16::MAX),
140                        ..Default::default()
141                    }),
142                    ..Default::default()
143                }),
144                ..Default::default()
145            }),
146            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
147                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
148                    nud: Some(fnet_interfaces_admin::NudConfiguration {
149                        max_multicast_solicitations: Some(u16::MAX),
150                        ..Default::default()
151                    }),
152                    ..Default::default()
153                }),
154                ..Default::default()
155            }),
156            ..Default::default()
157        })
158        .await
159        .map_err(|e| e.into())
160        .and_then(|r| {
161            r.map(|fnet_interfaces_admin::Configuration { .. }| ())
162                .map_err(|e| anyhow::anyhow!("can't set device configuration: {e:?}"))
163        })
164        .context("apply nud flake workaround")
165}