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 zx::AsHandleRef;
27use {
28    fidl_fuchsia_hardware_network as fnetwork, fidl_fuchsia_io as fio, fidl_fuchsia_net as fnet,
29    fidl_fuchsia_net_dhcp as fnet_dhcp, fidl_fuchsia_net_interfaces as fnet_interfaces,
30    fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
31    fidl_fuchsia_net_neighbor as fnet_neighbor, fidl_fuchsia_net_resources as fnet_resources,
32    fidl_fuchsia_net_root as fnet_root, fidl_fuchsia_net_routes as fnet_routes,
33    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::{Context as _, anyhow};
42use futures::future::{FutureExt as _, LocalBoxFuture, TryFutureExt as _};
43use futures::{SinkExt as _, TryStreamExt as _};
44use net_types::SpecifiedAddr;
45use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Subnet};
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    /// Creates a Stream [`socket2::Socket`] backed by the implementation of
797    /// `fuchsia.posix.socket/Provider` in this realm.
798    pub async fn stream_socket_with_options(
799        &self,
800        domain: fposix_socket::Domain,
801        proto: fposix_socket::StreamSocketProtocol,
802        options: fposix_socket::SocketCreationOptions,
803    ) -> Result<socket2::Socket> {
804        let socket_provider = self
805            .connect_to_protocol::<fposix_socket::ProviderMarker>()
806            .context("failed to connect to socket provider")?;
807        let sock = socket_provider
808            .stream_socket_with_options(domain, proto, options)
809            .await
810            .context("failed to call socket")?
811            .map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
812            .context("failed to create socket")?;
813
814        Ok(fdio::create_fd(sock.into()).context("failed to create fd")?.into())
815    }
816    /// Shuts down the realm.
817    ///
818    /// It is often useful to call this method to ensure that the realm
819    /// completes orderly shutdown before allowing other resources to be dropped
820    /// and get cleaned up, such as [`TestEndpoint`]s, which components in the
821    /// realm might be interacting with.
822    pub async fn shutdown(&self) -> Result {
823        let () = self.realm().shutdown().context("call shutdown")?;
824        // Turn off shutdown on drop from now on, we're already shutting down
825        // here.
826        self.set_checked_shutdown_on_drop(false);
827        let events = self
828            .realm()
829            .take_event_stream()
830            .try_collect::<Vec<_>>()
831            .await
832            .context("error on realm event stream")?;
833        // Ensure there are no more events sent on the event stream after `OnShutdown`.
834        assert_matches::assert_matches!(events[..], [fnetemul::ManagedRealmEvent::OnShutdown {}]);
835        Ok(())
836    }
837
838    /// Returns a stream that yields monikers of unclean exits in the realm.
839    pub async fn get_crash_stream(&self) -> Result<impl futures::Stream<Item = Result<String>>> {
840        let (listener, server_end) = fidl::endpoints::create_proxy();
841        self.realm().get_crash_listener(server_end).context("creating CrashListener")?;
842        Ok(futures::stream::try_unfold(listener, |listener| async move {
843            let next = listener.next().await.context("listener fetch next moniker")?;
844            Result::Ok(if next.is_empty() {
845                None
846            } else {
847                Some((futures::stream::iter(next.into_iter().map(Ok)), listener))
848            })
849        })
850        .try_flatten())
851    }
852
853    /// Constructs an ICMP socket.
854    pub async fn icmp_socket<Ip: ping::FuchsiaIpExt>(
855        &self,
856    ) -> Result<fuchsia_async::net::DatagramSocket> {
857        let sock = self
858            .datagram_socket(Ip::DOMAIN_FIDL, fposix_socket::DatagramSocketProtocol::IcmpEcho)
859            .await
860            .context("failed to create ICMP datagram socket")?;
861        fuchsia_async::net::DatagramSocket::new_from_socket(sock)
862            .context("failed to create async ICMP datagram socket")
863    }
864
865    /// Sends a single ICMP echo request to `addr`, and waits for the echo reply.
866    pub async fn ping_once<Ip: ping::FuchsiaIpExt>(&self, addr: Ip::SockAddr, seq: u16) -> Result {
867        let icmp_sock = self.icmp_socket::<Ip>().await?;
868
869        const MESSAGE: &'static str = "hello, world";
870        let (mut sink, mut stream) = ping::new_unicast_sink_and_stream::<
871            Ip,
872            _,
873            { MESSAGE.len() + ping::ICMP_HEADER_LEN },
874        >(&icmp_sock, &addr, MESSAGE.as_bytes());
875
876        let send_fut = sink.send(seq).map_err(anyhow::Error::new);
877        let recv_fut = stream.try_next().map(|r| match r {
878            Ok(Some(got)) if got == seq => Ok(()),
879            Ok(Some(got)) => Err(anyhow!("unexpected echo reply; got: {}, want: {}", got, seq)),
880            Ok(None) => Err(anyhow!("echo reply stream ended unexpectedly")),
881            Err(e) => Err(anyhow::Error::from(e)),
882        });
883
884        let ((), ()) = futures::future::try_join(send_fut, recv_fut)
885            .await
886            .with_context(|| format!("failed to ping from {} to {}", self.name(), addr,))?;
887        Ok(())
888    }
889
890    /// Add a static neighbor entry.
891    ///
892    /// Useful to prevent NUD resolving too slow and causing spurious test failures.
893    pub async fn add_neighbor_entry(
894        &self,
895        interface: u64,
896        addr: fnet::IpAddress,
897        mac: fnet::MacAddress,
898    ) -> Result {
899        let controller = self
900            .connect_to_protocol::<fnet_neighbor::ControllerMarker>()
901            .context("connect to protocol")?;
902        controller
903            .add_entry(interface, &addr, &mac)
904            .await
905            .context("add_entry")?
906            .map_err(zx::Status::from_raw)
907            .context("add_entry failed")
908    }
909
910    /// Get a stream of interface events from a new watcher with default
911    /// interest.
912    pub fn get_interface_event_stream(
913        &self,
914    ) -> Result<
915        impl futures::Stream<
916            Item = std::result::Result<
917                fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
918                fidl::Error,
919            >,
920        >,
921    > {
922        self.get_interface_event_stream_with_interest::<fnet_interfaces_ext::DefaultInterest>()
923    }
924
925    /// Get a stream of interface events from a new watcher with specified
926    /// interest.
927    pub fn get_interface_event_stream_with_interest<I: fnet_interfaces_ext::FieldInterests>(
928        &self,
929    ) -> Result<
930        impl futures::Stream<
931            Item = std::result::Result<fnet_interfaces_ext::EventWithInterest<I>, fidl::Error>,
932        >,
933    > {
934        let interface_state = self
935            .connect_to_protocol::<fnet_interfaces::StateMarker>()
936            .context("connect to protocol")?;
937        fnet_interfaces_ext::event_stream_from_state::<I>(
938            &interface_state,
939            fnet_interfaces_ext::IncludedAddresses::OnlyAssigned,
940        )
941        .context("get interface event stream")
942    }
943
944    /// Gets the table ID for the main route table.
945    pub async fn main_table_id<
946        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
947    >(
948        &self,
949    ) -> u32 {
950        let main_route_table = self
951            .connect_to_protocol::<I::RouteTableMarker>()
952            .expect("failed to connect to main route table");
953        fnet_routes_ext::admin::get_table_id::<I>(&main_route_table)
954            .await
955            .expect("failed to get_table_id")
956            .get()
957    }
958}
959
960/// A virtual Network.
961///
962/// `TestNetwork` is a single virtual broadcast domain backed by Netemul.
963/// Created through [`TestSandbox::create_network`].
964#[must_use]
965pub struct TestNetwork<'a> {
966    network: fnetemul_network::NetworkProxy,
967    name: Cow<'a, str>,
968    sandbox: &'a TestSandbox,
969}
970
971impl<'a> std::fmt::Debug for TestNetwork<'a> {
972    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
973        let Self { name, network: _, sandbox: _ } = self;
974        f.debug_struct("TestNetwork").field("name", name).finish_non_exhaustive()
975    }
976}
977
978impl<'a> TestNetwork<'a> {
979    /// Extracts the proxy to the backing network.
980    ///
981    /// Note that this defeats the lifetime semantics that ensure the sandbox in
982    /// which this network was created lives as long as the network. The caller of
983    /// [`TestNetwork::into_proxy`] is responsible for ensuring that the sandbox
984    /// outlives the network.
985    pub fn into_proxy(self) -> fnetemul_network::NetworkProxy {
986        let Self { network, name: _, sandbox: _ } = self;
987        network
988    }
989
990    /// Gets a FIDL client for the backing network.
991    async fn get_client_end_clone(
992        &self,
993    ) -> Result<fidl::endpoints::ClientEnd<fnetemul_network::NetworkMarker>> {
994        let network_manager =
995            self.sandbox.get_network_manager().context("get_network_manager failed")?;
996        let client = network_manager
997            .get_network(&self.name)
998            .await
999            .context("get_network failed")?
1000            .with_context(|| format!("no network found with name {}", self.name))?;
1001        Ok(client)
1002    }
1003
1004    /// Sets the configuration for this network to `config`.
1005    pub async fn set_config(&self, config: fnetemul_network::NetworkConfig) -> Result<()> {
1006        let status = self.network.set_config(&config).await.context("call set_config")?;
1007        zx::Status::ok(status).context("set config")
1008    }
1009
1010    /// Attaches `ep` to this network.
1011    pub async fn attach_endpoint(&self, ep: &TestEndpoint<'a>) -> Result<()> {
1012        let status =
1013            self.network.attach_endpoint(&ep.name).await.context("attach_endpoint FIDL error")?;
1014        let () = zx::Status::ok(status).context("attach_endpoint failed")?;
1015        Ok(())
1016    }
1017
1018    /// Creates a new endpoint with `name` attached to this network.
1019    ///
1020    /// Characters may be dropped from the front of `name` if it exceeds the maximum length.
1021    pub async fn create_endpoint<S>(&self, name: S) -> Result<TestEndpoint<'a>>
1022    where
1023        S: Into<Cow<'a, str>>,
1024    {
1025        let ep = self
1026            .sandbox
1027            .create_endpoint(name)
1028            .await
1029            .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1030        let () = self.attach_endpoint(&ep).await.with_context(|| {
1031            format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1032        })?;
1033        Ok(ep)
1034    }
1035
1036    /// Creates a new endpoint with `name` and `config` attached to this network.
1037    ///
1038    /// Characters may be dropped from the front of `name` if it exceeds the maximum length.
1039    pub async fn create_endpoint_with(
1040        &self,
1041        name: impl Into<Cow<'a, str>>,
1042        config: fnetemul_network::EndpointConfig,
1043    ) -> Result<TestEndpoint<'a>> {
1044        let ep = self
1045            .sandbox
1046            .create_endpoint_with(name, config)
1047            .await
1048            .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1049        let () = self.attach_endpoint(&ep).await.with_context(|| {
1050            format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1051        })?;
1052        Ok(ep)
1053    }
1054
1055    /// Returns a fake endpoint.
1056    pub fn create_fake_endpoint(&self) -> Result<TestFakeEndpoint<'a>> {
1057        let (endpoint, server) =
1058            fidl::endpoints::create_proxy::<fnetemul_network::FakeEndpointMarker>();
1059        let () = self.network.create_fake_endpoint(server)?;
1060        return Ok(TestFakeEndpoint { endpoint, _sandbox: self.sandbox });
1061    }
1062
1063    /// Starts capturing packet in this network.
1064    ///
1065    /// The packet capture will be stored under a predefined directory:
1066    /// `/custom_artifacts`. More details can be found here:
1067    /// https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#custom-artifacts
1068    pub async fn start_capture(&self, name: &str) -> Result<PacketCapture> {
1069        let manager = self.sandbox.get_network_manager()?;
1070        let client = manager.get_network(&self.name).await?.expect("network must exist");
1071        zx::ok(self.network.start_capture(name).await?)?;
1072        let sync_proxy = fnetemul_network::NetworkSynchronousProxy::new(client.into_channel());
1073        Ok(PacketCapture { sync_proxy })
1074    }
1075
1076    /// Stops packet capture in this network.
1077    pub async fn stop_capture(&self) -> Result<()> {
1078        Ok(self.network.stop_capture().await?)
1079    }
1080}
1081
1082/// The object that has the same life as the packet capture, once the object is
1083/// dropped, the underlying packet capture will be stopped.
1084pub struct PacketCapture {
1085    sync_proxy: fnetemul_network::NetworkSynchronousProxy,
1086}
1087
1088impl Drop for PacketCapture {
1089    fn drop(&mut self) {
1090        self.sync_proxy
1091            .stop_capture(zx::MonotonicInstant::INFINITE)
1092            .expect("failed to stop packet capture")
1093    }
1094}
1095
1096/// A virtual network endpoint backed by Netemul.
1097#[must_use]
1098pub struct TestEndpoint<'a> {
1099    endpoint: fnetemul_network::EndpointProxy,
1100    name: Cow<'a, str>,
1101    _sandbox: &'a TestSandbox,
1102}
1103
1104impl<'a> std::fmt::Debug for TestEndpoint<'a> {
1105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1106        let Self { endpoint: _, name, _sandbox } = self;
1107        f.debug_struct("TestEndpoint").field("name", name).finish_non_exhaustive()
1108    }
1109}
1110
1111impl<'a> std::ops::Deref for TestEndpoint<'a> {
1112    type Target = fnetemul_network::EndpointProxy;
1113
1114    fn deref(&self) -> &Self::Target {
1115        &self.endpoint
1116    }
1117}
1118
1119/// A virtual fake network endpoint backed by Netemul.
1120#[must_use]
1121pub struct TestFakeEndpoint<'a> {
1122    endpoint: fnetemul_network::FakeEndpointProxy,
1123    _sandbox: &'a TestSandbox,
1124}
1125
1126impl<'a> std::ops::Deref for TestFakeEndpoint<'a> {
1127    type Target = fnetemul_network::FakeEndpointProxy;
1128
1129    fn deref(&self) -> &Self::Target {
1130        &self.endpoint
1131    }
1132}
1133
1134impl<'a> TestFakeEndpoint<'a> {
1135    /// Return a stream of frames.
1136    ///
1137    /// Frames will be yielded as they are read from the fake endpoint.
1138    pub fn frame_stream(
1139        &self,
1140    ) -> impl futures::Stream<Item = std::result::Result<(Vec<u8>, u64), fidl::Error>> + '_ {
1141        futures::stream::try_unfold(&self.endpoint, |ep| ep.read().map_ok(move |r| Some((r, ep))))
1142    }
1143}
1144
1145/// Helper function to retrieve device and port information from a port
1146/// instance.
1147async fn to_netdevice_inner(
1148    port: fidl::endpoints::ClientEnd<fnetwork::PortMarker>,
1149) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1150    let port = port.into_proxy();
1151    let (device, server_end) = fidl::endpoints::create_endpoints::<fnetwork::DeviceMarker>();
1152    let () = port.get_device(server_end)?;
1153    let port_id = port
1154        .get_info()
1155        .await
1156        .context("get port info")?
1157        .id
1158        .ok_or_else(|| anyhow::anyhow!("missing port id"))?;
1159    Ok((device, port_id))
1160}
1161
1162impl<'a> TestEndpoint<'a> {
1163    /// Extracts the proxy to the backing endpoint.
1164    ///
1165    /// Note that this defeats the lifetime semantics that ensure the sandbox in
1166    /// which this endpoint was created lives as long as the endpoint. The caller of
1167    /// [`TestEndpoint::into_proxy`] is responsible for ensuring that the sandbox
1168    /// outlives the endpoint.
1169    pub fn into_proxy(self) -> fnetemul_network::EndpointProxy {
1170        let Self { endpoint, name: _, _sandbox: _ } = self;
1171        endpoint
1172    }
1173
1174    /// Gets access to this device's virtual Network device.
1175    ///
1176    /// Note that an error is returned if the Endpoint is not a
1177    /// [`fnetemul_network::DeviceConnection::NetworkDevice`].
1178    pub async fn get_netdevice(
1179        &self,
1180    ) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1181        let (port, server_end) = fidl::endpoints::create_endpoints();
1182        self.get_port(server_end)
1183            .with_context(|| format!("failed to get device connection for {}", self.name))?;
1184        to_netdevice_inner(port).await
1185    }
1186
1187    /// Installs the [`TestEndpoint`] via the provided [`fnet_interfaces_admin::InstallerProxy`].
1188    ///
1189    /// Returns the interface ID, and the associated interface
1190    /// [`Control`] and [`fnet_interfaces_admin::DeviceControlProxy`] on
1191    /// success.
1192    pub async fn install(
1193        &self,
1194        installer: fnet_interfaces_admin::InstallerProxy,
1195        InterfaceConfig {
1196            name,
1197            metric,
1198            ipv4_dad_transmits,
1199            ipv6_dad_transmits,
1200            temporary_addresses,
1201            netstack_managed_routes_designation,
1202        }: InterfaceConfig<'_>,
1203    ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1204        let name = name.map(|n| {
1205            truncate_dropping_front(n.into(), fnet_interfaces::INTERFACE_NAME_LENGTH.into())
1206                .to_string()
1207        });
1208        let (device, port_id) = self.get_netdevice().await?;
1209        let device_control = {
1210            let (control, server_end) =
1211                fidl::endpoints::create_proxy::<fnet_interfaces_admin::DeviceControlMarker>();
1212            let () = installer.install_device(device, server_end).context("install device")?;
1213            control
1214        };
1215        let (control, server_end) = Control::create_endpoints().context("create endpoints")?;
1216        let () = device_control
1217            .create_interface(
1218                &port_id,
1219                server_end,
1220                fnet_interfaces_admin::Options {
1221                    name,
1222                    metric,
1223                    netstack_managed_routes_designation,
1224                    __source_breaking: fidl::marker::SourceBreaking,
1225                },
1226            )
1227            .context("create interface")?;
1228        if let Some(ipv4_dad_transmits) = ipv4_dad_transmits {
1229            let _: Option<u16> = set_ipv4_dad_transmits(&control, ipv4_dad_transmits)
1230                .await
1231                .context("set dad transmits")?;
1232        }
1233        if let Some(ipv6_dad_transmits) = ipv6_dad_transmits {
1234            let _: Option<u16> = set_ipv6_dad_transmits(&control, ipv6_dad_transmits)
1235                .await
1236                .context("set dad transmits")?;
1237        }
1238        if let Some(enabled) = temporary_addresses {
1239            set_temporary_address_generation_enabled(&control, enabled)
1240                .await
1241                .context("set temporary addresses")?;
1242        }
1243
1244        let id = control.get_id().await.context("get id")?;
1245        Ok((id, control, device_control))
1246    }
1247
1248    /// Adds the [`TestEndpoint`] to the provided `realm` with an optional
1249    /// interface name.
1250    ///
1251    /// Returns the interface ID and control protocols on success.
1252    pub async fn add_to_stack(
1253        &self,
1254        realm: &TestRealm<'a>,
1255        config: InterfaceConfig<'a>,
1256    ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1257        let installer = realm
1258            .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1259            .context("connect to protocol")?;
1260
1261        self.install(installer, config).await
1262    }
1263
1264    /// Like `into_interface_realm_with_name` but with default parameters.
1265    pub async fn into_interface_in_realm(self, realm: &TestRealm<'a>) -> Result<TestInterface<'a>> {
1266        self.into_interface_in_realm_with_name(realm, Default::default()).await
1267    }
1268
1269    /// Consumes this `TestEndpoint` and tries to add it to the Netstack in
1270    /// `realm`, returning a [`TestInterface`] on success.
1271    pub async fn into_interface_in_realm_with_name(
1272        self,
1273        realm: &TestRealm<'a>,
1274        config: InterfaceConfig<'a>,
1275    ) -> Result<TestInterface<'a>> {
1276        let installer = realm
1277            .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1278            .context("connect to protocol")?;
1279
1280        let (id, control, device_control) =
1281            self.install(installer, config).await.context("failed to install")?;
1282
1283        Ok(TestInterface {
1284            endpoint: self,
1285            id,
1286            realm: realm.clone(),
1287            control,
1288            device_control: Some(device_control),
1289            dhcp_client_task: futures::lock::Mutex::default(),
1290        })
1291    }
1292}
1293
1294/// The DHCP client version.
1295#[derive(Copy, Clone, PartialEq, Debug)]
1296pub enum DhcpClientVersion {
1297    /// The in-Netstack2 DHCP client.
1298    InStack,
1299    /// The out-of-stack DHCP client.
1300    OutOfStack,
1301}
1302
1303/// Abstraction for how DHCP client functionality is provided.
1304pub trait DhcpClient {
1305    /// The DHCP client version to be used.
1306    const DHCP_CLIENT_VERSION: DhcpClientVersion;
1307}
1308
1309/// The in-Netstack2 DHCP client.
1310pub enum InStack {}
1311
1312impl DhcpClient for InStack {
1313    const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::InStack;
1314}
1315
1316/// The out-of-stack DHCP client.
1317pub enum OutOfStack {}
1318
1319impl DhcpClient for OutOfStack {
1320    const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::OutOfStack;
1321}
1322
1323/// A [`TestEndpoint`] that is installed in a realm's Netstack.
1324///
1325/// Note that a [`TestInterface`] adds to the reference count of the underlying
1326/// realm of its [`TestRealm`]. That is, a [`TestInterface`] that outlives the
1327/// [`TestRealm`] it created is sufficient to keep the underlying realm alive.
1328#[must_use]
1329pub struct TestInterface<'a> {
1330    endpoint: TestEndpoint<'a>,
1331    realm: TestRealm<'a>,
1332    id: u64,
1333    control: Control,
1334    device_control: Option<fnet_interfaces_admin::DeviceControlProxy>,
1335    dhcp_client_task: futures::lock::Mutex<Option<fnet_dhcp_ext::testutil::DhcpClientTask>>,
1336}
1337
1338impl<'a> std::fmt::Debug for TestInterface<'a> {
1339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1340        let Self { endpoint, id, realm: _, control: _, device_control: _, dhcp_client_task: _ } =
1341            self;
1342        f.debug_struct("TestInterface")
1343            .field("endpoint", endpoint)
1344            .field("id", id)
1345            .finish_non_exhaustive()
1346    }
1347}
1348
1349impl<'a> std::ops::Deref for TestInterface<'a> {
1350    type Target = fnetemul_network::EndpointProxy;
1351
1352    fn deref(&self) -> &Self::Target {
1353        &self.endpoint
1354    }
1355}
1356
1357impl<'a> TestInterface<'a> {
1358    /// Gets the interface identifier.
1359    pub fn id(&self) -> u64 {
1360        self.id
1361    }
1362
1363    /// Returns the endpoint associated with the interface.
1364    pub fn endpoint(&self) -> &TestEndpoint<'a> {
1365        &self.endpoint
1366    }
1367
1368    /// Returns the interface's control handle.
1369    pub fn control(&self) -> &Control {
1370        &self.control
1371    }
1372
1373    /// Returns the authorization token for this interface.
1374    pub async fn get_authorization(
1375        &self,
1376    ) -> Result<fnet_resources::GrantForInterfaceAuthorization> {
1377        Ok(self.control.get_authorization_for_interface().await?)
1378    }
1379
1380    /// Connects to fuchsia.net.stack in this interface's realm.
1381    pub fn connect_stack(&self) -> Result<fnet_stack::StackProxy> {
1382        self.realm.connect_to_protocol::<fnet_stack::StackMarker>()
1383    }
1384
1385    /// Installs a route in the realm's netstack's global route table with `self` as the outgoing
1386    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1387    ///
1388    /// Returns whether the route was newly added to the stack.
1389    async fn add_route<
1390        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1391    >(
1392        &self,
1393        destination: Subnet<I::Addr>,
1394        next_hop: Option<SpecifiedAddr<I::Addr>>,
1395        metric: fnet_routes::SpecifiedMetric,
1396    ) -> Result<bool> {
1397        let route_set = self.create_authenticated_global_route_set::<I>().await?;
1398        fnet_routes_ext::admin::add_route::<I>(
1399            &route_set,
1400            &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1401                .try_into()
1402                .expect("convert to FIDL should succeed"),
1403        )
1404        .await
1405        .context("FIDL error adding route")?
1406        .map_err(|e| anyhow::anyhow!("error adding route: {e:?}"))
1407    }
1408
1409    /// Installs a route in the realm's netstack's global route table with `self` as the outgoing
1410    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1411    ///
1412    /// Returns whether the route was newly added to the stack. Returns `Err` if `destination` and
1413    /// `next_hop` don't share the same IP version.
1414    pub async fn add_route_either(
1415        &self,
1416        destination: fnet::Subnet,
1417        next_hop: Option<fnet::IpAddress>,
1418        metric: fnet_routes::SpecifiedMetric,
1419    ) -> Result<bool> {
1420        let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1421        match destination_addr {
1422            fnet::IpAddress::Ipv4(destination_addr) => {
1423                let next_hop = match next_hop {
1424                    Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1425                        SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1426                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1427                    ),
1428                    Some(fnet::IpAddress::Ipv6(_)) => {
1429                        return Err(anyhow::anyhow!(
1430                            "next hop must be same IP version as destination"
1431                        ));
1432                    }
1433                    None => None,
1434                };
1435                self.add_route::<Ipv4>(
1436                    Subnet::new(destination_addr.into_ext(), prefix_len)
1437                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1438                    next_hop,
1439                    metric,
1440                )
1441                .await
1442            }
1443            fnet::IpAddress::Ipv6(destination_addr) => {
1444                let next_hop = match next_hop {
1445                    Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1446                        SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1447                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1448                    ),
1449                    Some(fnet::IpAddress::Ipv4(_)) => {
1450                        return Err(anyhow::anyhow!(
1451                            "next hop must be same IP version as destination"
1452                        ));
1453                    }
1454                    None => None,
1455                };
1456                self.add_route::<Ipv6>(
1457                    Subnet::new(destination_addr.into_ext(), prefix_len)
1458                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1459                    next_hop,
1460                    metric,
1461                )
1462                .await
1463            }
1464        }
1465    }
1466
1467    /// Removes a route from the realm's netstack's global route table with `self` as the outgoing
1468    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1469    ///
1470    /// Returns whether the route actually existed in the stack before it was removed.
1471    async fn remove_route<
1472        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1473    >(
1474        &self,
1475        destination: Subnet<I::Addr>,
1476        next_hop: Option<SpecifiedAddr<I::Addr>>,
1477        metric: fnet_routes::SpecifiedMetric,
1478    ) -> Result<bool> {
1479        let route_set = self.create_authenticated_global_route_set::<I>().await?;
1480        fnet_routes_ext::admin::remove_route::<I>(
1481            &route_set,
1482            &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1483                .try_into()
1484                .expect("convert to FIDL should succeed"),
1485        )
1486        .await
1487        .context("FIDL error removing route")?
1488        .map_err(|e| anyhow::anyhow!("error removing route: {e:?}"))
1489    }
1490
1491    /// Removes a route from the realm's netstack's global route table with `self` as the outgoing
1492    /// interface with the given `destination` and `metric`, optionally via the `next_hop`.
1493    ///
1494    /// Returns whether the route actually existed in the stack before it was removed. Returns `Err`
1495    /// if `destination` and `next_hop` don't share the same IP version.
1496    async fn remove_route_either(
1497        &self,
1498        destination: fnet::Subnet,
1499        next_hop: Option<fnet::IpAddress>,
1500        metric: fnet_routes::SpecifiedMetric,
1501    ) -> Result<bool> {
1502        let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1503        match destination_addr {
1504            fnet::IpAddress::Ipv4(destination_addr) => {
1505                let next_hop = match next_hop {
1506                    Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1507                        SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1508                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1509                    ),
1510                    Some(fnet::IpAddress::Ipv6(_)) => {
1511                        return Err(anyhow::anyhow!(
1512                            "next hop must be same IP version as destination"
1513                        ));
1514                    }
1515                    None => None,
1516                };
1517                self.remove_route::<Ipv4>(
1518                    Subnet::new(destination_addr.into_ext(), prefix_len)
1519                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1520                    next_hop,
1521                    metric,
1522                )
1523                .await
1524            }
1525            fnet::IpAddress::Ipv6(destination_addr) => {
1526                let next_hop = match next_hop {
1527                    Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1528                        SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1529                            .ok_or(anyhow::anyhow!("next hop must not be unspecified address"))?,
1530                    ),
1531                    Some(fnet::IpAddress::Ipv4(_)) => {
1532                        return Err(anyhow::anyhow!(
1533                            "next hop must be same IP version as destination"
1534                        ));
1535                    }
1536                    None => None,
1537                };
1538                self.remove_route::<Ipv6>(
1539                    Subnet::new(destination_addr.into_ext(), prefix_len)
1540                        .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1541                    next_hop,
1542                    metric,
1543                )
1544                .await
1545            }
1546        }
1547    }
1548
1549    /// Add a direct route from the interface to the given subnet.
1550    pub async fn add_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1551        let subnet = fnet_ext::apply_subnet_mask(subnet);
1552        let newly_added = self
1553            .add_route_either(
1554                subnet,
1555                None,
1556                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1557            )
1558            .await?;
1559
1560        if !newly_added {
1561            Err(anyhow::anyhow!(
1562                "route to {subnet:?} on {} should not have already existed",
1563                self.id()
1564            ))
1565        } else {
1566            Ok(())
1567        }
1568    }
1569
1570    /// Delete a direct route from the interface to the given subnet.
1571    pub async fn del_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1572        let subnet = fnet_ext::apply_subnet_mask(subnet);
1573        let newly_removed = self
1574            .remove_route_either(
1575                subnet,
1576                None,
1577                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1578            )
1579            .await?;
1580
1581        if !newly_removed {
1582            Err(anyhow::anyhow!(
1583                "route to {subnet:?} on {} should have previously existed before being removed",
1584                self.id()
1585            ))
1586        } else {
1587            Ok(())
1588        }
1589    }
1590
1591    /// Add a default route through the given `next_hop` with the given `metric`.
1592    pub async fn add_default_route_with_metric(
1593        &self,
1594        next_hop: fnet::IpAddress,
1595        metric: fnet_routes::SpecifiedMetric,
1596    ) -> Result<()> {
1597        let corresponding_default_subnet = match next_hop {
1598            fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1599            fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1600        };
1601
1602        let newly_added =
1603            self.add_route_either(corresponding_default_subnet, Some(next_hop), metric).await?;
1604
1605        if !newly_added {
1606            Err(anyhow::anyhow!(
1607                "default route through {} via {next_hop:?} already exists",
1608                self.id()
1609            ))
1610        } else {
1611            Ok(())
1612        }
1613    }
1614
1615    /// Add a default route through the given `next_hop` with the given `metric`.
1616    pub async fn add_default_route_with_explicit_metric(
1617        &self,
1618        next_hop: fnet::IpAddress,
1619        metric: u32,
1620    ) -> Result<()> {
1621        self.add_default_route_with_metric(
1622            next_hop,
1623            fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1624        )
1625        .await
1626    }
1627
1628    /// Add a default route through the given `next_hop`.
1629    pub async fn add_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1630        self.add_default_route_with_metric(
1631            next_hop,
1632            fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1633        )
1634        .await
1635    }
1636
1637    /// Remove a default route through the given address.
1638    pub async fn remove_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1639        let corresponding_default_subnet = match next_hop {
1640            fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1641            fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1642        };
1643
1644        let newly_removed = self
1645            .remove_route_either(
1646                corresponding_default_subnet,
1647                Some(next_hop),
1648                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1649            )
1650            .await?;
1651
1652        if !newly_removed {
1653            Err(anyhow::anyhow!(
1654                "default route through {} via {next_hop:?} does not exist",
1655                self.id()
1656            ))
1657        } else {
1658            Ok(())
1659        }
1660    }
1661
1662    /// Add a route to the given `destination` subnet via the given `next_hop`.
1663    pub async fn add_gateway_route(
1664        &self,
1665        destination: fnet::Subnet,
1666        next_hop: fnet::IpAddress,
1667    ) -> Result<()> {
1668        let newly_added = self
1669            .add_route_either(
1670                destination,
1671                Some(next_hop),
1672                fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1673            )
1674            .await?;
1675
1676        if !newly_added {
1677            Err(anyhow::anyhow!(
1678                "should have newly added route to {destination:?} via {next_hop:?} through {}",
1679                self.id()
1680            ))
1681        } else {
1682            Ok(())
1683        }
1684    }
1685
1686    /// Create a root route set authenticated to manage routes through this interface.
1687    pub async fn create_authenticated_global_route_set<
1688        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1689    >(
1690        &self,
1691    ) -> Result<<I::RouteSetMarker as ProtocolMarker>::Proxy> {
1692        #[derive(GenericOverIp)]
1693        #[generic_over_ip(I, Ip)]
1694        struct Out<'a, I: fnet_routes_ext::admin::FidlRouteAdminIpExt>(
1695            LocalBoxFuture<'a, <I::RouteSetMarker as ProtocolMarker>::Proxy>,
1696        );
1697
1698        let Out(proxy_fut) = I::map_ip_out(
1699            self,
1700            |this| {
1701                Out(this
1702                    .get_global_route_set_v4()
1703                    .map(|result| result.expect("get global route set"))
1704                    .boxed_local())
1705            },
1706            |this| {
1707                Out(this
1708                    .get_global_route_set_v6()
1709                    .map(|result| result.expect("get global route set"))
1710                    .boxed_local())
1711            },
1712        );
1713
1714        let route_set = proxy_fut.await;
1715        let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } =
1716            self.get_authorization().await.expect("get interface grant");
1717        fnet_routes_ext::admin::authenticate_for_interface::<I>(
1718            &route_set,
1719            fnet_resources::ProofOfInterfaceAuthorization { interface_id, token },
1720        )
1721        .await
1722        .expect("authentication should not have FIDL error")
1723        .expect("authentication should succeed");
1724        Ok(route_set)
1725    }
1726
1727    async fn get_global_route_set_v4(&self) -> Result<fnet_routes_admin::RouteSetV4Proxy> {
1728        let root_routes = self
1729            .realm
1730            .connect_to_protocol::<fnet_root::RoutesV4Marker>()
1731            .expect("get fuchsia.net.root.RoutesV4");
1732        let (route_set, server_end) =
1733            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1734        root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1735        Ok(route_set)
1736    }
1737
1738    async fn get_global_route_set_v6(&self) -> Result<fnet_routes_admin::RouteSetV6Proxy> {
1739        let root_routes = self
1740            .realm
1741            .connect_to_protocol::<fnet_root::RoutesV6Marker>()
1742            .expect("get fuchsia.net.root.RoutesV6");
1743        let (route_set, server_end) =
1744            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV6Marker>();
1745        root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1746        Ok(route_set)
1747    }
1748
1749    /// Gets the interface's properties with assigned addresses.
1750    async fn get_properties(
1751        &self,
1752        included_addresses: fnet_interfaces_ext::IncludedAddresses,
1753    ) -> Result<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>> {
1754        let interface_state = self.realm.connect_to_protocol::<fnet_interfaces::StateMarker>()?;
1755        let properties = fnet_interfaces_ext::existing(
1756            fnet_interfaces_ext::event_stream_from_state(&interface_state, included_addresses)?,
1757            fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(self.id),
1758        )
1759        .await
1760        .context("failed to get existing interfaces")?;
1761        match properties {
1762            fnet_interfaces_ext::InterfaceState::Unknown(id) => Err(anyhow::anyhow!(
1763                "could not find interface {} for endpoint {}",
1764                id,
1765                self.endpoint.name
1766            )),
1767            fnet_interfaces_ext::InterfaceState::Known(
1768                fnet_interfaces_ext::PropertiesAndState { properties, state: () },
1769            ) => Ok(properties),
1770        }
1771    }
1772
1773    /// Gets the interface's addresses.
1774    pub async fn get_addrs(
1775        &self,
1776        included_addresses: fnet_interfaces_ext::IncludedAddresses,
1777    ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
1778        let fnet_interfaces_ext::Properties { addresses, .. } =
1779            self.get_properties(included_addresses).await?;
1780        Ok(addresses)
1781    }
1782
1783    /// Gets the interface's device name.
1784    pub async fn get_interface_name(&self) -> Result<String> {
1785        let fnet_interfaces_ext::Properties { name, .. } =
1786            self.get_properties(fnet_interfaces_ext::IncludedAddresses::OnlyAssigned).await?;
1787        Ok(name)
1788    }
1789
1790    /// Gets the interface's port class.
1791    pub async fn get_port_class(&self) -> Result<fnet_interfaces_ext::PortClass> {
1792        let fnet_interfaces_ext::Properties { port_class, .. } =
1793            self.get_properties(fnet_interfaces_ext::IncludedAddresses::OnlyAssigned).await?;
1794        Ok(port_class)
1795    }
1796
1797    /// Gets the interface's MAC address.
1798    pub async fn mac(&self) -> fnet::MacAddress {
1799        let (port, server_end) =
1800            fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>();
1801        self.get_port(server_end).expect("get_port");
1802        let (mac_addressing, server_end) =
1803            fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::MacAddressingMarker>();
1804        port.get_mac(server_end).expect("get_mac");
1805        mac_addressing.get_unicast_address().await.expect("get_unicast_address")
1806    }
1807
1808    async fn set_dhcp_client_enabled(&self, enable: bool) -> Result<()> {
1809        self.connect_stack()
1810            .context("connect stack")?
1811            .set_dhcp_client_enabled(self.id, enable)
1812            .await
1813            .context("failed to call SetDhcpClientEnabled")?
1814            .map_err(|e| anyhow!("{:?}", e))
1815    }
1816
1817    /// Starts DHCP on this interface.
1818    pub async fn start_dhcp<D: DhcpClient>(&self) -> Result<()> {
1819        match D::DHCP_CLIENT_VERSION {
1820            DhcpClientVersion::InStack => self.start_dhcp_in_stack().await,
1821            DhcpClientVersion::OutOfStack => self.start_dhcp_client_out_of_stack().await,
1822        }
1823    }
1824
1825    async fn start_dhcp_in_stack(&self) -> Result<()> {
1826        self.set_dhcp_client_enabled(true).await.context("failed to start dhcp client")
1827    }
1828
1829    async fn start_dhcp_client_out_of_stack(&self) -> Result<()> {
1830        let Self { endpoint: _, realm, id, control, device_control: _, dhcp_client_task } = self;
1831        let id = NonZeroU64::new(*id).expect("interface ID should be nonzero");
1832        let mut dhcp_client_task = dhcp_client_task.lock().await;
1833        let dhcp_client_task = dhcp_client_task.deref_mut();
1834
1835        let provider = realm
1836            .connect_to_protocol::<fnet_dhcp::ClientProviderMarker>()
1837            .expect("get fuchsia.net.dhcp.ClientProvider");
1838
1839        provider.check_presence().await.expect("check presence should succeed");
1840
1841        let client = provider.new_client_ext(id, fnet_dhcp_ext::default_new_client_params());
1842        let control = control.clone();
1843        let route_set_provider = realm
1844            .connect_to_protocol::<fnet_routes_admin::RouteTableV4Marker>()
1845            .expect("get fuchsia.net.routes.RouteTableV4");
1846        let (route_set, server_end) =
1847            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1848        route_set_provider.new_route_set(server_end).expect("calling new_route_set should succeed");
1849        let task = fnet_dhcp_ext::testutil::DhcpClientTask::new(client, id, route_set, control);
1850        *dhcp_client_task = Some(task);
1851        Ok(())
1852    }
1853
1854    /// Stops DHCP on this interface.
1855    pub async fn stop_dhcp<D: DhcpClient>(&self) -> Result<()> {
1856        match D::DHCP_CLIENT_VERSION {
1857            DhcpClientVersion::InStack => self.stop_dhcp_in_stack().await,
1858            DhcpClientVersion::OutOfStack => {
1859                self.stop_dhcp_out_of_stack().await;
1860                Ok(())
1861            }
1862        }
1863    }
1864
1865    async fn stop_dhcp_in_stack(&self) -> Result<()> {
1866        self.set_dhcp_client_enabled(false).await.context("failed to stop dhcp client")
1867    }
1868
1869    async fn stop_dhcp_out_of_stack(&self) {
1870        let Self { endpoint: _, realm: _, id: _, control: _, device_control: _, dhcp_client_task } =
1871            self;
1872        let mut dhcp_client_task = dhcp_client_task.lock().await;
1873        if let Some(task) = dhcp_client_task.deref_mut().take() {
1874            task.shutdown().await.expect("client shutdown should succeed");
1875        }
1876    }
1877
1878    /// Adds an address, waiting until the address assignment state is
1879    /// `ASSIGNED`.
1880    pub async fn add_address(&self, subnet: fnet::Subnet) -> Result<()> {
1881        let (address_state_provider, server) =
1882            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1883        let () = address_state_provider.detach().context("detach address lifetime")?;
1884        let () = self
1885            .control
1886            .add_address(&subnet, &fnet_interfaces_admin::AddressParameters::default(), server)
1887            .context("FIDL error")?;
1888
1889        let mut state_stream =
1890            fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1891        fnet_interfaces_ext::admin::wait_assignment_state(
1892            &mut state_stream,
1893            fnet_interfaces::AddressAssignmentState::Assigned,
1894        )
1895        .await?;
1896        Ok(())
1897    }
1898
1899    /// Adds an address and a subnet route, waiting until the address assignment
1900    /// state is `ASSIGNED`.
1901    pub async fn add_address_and_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1902        let (address_state_provider, server) =
1903            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1904        address_state_provider.detach().context("detach address lifetime")?;
1905        self.control
1906            .add_address(
1907                &subnet,
1908                &fnet_interfaces_admin::AddressParameters {
1909                    add_subnet_route: Some(true),
1910                    ..Default::default()
1911                },
1912                server,
1913            )
1914            .context("FIDL error")?;
1915
1916        let state_stream =
1917            fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1918        let mut state_stream = pin!(state_stream);
1919
1920        fnet_interfaces_ext::admin::wait_assignment_state(
1921            &mut state_stream,
1922            fnet_interfaces::AddressAssignmentState::Assigned,
1923        )
1924        .await
1925        .context("assignment state")?;
1926        Ok(())
1927    }
1928
1929    /// Removes an address and its corresponding subnet route.
1930    pub async fn del_address_and_subnet_route(
1931        &self,
1932        addr_with_prefix: fnet::Subnet,
1933    ) -> Result<bool> {
1934        let did_remove =
1935            self.control.remove_address(&addr_with_prefix).await.context("FIDL error").and_then(
1936                |res| {
1937                    res.map_err(|e: fnet_interfaces_admin::ControlRemoveAddressError| {
1938                        anyhow::anyhow!("{:?}", e)
1939                    })
1940                },
1941            )?;
1942
1943        if did_remove {
1944            let destination = fnet_ext::apply_subnet_mask(addr_with_prefix);
1945            let newly_removed_route = self
1946                .remove_route_either(
1947                    destination,
1948                    None,
1949                    fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1950                )
1951                .await?;
1952
1953            // We don't assert on the route having been newly-removed because it could also
1954            // be removed due to the AddressStateProvider going away.
1955            let _: bool = newly_removed_route;
1956        }
1957        Ok(did_remove)
1958    }
1959
1960    /// Removes all IPv6 LinkLocal addresses on the interface.
1961    ///
1962    /// Useful to purge the interface of autogenerated SLAAC addresses.
1963    pub async fn remove_ipv6_linklocal_addresses(
1964        &self,
1965    ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
1966        let mut result = Vec::new();
1967        for address in self.get_addrs(fnet_interfaces_ext::IncludedAddresses::All).await? {
1968            let fnet_interfaces_ext::Address { addr: fnet::Subnet { addr, prefix_len }, .. } =
1969                &address;
1970            match addr {
1971                fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr: _ }) => {
1972                    continue;
1973                }
1974                fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
1975                    let v6_addr = net_types::ip::Ipv6Addr::from_bytes(*addr);
1976                    if !v6_addr.is_unicast_link_local() {
1977                        continue;
1978                    }
1979                }
1980            }
1981            let _newly_removed: bool = self
1982                .del_address_and_subnet_route(fnet::Subnet { addr: *addr, prefix_len: *prefix_len })
1983                .await?;
1984            result.push(address);
1985        }
1986        Ok(result)
1987    }
1988
1989    /// Set configuration on this interface.
1990    ///
1991    /// Returns an error if the operation is unsupported or a no-op.
1992    ///
1993    /// Note that this function should not be made public and should only be
1994    /// used to implement helpers for setting specific pieces of configuration,
1995    /// as it cannot be guaranteed that this function is kept up-to-date with
1996    /// the underlying FIDL types and thus may not always be able to uphold the
1997    /// error return contract.
1998    async fn set_configuration(&self, config: fnet_interfaces_admin::Configuration) -> Result<()> {
1999        let fnet_interfaces_admin::Configuration {
2000            ipv4: previous_ipv4, ipv6: previous_ipv6, ..
2001        } = self
2002            .control()
2003            .set_configuration(&config.clone())
2004            .await
2005            .context("FIDL error")?
2006            .map_err(|e| anyhow!("set configuration error: {:?}", e))?;
2007
2008        fn verify_config_changed<T: Eq>(previous: Option<T>, current: Option<T>) -> Result<()> {
2009            if let Some(current) = current {
2010                let previous = previous.ok_or_else(|| anyhow!("configuration not supported"))?;
2011                if previous == current {
2012                    return Err(anyhow!("configuration change is a no-op"));
2013                }
2014            }
2015            Ok(())
2016        }
2017
2018        let fnet_interfaces_admin::Configuration { ipv4, ipv6, .. } = config;
2019        if let Some(fnet_interfaces_admin::Ipv4Configuration {
2020            unicast_forwarding,
2021            multicast_forwarding,
2022            ..
2023        }) = ipv4
2024        {
2025            let fnet_interfaces_admin::Ipv4Configuration {
2026                unicast_forwarding: previous_unicast_forwarding,
2027                multicast_forwarding: previous_multicast_forwarding,
2028                ..
2029            } = previous_ipv4.ok_or_else(|| anyhow!("IPv4 configuration not supported"))?;
2030            verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2031                .context("IPv4 unicast forwarding")?;
2032            verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2033                .context("IPv4 multicast forwarding")?;
2034        }
2035        if let Some(fnet_interfaces_admin::Ipv6Configuration {
2036            unicast_forwarding,
2037            multicast_forwarding,
2038            ..
2039        }) = ipv6
2040        {
2041            let fnet_interfaces_admin::Ipv6Configuration {
2042                unicast_forwarding: previous_unicast_forwarding,
2043                multicast_forwarding: previous_multicast_forwarding,
2044                ..
2045            } = previous_ipv6.ok_or_else(|| anyhow!("IPv6 configuration not supported"))?;
2046            verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2047                .context("IPv6 unicast forwarding")?;
2048            verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2049                .context("IPv6 multicast forwarding")?;
2050        }
2051        Ok(())
2052    }
2053
2054    /// Enable/disable IPv6 forwarding on this interface.
2055    pub async fn set_ipv6_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2056        self.set_configuration(fnet_interfaces_admin::Configuration {
2057            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2058                unicast_forwarding: Some(enabled),
2059                ..Default::default()
2060            }),
2061            ..Default::default()
2062        })
2063        .await
2064    }
2065
2066    /// Enable/disable IPv4 forwarding on this interface.
2067    pub async fn set_ipv4_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2068        self.set_configuration(fnet_interfaces_admin::Configuration {
2069            ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2070                unicast_forwarding: Some(enabled),
2071                ..Default::default()
2072            }),
2073            ..Default::default()
2074        })
2075        .await
2076    }
2077
2078    /// Consumes this [`TestInterface`] and removes the associated interface
2079    /// in the Netstack, returning the device lifetime-carrying channels.
2080    pub async fn remove(
2081        self,
2082    ) -> Result<(fnetemul_network::EndpointProxy, Option<fnet_interfaces_admin::DeviceControlProxy>)>
2083    {
2084        let Self {
2085            endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2086            id: _,
2087            realm: _,
2088            control,
2089            device_control,
2090            dhcp_client_task: _,
2091        } = self;
2092        // For Network Devices, the `control` handle  is tied to the lifetime of
2093        // the interface; dropping it triggers interface removal in the
2094        // Netstack. For Ethernet devices this is a No-Op.
2095        std::mem::drop(control);
2096        Ok((endpoint, device_control))
2097    }
2098
2099    /// Consumes this [`TestInterface`] and removes the underlying device. The
2100    /// Netstack will implicitly remove the interface and clients can expect to
2101    /// observe a `PEER_CLOSED` event on the returned control channel.
2102    pub fn remove_device(self) -> (Control, Option<fnet_interfaces_admin::DeviceControlProxy>) {
2103        let Self {
2104            endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2105            id: _,
2106            realm: _,
2107            control,
2108            device_control,
2109            dhcp_client_task: _,
2110        } = self;
2111        std::mem::drop(endpoint);
2112        (control, device_control)
2113    }
2114
2115    /// Waits for this interface to signal that it's been removed.
2116    pub async fn wait_removal(self) -> Result<fnet_interfaces_admin::InterfaceRemovedReason> {
2117        let Self {
2118            // Keep this alive, we don't want to trigger removal.
2119            endpoint: _endpoint,
2120            id: _,
2121            realm: _,
2122            control,
2123            dhcp_client_task: _,
2124            // Keep this alive, we don't want to trigger removal.
2125            device_control: _device_control,
2126        } = self;
2127        match control.wait_termination().await {
2128            fnet_interfaces_ext::admin::TerminalError::Fidl(e) => {
2129                Err(e).context("waiting interface control termination")
2130            }
2131            fnet_interfaces_ext::admin::TerminalError::Terminal(reason) => Ok(reason),
2132        }
2133    }
2134
2135    /// Sets the number of IPv6 DAD transmits on this interface.
2136    ///
2137    /// Returns the previous configuration value, if reported by the API.
2138    pub async fn set_ipv4_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2139        set_ipv4_dad_transmits(self.control(), dad_transmits).await
2140    }
2141
2142    /// Sets the number of IPv6 DAD transmits on this interface.
2143    ///
2144    /// Returns the previous configuration value, if reported by the API.
2145    pub async fn set_ipv6_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2146        set_ipv6_dad_transmits(self.control(), dad_transmits).await
2147    }
2148
2149    /// Sets whether temporary SLAAC address generation is enabled
2150    /// or disabled on this interface.
2151    pub async fn set_temporary_address_generation_enabled(&self, enabled: bool) -> Result<()> {
2152        set_temporary_address_generation_enabled(self.control(), enabled).await
2153    }
2154}
2155
2156async fn set_ipv4_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2157    control
2158        .set_configuration(&fnet_interfaces_admin::Configuration {
2159            ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2160                arp: Some(fnet_interfaces_admin::ArpConfiguration {
2161                    dad: Some(fnet_interfaces_admin::DadConfiguration {
2162                        transmits: Some(dad_transmits),
2163                        ..Default::default()
2164                    }),
2165                    ..Default::default()
2166                }),
2167                ..Default::default()
2168            }),
2169            ..Default::default()
2170        })
2171        .await?
2172        .map(|config| config.ipv4?.arp?.dad?.transmits)
2173        .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2174}
2175
2176async fn set_ipv6_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2177    control
2178        .set_configuration(&fnet_interfaces_admin::Configuration {
2179            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2180                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2181                    dad: Some(fnet_interfaces_admin::DadConfiguration {
2182                        transmits: Some(dad_transmits),
2183                        ..Default::default()
2184                    }),
2185                    ..Default::default()
2186                }),
2187                ..Default::default()
2188            }),
2189            ..Default::default()
2190        })
2191        .await?
2192        .map(|config| config.ipv6?.ndp?.dad?.transmits)
2193        .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2194}
2195
2196async fn set_temporary_address_generation_enabled(control: &Control, enabled: bool) -> Result<()> {
2197    let _config: fnet_interfaces_admin::Configuration = control
2198        .set_configuration(&fnet_interfaces_admin::Configuration {
2199            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2200                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2201                    slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
2202                        temporary_address: Some(enabled),
2203                        ..Default::default()
2204                    }),
2205                    ..Default::default()
2206                }),
2207                ..Default::default()
2208            }),
2209            ..Default::default()
2210        })
2211        .await
2212        .context("FIDL error")?
2213        .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))?;
2214    Ok(())
2215}
2216
2217/// Get the [`socket2::Domain`] for `addr`.
2218fn get_socket2_domain(addr: &std::net::SocketAddr) -> fposix_socket::Domain {
2219    let domain = match addr {
2220        std::net::SocketAddr::V4(_) => fposix_socket::Domain::Ipv4,
2221        std::net::SocketAddr::V6(_) => fposix_socket::Domain::Ipv6,
2222    };
2223
2224    domain
2225}
2226
2227/// Trait describing UDP sockets that can be bound in a testing realm.
2228pub trait RealmUdpSocket: Sized {
2229    /// Creates a UDP socket in `realm` bound to `addr`.
2230    fn bind_in_realm<'a>(
2231        realm: &'a TestRealm<'a>,
2232        addr: std::net::SocketAddr,
2233    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2234}
2235
2236impl RealmUdpSocket for std::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        async move {
2242            let sock = realm
2243                .datagram_socket(
2244                    get_socket2_domain(&addr),
2245                    fposix_socket::DatagramSocketProtocol::Udp,
2246                )
2247                .await
2248                .context("failed to create socket")?;
2249
2250            let () = sock.bind(&addr.into()).context("bind failed")?;
2251
2252            Result::Ok(sock.into())
2253        }
2254        .boxed_local()
2255    }
2256}
2257
2258impl RealmUdpSocket for fuchsia_async::net::UdpSocket {
2259    fn bind_in_realm<'a>(
2260        realm: &'a TestRealm<'a>,
2261        addr: std::net::SocketAddr,
2262    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2263        std::net::UdpSocket::bind_in_realm(realm, addr)
2264            .and_then(|udp| {
2265                futures::future::ready(
2266                    fuchsia_async::net::UdpSocket::from_socket(udp)
2267                        .context("failed to create fuchsia_async socket"),
2268                )
2269            })
2270            .boxed_local()
2271    }
2272}
2273
2274/// Trait describing TCP listeners bound in a testing realm.
2275pub trait RealmTcpListener: Sized {
2276    /// Creates a TCP listener in `realm` bound to `addr`.
2277    fn listen_in_realm<'a>(
2278        realm: &'a TestRealm<'a>,
2279        addr: std::net::SocketAddr,
2280    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2281        Self::listen_in_realm_with(realm, addr, |_: &socket2::Socket| Ok(()))
2282    }
2283
2284    /// Creates a TCP listener by creating a Socket2 socket in `realm`. Closure `setup` is called
2285    /// with the reference of the socket before the socket is bound to `addr`.
2286    fn listen_in_realm_with<'a>(
2287        realm: &'a TestRealm<'a>,
2288        addr: std::net::SocketAddr,
2289        setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2290    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2291}
2292
2293impl RealmTcpListener for std::net::TcpListener {
2294    fn listen_in_realm_with<'a>(
2295        realm: &'a TestRealm<'a>,
2296        addr: std::net::SocketAddr,
2297        setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2298    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2299        async move {
2300            let sock = realm
2301                .stream_socket(get_socket2_domain(&addr), fposix_socket::StreamSocketProtocol::Tcp)
2302                .await
2303                .context("failed to create server socket")?;
2304            let () = setup(&sock)?;
2305            let () = sock.bind(&addr.into()).context("failed to bind server socket")?;
2306            // Use 128 for the listen() backlog, same as the original implementation of TcpListener
2307            // in Rust std (see https://doc.rust-lang.org/src/std/sys_common/net.rs.html#386).
2308            let () = sock.listen(128).context("failed to listen on server socket")?;
2309
2310            Result::Ok(sock.into())
2311        }
2312        .boxed_local()
2313    }
2314}
2315
2316impl RealmTcpListener for fuchsia_async::net::TcpListener {
2317    fn listen_in_realm_with<'a>(
2318        realm: &'a TestRealm<'a>,
2319        addr: std::net::SocketAddr,
2320        setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2321    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2322        std::net::TcpListener::listen_in_realm_with(realm, addr, setup)
2323            .and_then(|listener| {
2324                futures::future::ready(
2325                    fuchsia_async::net::TcpListener::from_std(listener)
2326                        .context("failed to create fuchsia_async socket"),
2327                )
2328            })
2329            .boxed_local()
2330    }
2331}
2332
2333/// Trait describing TCP streams in a testing realm.
2334pub trait RealmTcpStream: Sized {
2335    /// Creates a TCP stream in `realm` connected to `addr`.
2336    fn connect_in_realm<'a>(
2337        realm: &'a TestRealm<'a>,
2338        addr: std::net::SocketAddr,
2339    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2340
2341    /// Creates a TCP stream in `realm` bound to `local` and connected to `dst`.
2342    fn bind_and_connect_in_realm<'a>(
2343        realm: &'a TestRealm<'a>,
2344        local: std::net::SocketAddr,
2345        dst: std::net::SocketAddr,
2346    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2347
2348    /// Creates a TCP stream in `realm` connected to `addr`.
2349    ///
2350    /// Closure `with_sock` is called with the reference of the socket before
2351    /// the socket is connected to `addr`.
2352    fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2353        realm: &'a TestRealm<'a>,
2354        dst: std::net::SocketAddr,
2355        with_sock: F,
2356    ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2357
2358    // TODO: Implement this trait for std::net::TcpStream.
2359}
2360
2361impl RealmTcpStream for fuchsia_async::net::TcpStream {
2362    fn connect_in_realm<'a>(
2363        realm: &'a TestRealm<'a>,
2364        addr: std::net::SocketAddr,
2365    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2366        Self::connect_in_realm_with_sock(realm, addr, |_: &socket2::Socket| Ok(()))
2367    }
2368
2369    fn bind_and_connect_in_realm<'a>(
2370        realm: &'a TestRealm<'a>,
2371        local: std::net::SocketAddr,
2372        dst: std::net::SocketAddr,
2373    ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2374        Self::connect_in_realm_with_sock(realm, dst, move |sock| {
2375            sock.bind(&local.into()).context("failed to bind")
2376        })
2377    }
2378
2379    fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2380        realm: &'a TestRealm<'a>,
2381        dst: std::net::SocketAddr,
2382        with_sock: F,
2383    ) -> futures::future::LocalBoxFuture<'a, Result<fuchsia_async::net::TcpStream>> {
2384        async move {
2385            let sock = realm
2386                .stream_socket(get_socket2_domain(&dst), fposix_socket::StreamSocketProtocol::Tcp)
2387                .await
2388                .context("failed to create socket")?;
2389
2390            with_sock(&sock)?;
2391
2392            let stream = fuchsia_async::net::TcpStream::connect_from_raw(sock, dst)
2393                .context("failed to create client tcp stream")?
2394                .await
2395                .context("failed to connect to server")?;
2396
2397            Result::Ok(stream)
2398        }
2399        .boxed_local()
2400    }
2401}
2402
2403fn truncate_dropping_front(s: Cow<'_, str>, len: usize) -> Cow<'_, str> {
2404    match s.len().checked_sub(len) {
2405        None => s,
2406        Some(start) => {
2407            // NB: Drop characters from the front because it's likely that a name that
2408            // exceeds the length limit is the full name of a test whose suffix is more
2409            // informative because nesting of test cases appends suffixes.
2410            match s {
2411                Cow::Borrowed(s) => Cow::Borrowed(&s[start..]),
2412                Cow::Owned(mut s) => {
2413                    let _: std::string::Drain<'_> = s.drain(..start);
2414                    Cow::Owned(s)
2415                }
2416            }
2417        }
2418    }
2419}