Skip to main content

netemul/
lib.rs

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