netemul/
lib.rs

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