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