Skip to main content

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