Skip to main content

netemul/
lib.rs

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