netemul/
lib.rs

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