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