netemul/
lib.rs

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