Skip to main content

netemul/
lib.rs

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