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