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