netemul/
lib.rs

1// Copyright 2020 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#![warn(missing_docs, unreachable_patterns)]
6
7//! Netemul utilities.
8
9/// Methods for creating and interacting with virtualized guests in netemul tests.
10pub mod guest;
11
12use std::borrow::Cow;
13use std::collections::HashSet;
14use std::num::NonZeroU64;
15use std::ops::DerefMut as _;
16use std::path::Path;
17use std::pin::pin;
18use std::sync::{Arc, Mutex};
19
20use fidl::endpoints::{ProtocolMarker, Proxy as _};
21use fidl_fuchsia_net_dhcp_ext::{self as fnet_dhcp_ext, ClientProviderExt};
22use fidl_fuchsia_net_ext::{self as fnet_ext};
23use fidl_fuchsia_net_interfaces_ext::admin::Control;
24use fidl_fuchsia_net_interfaces_ext::{self as fnet_interfaces_ext};
25use fnet_ext::{FromExt as _, IntoExt as _};
26use fnet_interfaces_admin::GrantForInterfaceAuthorization;
27use zx::AsHandleRef;
28use {
29    fidl_fuchsia_hardware_network as fnetwork, fidl_fuchsia_io as fio, fidl_fuchsia_net as fnet,
30    fidl_fuchsia_net_dhcp as fnet_dhcp, fidl_fuchsia_net_interfaces as fnet_interfaces,
31    fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
32    fidl_fuchsia_net_neighbor as fnet_neighbor, fidl_fuchsia_net_root as fnet_root,
33    fidl_fuchsia_net_routes as fnet_routes, fidl_fuchsia_net_routes_admin as fnet_routes_admin,
34    fidl_fuchsia_net_routes_ext as fnet_routes_ext, fidl_fuchsia_net_stack as fnet_stack,
35    fidl_fuchsia_netemul as fnetemul, fidl_fuchsia_netemul_network as fnetemul_network,
36    fidl_fuchsia_posix_socket as fposix_socket, fidl_fuchsia_posix_socket_ext as fposix_socket_ext,
37    fidl_fuchsia_posix_socket_packet as fposix_socket_packet,
38    fidl_fuchsia_posix_socket_raw as fposix_socket_raw,
39};
40
41use anyhow::{anyhow, Context as _};
42use futures::future::{FutureExt as _, LocalBoxFuture, TryFutureExt as _};
43use futures::{SinkExt as _, TryStreamExt as _};
44use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Subnet};
45use net_types::SpecifiedAddr;
46
47type Result<T = ()> = std::result::Result<T, anyhow::Error>;
48
49/// The default MTU used in netemul endpoint configurations.
50pub const DEFAULT_MTU: u16 = 1500;
51
52/// The devfs path at which endpoints show up.
53pub const NETDEVICE_DEVFS_PATH: &'static str = "class/network";
54
55/// Returns the full path for a device node `node_name` relative to devfs root.
56pub fn devfs_device_path(node_name: &str) -> std::path::PathBuf {
57    std::path::Path::new(NETDEVICE_DEVFS_PATH).join(node_name)
58}
59
60/// Creates a common netemul endpoint configuration for tests.
61pub fn new_endpoint_config(
62    mtu: u16,
63    mac: Option<fnet::MacAddress>,
64) -> fnetemul_network::EndpointConfig {
65    fnetemul_network::EndpointConfig {
66        mtu,
67        mac: mac.map(Box::new),
68        port_class: fnetwork::PortClass::Virtual,
69    }
70}
71
72/// A test sandbox backed by a [`fnetemul::SandboxProxy`].
73///
74/// `TestSandbox` provides various utility methods to set up network realms for
75/// use in testing. The lifetime of the `TestSandbox` is tied to the netemul
76/// sandbox itself, dropping it will cause all the created realms, networks, and
77/// endpoints to be destroyed.
78#[must_use]
79pub struct TestSandbox {
80    sandbox: fnetemul::SandboxProxy,
81}
82
83impl TestSandbox {
84    /// Creates a new empty sandbox.
85    pub fn new() -> Result<TestSandbox> {
86        fuchsia_component::client::connect_to_protocol::<fnetemul::SandboxMarker>()
87            .context("failed to connect to sandbox protocol")
88            .map(|sandbox| TestSandbox { sandbox })
89    }
90
91    /// Creates a realm with `name` and `children`.
92    pub fn create_realm<'a, I>(
93        &'a self,
94        name: impl Into<Cow<'a, str>>,
95        children: I,
96    ) -> Result<TestRealm<'a>>
97    where
98        I: IntoIterator,
99        I::Item: Into<fnetemul::ChildDef>,
100    {
101        let (realm, server) = fidl::endpoints::create_proxy::<fnetemul::ManagedRealmMarker>();
102        let name = name.into();
103        let () = self.sandbox.create_realm(
104            server,
105            fnetemul::RealmOptions {
106                name: Some(name.clone().into_owned()),
107                children: Some(children.into_iter().map(Into::into).collect()),
108                ..Default::default()
109            },
110        )?;
111        Ok(TestRealm(Arc::new(TestRealmInner {
112            realm,
113            name,
114            _sandbox: self,
115            shutdown_on_drop: Mutex::new(ShutdownOnDropConfig {
116                enabled: true,
117                ignore_monikers: HashSet::new(),
118            }),
119        })))
120    }
121
122    /// Creates a realm with no components.
123    pub fn create_empty_realm<'a>(
124        &'a self,
125        name: impl Into<Cow<'a, str>>,
126    ) -> Result<TestRealm<'a>> {
127        self.create_realm(name, std::iter::empty::<fnetemul::ChildDef>())
128    }
129
130    /// Connects to the sandbox's `NetworkContext`.
131    fn get_network_context(&self) -> Result<fnetemul_network::NetworkContextProxy> {
132        let (ctx, server) =
133            fidl::endpoints::create_proxy::<fnetemul_network::NetworkContextMarker>();
134        let () = self.sandbox.get_network_context(server)?;
135        Ok(ctx)
136    }
137
138    /// Connects to the sandbox's `NetworkManager`.
139    pub fn get_network_manager(&self) -> Result<fnetemul_network::NetworkManagerProxy> {
140        let ctx = self.get_network_context()?;
141        let (network_manager, server) =
142            fidl::endpoints::create_proxy::<fnetemul_network::NetworkManagerMarker>();
143        let () = ctx.get_network_manager(server)?;
144        Ok(network_manager)
145    }
146
147    /// Connects to the sandbox's `EndpointManager`.
148    pub fn get_endpoint_manager(&self) -> Result<fnetemul_network::EndpointManagerProxy> {
149        let ctx = self.get_network_context()?;
150        let (ep_manager, server) =
151            fidl::endpoints::create_proxy::<fnetemul_network::EndpointManagerMarker>();
152        let () = ctx.get_endpoint_manager(server)?;
153        Ok(ep_manager)
154    }
155
156    /// Creates a new empty network with default configurations and `name`.
157    pub async fn create_network<'a>(
158        &'a self,
159        name: impl Into<Cow<'a, str>>,
160    ) -> Result<TestNetwork<'a>> {
161        let name = name.into();
162        let netm = self.get_network_manager()?;
163        let (status, network) = netm
164            .create_network(
165                &name,
166                &fnetemul_network::NetworkConfig {
167                    latency: None,
168                    packet_loss: None,
169                    reorder: None,
170                    ..Default::default()
171                },
172            )
173            .await
174            .context("create_network FIDL error")?;
175        let () = zx::Status::ok(status).context("create_network failed")?;
176        let network = network
177            .ok_or_else(|| anyhow::anyhow!("create_network didn't return a valid network"))?
178            .into_proxy();
179        Ok(TestNetwork { network, name, sandbox: self })
180    }
181
182    /// Creates new networks and endpoints as specified in `networks`.
183    pub async fn setup_networks<'a>(
184        &'a self,
185        networks: Vec<fnetemul_network::NetworkSetup>,
186    ) -> Result<TestNetworkSetup<'a>> {
187        let ctx = self.get_network_context()?;
188        let (status, handle) = ctx.setup(&networks).await.context("setup FIDL error")?;
189        let () = zx::Status::ok(status).context("setup failed")?;
190        let handle = handle
191            .ok_or_else(|| anyhow::anyhow!("setup didn't return a valid handle"))?
192            .into_proxy();
193        Ok(TestNetworkSetup { _setup: handle, _sandbox: self })
194    }
195
196    /// Creates a new unattached endpoint with default configurations and `name`.
197    ///
198    /// Characters may be dropped from the front of `name` if it exceeds the maximum length.
199    pub async fn create_endpoint<'a, S>(&'a self, name: S) -> Result<TestEndpoint<'a>>
200    where
201        S: Into<Cow<'a, str>>,
202    {
203        self.create_endpoint_with(name, new_endpoint_config(DEFAULT_MTU, None)).await
204    }
205
206    /// Creates a new unattached endpoint with the provided configuration.
207    ///
208    /// Characters may be dropped from the front of `name` if it exceeds the maximum length.
209    pub async fn create_endpoint_with<'a>(
210        &'a self,
211        name: impl Into<Cow<'a, str>>,
212        config: fnetemul_network::EndpointConfig,
213    ) -> Result<TestEndpoint<'a>> {
214        let name = name.into();
215        let epm = self.get_endpoint_manager()?;
216        let (status, endpoint) =
217            epm.create_endpoint(&name, &config).await.context("create_endpoint FIDL error")?;
218        let () = zx::Status::ok(status).context("create_endpoint failed")?;
219        let endpoint = endpoint
220            .ok_or_else(|| anyhow::anyhow!("create_endpoint didn't return a valid endpoint"))?
221            .into_proxy();
222        Ok(TestEndpoint { endpoint, name, _sandbox: self })
223    }
224}
225
226/// A set of virtual networks and endpoints.
227///
228/// Created through [`TestSandbox::setup_networks`].
229#[must_use]
230pub struct TestNetworkSetup<'a> {
231    _setup: fnetemul_network::SetupHandleProxy,
232    _sandbox: &'a TestSandbox,
233}
234
235impl TestNetworkSetup<'_> {
236    /// Extracts the proxy to the backing setup handle.
237    ///
238    /// Note that this defeats the lifetime semantics that ensure the sandbox in
239    /// which these networks were created lives as long as the networks. The caller
240    /// of [`TestNetworkSetup::into_proxy`] is responsible for ensuring that the
241    /// sandbox outlives the networks.
242    pub fn into_proxy(self) -> fnetemul_network::SetupHandleProxy {
243        let Self { _setup, _sandbox: _ } = self;
244        _setup
245    }
246}
247
248/// [`TestInterface`] configuration.
249#[derive(Default)]
250pub struct InterfaceConfig<'a> {
251    /// Optional interface name.
252    pub name: Option<Cow<'a, str>>,
253    /// Optional default route metric.
254    pub metric: Option<u32>,
255    /// Number of DAD transmits to use before marking an IPv4 address as
256    /// Assigned.
257    pub ipv4_dad_transmits: Option<u16>,
258    /// Number of DAD transmits to use before marking an IPv6 address as
259    /// Assigned.
260    pub ipv6_dad_transmits: Option<u16>,
261    /// Whether to generate temporary SLAAC addresses.
262    ///
263    /// If `None`, the interface configuration will not be modified and will remain
264    /// the netstack-chosen default.
265    pub temporary_addresses: Option<bool>,
266    /// Whether to use interface-local route table.
267    ///
268    /// If `None`, the interface configuration will not be modified and will remain
269    /// the netstack-chosen default.
270    pub netstack_managed_routes_designation:
271        Option<fnet_interfaces_admin::NetstackManagedRoutesDesignation>,
272}
273
274impl InterfaceConfig<'_> {
275    /// Creates a config that uses the interface local tables;
276    pub fn use_local_table() -> Self {
277        Self {
278            netstack_managed_routes_designation: Some(
279                fnet_interfaces_admin::NetstackManagedRoutesDesignation::InterfaceLocal(
280                    fnet_interfaces_admin::Empty,
281                ),
282            ),
283            ..Default::default()
284        }
285    }
286}
287
288#[derive(Debug)]
289struct ShutdownOnDropConfig {
290    enabled: bool,
291    ignore_monikers: HashSet<String>,
292}
293
294struct TestRealmInner<'a> {
295    realm: fnetemul::ManagedRealmProxy,
296    name: Cow<'a, str>,
297    _sandbox: &'a TestSandbox,
298    shutdown_on_drop: Mutex<ShutdownOnDropConfig>,
299}
300
301impl Drop for TestRealmInner<'_> {
302    fn drop(&mut self) {
303        let ShutdownOnDropConfig { enabled, ignore_monikers } =
304            self.shutdown_on_drop.get_mut().unwrap();
305        if !*enabled {
306            return;
307        }
308        let ignore_monikers = std::mem::take(ignore_monikers);
309        let mut crashed = match self.shutdown_sync() {
310            Ok(crashed) => crashed,
311            Err(e) => {
312                // If we observe a FIDL closed error don't panic. This likely
313                // just means the realm or listener were already shutdown, due
314                // to a pipelined realm building error, for example.
315                if !e.is_closed() {
316                    panic!("error verifying clean shutdown on test realm {}: {:?}", self.name, e);
317                }
318                return;
319            }
320        };
321
322        crashed.retain(|m| !ignore_monikers.contains(m));
323        if !crashed.is_empty() {
324            panic!(
325                "TestRealm {} found unclean component stops with monikers: {:?}",
326                self.name, crashed
327            );
328        }
329    }
330}
331
332impl TestRealmInner<'_> {
333    fn shutdown_sync(&self) -> std::result::Result<Vec<String>, fidl::Error> {
334        let (listener, server_end) = fidl::endpoints::create_sync_proxy();
335        self.realm.get_crash_listener(server_end)?;
336        self.realm.shutdown()?;
337        // Wait for the realm to go away.
338        let _: zx::Signals = self
339            .realm
340            .as_channel()
341            .wait_handle(zx::Signals::CHANNEL_PEER_CLOSED, zx::MonotonicInstant::INFINITE)
342            .to_result()
343            .expect("wait channel closed");
344        // Drive the listener to get the monikers of any components that did not
345        // exit cleanly.
346        let mut unclean_stop = Vec::new();
347        while let Some(unclean) =
348            listener.next(zx::MonotonicInstant::INFINITE).map(|v| (!v.is_empty()).then_some(v))?
349        {
350            unclean_stop.extend(unclean);
351        }
352        Ok(unclean_stop)
353    }
354}
355
356/// A realm within a netemul sandbox.
357///
358/// Note: A [`TestRealm`] by default attempts to perform a checked shutdown of
359/// the realm on drop. Which panics if any unclean component exits occur. This
360/// behavior can be opted out with
361/// [`TestRealm::set_checked_shutdown_on_drop`].
362#[must_use]
363#[derive(Clone)]
364pub struct TestRealm<'a>(Arc<TestRealmInner<'a>>);
365
366impl<'a> std::fmt::Debug for TestRealm<'a> {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
368        let Self(inner) = self;
369        let TestRealmInner { realm: _, name, _sandbox, shutdown_on_drop } = &**inner;
370        f.debug_struct("TestRealm")
371            .field("name", name)
372            .field("shutdown_on_drop", shutdown_on_drop)
373            .finish_non_exhaustive()
374    }
375}
376
377impl<'a> TestRealm<'a> {
378    fn realm(&self) -> &fnetemul::ManagedRealmProxy {
379        let Self(inner) = self;
380        &inner.realm
381    }
382
383    /// Returns the realm name.
384    pub fn name(&self) -> &str {
385        let Self(inner) = self;
386        &inner.name
387    }
388
389    /// Changes the behavior when `TestRealm` (and all its clones) are dropped.
390    ///
391    /// When `shutdown_on_drop` is `true` (the default value on creation), then
392    /// dropping a `TestRealm` performs a synchornous shutdown of the entire
393    /// component tree under the realm and *panics* if any unexpected
394    /// termination codes are observed.
395    pub fn set_checked_shutdown_on_drop(&self, shutdown_on_drop: bool) {
396        let Self(inner) = self;
397        inner.shutdown_on_drop.lock().unwrap().enabled = shutdown_on_drop;
398    }
399
400    /// Adds `monikers` to a list of monikers whose exit status is _ignored_
401    /// when `TestRealm` is dropped.
402    ///
403    /// This can be used for components that are not long-living or use nonzero
404    /// exit codes that are caught as crashes otherwise.
405    pub fn ignore_checked_shutdown_monikers(
406        &self,
407        monikers: impl IntoIterator<Item: Into<String>>,
408    ) {
409        let Self(inner) = self;
410        inner
411            .shutdown_on_drop
412            .lock()
413            .unwrap()
414            .ignore_monikers
415            .extend(monikers.into_iter().map(|m| m.into()));
416    }
417
418    /// Connects to a protocol within the realm.
419    pub fn connect_to_protocol<S>(&self) -> Result<S::Proxy>
420    where
421        S: fidl::endpoints::DiscoverableProtocolMarker,
422    {
423        (|| {
424            let (proxy, server_end) = fidl::endpoints::create_proxy::<S>();
425            let () = self
426                .connect_to_protocol_with_server_end(server_end)
427                .context("connect to protocol name with server end")?;
428            Result::Ok(proxy)
429        })()
430        .context(S::DEBUG_NAME)
431    }
432
433    /// Connects to a protocol from a child within the realm.
434    pub fn connect_to_protocol_from_child<S>(&self, child: &str) -> Result<S::Proxy>
435    where
436        S: fidl::endpoints::DiscoverableProtocolMarker,
437    {
438        (|| {
439            let (proxy, server_end) = fidl::endpoints::create_proxy::<S>();
440            let () = self
441                .connect_to_protocol_from_child_at_path_with_server_end(
442                    S::PROTOCOL_NAME,
443                    child,
444                    server_end,
445                )
446                .context("connect to protocol name with server end")?;
447            Result::Ok(proxy)
448        })()
449        .with_context(|| format!("{} from {child}", S::DEBUG_NAME))
450    }
451
452    /// Opens the diagnostics directory of a component.
453    pub fn open_diagnostics_directory(&self, child_name: &str) -> Result<fio::DirectoryProxy> {
454        let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
455        let () = self
456            .realm()
457            .open_diagnostics_directory(child_name, server_end)
458            .context("open diagnostics dir")?;
459        Ok(proxy)
460    }
461
462    /// Connects to a protocol within the realm.
463    pub fn connect_to_protocol_with_server_end<S: fidl::endpoints::DiscoverableProtocolMarker>(
464        &self,
465        server_end: fidl::endpoints::ServerEnd<S>,
466    ) -> Result {
467        self.realm()
468            .connect_to_protocol(S::PROTOCOL_NAME, None, server_end.into_channel())
469            .context("connect to protocol")
470    }
471
472    /// Connects to a protocol from a child at a path within the realm.
473    pub fn connect_to_protocol_from_child_at_path_with_server_end<
474        S: fidl::endpoints::DiscoverableProtocolMarker,
475    >(
476        &self,
477        protocol_path: &str,
478        child: &str,
479        server_end: fidl::endpoints::ServerEnd<S>,
480    ) -> Result {
481        self.realm()
482            .connect_to_protocol(protocol_path, Some(child), server_end.into_channel())
483            .context("connect to protocol")
484    }
485
486    /// Gets the moniker of the root of the managed realm.
487    pub async fn get_moniker(&self) -> Result<String> {
488        self.realm().get_moniker().await.context("failed to call get moniker")
489    }
490
491    /// Starts the specified child component of the managed realm.
492    pub async fn start_child_component(&self, child_name: &str) -> Result {
493        self.realm()
494            .start_child_component(child_name)
495            .await?
496            .map_err(zx::Status::from_raw)
497            .with_context(|| format!("failed to start child component '{}'", child_name))
498    }
499
500    /// Stops the specified child component of the managed realm.
501    pub async fn stop_child_component(&self, child_name: &str) -> Result {
502        self.realm()
503            .stop_child_component(child_name)
504            .await?
505            .map_err(zx::Status::from_raw)
506            .with_context(|| format!("failed to stop child component '{}'", child_name))
507    }
508
509    /// Use default endpoint/interface configuration and the specified address
510    /// configuration to create a test interface.
511    ///
512    /// Characters may be dropped from the front of `ep_name` if it exceeds the
513    /// maximum length.
514    pub async fn join_network<S>(
515        &self,
516        network: &TestNetwork<'a>,
517        ep_name: S,
518    ) -> Result<TestInterface<'a>>
519    where
520        S: Into<Cow<'a, str>>,
521    {
522        self.join_network_with_if_config(network, ep_name, Default::default()).await
523    }
524
525    /// Use default endpoint configuration and the specified interface/address
526    /// configuration to create a test interface.
527    ///
528    /// Characters may be dropped from the front of `ep_name` if it exceeds the
529    /// maximum length.
530    pub async fn join_network_with_if_config<S>(
531        &self,
532        network: &TestNetwork<'a>,
533        ep_name: S,
534        if_config: InterfaceConfig<'a>,
535    ) -> Result<TestInterface<'a>>
536    where
537        S: Into<Cow<'a, str>>,
538    {
539        let endpoint =
540            network.create_endpoint(ep_name).await.context("failed to create endpoint")?;
541        self.install_endpoint(endpoint, if_config).await
542    }
543
544    /// Joins `network` with by creating an endpoint with `ep_config` and
545    /// installing it into the realm with `if_config`.
546    ///
547    /// Returns a [`TestInterface`] corresponding to the added interface. The
548    /// interface is guaranteed to have its link up and be enabled when this
549    /// async function resolves.
550    ///
551    /// Note that this realm needs a Netstack for this operation to succeed.
552    ///
553    /// Characters may be dropped from the front of `ep_name` if it exceeds the maximum length.
554    pub async fn join_network_with(
555        &self,
556        network: &TestNetwork<'a>,
557        ep_name: impl Into<Cow<'a, str>>,
558        ep_config: fnetemul_network::EndpointConfig,
559        if_config: InterfaceConfig<'a>,
560    ) -> Result<TestInterface<'a>> {
561        let installer = self
562            .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
563            .context("failed to connect to fuchsia.net.interfaces.admin.Installer")?;
564        let interface_state = self
565            .connect_to_protocol::<fnet_interfaces::StateMarker>()
566            .context("failed to connect to fuchsia.net.interfaces.State")?;
567        let (endpoint, id, control, device_control) = self
568            .join_network_with_installer(
569                network,
570                installer,
571                interface_state,
572                ep_name,
573                ep_config,
574                if_config,
575            )
576            .await?;
577
578        Ok(TestInterface {
579            endpoint,
580            id,
581            realm: self.clone(),
582            control,
583            device_control: Some(device_control),
584            dhcp_client_task: futures::lock::Mutex::default(),
585        })
586    }
587
588    /// Joins `network` by creating an endpoint with `ep_config` and installing it with
589    /// `installer` and `if_config`.
590    ///
591    /// This inherits the lifetime of `self`, so there's an assumption that `installer` is served
592    /// by something in this [`TestRealm`], but there's nothing enforcing this.
593    ///
594    /// Returns the created endpoint, the interface ID, and the associated interface
595    /// [`Control`] and [`fnet_interfaces_admin::DeviceControlProxy`].
596    ///
597    /// Characters may be dropped from the front of `ep_name` if it exceeds the maximum length.
598    pub async fn join_network_with_installer(
599        &self,
600        network: &TestNetwork<'a>,
601        installer: fnet_interfaces_admin::InstallerProxy,
602        interface_state: fnet_interfaces::StateProxy,
603        ep_name: impl Into<Cow<'a, str>>,
604        ep_config: fnetemul_network::EndpointConfig,
605        if_config: InterfaceConfig<'a>,
606    ) -> Result<(TestEndpoint<'a>, u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
607        let endpoint = network
608            .create_endpoint_with(ep_name, ep_config)
609            .await
610            .context("failed to create endpoint")?;
611        let (id, control, device_control) = self
612            .install_endpoint_with_installer(installer, interface_state, &endpoint, if_config)
613            .await?;
614        Ok((endpoint, id, control, device_control))
615    }
616
617    /// Installs and configures the endpoint as an interface. Uses `interface_state` to observe that
618    /// the interface is up after it is installed.
619    ///
620    /// This inherits the lifetime of `self`, so there's an assumption that `installer` is served
621    /// by something in this [`TestRealm`], but there's nothing enforcing this.
622    ///
623    /// Note that if `name` is not `None`, the string must fit within interface name limits.
624    pub async fn install_endpoint_with_installer(
625        &self,
626        installer: fnet_interfaces_admin::InstallerProxy,
627        interface_state: fnet_interfaces::StateProxy,
628        endpoint: &TestEndpoint<'a>,
629        if_config: InterfaceConfig<'a>,
630    ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
631        let (id, control, device_control) =
632            endpoint.install(installer, if_config).await.context("failed to add endpoint")?;
633
634        let () = endpoint.set_link_up(true).await.context("failed to start endpoint")?;
635        let _did_enable: bool = control
636            .enable()
637            .await
638            .map_err(anyhow::Error::new)
639            .and_then(|res| {
640                res.map_err(|e: fnet_interfaces_admin::ControlEnableError| {
641                    anyhow::anyhow!("{:?}", e)
642                })
643            })
644            .context("failed to enable interface")?;
645
646        // Wait for Netstack to observe interface up so callers can safely
647        // assume the state of the world on return.
648        let () = fnet_interfaces_ext::wait_interface_with_id(
649            fnet_interfaces_ext::event_stream_from_state::<fnet_interfaces_ext::DefaultInterest>(
650                &interface_state,
651                fnet_interfaces_ext::IncludedAddresses::OnlyAssigned,
652            )?,
653            &mut fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(id),
654            |properties_and_state| properties_and_state.properties.online.then_some(()),
655        )
656        .await
657        .context("failed to observe interface up")?;
658
659        Ok((id, control, device_control))
660    }
661
662    /// Installs and configures the endpoint in this realm.
663    ///
664    /// Note that if `name` is not `None`, the string must fit within interface name limits.
665    pub async fn install_endpoint(
666        &self,
667        endpoint: TestEndpoint<'a>,
668        if_config: InterfaceConfig<'a>,
669    ) -> Result<TestInterface<'a>> {
670        let installer = self
671            .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
672            .context("failed to connect to fuchsia.net.interfaces.admin.Installer")?;
673        let interface_state = self
674            .connect_to_protocol::<fnet_interfaces::StateMarker>()
675            .context("failed to connect to fuchsia.net.interfaces.State")?;
676        let (id, control, device_control) = self
677            .install_endpoint_with_installer(installer, interface_state, &endpoint, if_config)
678            .await?;
679        Ok(TestInterface {
680            endpoint,
681            id,
682            realm: self.clone(),
683            control,
684            device_control: Some(device_control),
685            dhcp_client_task: futures::lock::Mutex::default(),
686        })
687    }
688
689    /// Adds a raw device connector to the realm's devfs.
690    pub async fn add_raw_device(
691        &self,
692        path: &Path,
693        device: fidl::endpoints::ClientEnd<fnetemul_network::DeviceProxy_Marker>,
694    ) -> Result {
695        let path = path.to_str().with_context(|| format!("convert {} to str", path.display()))?;
696        self.realm()
697            .add_device(path, device)
698            .await
699            .context("add device")?
700            .map_err(zx::Status::from_raw)
701            .context("add device error")
702    }
703
704    /// Adds a device to the realm's virtual device filesystem.
705    pub async fn add_virtual_device(&self, e: &TestEndpoint<'_>, path: &Path) -> Result {
706        let (device, device_server_end) =
707            fidl::endpoints::create_endpoints::<fnetemul_network::DeviceProxy_Marker>();
708        e.get_proxy_(device_server_end).context("get proxy")?;
709
710        self.add_raw_device(path, device).await
711    }
712
713    /// Removes a device from the realm's virtual device filesystem.
714    pub async fn remove_virtual_device(&self, path: &Path) -> Result {
715        let path = path.to_str().with_context(|| format!("convert {} to str", path.display()))?;
716        self.realm()
717            .remove_device(path)
718            .await
719            .context("remove device")?
720            .map_err(zx::Status::from_raw)
721            .context("remove device error")
722    }
723
724    /// Creates a Datagram [`socket2::Socket`] backed by the implementation of
725    /// `fuchsia.posix.socket/Provider` in this realm.
726    pub async fn datagram_socket(
727        &self,
728        domain: fposix_socket::Domain,
729        proto: fposix_socket::DatagramSocketProtocol,
730    ) -> Result<socket2::Socket> {
731        let socket_provider = self
732            .connect_to_protocol::<fposix_socket::ProviderMarker>()
733            .context("failed to connect to socket provider")?;
734
735        fposix_socket_ext::datagram_socket(&socket_provider, domain, proto)
736            .await
737            .context("failed to call socket")?
738            .context("failed to create socket")
739    }
740
741    /// Creates a raw [`socket2::Socket`] backed by the implementation of
742    /// `fuchsia.posix.socket.raw/Provider` in this realm.
743    pub async fn raw_socket(
744        &self,
745        domain: fposix_socket::Domain,
746        association: fposix_socket_raw::ProtocolAssociation,
747    ) -> Result<socket2::Socket> {
748        let socket_provider = self
749            .connect_to_protocol::<fposix_socket_raw::ProviderMarker>()
750            .context("failed to connect to socket provider")?;
751        let sock = socket_provider
752            .socket(domain, &association)
753            .await
754            .context("failed to call socket")?
755            .map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
756            .context("failed to create socket")?;
757
758        Ok(fdio::create_fd(sock.into()).context("failed to create fd")?.into())
759    }
760
761    /// Creates a [`socket2::Socket`] backed by the implementation of
762    /// [`fuchsia.posix.socket.packet/Provider`] in this realm.
763    ///
764    /// [`fuchsia.posix.socket.packet/Provider`]: fposix_socket_packet::ProviderMarker
765    pub async fn packet_socket(&self, kind: fposix_socket_packet::Kind) -> Result<socket2::Socket> {
766        let socket_provider = self
767            .connect_to_protocol::<fposix_socket_packet::ProviderMarker>()
768            .context("failed to connect to socket provider")?;
769
770        fposix_socket_ext::packet_socket(&socket_provider, kind)
771            .await
772            .context("failed to call socket")?
773            .context("failed to create socket")
774    }
775
776    /// Creates a Stream [`socket2::Socket`] backed by the implementation of
777    /// `fuchsia.posix.socket/Provider` in this realm.
778    pub async fn stream_socket(
779        &self,
780        domain: fposix_socket::Domain,
781        proto: fposix_socket::StreamSocketProtocol,
782    ) -> Result<socket2::Socket> {
783        let socket_provider = self
784            .connect_to_protocol::<fposix_socket::ProviderMarker>()
785            .context("failed to connect to socket provider")?;
786        let sock = socket_provider
787            .stream_socket(domain, proto)
788            .await
789            .context("failed to call socket")?
790            .map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
791            .context("failed to create socket")?;
792
793        Ok(fdio::create_fd(sock.into()).context("failed to create fd")?.into())
794    }
795
796    /// Shuts down the realm.
797    ///
798    /// It is often useful to call this method to ensure that the realm
799    /// completes orderly shutdown before allowing other resources to be dropped
800    /// and get cleaned up, such as [`TestEndpoint`]s, which components in the
801    /// realm might be interacting with.
802    pub async fn shutdown(&self) -> Result {
803        let () = self.realm().shutdown().context("call shutdown")?;
804        // Turn off shutdown on drop from now on, we're already shutting down
805        // here.
806        self.set_checked_shutdown_on_drop(false);
807        let events = self
808            .realm()
809            .take_event_stream()
810            .try_collect::<Vec<_>>()
811            .await
812            .context("error on realm event stream")?;
813        // Ensure there are no more events sent on the event stream after `OnShutdown`.
814        assert_matches::assert_matches!(events[..], [fnetemul::ManagedRealmEvent::OnShutdown {}]);
815        Ok(())
816    }
817
818    /// Returns a stream that yields monikers of unclean exits in the realm.
819    pub async fn get_crash_stream(&self) -> Result<impl futures::Stream<Item = Result<String>>> {
820        let (listener, server_end) = fidl::endpoints::create_proxy();
821        self.realm().get_crash_listener(server_end).context("creating CrashListener")?;
822        Ok(futures::stream::try_unfold(listener, |listener| async move {
823            let next = listener.next().await.context("listener fetch next moniker")?;
824            Result::Ok(if next.is_empty() {
825                None
826            } else {
827                Some((futures::stream::iter(next.into_iter().map(Ok)), listener))
828            })
829        })
830        .try_flatten())
831    }
832
833    /// Constructs an ICMP socket.
834    pub async fn icmp_socket<Ip: ping::FuchsiaIpExt>(
835        &self,
836    ) -> Result<fuchsia_async::net::DatagramSocket> {
837        let sock = self
838            .datagram_socket(Ip::DOMAIN_FIDL, fposix_socket::DatagramSocketProtocol::IcmpEcho)
839            .await
840            .context("failed to create ICMP datagram socket")?;
841        fuchsia_async::net::DatagramSocket::new_from_socket(sock)
842            .context("failed to create async ICMP datagram socket")
843    }
844
845    /// Sends a single ICMP echo request to `addr`, and waits for the echo reply.
846    pub async fn ping_once<Ip: ping::FuchsiaIpExt>(&self, addr: Ip::SockAddr, seq: u16) -> Result {
847        let icmp_sock = self.icmp_socket::<Ip>().await?;
848
849        const MESSAGE: &'static str = "hello, world";
850        let (mut sink, mut stream) = ping::new_unicast_sink_and_stream::<
851            Ip,
852            _,
853            { MESSAGE.len() + ping::ICMP_HEADER_LEN },
854        >(&icmp_sock, &addr, MESSAGE.as_bytes());
855
856        let send_fut = sink.send(seq).map_err(anyhow::Error::new);
857        let recv_fut = stream.try_next().map(|r| match r {
858            Ok(Some(got)) if got == seq => Ok(()),
859            Ok(Some(got)) => Err(anyhow!("unexpected echo reply; got: {}, want: {}", got, seq)),
860            Ok(None) => Err(anyhow!("echo reply stream ended unexpectedly")),
861            Err(e) => Err(anyhow::Error::from(e)),
862        });
863
864        let ((), ()) = futures::future::try_join(send_fut, recv_fut)
865            .await
866            .with_context(|| format!("failed to ping from {} to {}", self.name(), addr,))?;
867        Ok(())
868    }
869
870    /// Add a static neighbor entry.
871    ///
872    /// Useful to prevent NUD resolving too slow and causing spurious test failures.
873    pub async fn add_neighbor_entry(
874        &self,
875        interface: u64,
876        addr: fnet::IpAddress,
877        mac: fnet::MacAddress,
878    ) -> Result {
879        let controller = self
880            .connect_to_protocol::<fnet_neighbor::ControllerMarker>()
881            .context("connect to protocol")?;
882        controller
883            .add_entry(interface, &addr, &mac)
884            .await
885            .context("add_entry")?
886            .map_err(zx::Status::from_raw)
887            .context("add_entry failed")
888    }
889
890    /// Get a stream of interface events from a new watcher with default
891    /// interest.
892    pub fn get_interface_event_stream(
893        &self,
894    ) -> Result<
895        impl futures::Stream<
896            Item = std::result::Result<
897                fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
898                fidl::Error,
899            >,
900        >,
901    > {
902        self.get_interface_event_stream_with_interest::<fnet_interfaces_ext::DefaultInterest>()
903    }
904
905    /// Get a stream of interface events from a new watcher with specified
906    /// interest.
907    pub fn get_interface_event_stream_with_interest<I: fnet_interfaces_ext::FieldInterests>(
908        &self,
909    ) -> Result<
910        impl futures::Stream<
911            Item = std::result::Result<fnet_interfaces_ext::EventWithInterest<I>, fidl::Error>,
912        >,
913    > {
914        let interface_state = self
915            .connect_to_protocol::<fnet_interfaces::StateMarker>()
916            .context("connect to protocol")?;
917        fnet_interfaces_ext::event_stream_from_state::<I>(
918            &interface_state,
919            fnet_interfaces_ext::IncludedAddresses::OnlyAssigned,
920        )
921        .context("get interface event stream")
922    }
923
924    /// Gets the table ID for the main route table.
925    pub async fn main_table_id<
926        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
927    >(
928        &self,
929    ) -> u32 {
930        let main_route_table = self
931            .connect_to_protocol::<I::RouteTableMarker>()
932            .expect("failed to connect to main route table");
933        fnet_routes_ext::admin::get_table_id::<I>(&main_route_table)
934            .await
935            .expect("failed to get_table_id")
936            .get()
937    }
938}
939
940/// A virtual Network.
941///
942/// `TestNetwork` is a single virtual broadcast domain backed by Netemul.
943/// Created through [`TestSandbox::create_network`].
944#[must_use]
945pub struct TestNetwork<'a> {
946    network: fnetemul_network::NetworkProxy,
947    name: Cow<'a, str>,
948    sandbox: &'a TestSandbox,
949}
950
951impl<'a> std::fmt::Debug for TestNetwork<'a> {
952    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
953        let Self { name, network: _, sandbox: _ } = self;
954        f.debug_struct("TestNetwork").field("name", name).finish_non_exhaustive()
955    }
956}
957
958impl<'a> TestNetwork<'a> {
959    /// Extracts the proxy to the backing network.
960    ///
961    /// Note that this defeats the lifetime semantics that ensure the sandbox in
962    /// which this network was created lives as long as the network. The caller of
963    /// [`TestNetwork::into_proxy`] is responsible for ensuring that the sandbox
964    /// outlives the network.
965    pub fn into_proxy(self) -> fnetemul_network::NetworkProxy {
966        let Self { network, name: _, sandbox: _ } = self;
967        network
968    }
969
970    /// Gets a FIDL client for the backing network.
971    async fn get_client_end_clone(
972        &self,
973    ) -> Result<fidl::endpoints::ClientEnd<fnetemul_network::NetworkMarker>> {
974        let network_manager =
975            self.sandbox.get_network_manager().context("get_network_manager failed")?;
976        let client = network_manager
977            .get_network(&self.name)
978            .await
979            .context("get_network failed")?
980            .with_context(|| format!("no network found with name {}", self.name))?;
981        Ok(client)
982    }
983
984    /// Sets the configuration for this network to `config`.
985    pub async fn set_config(&self, config: fnetemul_network::NetworkConfig) -> Result<()> {
986        let status = self.network.set_config(&config).await.context("call set_config")?;
987        zx::Status::ok(status).context("set config")
988    }
989
990    /// Attaches `ep` to this network.
991    pub async fn attach_endpoint(&self, ep: &TestEndpoint<'a>) -> Result<()> {
992        let status =
993            self.network.attach_endpoint(&ep.name).await.context("attach_endpoint FIDL error")?;
994        let () = zx::Status::ok(status).context("attach_endpoint failed")?;
995        Ok(())
996    }
997
998    /// Creates a new endpoint with `name` attached to this network.
999    ///
1000    /// Characters may be dropped from the front of `name` if it exceeds the maximum length.
1001    pub async fn create_endpoint<S>(&self, name: S) -> Result<TestEndpoint<'a>>
1002    where
1003        S: Into<Cow<'a, str>>,
1004    {
1005        let ep = self
1006            .sandbox
1007            .create_endpoint(name)
1008            .await
1009            .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1010        let () = self.attach_endpoint(&ep).await.with_context(|| {
1011            format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1012        })?;
1013        Ok(ep)
1014    }
1015
1016    /// Creates a new endpoint with `name` and `config` attached to this network.
1017    ///
1018    /// Characters may be dropped from the front of `name` if it exceeds the maximum length.
1019    pub async fn create_endpoint_with(
1020        &self,
1021        name: impl Into<Cow<'a, str>>,
1022        config: fnetemul_network::EndpointConfig,
1023    ) -> Result<TestEndpoint<'a>> {
1024        let ep = self
1025            .sandbox
1026            .create_endpoint_with(name, config)
1027            .await
1028            .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1029        let () = self.attach_endpoint(&ep).await.with_context(|| {
1030            format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1031        })?;
1032        Ok(ep)
1033    }
1034
1035    /// Returns a fake endpoint.
1036    pub fn create_fake_endpoint(&self) -> Result<TestFakeEndpoint<'a>> {
1037        let (endpoint, server) =
1038            fidl::endpoints::create_proxy::<fnetemul_network::FakeEndpointMarker>();
1039        let () = self.network.create_fake_endpoint(server)?;
1040        return Ok(TestFakeEndpoint { endpoint, _sandbox: self.sandbox });
1041    }
1042
1043    /// Starts capturing packet in this network.
1044    ///
1045    /// The packet capture will be stored under a predefined directory:
1046    /// `/custom_artifacts`. More details can be found here:
1047    /// https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#custom-artifacts
1048    pub async fn start_capture(&self, name: &str) -> Result<PacketCapture> {
1049        let manager = self.sandbox.get_network_manager()?;
1050        let client = manager.get_network(&self.name).await?.expect("network must exist");
1051        zx::ok(self.network.start_capture(name).await?)?;
1052        let sync_proxy = fnetemul_network::NetworkSynchronousProxy::new(client.into_channel());
1053        Ok(PacketCapture { sync_proxy })
1054    }
1055
1056    /// Stops packet capture in this network.
1057    pub async fn stop_capture(&self) -> Result<()> {
1058        Ok(self.network.stop_capture().await?)
1059    }
1060}
1061
1062/// The object that has the same life as the packet capture, once the object is
1063/// dropped, the underlying packet capture will be stopped.
1064pub struct PacketCapture {
1065    sync_proxy: fnetemul_network::NetworkSynchronousProxy,
1066}
1067
1068impl Drop for PacketCapture {
1069    fn drop(&mut self) {
1070        self.sync_proxy
1071            .stop_capture(zx::MonotonicInstant::INFINITE)
1072            .expect("failed to stop packet capture")
1073    }
1074}
1075
1076/// A virtual network endpoint backed by Netemul.
1077#[must_use]
1078pub struct TestEndpoint<'a> {
1079    endpoint: fnetemul_network::EndpointProxy,
1080    name: Cow<'a, str>,
1081    _sandbox: &'a TestSandbox,
1082}
1083
1084impl<'a> std::fmt::Debug for TestEndpoint<'a> {
1085    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1086        let Self { endpoint: _, name, _sandbox } = self;
1087        f.debug_struct("TestEndpoint").field("name", name).finish_non_exhaustive()
1088    }
1089}
1090
1091impl<'a> std::ops::Deref for TestEndpoint<'a> {
1092    type Target = fnetemul_network::EndpointProxy;
1093
1094    fn deref(&self) -> &Self::Target {
1095        &self.endpoint
1096    }
1097}
1098
1099/// A virtual fake network endpoint backed by Netemul.
1100#[must_use]
1101pub struct TestFakeEndpoint<'a> {
1102    endpoint: fnetemul_network::FakeEndpointProxy,
1103    _sandbox: &'a TestSandbox,
1104}
1105
1106impl<'a> std::ops::Deref for TestFakeEndpoint<'a> {
1107    type Target = fnetemul_network::FakeEndpointProxy;
1108
1109    fn deref(&self) -> &Self::Target {
1110        &self.endpoint
1111    }
1112}
1113
1114impl<'a> TestFakeEndpoint<'a> {
1115    /// Return a stream of frames.
1116    ///
1117    /// Frames will be yielded as they are read from the fake endpoint.
1118    pub fn frame_stream(
1119        &self,
1120    ) -> impl futures::Stream<Item = std::result::Result<(Vec<u8>, u64), fidl::Error>> + '_ {
1121        futures::stream::try_unfold(&self.endpoint, |ep| ep.read().map_ok(move |r| Some((r, ep))))
1122    }
1123}
1124
1125/// Helper function to retrieve device and port information from a port
1126/// instance.
1127async fn to_netdevice_inner(
1128    port: fidl::endpoints::ClientEnd<fnetwork::PortMarker>,
1129) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1130    let port = port.into_proxy();
1131    let (device, server_end) = fidl::endpoints::create_endpoints::<fnetwork::DeviceMarker>();
1132    let () = port.get_device(server_end)?;
1133    let port_id = port
1134        .get_info()
1135        .await
1136        .context("get port info")?
1137        .id
1138        .ok_or_else(|| anyhow::anyhow!("missing port id"))?;
1139    Ok((device, port_id))
1140}
1141
1142impl<'a> TestEndpoint<'a> {
1143    /// Extracts the proxy to the backing endpoint.
1144    ///
1145    /// Note that this defeats the lifetime semantics that ensure the sandbox in
1146    /// which this endpoint was created lives as long as the endpoint. The caller of
1147    /// [`TestEndpoint::into_proxy`] is responsible for ensuring that the sandbox
1148    /// outlives the endpoint.
1149    pub fn into_proxy(self) -> fnetemul_network::EndpointProxy {
1150        let Self { endpoint, name: _, _sandbox: _ } = self;
1151        endpoint
1152    }
1153
1154    /// Gets access to this device's virtual Network device.
1155    ///
1156    /// Note that an error is returned if the Endpoint is not a
1157    /// [`fnetemul_network::DeviceConnection::NetworkDevice`].
1158    pub async fn get_netdevice(
1159        &self,
1160    ) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1161        let (port, server_end) = fidl::endpoints::create_endpoints();
1162        self.get_port(server_end)
1163            .with_context(|| format!("failed to get device connection for {}", self.name))?;
1164        to_netdevice_inner(port).await
1165    }
1166
1167    /// Installs the [`TestEndpoint`] via the provided [`fnet_interfaces_admin::InstallerProxy`].
1168    ///
1169    /// Returns the interface ID, and the associated interface
1170    /// [`Control`] and [`fnet_interfaces_admin::DeviceControlProxy`] on
1171    /// success.
1172    pub async fn install(
1173        &self,
1174        installer: fnet_interfaces_admin::InstallerProxy,
1175        InterfaceConfig {
1176            name,
1177            metric,
1178            ipv4_dad_transmits,
1179            ipv6_dad_transmits,
1180            temporary_addresses,
1181            netstack_managed_routes_designation,
1182        }: InterfaceConfig<'_>,
1183    ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1184        let name = name.map(|n| {
1185            truncate_dropping_front(n.into(), fnet_interfaces::INTERFACE_NAME_LENGTH.into())
1186                .to_string()
1187        });
1188        let (device, port_id) = self.get_netdevice().await?;
1189        let device_control = {
1190            let (control, server_end) =
1191                fidl::endpoints::create_proxy::<fnet_interfaces_admin::DeviceControlMarker>();
1192            let () = installer.install_device(device, server_end).context("install device")?;
1193            control
1194        };
1195        let (control, server_end) = Control::create_endpoints().context("create endpoints")?;
1196        let () = device_control
1197            .create_interface(
1198                &port_id,
1199                server_end,
1200                fnet_interfaces_admin::Options {
1201                    name,
1202                    metric,
1203                    netstack_managed_routes_designation,
1204                    __source_breaking: fidl::marker::SourceBreaking,
1205                },
1206            )
1207            .context("create interface")?;
1208        if let Some(ipv4_dad_transmits) = ipv4_dad_transmits {
1209            let _: Option<u16> = set_ipv4_dad_transmits(&control, ipv4_dad_transmits)
1210                .await
1211                .context("set dad transmits")?;
1212        }
1213        if let Some(ipv6_dad_transmits) = ipv6_dad_transmits {
1214            let _: Option<u16> = set_ipv6_dad_transmits(&control, ipv6_dad_transmits)
1215                .await
1216                .context("set dad transmits")?;
1217        }
1218        if let Some(enabled) = temporary_addresses {
1219            set_temporary_address_generation_enabled(&control, enabled)
1220                .await
1221                .context("set temporary addresses")?;
1222        }
1223
1224        let id = control.get_id().await.context("get id")?;
1225        Ok((id, control, device_control))
1226    }
1227
1228    /// Adds the [`TestEndpoint`] to the provided `realm` with an optional
1229    /// interface name.
1230    ///
1231    /// Returns the interface ID and control protocols on success.
1232    pub async fn add_to_stack(
1233        &self,
1234        realm: &TestRealm<'a>,
1235        config: InterfaceConfig<'a>,
1236    ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1237        let installer = realm
1238            .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1239            .context("connect to protocol")?;
1240
1241        self.install(installer, config).await
1242    }
1243
1244    /// Like `into_interface_realm_with_name` but with default parameters.
1245    pub async fn into_interface_in_realm(self, realm: &TestRealm<'a>) -> Result<TestInterface<'a>> {
1246        self.into_interface_in_realm_with_name(realm, Default::default()).await
1247    }
1248
1249    /// Consumes this `TestEndpoint` and tries to add it to the Netstack in
1250    /// `realm`, returning a [`TestInterface`] on success.
1251    pub async fn into_interface_in_realm_with_name(
1252        self,
1253        realm: &TestRealm<'a>,
1254        config: InterfaceConfig<'a>,
1255    ) -> Result<TestInterface<'a>> {
1256        let installer = realm
1257            .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1258            .context("connect to protocol")?;
1259
1260        let (id, control, device_control) =
1261            self.install(installer, config).await.context("failed to install")?;
1262
1263        Ok(TestInterface {
1264            endpoint: self,
1265            id,
1266            realm: realm.clone(),
1267            control,
1268            device_control: Some(device_control),
1269            dhcp_client_task: futures::lock::Mutex::default(),
1270        })
1271    }
1272}
1273
1274/// The DHCP client version.
1275#[derive(Copy, Clone, PartialEq, Debug)]
1276pub enum DhcpClientVersion {
1277    /// The in-Netstack2 DHCP client.
1278    InStack,
1279    /// The out-of-stack DHCP client.
1280    OutOfStack,
1281}
1282
1283/// Abstraction for how DHCP client functionality is provided.
1284pub trait DhcpClient {
1285    /// The DHCP client version to be used.
1286    const DHCP_CLIENT_VERSION: DhcpClientVersion;
1287}
1288
1289/// The in-Netstack2 DHCP client.
1290pub enum InStack {}
1291
1292impl DhcpClient for InStack {
1293    const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::InStack;
1294}
1295
1296/// The out-of-stack DHCP client.
1297pub enum OutOfStack {}
1298
1299impl DhcpClient for OutOfStack {
1300    const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::OutOfStack;
1301}
1302
1303/// A [`TestEndpoint`] that is installed in a realm's Netstack.
1304///
1305/// Note that a [`TestInterface`] adds to the reference count of the underlying
1306/// realm of its [`TestRealm`]. That is, a [`TestInterface`] that outlives the
1307/// [`TestRealm`] it created is sufficient to keep the underlying realm alive.
1308#[must_use]
1309pub struct TestInterface<'a> {
1310    endpoint: TestEndpoint<'a>,
1311    realm: TestRealm<'a>,
1312    id: u64,
1313    control: Control,
1314    device_control: Option<fnet_interfaces_admin::DeviceControlProxy>,
1315    dhcp_client_task: futures::lock::Mutex<Option<fnet_dhcp_ext::testutil::DhcpClientTask>>,
1316}
1317
1318impl<'a> std::fmt::Debug for TestInterface<'a> {
1319    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1320        let Self { endpoint, id, realm: _, control: _, device_control: _, dhcp_client_task: _ } =
1321            self;
1322        f.debug_struct("TestInterface")
1323            .field("endpoint", endpoint)
1324            .field("id", id)
1325            .finish_non_exhaustive()
1326    }
1327}
1328
1329impl<'a> std::ops::Deref for TestInterface<'a> {
1330    type Target = fnetemul_network::EndpointProxy;
1331
1332    fn deref(&self) -> &Self::Target {
1333        &self.endpoint
1334    }
1335}
1336
1337impl<'a> TestInterface<'a> {
1338    /// Gets the interface identifier.
1339    pub fn id(&self) -> u64 {
1340        self.id
1341    }
1342
1343    /// Returns the endpoint associated with the interface.
1344    pub fn endpoint(&self) -> &TestEndpoint<'a> {
1345        &self.endpoint
1346    }
1347
1348    /// Returns the interface's control handle.
1349    pub fn control(&self) -> &Control {
1350        &self.control
1351    }
1352
1353    /// Returns the authorization token for this interface.
1354    pub async fn get_authorization(&self) -> Result<GrantForInterfaceAuthorization> {
1355        Ok(self.control.get_authorization_for_interface().await?)
1356    }
1357
1358    /// Connects to fuchsia.net.stack in this interface's realm.
1359    pub fn connect_stack(&self) -> Result<fnet_stack::StackProxy> {
1360        self.realm.connect_to_protocol::<fnet_stack::StackMarker>()
1361    }
1362
1363    /// Installs a route in the realm's netstack's global route table with `self` as the outgoing
1364    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1365    ///
1366    /// Returns whether the route was newly added to the stack.
1367    async fn add_route<
1368        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1369    >(
1370        &self,
1371        destination: Subnet<I::Addr>,
1372        next_hop: Option<SpecifiedAddr<I::Addr>>,
1373        metric: fnet_routes::SpecifiedMetric,
1374    ) -> Result<bool> {
1375        let route_set = self.create_authenticated_global_route_set::<I>().await?;
1376        fnet_routes_ext::admin::add_route::<I>(
1377            &route_set,
1378            &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1379                .try_into()
1380                .expect("convert to FIDL should succeed"),
1381        )
1382        .await
1383        .context("FIDL error adding route")?
1384        .map_err(|e| anyhow::anyhow!("error adding route: {e:?}"))
1385    }
1386
1387    /// Installs a route in the realm's netstack's global route table with `self` as the outgoing
1388    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1389    ///
1390    /// Returns whether the route was newly added to the stack. Returns `Err` if `destination` and
1391    /// `next_hop` don't share the same IP version.
1392    pub async fn add_route_either(
1393        &self,
1394        destination: fnet::Subnet,
1395        next_hop: Option<fnet::IpAddress>,
1396        metric: fnet_routes::SpecifiedMetric,
1397    ) -> Result<bool> {
1398        let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1399        match destination_addr {
1400            fnet::IpAddress::Ipv4(destination_addr) => {
1401                let next_hop = match next_hop {
1402                    Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1403                        SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1404                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1405                    ),
1406                    Some(fnet::IpAddress::Ipv6(_)) => {
1407                        return Err(anyhow::anyhow!(
1408                            "next hop must be same IP version as destination"
1409                        ))
1410                    }
1411                    None => None,
1412                };
1413                self.add_route::<Ipv4>(
1414                    Subnet::new(destination_addr.into_ext(), prefix_len)
1415                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1416                    next_hop,
1417                    metric,
1418                )
1419                .await
1420            }
1421            fnet::IpAddress::Ipv6(destination_addr) => {
1422                let next_hop = match next_hop {
1423                    Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1424                        SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1425                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1426                    ),
1427                    Some(fnet::IpAddress::Ipv4(_)) => {
1428                        return Err(anyhow::anyhow!(
1429                            "next hop must be same IP version as destination"
1430                        ))
1431                    }
1432                    None => None,
1433                };
1434                self.add_route::<Ipv6>(
1435                    Subnet::new(destination_addr.into_ext(), prefix_len)
1436                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1437                    next_hop,
1438                    metric,
1439                )
1440                .await
1441            }
1442        }
1443    }
1444
1445    /// Removes a route from the realm's netstack's global route table with `self` as the outgoing
1446    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1447    ///
1448    /// Returns whether the route actually existed in the stack before it was removed.
1449    async fn remove_route<
1450        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1451    >(
1452        &self,
1453        destination: Subnet<I::Addr>,
1454        next_hop: Option<SpecifiedAddr<I::Addr>>,
1455        metric: fnet_routes::SpecifiedMetric,
1456    ) -> Result<bool> {
1457        let route_set = self.create_authenticated_global_route_set::<I>().await?;
1458        fnet_routes_ext::admin::remove_route::<I>(
1459            &route_set,
1460            &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1461                .try_into()
1462                .expect("convert to FIDL should succeed"),
1463        )
1464        .await
1465        .context("FIDL error removing route")?
1466        .map_err(|e| anyhow::anyhow!("error removing route: {e:?}"))
1467    }
1468
1469    /// Removes a route from the realm's netstack's global route table with `self` as the outgoing
1470    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1471    ///
1472    /// Returns whether the route actually existed in the stack before it was removed. Returns `Err`
1473    /// if `destination` and `next_hop` don't share the same IP version.
1474    async fn remove_route_either(
1475        &self,
1476        destination: fnet::Subnet,
1477        next_hop: Option<fnet::IpAddress>,
1478        metric: fnet_routes::SpecifiedMetric,
1479    ) -> Result<bool> {
1480        let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1481        match destination_addr {
1482            fnet::IpAddress::Ipv4(destination_addr) => {
1483                let next_hop = match next_hop {
1484                    Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1485                        SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1486                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1487                    ),
1488                    Some(fnet::IpAddress::Ipv6(_)) => {
1489                        return Err(anyhow::anyhow!(
1490                            "next hop must be same IP version as destination"
1491                        ))
1492                    }
1493                    None => None,
1494                };
1495                self.remove_route::<Ipv4>(
1496                    Subnet::new(destination_addr.into_ext(), prefix_len)
1497                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1498                    next_hop,
1499                    metric,
1500                )
1501                .await
1502            }
1503            fnet::IpAddress::Ipv6(destination_addr) => {
1504                let next_hop = match next_hop {
1505                    Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1506                        SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1507                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1508                    ),
1509                    Some(fnet::IpAddress::Ipv4(_)) => {
1510                        return Err(anyhow::anyhow!(
1511                            "next hop must be same IP version as destination"
1512                        ))
1513                    }
1514                    None => None,
1515                };
1516                self.remove_route::<Ipv6>(
1517                    Subnet::new(destination_addr.into_ext(), prefix_len)
1518                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1519                    next_hop,
1520                    metric,
1521                )
1522                .await
1523            }
1524        }
1525    }
1526
1527    /// Add a direct route from the interface to the given subnet.
1528    pub async fn add_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1529        let subnet = fnet_ext::apply_subnet_mask(subnet);
1530        let newly_added = self
1531            .add_route_either(
1532                subnet,
1533                None,
1534                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1535            )
1536            .await?;
1537
1538        if !newly_added {
1539            Err(anyhow::anyhow!(
1540                "route to {subnet:?} on {} should not have already existed",
1541                self.id()
1542            ))
1543        } else {
1544            Ok(())
1545        }
1546    }
1547
1548    /// Delete a direct route from the interface to the given subnet.
1549    pub async fn del_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1550        let subnet = fnet_ext::apply_subnet_mask(subnet);
1551        let newly_removed = self
1552            .remove_route_either(
1553                subnet,
1554                None,
1555                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1556            )
1557            .await?;
1558
1559        if !newly_removed {
1560            Err(anyhow::anyhow!(
1561                "route to {subnet:?} on {} should have previously existed before being removed",
1562                self.id()
1563            ))
1564        } else {
1565            Ok(())
1566        }
1567    }
1568
1569    /// Add a default route through the given `next_hop` with the given `metric`.
1570    pub async fn add_default_route_with_metric(
1571        &self,
1572        next_hop: fnet::IpAddress,
1573        metric: fnet_routes::SpecifiedMetric,
1574    ) -> Result<()> {
1575        let corresponding_default_subnet = match next_hop {
1576            fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1577            fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1578        };
1579
1580        let newly_added =
1581            self.add_route_either(corresponding_default_subnet, Some(next_hop), metric).await?;
1582
1583        if !newly_added {
1584            Err(anyhow::anyhow!(
1585                "default route through {} via {next_hop:?} already exists",
1586                self.id()
1587            ))
1588        } else {
1589            Ok(())
1590        }
1591    }
1592
1593    /// Add a default route through the given `next_hop` with the given `metric`.
1594    pub async fn add_default_route_with_explicit_metric(
1595        &self,
1596        next_hop: fnet::IpAddress,
1597        metric: u32,
1598    ) -> Result<()> {
1599        self.add_default_route_with_metric(
1600            next_hop,
1601            fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1602        )
1603        .await
1604    }
1605
1606    /// Add a default route through the given `next_hop`.
1607    pub async fn add_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1608        self.add_default_route_with_metric(
1609            next_hop,
1610            fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1611        )
1612        .await
1613    }
1614
1615    /// Remove a default route through the given address.
1616    pub async fn remove_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1617        let corresponding_default_subnet = match next_hop {
1618            fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1619            fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1620        };
1621
1622        let newly_removed = self
1623            .remove_route_either(
1624                corresponding_default_subnet,
1625                Some(next_hop),
1626                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1627            )
1628            .await?;
1629
1630        if !newly_removed {
1631            Err(anyhow::anyhow!(
1632                "default route through {} via {next_hop:?} does not exist",
1633                self.id()
1634            ))
1635        } else {
1636            Ok(())
1637        }
1638    }
1639
1640    /// Add a route to the given `destination` subnet via the given `next_hop`.
1641    pub async fn add_gateway_route(
1642        &self,
1643        destination: fnet::Subnet,
1644        next_hop: fnet::IpAddress,
1645    ) -> Result<()> {
1646        let newly_added = self
1647            .add_route_either(
1648                destination,
1649                Some(next_hop),
1650                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1651            )
1652            .await?;
1653
1654        if !newly_added {
1655            Err(anyhow::anyhow!(
1656                "should have newly added route to {destination:?} via {next_hop:?} through {}",
1657                self.id()
1658            ))
1659        } else {
1660            Ok(())
1661        }
1662    }
1663
1664    /// Create a root route set authenticated to manage routes through this interface.
1665    pub async fn create_authenticated_global_route_set<
1666        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1667    >(
1668        &self,
1669    ) -> Result<<I::RouteSetMarker as ProtocolMarker>::Proxy> {
1670        #[derive(GenericOverIp)]
1671        #[generic_over_ip(I, Ip)]
1672        struct Out<'a, I: fnet_routes_ext::admin::FidlRouteAdminIpExt>(
1673            LocalBoxFuture<'a, <I::RouteSetMarker as ProtocolMarker>::Proxy>,
1674        );
1675
1676        let Out(proxy_fut) = I::map_ip_out(
1677            self,
1678            |this| {
1679                Out(this
1680                    .get_global_route_set_v4()
1681                    .map(|result| result.expect("get global route set"))
1682                    .boxed_local())
1683            },
1684            |this| {
1685                Out(this
1686                    .get_global_route_set_v6()
1687                    .map(|result| result.expect("get global route set"))
1688                    .boxed_local())
1689            },
1690        );
1691
1692        let route_set = proxy_fut.await;
1693        let fnet_interfaces_admin::GrantForInterfaceAuthorization { interface_id, token } =
1694            self.get_authorization().await.expect("get interface grant");
1695        fnet_routes_ext::admin::authenticate_for_interface::<I>(
1696            &route_set,
1697            fnet_interfaces_admin::ProofOfInterfaceAuthorization { interface_id, token },
1698        )
1699        .await
1700        .expect("authentication should not have FIDL error")
1701        .expect("authentication should succeed");
1702        Ok(route_set)
1703    }
1704
1705    async fn get_global_route_set_v4(&self) -> Result<fnet_routes_admin::RouteSetV4Proxy> {
1706        let root_routes = self
1707            .realm
1708            .connect_to_protocol::<fnet_root::RoutesV4Marker>()
1709            .expect("get fuchsia.net.root.RoutesV4");
1710        let (route_set, server_end) =
1711            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1712        root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1713        Ok(route_set)
1714    }
1715
1716    async fn get_global_route_set_v6(&self) -> Result<fnet_routes_admin::RouteSetV6Proxy> {
1717        let root_routes = self
1718            .realm
1719            .connect_to_protocol::<fnet_root::RoutesV6Marker>()
1720            .expect("get fuchsia.net.root.RoutesV6");
1721        let (route_set, server_end) =
1722            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV6Marker>();
1723        root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1724        Ok(route_set)
1725    }
1726
1727    /// Gets the interface's properties with assigned addresses.
1728    async fn get_properties(
1729        &self,
1730        included_addresses: fnet_interfaces_ext::IncludedAddresses,
1731    ) -> Result<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>> {
1732        let interface_state = self.realm.connect_to_protocol::<fnet_interfaces::StateMarker>()?;
1733        let properties = fnet_interfaces_ext::existing(
1734            fnet_interfaces_ext::event_stream_from_state(&interface_state, included_addresses)?,
1735            fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(self.id),
1736        )
1737        .await
1738        .context("failed to get existing interfaces")?;
1739        match properties {
1740            fnet_interfaces_ext::InterfaceState::Unknown(id) => Err(anyhow::anyhow!(
1741                "could not find interface {} for endpoint {}",
1742                id,
1743                self.endpoint.name
1744            )),
1745            fnet_interfaces_ext::InterfaceState::Known(
1746                fnet_interfaces_ext::PropertiesAndState { properties, state: () },
1747            ) => Ok(properties),
1748        }
1749    }
1750
1751    /// Gets the interface's addresses.
1752    pub async fn get_addrs(
1753        &self,
1754        included_addresses: fnet_interfaces_ext::IncludedAddresses,
1755    ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
1756        let fnet_interfaces_ext::Properties { addresses, .. } =
1757            self.get_properties(included_addresses).await?;
1758        Ok(addresses)
1759    }
1760
1761    /// Gets the interface's device name.
1762    pub async fn get_interface_name(&self) -> Result<String> {
1763        let fnet_interfaces_ext::Properties { name, .. } =
1764            self.get_properties(fnet_interfaces_ext::IncludedAddresses::OnlyAssigned).await?;
1765        Ok(name)
1766    }
1767
1768    /// Gets the interface's port class.
1769    pub async fn get_port_class(&self) -> Result<fnet_interfaces_ext::PortClass> {
1770        let fnet_interfaces_ext::Properties { port_class, .. } =
1771            self.get_properties(fnet_interfaces_ext::IncludedAddresses::OnlyAssigned).await?;
1772        Ok(port_class)
1773    }
1774
1775    /// Gets the interface's MAC address.
1776    pub async fn mac(&self) -> fnet::MacAddress {
1777        let (port, server_end) =
1778            fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>();
1779        self.get_port(server_end).expect("get_port");
1780        let (mac_addressing, server_end) =
1781            fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::MacAddressingMarker>();
1782        port.get_mac(server_end).expect("get_mac");
1783        mac_addressing.get_unicast_address().await.expect("get_unicast_address")
1784    }
1785
1786    async fn set_dhcp_client_enabled(&self, enable: bool) -> Result<()> {
1787        self.connect_stack()
1788            .context("connect stack")?
1789            .set_dhcp_client_enabled(self.id, enable)
1790            .await
1791            .context("failed to call SetDhcpClientEnabled")?
1792            .map_err(|e| anyhow!("{:?}", e))
1793    }
1794
1795    /// Starts DHCP on this interface.
1796    pub async fn start_dhcp<D: DhcpClient>(&self) -> Result<()> {
1797        match D::DHCP_CLIENT_VERSION {
1798            DhcpClientVersion::InStack => self.start_dhcp_in_stack().await,
1799            DhcpClientVersion::OutOfStack => self.start_dhcp_client_out_of_stack().await,
1800        }
1801    }
1802
1803    async fn start_dhcp_in_stack(&self) -> Result<()> {
1804        self.set_dhcp_client_enabled(true).await.context("failed to start dhcp client")
1805    }
1806
1807    async fn start_dhcp_client_out_of_stack(&self) -> Result<()> {
1808        let Self { endpoint: _, realm, id, control, device_control: _, dhcp_client_task } = self;
1809        let id = NonZeroU64::new(*id).expect("interface ID should be nonzero");
1810        let mut dhcp_client_task = dhcp_client_task.lock().await;
1811        let dhcp_client_task = dhcp_client_task.deref_mut();
1812
1813        let provider = realm
1814            .connect_to_protocol::<fnet_dhcp::ClientProviderMarker>()
1815            .expect("get fuchsia.net.dhcp.ClientProvider");
1816
1817        provider.check_presence().await.expect("check presence should succeed");
1818
1819        let client = provider.new_client_ext(id, fnet_dhcp_ext::default_new_client_params());
1820        let control = control.clone();
1821        let route_set_provider = realm
1822            .connect_to_protocol::<fnet_routes_admin::RouteTableV4Marker>()
1823            .expect("get fuchsia.net.routes.RouteTableV4");
1824        let (route_set, server_end) =
1825            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1826        route_set_provider.new_route_set(server_end).expect("calling new_route_set should succeed");
1827        let task = fnet_dhcp_ext::testutil::DhcpClientTask::new(client, id, route_set, control);
1828        *dhcp_client_task = Some(task);
1829        Ok(())
1830    }
1831
1832    /// Stops DHCP on this interface.
1833    pub async fn stop_dhcp<D: DhcpClient>(&self) -> Result<()> {
1834        match D::DHCP_CLIENT_VERSION {
1835            DhcpClientVersion::InStack => self.stop_dhcp_in_stack().await,
1836            DhcpClientVersion::OutOfStack => {
1837                self.stop_dhcp_out_of_stack().await;
1838                Ok(())
1839            }
1840        }
1841    }
1842
1843    async fn stop_dhcp_in_stack(&self) -> Result<()> {
1844        self.set_dhcp_client_enabled(false).await.context("failed to stop dhcp client")
1845    }
1846
1847    async fn stop_dhcp_out_of_stack(&self) {
1848        let Self { endpoint: _, realm: _, id: _, control: _, device_control: _, dhcp_client_task } =
1849            self;
1850        let mut dhcp_client_task = dhcp_client_task.lock().await;
1851        if let Some(task) = dhcp_client_task.deref_mut().take() {
1852            task.shutdown().await.expect("client shutdown should succeed");
1853        }
1854    }
1855
1856    /// Adds an address, waiting until the address assignment state is
1857    /// `ASSIGNED`.
1858    pub async fn add_address(&self, subnet: fnet::Subnet) -> Result<()> {
1859        let (address_state_provider, server) =
1860            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1861        let () = address_state_provider.detach().context("detach address lifetime")?;
1862        let () = self
1863            .control
1864            .add_address(&subnet, &fnet_interfaces_admin::AddressParameters::default(), server)
1865            .context("FIDL error")?;
1866
1867        let mut state_stream =
1868            fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1869        fnet_interfaces_ext::admin::wait_assignment_state(
1870            &mut state_stream,
1871            fnet_interfaces::AddressAssignmentState::Assigned,
1872        )
1873        .await?;
1874        Ok(())
1875    }
1876
1877    /// Adds an address and a subnet route, waiting until the address assignment
1878    /// state is `ASSIGNED`.
1879    pub async fn add_address_and_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1880        let (address_state_provider, server) =
1881            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1882        address_state_provider.detach().context("detach address lifetime")?;
1883        self.control
1884            .add_address(
1885                &subnet,
1886                &fnet_interfaces_admin::AddressParameters {
1887                    add_subnet_route: Some(true),
1888                    ..Default::default()
1889                },
1890                server,
1891            )
1892            .context("FIDL error")?;
1893
1894        let state_stream =
1895            fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1896        let mut state_stream = pin!(state_stream);
1897
1898        fnet_interfaces_ext::admin::wait_assignment_state(
1899            &mut state_stream,
1900            fnet_interfaces::AddressAssignmentState::Assigned,
1901        )
1902        .await
1903        .context("assignment state")?;
1904        Ok(())
1905    }
1906
1907    /// Removes an address and its corresponding subnet route.
1908    pub async fn del_address_and_subnet_route(
1909        &self,
1910        addr_with_prefix: fnet::Subnet,
1911    ) -> Result<bool> {
1912        let did_remove =
1913            self.control.remove_address(&addr_with_prefix).await.context("FIDL error").and_then(
1914                |res| {
1915                    res.map_err(|e: fnet_interfaces_admin::ControlRemoveAddressError| {
1916                        anyhow::anyhow!("{:?}", e)
1917                    })
1918                },
1919            )?;
1920
1921        if did_remove {
1922            let destination = fnet_ext::apply_subnet_mask(addr_with_prefix);
1923            let newly_removed_route = self
1924                .remove_route_either(
1925                    destination,
1926                    None,
1927                    fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1928                )
1929                .await?;
1930
1931            // We don't assert on the route having been newly-removed because it could also
1932            // be removed due to the AddressStateProvider going away.
1933            let _: bool = newly_removed_route;
1934        }
1935        Ok(did_remove)
1936    }
1937
1938    /// Removes all IPv6 LinkLocal addresses on the interface.
1939    ///
1940    /// Useful to purge the interface of autogenerated SLAAC addresses.
1941    pub async fn remove_ipv6_linklocal_addresses(
1942        &self,
1943    ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
1944        let mut result = Vec::new();
1945        for address in self.get_addrs(fnet_interfaces_ext::IncludedAddresses::All).await? {
1946            let fnet_interfaces_ext::Address { addr: fnet::Subnet { addr, prefix_len }, .. } =
1947                &address;
1948            match addr {
1949                fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr: _ }) => {
1950                    continue
1951                }
1952                fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
1953                    let v6_addr = net_types::ip::Ipv6Addr::from_bytes(*addr);
1954                    if !v6_addr.is_unicast_link_local() {
1955                        continue;
1956                    }
1957                }
1958            }
1959            let _newly_removed: bool = self
1960                .del_address_and_subnet_route(fnet::Subnet { addr: *addr, prefix_len: *prefix_len })
1961                .await?;
1962            result.push(address);
1963        }
1964        Ok(result)
1965    }
1966
1967    /// Set configuration on this interface.
1968    ///
1969    /// Returns an error if the operation is unsupported or a no-op.
1970    ///
1971    /// Note that this function should not be made public and should only be
1972    /// used to implement helpers for setting specific pieces of configuration,
1973    /// as it cannot be guaranteed that this function is kept up-to-date with
1974    /// the underlying FIDL types and thus may not always be able to uphold the
1975    /// error return contract.
1976    async fn set_configuration(&self, config: fnet_interfaces_admin::Configuration) -> Result<()> {
1977        let fnet_interfaces_admin::Configuration {
1978            ipv4: previous_ipv4, ipv6: previous_ipv6, ..
1979        } = self
1980            .control()
1981            .set_configuration(&config.clone())
1982            .await
1983            .context("FIDL error")?
1984            .map_err(|e| anyhow!("set configuration error: {:?}", e))?;
1985
1986        fn verify_config_changed<T: Eq>(previous: Option<T>, current: Option<T>) -> Result<()> {
1987            if let Some(current) = current {
1988                let previous = previous.ok_or_else(|| anyhow!("configuration not supported"))?;
1989                if previous == current {
1990                    return Err(anyhow!("configuration change is a no-op"));
1991                }
1992            }
1993            Ok(())
1994        }
1995
1996        let fnet_interfaces_admin::Configuration { ipv4, ipv6, .. } = config;
1997        if let Some(fnet_interfaces_admin::Ipv4Configuration {
1998            unicast_forwarding,
1999            multicast_forwarding,
2000            ..
2001        }) = ipv4
2002        {
2003            let fnet_interfaces_admin::Ipv4Configuration {
2004                unicast_forwarding: previous_unicast_forwarding,
2005                multicast_forwarding: previous_multicast_forwarding,
2006                ..
2007            } = previous_ipv4.ok_or_else(|| anyhow!("IPv4 configuration not supported"))?;
2008            verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2009                .context("IPv4 unicast forwarding")?;
2010            verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2011                .context("IPv4 multicast forwarding")?;
2012        }
2013        if let Some(fnet_interfaces_admin::Ipv6Configuration {
2014            unicast_forwarding,
2015            multicast_forwarding,
2016            ..
2017        }) = ipv6
2018        {
2019            let fnet_interfaces_admin::Ipv6Configuration {
2020                unicast_forwarding: previous_unicast_forwarding,
2021                multicast_forwarding: previous_multicast_forwarding,
2022                ..
2023            } = previous_ipv6.ok_or_else(|| anyhow!("IPv6 configuration not supported"))?;
2024            verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2025                .context("IPv6 unicast forwarding")?;
2026            verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2027                .context("IPv6 multicast forwarding")?;
2028        }
2029        Ok(())
2030    }
2031
2032    /// Enable/disable IPv6 forwarding on this interface.
2033    pub async fn set_ipv6_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2034        self.set_configuration(fnet_interfaces_admin::Configuration {
2035            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2036                unicast_forwarding: Some(enabled),
2037                ..Default::default()
2038            }),
2039            ..Default::default()
2040        })
2041        .await
2042    }
2043
2044    /// Enable/disable IPv4 forwarding on this interface.
2045    pub async fn set_ipv4_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2046        self.set_configuration(fnet_interfaces_admin::Configuration {
2047            ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2048                unicast_forwarding: Some(enabled),
2049                ..Default::default()
2050            }),
2051            ..Default::default()
2052        })
2053        .await
2054    }
2055
2056    /// Consumes this [`TestInterface`] and removes the associated interface
2057    /// in the Netstack, returning the device lifetime-carrying channels.
2058    pub async fn remove(
2059        self,
2060    ) -> Result<(fnetemul_network::EndpointProxy, Option<fnet_interfaces_admin::DeviceControlProxy>)>
2061    {
2062        let Self {
2063            endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2064            id: _,
2065            realm: _,
2066            control,
2067            device_control,
2068            dhcp_client_task: _,
2069        } = self;
2070        // For Network Devices, the `control` handle  is tied to the lifetime of
2071        // the interface; dropping it triggers interface removal in the
2072        // Netstack. For Ethernet devices this is a No-Op.
2073        std::mem::drop(control);
2074        Ok((endpoint, device_control))
2075    }
2076
2077    /// Consumes this [`TestInterface`] and removes the underlying device. The
2078    /// Netstack will implicitly remove the interface and clients can expect to
2079    /// observe a `PEER_CLOSED` event on the returned control channel.
2080    pub fn remove_device(self) -> (Control, Option<fnet_interfaces_admin::DeviceControlProxy>) {
2081        let Self {
2082            endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2083            id: _,
2084            realm: _,
2085            control,
2086            device_control,
2087            dhcp_client_task: _,
2088        } = self;
2089        std::mem::drop(endpoint);
2090        (control, device_control)
2091    }
2092
2093    /// Waits for this interface to signal that it's been removed.
2094    pub async fn wait_removal(self) -> Result<fnet_interfaces_admin::InterfaceRemovedReason> {
2095        let Self {
2096            // Keep this alive, we don't want to trigger removal.
2097            endpoint: _endpoint,
2098            id: _,
2099            realm: _,
2100            control,
2101            dhcp_client_task: _,
2102            // Keep this alive, we don't want to trigger removal.
2103            device_control: _device_control,
2104        } = self;
2105        match control.wait_termination().await {
2106            fnet_interfaces_ext::admin::TerminalError::Fidl(e) => {
2107                Err(e).context("waiting interface control termination")
2108            }
2109            fnet_interfaces_ext::admin::TerminalError::Terminal(reason) => Ok(reason),
2110        }
2111    }
2112
2113    /// Sets the number of IPv6 DAD transmits on this interface.
2114    ///
2115    /// Returns the previous configuration value, if reported by the API.
2116    pub async fn set_ipv4_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2117        set_ipv4_dad_transmits(self.control(), dad_transmits).await
2118    }
2119
2120    /// Sets the number of IPv6 DAD transmits on this interface.
2121    ///
2122    /// Returns the previous configuration value, if reported by the API.
2123    pub async fn set_ipv6_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2124        set_ipv6_dad_transmits(self.control(), dad_transmits).await
2125    }
2126
2127    /// Sets whether temporary SLAAC address generation is enabled
2128    /// or disabled on this interface.
2129    pub async fn set_temporary_address_generation_enabled(&self, enabled: bool) -> Result<()> {
2130        set_temporary_address_generation_enabled(self.control(), enabled).await
2131    }
2132}
2133
2134async fn set_ipv4_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2135    control
2136        .set_configuration(&fnet_interfaces_admin::Configuration {
2137            ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2138                arp: Some(fnet_interfaces_admin::ArpConfiguration {
2139                    dad: Some(fnet_interfaces_admin::DadConfiguration {
2140                        transmits: Some(dad_transmits),
2141                        ..Default::default()
2142                    }),
2143                    ..Default::default()
2144                }),
2145                ..Default::default()
2146            }),
2147            ..Default::default()
2148        })
2149        .await?
2150        .map(|config| config.ipv4?.arp?.dad?.transmits)
2151        .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2152}
2153
2154async fn set_ipv6_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2155    control
2156        .set_configuration(&fnet_interfaces_admin::Configuration {
2157            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2158                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2159                    dad: Some(fnet_interfaces_admin::DadConfiguration {
2160                        transmits: Some(dad_transmits),
2161                        ..Default::default()
2162                    }),
2163                    ..Default::default()
2164                }),
2165                ..Default::default()
2166            }),
2167            ..Default::default()
2168        })
2169        .await?
2170        .map(|config| config.ipv6?.ndp?.dad?.transmits)
2171        .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2172}
2173
2174async fn set_temporary_address_generation_enabled(control: &Control, enabled: bool) -> Result<()> {
2175    let _config: fnet_interfaces_admin::Configuration = control
2176        .set_configuration(&fnet_interfaces_admin::Configuration {
2177            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2178                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2179                    slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
2180                        temporary_address: Some(enabled),
2181                        ..Default::default()
2182                    }),
2183                    ..Default::default()
2184                }),
2185                ..Default::default()
2186            }),
2187            ..Default::default()
2188        })
2189        .await
2190        .context("FIDL error")?
2191        .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))?;
2192    Ok(())
2193}
2194
2195/// Get the [`socket2::Domain`] for `addr`.
2196fn get_socket2_domain(addr: &std::net::SocketAddr) -> fposix_socket::Domain {
2197    let domain = match addr {
2198        std::net::SocketAddr::V4(_) => fposix_socket::Domain::Ipv4,
2199        std::net::SocketAddr::V6(_) => fposix_socket::Domain::Ipv6,
2200    };
2201
2202    domain
2203}
2204
2205/// Trait describing UDP sockets that can be bound in a testing realm.
2206pub trait RealmUdpSocket: Sized {
2207    /// Creates a UDP socket in `realm` bound to `addr`.
2208    fn bind_in_realm<'a>(
2209        realm: &'a TestRealm<'a>,
2210        addr: std::net::SocketAddr,
2211    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2212}
2213
2214impl RealmUdpSocket for std::net::UdpSocket {
2215    fn bind_in_realm<'a>(
2216        realm: &'a TestRealm<'a>,
2217        addr: std::net::SocketAddr,
2218    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2219        async move {
2220            let sock = realm
2221                .datagram_socket(
2222                    get_socket2_domain(&addr),
2223                    fposix_socket::DatagramSocketProtocol::Udp,
2224                )
2225                .await
2226                .context("failed to create socket")?;
2227
2228            let () = sock.bind(&addr.into()).context("bind failed")?;
2229
2230            Result::Ok(sock.into())
2231        }
2232        .boxed_local()
2233    }
2234}
2235
2236impl RealmUdpSocket for fuchsia_async::net::UdpSocket {
2237    fn bind_in_realm<'a>(
2238        realm: &'a TestRealm<'a>,
2239        addr: std::net::SocketAddr,
2240    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2241        std::net::UdpSocket::bind_in_realm(realm, addr)
2242            .and_then(|udp| {
2243                futures::future::ready(
2244                    fuchsia_async::net::UdpSocket::from_socket(udp)
2245                        .context("failed to create fuchsia_async socket"),
2246                )
2247            })
2248            .boxed_local()
2249    }
2250}
2251
2252/// Trait describing TCP listeners bound in a testing realm.
2253pub trait RealmTcpListener: Sized {
2254    /// Creates a TCP listener in `realm` bound to `addr`.
2255    fn listen_in_realm<'a>(
2256        realm: &'a TestRealm<'a>,
2257        addr: std::net::SocketAddr,
2258    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2259        Self::listen_in_realm_with(realm, addr, |_: &socket2::Socket| Ok(()))
2260    }
2261
2262    /// Creates a TCP listener by creating a Socket2 socket in `realm`. Closure `setup` is called
2263    /// with the reference of the socket before the socket is bound to `addr`.
2264    fn listen_in_realm_with<'a>(
2265        realm: &'a TestRealm<'a>,
2266        addr: std::net::SocketAddr,
2267        setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2268    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2269}
2270
2271impl RealmTcpListener for std::net::TcpListener {
2272    fn listen_in_realm_with<'a>(
2273        realm: &'a TestRealm<'a>,
2274        addr: std::net::SocketAddr,
2275        setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2276    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2277        async move {
2278            let sock = realm
2279                .stream_socket(get_socket2_domain(&addr), fposix_socket::StreamSocketProtocol::Tcp)
2280                .await
2281                .context("failed to create server socket")?;
2282            let () = setup(&sock)?;
2283            let () = sock.bind(&addr.into()).context("failed to bind server socket")?;
2284            // Use 128 for the listen() backlog, same as the original implementation of TcpListener
2285            // in Rust std (see https://doc.rust-lang.org/src/std/sys_common/net.rs.html#386).
2286            let () = sock.listen(128).context("failed to listen on server socket")?;
2287
2288            Result::Ok(sock.into())
2289        }
2290        .boxed_local()
2291    }
2292}
2293
2294impl RealmTcpListener for fuchsia_async::net::TcpListener {
2295    fn listen_in_realm_with<'a>(
2296        realm: &'a TestRealm<'a>,
2297        addr: std::net::SocketAddr,
2298        setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2299    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2300        std::net::TcpListener::listen_in_realm_with(realm, addr, setup)
2301            .and_then(|listener| {
2302                futures::future::ready(
2303                    fuchsia_async::net::TcpListener::from_std(listener)
2304                        .context("failed to create fuchsia_async socket"),
2305                )
2306            })
2307            .boxed_local()
2308    }
2309}
2310
2311/// Trait describing TCP streams in a testing realm.
2312pub trait RealmTcpStream: Sized {
2313    /// Creates a TCP stream in `realm` connected to `addr`.
2314    fn connect_in_realm<'a>(
2315        realm: &'a TestRealm<'a>,
2316        addr: std::net::SocketAddr,
2317    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2318
2319    /// Creates a TCP stream in `realm` bound to `local` and connected to `dst`.
2320    fn bind_and_connect_in_realm<'a>(
2321        realm: &'a TestRealm<'a>,
2322        local: std::net::SocketAddr,
2323        dst: std::net::SocketAddr,
2324    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2325
2326    /// Creates a TCP stream in `realm` connected to `addr`.
2327    ///
2328    /// Closure `with_sock` is called with the reference of the socket before
2329    /// the socket is connected to `addr`.
2330    fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2331        realm: &'a TestRealm<'a>,
2332        dst: std::net::SocketAddr,
2333        with_sock: F,
2334    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2335
2336    // TODO: Implement this trait for std::net::TcpStream.
2337}
2338
2339impl RealmTcpStream for fuchsia_async::net::TcpStream {
2340    fn connect_in_realm<'a>(
2341        realm: &'a TestRealm<'a>,
2342        addr: std::net::SocketAddr,
2343    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2344        Self::connect_in_realm_with_sock(realm, addr, |_: &socket2::Socket| Ok(()))
2345    }
2346
2347    fn bind_and_connect_in_realm<'a>(
2348        realm: &'a TestRealm<'a>,
2349        local: std::net::SocketAddr,
2350        dst: std::net::SocketAddr,
2351    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2352        Self::connect_in_realm_with_sock(realm, dst, move |sock| {
2353            sock.bind(&local.into()).context("failed to bind")
2354        })
2355    }
2356
2357    fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2358        realm: &'a TestRealm<'a>,
2359        dst: std::net::SocketAddr,
2360        with_sock: F,
2361    ) -> futures::future::LocalBoxFuture<'a, Result<fuchsia_async::net::TcpStream>> {
2362        async move {
2363            let sock = realm
2364                .stream_socket(get_socket2_domain(&dst), fposix_socket::StreamSocketProtocol::Tcp)
2365                .await
2366                .context("failed to create socket")?;
2367
2368            with_sock(&sock)?;
2369
2370            let stream = fuchsia_async::net::TcpStream::connect_from_raw(sock, dst)
2371                .context("failed to create client tcp stream")?
2372                .await
2373                .context("failed to connect to server")?;
2374
2375            Result::Ok(stream)
2376        }
2377        .boxed_local()
2378    }
2379}
2380
2381fn truncate_dropping_front(s: Cow<'_, str>, len: usize) -> Cow<'_, str> {
2382    match s.len().checked_sub(len) {
2383        None => s,
2384        Some(start) => {
2385            // NB: Drop characters from the front because it's likely that a name that
2386            // exceeds the length limit is the full name of a test whose suffix is more
2387            // informative because nesting of test cases appends suffixes.
2388            match s {
2389                Cow::Borrowed(s) => Cow::Borrowed(&s[start..]),
2390                Cow::Owned(mut s) => {
2391                    let _: std::string::Drain<'_> = s.drain(..start);
2392                    Cow::Owned(s)
2393                }
2394            }
2395        }
2396    }
2397}