Skip to main content

netstack_testing_common/
realms.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//! Provides utilities for test realms.
6
7use std::borrow::Cow;
8use std::collections::HashMap;
9use std::future::Future;
10
11use cm_rust::NativeIntoFidl as _;
12use fidl::endpoints::{DiscoverableProtocolMarker as _, ServiceMarker};
13use fidl_fuchsia_component as fcomponent;
14use fidl_fuchsia_hardware_network as fhwnet;
15use fidl_fuchsia_net_debug as fnet_debug;
16use fidl_fuchsia_net_dhcp as fnet_dhcp;
17use fidl_fuchsia_net_dhcpv6 as fnet_dhcpv6;
18use fidl_fuchsia_net_filter as fnet_filter;
19use fidl_fuchsia_net_filter_deprecated as fnet_filter_deprecated;
20use fidl_fuchsia_net_interfaces as fnet_interfaces;
21use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
22use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
23use fidl_fuchsia_net_masquerade as fnet_masquerade;
24use fidl_fuchsia_net_multicast_admin as fnet_multicast_admin;
25use fidl_fuchsia_net_name as fnet_name;
26use fidl_fuchsia_net_ndp as fnet_ndp;
27use fidl_fuchsia_net_neighbor as fnet_neighbor;
28use fidl_fuchsia_net_policy_properties as fnp_properties;
29use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
30use fidl_fuchsia_net_power as fnet_power;
31use fidl_fuchsia_net_reachability as fnet_reachability;
32use fidl_fuchsia_net_root as fnet_root;
33use fidl_fuchsia_net_routes as fnet_routes;
34use fidl_fuchsia_net_routes_admin as fnet_routes_admin;
35use fidl_fuchsia_net_settings as fnet_settings;
36use fidl_fuchsia_net_sockets as fnet_sockets;
37use fidl_fuchsia_net_stack as fnet_stack;
38use fidl_fuchsia_net_test_realm as fntr;
39use fidl_fuchsia_net_virtualization as fnet_virtualization;
40use fidl_fuchsia_netemul as fnetemul;
41use fidl_fuchsia_posix_socket as fposix_socket;
42use fidl_fuchsia_posix_socket_packet as fposix_socket_packet;
43use fidl_fuchsia_posix_socket_raw as fposix_socket_raw;
44use fidl_fuchsia_scheduler as fscheduler;
45use fidl_fuchsia_stash as fstash;
46use fidl_fuchsia_update_verify as fupdate_verify;
47
48use anyhow::Context as _;
49
50use crate::Result;
51
52/// The Netstack version. Used to specify which Netstack version to use in a
53/// [`KnownServiceProvider::Netstack`].
54#[derive(Copy, Clone, Eq, PartialEq, Debug)]
55#[allow(missing_docs)]
56pub enum NetstackVersion {
57    Netstack2 { tracing: bool, fast_udp: bool },
58    Netstack3,
59    ProdNetstack2,
60    ProdNetstack3,
61}
62
63impl NetstackVersion {
64    /// Gets the Fuchsia URL for this Netstack component.
65    pub fn get_url(&self) -> &'static str {
66        match self {
67            NetstackVersion::Netstack2 { tracing, fast_udp } => match (tracing, fast_udp) {
68                (false, false) => "#meta/netstack-debug.cm",
69                (false, true) => "#meta/netstack-with-fast-udp-debug.cm",
70                (true, false) => "#meta/netstack-with-tracing.cm",
71                (true, true) => "#meta/netstack-with-fast-udp-tracing.cm",
72            },
73            NetstackVersion::Netstack3 => "#meta/netstack3-debug.cm",
74            NetstackVersion::ProdNetstack2 => "#meta/netstack.cm",
75            NetstackVersion::ProdNetstack3 => "#meta/netstack3.cm",
76        }
77    }
78
79    /// Gets the services exposed by this Netstack component.
80    pub fn get_services(&self) -> &[&'static str] {
81        macro_rules! common_services_and {
82            ($($name:expr),*) => {[
83                fnet_debug::InterfacesMarker::PROTOCOL_NAME,
84                fnet_interfaces_admin::InstallerMarker::PROTOCOL_NAME,
85                fnet_interfaces::StateMarker::PROTOCOL_NAME,
86                fnet_multicast_admin::Ipv4RoutingTableControllerMarker::PROTOCOL_NAME,
87                fnet_multicast_admin::Ipv6RoutingTableControllerMarker::PROTOCOL_NAME,
88                fnet_name::DnsServerWatcherMarker::PROTOCOL_NAME,
89                fnet_neighbor::ControllerMarker::PROTOCOL_NAME,
90                fnet_neighbor::ViewMarker::PROTOCOL_NAME,
91                fnet_root::InterfacesMarker::PROTOCOL_NAME,
92                fnet_root::RoutesV4Marker::PROTOCOL_NAME,
93                fnet_root::RoutesV6Marker::PROTOCOL_NAME,
94                fnet_routes::StateMarker::PROTOCOL_NAME,
95                fnet_routes::StateV4Marker::PROTOCOL_NAME,
96                fnet_routes::StateV6Marker::PROTOCOL_NAME,
97                fnet_routes_admin::RouteTableProviderV4Marker::PROTOCOL_NAME,
98                fnet_routes_admin::RouteTableProviderV6Marker::PROTOCOL_NAME,
99                fnet_routes_admin::RouteTableV4Marker::PROTOCOL_NAME,
100                fnet_routes_admin::RouteTableV6Marker::PROTOCOL_NAME,
101                fnet_routes_admin::RuleTableV4Marker::PROTOCOL_NAME,
102                fnet_routes_admin::RuleTableV6Marker::PROTOCOL_NAME,
103                fnet_stack::StackMarker::PROTOCOL_NAME,
104                fposix_socket_packet::ProviderMarker::PROTOCOL_NAME,
105                fposix_socket_raw::ProviderMarker::PROTOCOL_NAME,
106                fposix_socket::ProviderMarker::PROTOCOL_NAME,
107                fnet_debug::DiagnosticsMarker::PROTOCOL_NAME,
108                fnet_debug::PacketCaptureProviderMarker::PROTOCOL_NAME,
109
110                fupdate_verify::ComponentOtaHealthCheckMarker::PROTOCOL_NAME,
111                $($name),*
112            ]};
113            // Strip trailing comma.
114            ($($name:expr),*,) => {common_services_and!($($name),*)}
115        }
116        match self {
117            NetstackVersion::Netstack2 { tracing: _, fast_udp: _ }
118            | NetstackVersion::ProdNetstack2 => &common_services_and!(
119                fnet_filter_deprecated::FilterMarker::PROTOCOL_NAME,
120                fnet_stack::LogMarker::PROTOCOL_NAME,
121            ),
122            NetstackVersion::Netstack3 | NetstackVersion::ProdNetstack3 => &common_services_and!(
123                fnet_filter::ControlMarker::PROTOCOL_NAME,
124                fnet_filter::StateMarker::PROTOCOL_NAME,
125                fnet_ndp::RouterAdvertisementOptionWatcherProviderMarker::PROTOCOL_NAME,
126                fnet_power::WakeGroupProviderMarker::PROTOCOL_NAME,
127                fnet_root::FilterMarker::PROTOCOL_NAME,
128                fnet_settings::StateMarker::PROTOCOL_NAME,
129                fnet_settings::ControlMarker::PROTOCOL_NAME,
130                fnet_sockets::DiagnosticsMarker::PROTOCOL_NAME,
131                fnet_sockets::ControlMarker::PROTOCOL_NAME,
132            ),
133        }
134    }
135
136    /// Returns true if this is a netstack3 version.
137    pub const fn is_netstack3(&self) -> bool {
138        match self {
139            Self::Netstack3 | Self::ProdNetstack3 => true,
140            Self::Netstack2 { .. } | Self::ProdNetstack2 => false,
141        }
142    }
143}
144
145/// An extension trait for [`Netstack`].
146pub trait NetstackExt {
147    /// Whether to use the out of stack DHCP client for the given Netstack.
148    const USE_OUT_OF_STACK_DHCP_CLIENT: bool;
149}
150
151impl<N: Netstack> NetstackExt for N {
152    const USE_OUT_OF_STACK_DHCP_CLIENT: bool = match Self::VERSION {
153        NetstackVersion::Netstack3 | NetstackVersion::ProdNetstack3 => true,
154        NetstackVersion::Netstack2 { .. } | NetstackVersion::ProdNetstack2 => false,
155    };
156}
157
158/// The NetCfg version.
159#[derive(Copy, Clone, Eq, PartialEq, Debug)]
160pub enum NetCfgVersion {
161    /// The basic NetCfg version.
162    Basic,
163    /// The advanced NetCfg version.
164    Advanced,
165}
166
167/// The network manager to use in a [`KnownServiceProvider::Manager`].
168#[derive(Copy, Clone, Eq, PartialEq, Debug)]
169pub enum ManagementAgent {
170    /// A version of netcfg.
171    NetCfg(NetCfgVersion),
172}
173
174impl ManagementAgent {
175    /// Gets the URL for this network manager component.
176    pub fn get_url(&self) -> &'static str {
177        match self {
178            Self::NetCfg(NetCfgVersion::Basic) => constants::netcfg::basic::COMPONENT_URL,
179            Self::NetCfg(NetCfgVersion::Advanced) => constants::netcfg::advanced::COMPONENT_URL,
180        }
181    }
182
183    /// Default arguments that should be passed to the component when run in a
184    /// test realm.
185    pub fn get_program_args(&self) -> &[&'static str] {
186        match self {
187            Self::NetCfg(NetCfgVersion::Basic) | Self::NetCfg(NetCfgVersion::Advanced) => {
188                &["--min-severity", "DEBUG"]
189            }
190        }
191    }
192
193    /// Gets the services exposed by this management agent.
194    pub fn get_services(&self) -> &[&'static str] {
195        match self {
196            Self::NetCfg(NetCfgVersion::Basic) => &[
197                fnet_dhcpv6::PrefixProviderMarker::PROTOCOL_NAME,
198                fnet_masquerade::FactoryMarker::PROTOCOL_NAME,
199                fnet_name::DnsServerWatcherMarker::PROTOCOL_NAME,
200                fnp_properties::NetworksMarker::PROTOCOL_NAME,
201                fnp_socketproxy::NetworkRegistryMarker::PROTOCOL_NAME,
202                fnp_properties::NetworkTokenResolverMarker::PROTOCOL_NAME,
203            ],
204            Self::NetCfg(NetCfgVersion::Advanced) => &[
205                fnet_dhcpv6::PrefixProviderMarker::PROTOCOL_NAME,
206                fnet_masquerade::FactoryMarker::PROTOCOL_NAME,
207                fnet_name::DnsServerWatcherMarker::PROTOCOL_NAME,
208                fnet_virtualization::ControlMarker::PROTOCOL_NAME,
209                fnp_properties::NetworksMarker::PROTOCOL_NAME,
210                fnp_socketproxy::NetworkRegistryMarker::PROTOCOL_NAME,
211                fnp_properties::NetworkTokenResolverMarker::PROTOCOL_NAME,
212            ],
213        }
214    }
215}
216
217/// Available configurations for a Manager.
218#[derive(Clone, Eq, PartialEq, Debug)]
219#[allow(missing_docs)]
220pub enum ManagerConfig {
221    Empty,
222    Dhcpv6,
223    Forwarding,
224    AllDelegated,
225    IfacePrefix,
226    DuplicateNames,
227    EnableSocketProxy,
228    EnableSocketProxyAllDelegated,
229    PacketFilterEthernet,
230    PacketFilterWlan,
231    WithBlackhole,
232    AllInterfaceLocalDelegated,
233}
234
235impl ManagerConfig {
236    fn as_str(&self) -> &'static str {
237        match self {
238            ManagerConfig::Empty => "/pkg/netcfg/empty.json",
239            ManagerConfig::Dhcpv6 => "/pkg/netcfg/dhcpv6.json",
240            ManagerConfig::Forwarding => "/pkg/netcfg/forwarding.json",
241            ManagerConfig::AllDelegated => "/pkg/netcfg/all_delegated.json",
242            ManagerConfig::IfacePrefix => "/pkg/netcfg/iface_prefix.json",
243            ManagerConfig::DuplicateNames => "/pkg/netcfg/duplicate_names.json",
244            ManagerConfig::EnableSocketProxy => "/pkg/netcfg/enable_socket_proxy.json",
245            ManagerConfig::EnableSocketProxyAllDelegated => {
246                "/pkg/netcfg/enable_socket_proxy_all_delegated.json"
247            }
248            ManagerConfig::PacketFilterEthernet => "/pkg/netcfg/packet_filter_ethernet.json",
249            ManagerConfig::PacketFilterWlan => "/pkg/netcfg/packet_filter_wlan.json",
250            ManagerConfig::WithBlackhole => "/pkg/netcfg/with_blackhole.json",
251            ManagerConfig::AllInterfaceLocalDelegated => {
252                "/pkg/netcfg/all_interface_local_delegated.json"
253            }
254        }
255    }
256}
257
258#[derive(Copy, Clone, Default, Eq, PartialEq, Debug)]
259/// The type of `socket-proxy` implementation to use.
260pub enum SocketProxyType {
261    #[default]
262    /// No socket proxy is present.
263    None,
264    /// Use the real socket proxy implementation.
265    Real,
266    /// Use the fake socket proxy implementation to allow mocking behavior.
267    Fake,
268}
269
270impl SocketProxyType {
271    /// Returns the appropriate `KnownServiceProvider` variant for this type.
272    pub fn known_service_provider(&self) -> Option<KnownServiceProvider> {
273        match self {
274            SocketProxyType::None => None,
275            SocketProxyType::Real => Some(KnownServiceProvider::SocketProxy),
276            SocketProxyType::Fake => Some(KnownServiceProvider::FakeSocketProxy),
277        }
278    }
279
280    fn component_name(&self) -> Option<&'static str> {
281        match self {
282            SocketProxyType::None => None,
283            SocketProxyType::Real => Some(constants::socket_proxy::COMPONENT_NAME),
284            SocketProxyType::Fake => Some(constants::fake_socket_proxy::COMPONENT_NAME),
285        }
286    }
287}
288
289/// Components that provide known services used in tests.
290#[derive(Clone, Eq, PartialEq, Debug)]
291#[allow(missing_docs)]
292pub enum KnownServiceProvider {
293    Netstack(NetstackVersion),
294    Manager {
295        agent: ManagementAgent,
296        config: ManagerConfig,
297        use_dhcp_server: bool,
298        use_out_of_stack_dhcp_client: bool,
299        socket_proxy_type: SocketProxyType,
300    },
301    SecureStash,
302    DhcpServer {
303        persistent: bool,
304    },
305    DhcpClient,
306    Dhcpv6Client,
307    DnsResolver,
308    Reachability {
309        eager: bool,
310    },
311    SocketProxy,
312    NetworkTestRealm {
313        require_outer_netstack: bool,
314    },
315    FakeClock,
316    FakeSocketProxy,
317    FakeNetcfg,
318}
319
320/// Constant properties of components used in networking integration tests, such
321/// as monikers and URLs.
322#[allow(missing_docs)]
323pub mod constants {
324    pub mod netstack {
325        pub const COMPONENT_NAME: &str = "netstack";
326    }
327    pub mod netcfg {
328        pub const COMPONENT_NAME: &str = "netcfg";
329        pub mod basic {
330            pub const COMPONENT_URL: &str = "#meta/netcfg-basic.cm";
331        }
332        pub mod advanced {
333            pub const COMPONENT_URL: &str = "#meta/netcfg-advanced.cm";
334        }
335        pub mod fake {
336            pub const COMPONENT_URL: &str = "#meta/fake_netcfg.cm";
337        }
338        // These capability names and filepaths should match the devfs capabilities used by netcfg
339        // in its component manifest, i.e. netcfg.cml.
340        pub const DEV_CLASS_NETWORK: &str = "dev-class-network";
341        pub const CLASS_NETWORK_PATH: &str = "class/network";
342    }
343    pub mod socket_proxy {
344        pub const COMPONENT_NAME: &str = "network-socket-proxy";
345        pub const COMPONENT_URL: &str = "#meta/network-socket-proxy.cm";
346    }
347    pub mod secure_stash {
348        pub const COMPONENT_NAME: &str = "stash_secure";
349        pub const COMPONENT_URL: &str = "#meta/stash_secure.cm";
350    }
351    pub mod dhcp_server {
352        pub const COMPONENT_NAME: &str = "dhcpd";
353        pub const COMPONENT_URL: &str = "#meta/dhcpv4_server.cm";
354    }
355    pub mod dhcp_client {
356        pub const COMPONENT_NAME: &str = "dhcp-client";
357        pub const COMPONENT_URL: &str = "#meta/dhcp-client.cm";
358    }
359    pub mod dhcpv6_client {
360        pub const COMPONENT_NAME: &str = "dhcpv6-client";
361        pub const COMPONENT_URL: &str = "#meta/dhcpv6-client.cm";
362    }
363    pub mod dns_resolver {
364        pub const COMPONENT_NAME: &str = "dns_resolver";
365        pub const COMPONENT_URL: &str = "#meta/dns_resolver_with_fake_time.cm";
366    }
367    pub mod reachability {
368        pub const COMPONENT_NAME: &str = "reachability";
369        pub const COMPONENT_URL: &str = "#meta/reachability_with_fake_time.cm";
370    }
371    pub mod network_test_realm {
372        pub const COMPONENT_NAME: &str = "controller";
373        pub const COMPONENT_URL: &str = "#meta/controller.cm";
374    }
375    pub mod fake_clock {
376        pub const COMPONENT_NAME: &str = "fake_clock";
377        pub const COMPONENT_URL: &str = "#meta/fake_clock.cm";
378    }
379    pub mod fake_socket_proxy {
380        pub const COMPONENT_NAME: &str = "fake_socket_proxy";
381        pub const COMPONENT_URL: &str = "#meta/fake_socket_proxy.cm";
382    }
383}
384
385fn protocol_dep<P>(component_name: &'static str) -> fnetemul::ChildDep
386where
387    P: fidl::endpoints::DiscoverableProtocolMarker,
388{
389    fnetemul::ChildDep {
390        name: Some(component_name.into()),
391        capability: Some(fnetemul::ExposedCapability::Protocol(P::PROTOCOL_NAME.to_string())),
392        ..Default::default()
393    }
394}
395
396fn or_void_protocol_dep<P>(
397    component_name: &'static str,
398    is_child_present: bool,
399) -> fnetemul::ChildDep
400where
401    P: fidl::endpoints::DiscoverableProtocolMarker,
402{
403    if is_child_present { protocol_dep::<P>(component_name) } else { void_protocol_dep::<P>() }
404}
405
406fn void_protocol_dep<P>() -> fnetemul::ChildDep
407where
408    P: fidl::endpoints::DiscoverableProtocolMarker,
409{
410    fnetemul::ChildDep {
411        name: None,
412        capability: Some(fnetemul::ExposedCapability::Protocol(P::PROTOCOL_NAME.to_string())),
413        ..Default::default()
414    }
415}
416
417impl From<KnownServiceProvider> for fnetemul::ChildDef {
418    fn from(s: KnownServiceProvider) -> Self {
419        (&s).into()
420    }
421}
422
423impl<'a> From<&'a KnownServiceProvider> for fnetemul::ChildDef {
424    fn from(s: &'a KnownServiceProvider) -> Self {
425        match s {
426            KnownServiceProvider::Netstack(version) => fnetemul::ChildDef {
427                name: Some(constants::netstack::COMPONENT_NAME.to_string()),
428                source: Some(fnetemul::ChildSource::Component(version.get_url().to_string())),
429                exposes: Some(
430                    version.get_services().iter().map(|service| service.to_string()).collect(),
431                ),
432                uses: {
433                    let mut uses = vec![fnetemul::Capability::LogSink(fnetemul::Empty {})];
434                    match version {
435                        // NB: intentionally do not route SecureStore; it is
436                        // intentionally not available in all tests to
437                        // ensure that its absence is handled gracefully.
438                        // Note also that netstack-debug does not have a use
439                        // declaration for this protocol for the same
440                        // reason.
441                        NetstackVersion::Netstack2 { tracing: false, fast_udp: _ } => {}
442                        NetstackVersion::Netstack2 { tracing: true, fast_udp: _ } => {
443                            uses.push(fnetemul::Capability::TracingProvider(fnetemul::Empty));
444                        }
445                        NetstackVersion::ProdNetstack2 => {
446                            uses.push(fnetemul::Capability::ChildDep(protocol_dep::<
447                                fstash::SecureStoreMarker,
448                            >(
449                                constants::secure_stash::COMPONENT_NAME,
450                            )));
451                        }
452                        NetstackVersion::Netstack3 | NetstackVersion::ProdNetstack3 => {
453                            uses.push(fnetemul::Capability::TracingProvider(fnetemul::Empty));
454                            uses.push(fnetemul::Capability::StorageDep(fnetemul::StorageDep {
455                                variant: Some(fnetemul::StorageVariant::Data),
456                                path: Some("/data".to_string()),
457                                ..Default::default()
458                            }));
459                        }
460                    }
461                    Some(fnetemul::ChildUses::Capabilities(uses))
462                },
463                ..Default::default()
464            },
465            KnownServiceProvider::Manager {
466                agent,
467                use_dhcp_server,
468                config,
469                use_out_of_stack_dhcp_client,
470                socket_proxy_type,
471            } => {
472                let enable_dhcpv6 = match config {
473                    ManagerConfig::Dhcpv6 => true,
474                    ManagerConfig::Forwarding
475                    | ManagerConfig::Empty
476                    | ManagerConfig::AllDelegated
477                    | ManagerConfig::IfacePrefix
478                    | ManagerConfig::DuplicateNames
479                    | ManagerConfig::EnableSocketProxy
480                    | ManagerConfig::EnableSocketProxyAllDelegated
481                    | ManagerConfig::PacketFilterEthernet
482                    | ManagerConfig::PacketFilterWlan
483                    | ManagerConfig::WithBlackhole
484                    | ManagerConfig::AllInterfaceLocalDelegated => false,
485                };
486
487                fnetemul::ChildDef {
488                    name: Some(constants::netcfg::COMPONENT_NAME.to_string()),
489                    source: Some(fnetemul::ChildSource::Component(agent.get_url().to_string())),
490                    program_args: Some(
491                        agent
492                            .get_program_args()
493                            .iter()
494                            .cloned()
495                            .chain(std::iter::once("--config-data"))
496                            .chain(std::iter::once(config.as_str()))
497                            .map(Into::into)
498                            .collect(),
499                    ),
500                    exposes: Some(
501                        agent.get_services().iter().map(|service| service.to_string()).collect(),
502                    ),
503                    uses: Some(fnetemul::ChildUses::Capabilities(
504                        std::iter::once(fnetemul::Capability::ChildDep(or_void_protocol_dep::<
505                            fnet_dhcp::Server_Marker,
506                        >(
507                            constants::dhcp_server::COMPONENT_NAME,
508                            *use_dhcp_server,
509                        )))
510                        .chain(std::iter::once(fnetemul::Capability::ChildDep(
511                            or_void_protocol_dep::<fnet_dhcpv6::ClientProviderMarker>(
512                                constants::dhcpv6_client::COMPONENT_NAME,
513                                enable_dhcpv6,
514                            ),
515                        )))
516                        .chain(std::iter::once(fnetemul::Capability::ChildDep(
517                            or_void_protocol_dep::<fnet_dhcp::ClientProviderMarker>(
518                                constants::dhcp_client::COMPONENT_NAME,
519                                *use_out_of_stack_dhcp_client,
520                            ),
521                        )))
522                        .chain(
523                            socket_proxy_type
524                                .component_name()
525                                .map(|component_name| {
526                                    [
527                                        fnetemul::Capability::ChildDep(protocol_dep::<
528                                            fnp_socketproxy::FuchsiaNetworksMarker,
529                                        >(
530                                            component_name
531                                        )),
532                                        fnetemul::Capability::ChildDep(protocol_dep::<
533                                            fnp_socketproxy::NetworkRegistryMarker,
534                                        >(
535                                            component_name
536                                        )),
537                                    ]
538                                })
539                                .into_iter()
540                                .flatten(),
541                        )
542                        .chain(
543                            [
544                                fnetemul::Capability::LogSink(fnetemul::Empty {}),
545                                fnetemul::Capability::ChildDep(fnetemul::ChildDep {
546                                    dynamically_offer_from_void: Some(true),
547                                    ..protocol_dep::<fnet_filter::ControlMarker>(
548                                        constants::netstack::COMPONENT_NAME,
549                                    )
550                                }),
551                                fnetemul::Capability::ChildDep(fnetemul::ChildDep {
552                                    dynamically_offer_from_void: Some(true),
553                                    ..protocol_dep::<fnet_filter_deprecated::FilterMarker>(
554                                        constants::netstack::COMPONENT_NAME,
555                                    )
556                                }),
557                                fnetemul::Capability::ChildDep(protocol_dep::<
558                                    fnet_interfaces::StateMarker,
559                                >(
560                                    constants::netstack::COMPONENT_NAME,
561                                )),
562                                fnetemul::Capability::ChildDep(protocol_dep::<
563                                    fnet_interfaces_admin::InstallerMarker,
564                                >(
565                                    constants::netstack::COMPONENT_NAME,
566                                )),
567                                fnetemul::Capability::ChildDep(protocol_dep::<
568                                    fnet_stack::StackMarker,
569                                >(
570                                    constants::netstack::COMPONENT_NAME,
571                                )),
572                                fnetemul::Capability::ChildDep(protocol_dep::<
573                                    fnet_routes_admin::RouteTableV4Marker,
574                                >(
575                                    constants::netstack::COMPONENT_NAME,
576                                )),
577                                fnetemul::Capability::ChildDep(protocol_dep::<
578                                    fnet_routes_admin::RouteTableV6Marker,
579                                >(
580                                    constants::netstack::COMPONENT_NAME,
581                                )),
582                                fnetemul::Capability::ChildDep(protocol_dep::<
583                                    fnet_routes_admin::RuleTableV4Marker,
584                                >(
585                                    constants::netstack::COMPONENT_NAME,
586                                )),
587                                fnetemul::Capability::ChildDep(protocol_dep::<
588                                    fnet_routes_admin::RuleTableV6Marker,
589                                >(
590                                    constants::netstack::COMPONENT_NAME,
591                                )),
592                                fnetemul::Capability::ChildDep(protocol_dep::<
593                                    fnet_name::DnsServerWatcherMarker,
594                                >(
595                                    constants::netstack::COMPONENT_NAME,
596                                )),
597                                fnetemul::Capability::ChildDep(protocol_dep::<
598                                    fnet_name::LookupAdminMarker,
599                                >(
600                                    constants::dns_resolver::COMPONENT_NAME,
601                                )),
602                                fnetemul::Capability::ChildDep(protocol_dep::<
603                                    fnet_ndp::RouterAdvertisementOptionWatcherProviderMarker,
604                                >(
605                                    constants::netstack::COMPONENT_NAME,
606                                )),
607                                fnetemul::Capability::ChildDep(fnetemul::ChildDep {
608                                    name: Some(
609                                        fnetemul::NETEMUL_SERVICES_COMPONENT_NAME.to_string(),
610                                    ),
611                                    capability: Some(fnetemul::ExposedCapability::Service(
612                                        fhwnet::ServiceMarker::SERVICE_NAME.to_string(),
613                                    )),
614                                    ..Default::default()
615                                }),
616                                fnetemul::Capability::StorageDep(fnetemul::StorageDep {
617                                    variant: Some(fnetemul::StorageVariant::Data),
618                                    path: Some("/data".to_string()),
619                                    ..Default::default()
620                                }),
621                            ]
622                            .into_iter(),
623                        )
624                        .collect(),
625                    )),
626                    eager: Some(true),
627                    ..Default::default()
628                }
629            }
630            KnownServiceProvider::SecureStash => fnetemul::ChildDef {
631                name: Some(constants::secure_stash::COMPONENT_NAME.to_string()),
632                source: Some(fnetemul::ChildSource::Component(
633                    constants::secure_stash::COMPONENT_URL.to_string(),
634                )),
635                exposes: Some(vec![fstash::SecureStoreMarker::PROTOCOL_NAME.to_string()]),
636                uses: Some(fnetemul::ChildUses::Capabilities(vec![
637                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
638                    fnetemul::Capability::StorageDep(fnetemul::StorageDep {
639                        variant: Some(fnetemul::StorageVariant::Data),
640                        path: Some("/data".to_string()),
641                        ..Default::default()
642                    }),
643                ])),
644                ..Default::default()
645            },
646            KnownServiceProvider::DhcpServer { persistent } => fnetemul::ChildDef {
647                name: Some(constants::dhcp_server::COMPONENT_NAME.to_string()),
648                source: Some(fnetemul::ChildSource::Component(
649                    constants::dhcp_server::COMPONENT_URL.to_string(),
650                )),
651                exposes: Some(vec![fnet_dhcp::Server_Marker::PROTOCOL_NAME.to_string()]),
652                uses: Some(fnetemul::ChildUses::Capabilities(
653                    [
654                        fnetemul::Capability::LogSink(fnetemul::Empty {}),
655                        fnetemul::Capability::ChildDep(protocol_dep::<
656                            fnet_neighbor::ControllerMarker,
657                        >(
658                            constants::netstack::COMPONENT_NAME
659                        )),
660                        fnetemul::Capability::ChildDep(
661                            protocol_dep::<fposix_socket::ProviderMarker>(
662                                constants::netstack::COMPONENT_NAME,
663                            ),
664                        ),
665                        fnetemul::Capability::ChildDep(protocol_dep::<
666                            fposix_socket_packet::ProviderMarker,
667                        >(
668                            constants::netstack::COMPONENT_NAME
669                        )),
670                    ]
671                    .into_iter()
672                    .chain(persistent.then_some(fnetemul::Capability::ChildDep(protocol_dep::<
673                        fstash::SecureStoreMarker,
674                    >(
675                        constants::secure_stash::COMPONENT_NAME,
676                    ))))
677                    .collect(),
678                )),
679                program_args: if *persistent {
680                    Some(vec![String::from("--persistent")])
681                } else {
682                    None
683                },
684                ..Default::default()
685            },
686            KnownServiceProvider::DhcpClient => fnetemul::ChildDef {
687                name: Some(constants::dhcp_client::COMPONENT_NAME.to_string()),
688                source: Some(fnetemul::ChildSource::Component(
689                    constants::dhcp_client::COMPONENT_URL.to_string(),
690                )),
691                exposes: Some(vec![fnet_dhcp::ClientProviderMarker::PROTOCOL_NAME.to_string()]),
692                uses: Some(fnetemul::ChildUses::Capabilities(vec![
693                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
694                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
695                        constants::netstack::COMPONENT_NAME,
696                    )),
697                    fnetemul::Capability::ChildDep(protocol_dep::<
698                        fposix_socket_packet::ProviderMarker,
699                    >(
700                        constants::netstack::COMPONENT_NAME
701                    )),
702                ])),
703                program_args: None,
704                ..Default::default()
705            },
706            KnownServiceProvider::Dhcpv6Client => fnetemul::ChildDef {
707                name: Some(constants::dhcpv6_client::COMPONENT_NAME.to_string()),
708                source: Some(fnetemul::ChildSource::Component(
709                    constants::dhcpv6_client::COMPONENT_URL.to_string(),
710                )),
711                exposes: Some(vec![fnet_dhcpv6::ClientProviderMarker::PROTOCOL_NAME.to_string()]),
712                uses: Some(fnetemul::ChildUses::Capabilities(vec![
713                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
714                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
715                        constants::netstack::COMPONENT_NAME,
716                    )),
717                ])),
718                ..Default::default()
719            },
720            KnownServiceProvider::DnsResolver => fnetemul::ChildDef {
721                name: Some(constants::dns_resolver::COMPONENT_NAME.to_string()),
722                source: Some(fnetemul::ChildSource::Component(
723                    constants::dns_resolver::COMPONENT_URL.to_string(),
724                )),
725                exposes: Some(vec![
726                    fnet_name::LookupAdminMarker::PROTOCOL_NAME.to_string(),
727                    fnet_name::LookupMarker::PROTOCOL_NAME.to_string(),
728                ]),
729                uses: Some(fnetemul::ChildUses::Capabilities(vec![
730                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
731                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateMarker>(
732                        constants::netstack::COMPONENT_NAME,
733                    )),
734                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
735                        constants::netstack::COMPONENT_NAME,
736                    )),
737                    fnetemul::Capability::ChildDep(protocol_dep::<
738                        fidl_fuchsia_testing::FakeClockMarker,
739                    >(
740                        constants::fake_clock::COMPONENT_NAME
741                    )),
742                    fnetemul::Capability::ChildDep(void_protocol_dep::<
743                        fscheduler::RoleManagerMarker,
744                    >()),
745                ])),
746                ..Default::default()
747            },
748            KnownServiceProvider::Reachability { eager } => fnetemul::ChildDef {
749                name: Some(constants::reachability::COMPONENT_NAME.to_string()),
750                source: Some(fnetemul::ChildSource::Component(
751                    constants::reachability::COMPONENT_URL.to_string(),
752                )),
753                exposes: Some(vec![fnet_reachability::MonitorMarker::PROTOCOL_NAME.to_string()]),
754                uses: Some(fnetemul::ChildUses::Capabilities(vec![
755                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
756                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_interfaces::StateMarker>(
757                        constants::netstack::COMPONENT_NAME,
758                    )),
759                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
760                        constants::netstack::COMPONENT_NAME,
761                    )),
762                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_name::LookupMarker>(
763                        constants::dns_resolver::COMPONENT_NAME,
764                    )),
765                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_neighbor::ViewMarker>(
766                        constants::netstack::COMPONENT_NAME,
767                    )),
768                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_debug::InterfacesMarker>(
769                        constants::netstack::COMPONENT_NAME,
770                    )),
771                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_root::InterfacesMarker>(
772                        constants::netstack::COMPONENT_NAME,
773                    )),
774                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateV4Marker>(
775                        constants::netstack::COMPONENT_NAME,
776                    )),
777                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateV6Marker>(
778                        constants::netstack::COMPONENT_NAME,
779                    )),
780                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_debug::DiagnosticsMarker>(
781                        constants::netstack::COMPONENT_NAME,
782                    )),
783                    fnetemul::Capability::ChildDep(protocol_dep::<
784                        fidl_fuchsia_testing::FakeClockMarker,
785                    >(
786                        constants::fake_clock::COMPONENT_NAME
787                    )),
788                ])),
789                eager: Some(*eager),
790                ..Default::default()
791            },
792            KnownServiceProvider::SocketProxy => fnetemul::ChildDef {
793                name: Some(constants::socket_proxy::COMPONENT_NAME.to_string()),
794                source: Some(fnetemul::ChildSource::Component(
795                    constants::socket_proxy::COMPONENT_URL.to_string(),
796                )),
797                exposes: Some(vec![
798                    fposix_socket::ProviderMarker::PROTOCOL_NAME.to_string(),
799                    fposix_socket_raw::ProviderMarker::PROTOCOL_NAME.to_string(),
800                    fnp_socketproxy::StarnixNetworksMarker::PROTOCOL_NAME.to_string(),
801                    fnp_socketproxy::FuchsiaNetworksMarker::PROTOCOL_NAME.to_string(),
802                ]),
803                uses: Some(fnetemul::ChildUses::Capabilities(vec![
804                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
805                        constants::netstack::COMPONENT_NAME,
806                    )),
807                    fnetemul::Capability::ChildDep(
808                        protocol_dep::<fposix_socket_raw::ProviderMarker>(
809                            constants::netstack::COMPONENT_NAME,
810                        ),
811                    ),
812                    fnetemul::Capability::ChildDep(fnetemul::ChildDep {
813                        is_weak: Some(true),
814                        ..protocol_dep::<fnp_socketproxy::NetworkRegistryMarker>(
815                            constants::netcfg::COMPONENT_NAME,
816                        )
817                    }),
818                ])),
819                ..Default::default()
820            },
821            KnownServiceProvider::NetworkTestRealm { require_outer_netstack } => {
822                fnetemul::ChildDef {
823                    name: Some(constants::network_test_realm::COMPONENT_NAME.to_string()),
824                    source: Some(fnetemul::ChildSource::Component(
825                        constants::network_test_realm::COMPONENT_URL.to_string(),
826                    )),
827                    exposes: Some(vec![
828                        fntr::ControllerMarker::PROTOCOL_NAME.to_string(),
829                        fcomponent::RealmMarker::PROTOCOL_NAME.to_string(),
830                    ]),
831                    uses: Some(fnetemul::ChildUses::Capabilities(
832                        [
833                            fnetemul::Capability::LogSink(fnetemul::Empty {}),
834                            fnetemul::Capability::ChildDep(fnetemul::ChildDep {
835                                name: Some(fnetemul::NETEMUL_SERVICES_COMPONENT_NAME.to_string()),
836                                capability: Some(fnetemul::ExposedCapability::Service(
837                                    fhwnet::ServiceMarker::SERVICE_NAME.to_string(),
838                                )),
839                                ..Default::default()
840                            }),
841                        ]
842                        .into_iter()
843                        .chain(
844                            require_outer_netstack
845                                .then_some([
846                                    fnetemul::Capability::ChildDep(protocol_dep::<
847                                        fnet_stack::StackMarker,
848                                    >(
849                                        constants::netstack::COMPONENT_NAME,
850                                    )),
851                                    fnetemul::Capability::ChildDep(protocol_dep::<
852                                        fnet_debug::InterfacesMarker,
853                                    >(
854                                        constants::netstack::COMPONENT_NAME,
855                                    )),
856                                    fnetemul::Capability::ChildDep(protocol_dep::<
857                                        fnet_root::InterfacesMarker,
858                                    >(
859                                        constants::netstack::COMPONENT_NAME,
860                                    )),
861                                    fnetemul::Capability::ChildDep(protocol_dep::<
862                                        fnet_interfaces::StateMarker,
863                                    >(
864                                        constants::netstack::COMPONENT_NAME,
865                                    )),
866                                ])
867                                .into_iter()
868                                .flatten(),
869                        )
870                        .collect::<Vec<_>>(),
871                    )),
872                    ..Default::default()
873                }
874            }
875            KnownServiceProvider::FakeClock => fnetemul::ChildDef {
876                name: Some(constants::fake_clock::COMPONENT_NAME.to_string()),
877                source: Some(fnetemul::ChildSource::Component(
878                    constants::fake_clock::COMPONENT_URL.to_string(),
879                )),
880                exposes: Some(vec![
881                    fidl_fuchsia_testing::FakeClockMarker::PROTOCOL_NAME.to_string(),
882                    fidl_fuchsia_testing::FakeClockControlMarker::PROTOCOL_NAME.to_string(),
883                ]),
884                uses: Some(fnetemul::ChildUses::Capabilities(vec![fnetemul::Capability::LogSink(
885                    fnetemul::Empty {},
886                )])),
887                ..Default::default()
888            },
889            KnownServiceProvider::FakeSocketProxy => fnetemul::ChildDef {
890                name: Some(constants::fake_socket_proxy::COMPONENT_NAME.to_string()),
891                source: Some(fnetemul::ChildSource::Component(
892                    constants::fake_socket_proxy::COMPONENT_URL.to_string(),
893                )),
894                exposes: Some(vec![
895                    fnp_socketproxy::FuchsiaNetworksMarker::PROTOCOL_NAME.to_string(),
896                    fnp_socketproxy::NetworkRegistryMarker::PROTOCOL_NAME.to_string(),
897                ]),
898                uses: Some(fnetemul::ChildUses::Capabilities(vec![
899                    fnetemul::Capability::ChildDep(fnetemul::ChildDep {
900                        is_weak: Some(true),
901                        ..protocol_dep::<fnp_socketproxy::NetworkRegistryMarker>(
902                            constants::netcfg::COMPONENT_NAME,
903                        )
904                    }),
905                ])),
906                ..Default::default()
907            },
908            KnownServiceProvider::FakeNetcfg => fnetemul::ChildDef {
909                name: Some(constants::netcfg::COMPONENT_NAME.to_string()),
910                source: Some(fnetemul::ChildSource::Component(
911                    constants::netcfg::fake::COMPONENT_URL.to_string(),
912                )),
913                exposes: Some(vec![
914                    fnp_properties::NetworksMarker::PROTOCOL_NAME.to_string(),
915                    fnp_socketproxy::NetworkRegistryMarker::PROTOCOL_NAME.to_string(),
916                ]),
917                ..Default::default()
918            },
919        }
920    }
921}
922
923/// Set the `opaque_iids` structured configuration value for Netstack3.
924pub fn set_netstack3_opaque_iids(netstack: &mut fnetemul::ChildDef, value: bool) {
925    const KEY: &str = "opaque_iids";
926    set_structured_config_value(netstack, KEY.to_owned(), cm_rust::ConfigValue::from(value));
927}
928
929/// Set the `suspend_enabled` structured configuration value for Netstack3.
930pub fn set_netstack3_suspend_enabled(netstack: &mut fnetemul::ChildDef, value: bool) {
931    const KEY: &str = "suspend_enabled";
932    set_structured_config_value(netstack, KEY.to_owned(), cm_rust::ConfigValue::from(value));
933}
934
935/// Set a structured configuration value for the provided component.
936fn set_structured_config_value(
937    component: &mut fnetemul::ChildDef,
938    key: String,
939    value: cm_rust::ConfigValue,
940) {
941    component
942        .config_values
943        .get_or_insert_default()
944        .push(fnetemul::ChildConfigValue { key, value: value.native_into_fidl() });
945}
946
947/// Abstraction for a Fuchsia component which offers network stack services.
948pub trait Netstack: Copy + Clone {
949    /// The Netstack version.
950    const VERSION: NetstackVersion;
951}
952
953/// Uninstantiable type that represents Netstack2's implementation of a
954/// network stack.
955#[derive(Copy, Clone)]
956pub enum Netstack2 {}
957
958impl Netstack for Netstack2 {
959    const VERSION: NetstackVersion = NetstackVersion::Netstack2 { tracing: false, fast_udp: false };
960}
961
962/// Uninstantiable type that represents Netstack2's production implementation of
963/// a network stack.
964#[derive(Copy, Clone)]
965pub enum ProdNetstack2 {}
966
967impl Netstack for ProdNetstack2 {
968    const VERSION: NetstackVersion = NetstackVersion::ProdNetstack2;
969}
970
971/// Uninstantiable type that represents Netstack3's implementation of a
972/// network stack.
973#[derive(Copy, Clone)]
974pub enum Netstack3 {}
975
976impl Netstack for Netstack3 {
977    const VERSION: NetstackVersion = NetstackVersion::Netstack3;
978}
979
980/// Uninstantiable type that represents Netstack3's production implementation of
981/// a network stack.
982#[derive(Copy, Clone)]
983pub enum ProdNetstack3 {}
984
985impl Netstack for ProdNetstack3 {
986    const VERSION: NetstackVersion = NetstackVersion::ProdNetstack3;
987}
988
989/// Abstraction for a Fuchsia component which offers network configuration services.
990pub trait Manager: Copy + Clone {
991    /// The management agent to be used.
992    const MANAGEMENT_AGENT: ManagementAgent;
993}
994
995/// Uninstantiable type that represents netcfg_basic's implementation of a network manager.
996#[derive(Copy, Clone)]
997pub enum NetCfgBasic {}
998
999impl Manager for NetCfgBasic {
1000    const MANAGEMENT_AGENT: ManagementAgent = ManagementAgent::NetCfg(NetCfgVersion::Basic);
1001}
1002
1003/// Uninstantiable type that represents netcfg_advanced's implementation of a
1004/// network manager.
1005#[derive(Copy, Clone)]
1006pub enum NetCfgAdvanced {}
1007
1008impl Manager for NetCfgAdvanced {
1009    const MANAGEMENT_AGENT: ManagementAgent = ManagementAgent::NetCfg(NetCfgVersion::Advanced);
1010}
1011
1012pub use netemul::{DhcpClient, DhcpClientVersion, InStack, OutOfStack};
1013
1014/// A combination of Netstack and DhcpClient guaranteed to be compatible with
1015/// each other.
1016pub trait NetstackAndDhcpClient: Copy + Clone {
1017    /// The netstack to be used.
1018    type Netstack: Netstack;
1019    /// The DHCP client to be used.
1020    type DhcpClient: DhcpClient;
1021}
1022
1023/// Netstack2 with the in-stack DHCP client.
1024#[derive(Copy, Clone)]
1025pub enum Netstack2AndInStackDhcpClient {}
1026
1027impl NetstackAndDhcpClient for Netstack2AndInStackDhcpClient {
1028    type Netstack = Netstack2;
1029    type DhcpClient = InStack;
1030}
1031
1032/// Netstack2 with the out-of-stack DHCP client.
1033#[derive(Copy, Clone)]
1034pub enum Netstack2AndOutOfStackDhcpClient {}
1035
1036impl NetstackAndDhcpClient for Netstack2AndOutOfStackDhcpClient {
1037    type Netstack = Netstack2;
1038    type DhcpClient = OutOfStack;
1039}
1040
1041/// Netstack3 with the out-of-stack DHCP client.
1042#[derive(Copy, Clone)]
1043pub enum Netstack3AndOutOfStackDhcpClient {}
1044
1045impl NetstackAndDhcpClient for Netstack3AndOutOfStackDhcpClient {
1046    type Netstack = Netstack3;
1047    type DhcpClient = OutOfStack;
1048}
1049
1050/// Helpers for `netemul::TestSandbox`.
1051pub trait TestSandboxExt {
1052    /// Creates a realm with Netstack services.
1053    fn create_netstack_realm<'a, N, S>(&'a self, name: S) -> Result<netemul::TestRealm<'a>>
1054    where
1055        N: Netstack,
1056        S: Into<Cow<'a, str>>;
1057
1058    /// Creates a realm with the base Netstack services plus additional ones in
1059    /// `children`.
1060    fn create_netstack_realm_with<'a, N, S, I>(
1061        &'a self,
1062        name: S,
1063        children: I,
1064    ) -> Result<netemul::TestRealm<'a>>
1065    where
1066        S: Into<Cow<'a, str>>,
1067        N: Netstack,
1068        I: IntoIterator,
1069        I::Item: Into<fnetemul::ChildDef>;
1070}
1071
1072impl TestSandboxExt for netemul::TestSandbox {
1073    fn create_netstack_realm<'a, N, S>(&'a self, name: S) -> Result<netemul::TestRealm<'a>>
1074    where
1075        N: Netstack,
1076        S: Into<Cow<'a, str>>,
1077    {
1078        self.create_netstack_realm_with::<N, _, _>(name, std::iter::empty::<fnetemul::ChildDef>())
1079    }
1080
1081    fn create_netstack_realm_with<'a, N, S, I>(
1082        &'a self,
1083        name: S,
1084        children: I,
1085    ) -> Result<netemul::TestRealm<'a>>
1086    where
1087        S: Into<Cow<'a, str>>,
1088        N: Netstack,
1089        I: IntoIterator,
1090        I::Item: Into<fnetemul::ChildDef>,
1091    {
1092        self.create_realm(
1093            name,
1094            [KnownServiceProvider::Netstack(N::VERSION)]
1095                .iter()
1096                .map(fnetemul::ChildDef::from)
1097                .chain(children.into_iter().map(Into::into)),
1098        )
1099    }
1100}
1101
1102/// Helpers for `netemul::TestRealm`.
1103pub trait TestRealmExt {
1104    /// Returns the properties of the loopback interface, or `None` if there is no
1105    /// loopback interface.
1106    fn loopback_properties(
1107        &self,
1108    ) -> impl Future<
1109        Output = Result<Option<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>>>,
1110    >;
1111
1112    /// Get a `fuchsia.net.interfaces.admin/Control` client proxy for the
1113    /// interface identified by [`id`] via `fuchsia.net.root`.
1114    ///
1115    /// Note that one should prefer to operate on a `TestInterface` if it is
1116    /// available; but this method exists in order to obtain a Control channel
1117    /// for interfaces such as loopback.
1118    fn interface_control(&self, id: u64) -> Result<fnet_interfaces_ext::admin::Control>;
1119}
1120
1121impl TestRealmExt for netemul::TestRealm<'_> {
1122    async fn loopback_properties(
1123        &self,
1124    ) -> Result<Option<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>>> {
1125        let interface_state = self
1126            .connect_to_protocol::<fnet_interfaces::StateMarker>()
1127            .context("failed to connect to fuchsia.net.interfaces/State")?;
1128
1129        let properties = fnet_interfaces_ext::existing(
1130            fnet_interfaces_ext::event_stream_from_state(&interface_state, Default::default())
1131                .expect("create watcher event stream"),
1132            HashMap::<u64, fnet_interfaces_ext::PropertiesAndState<(), _>>::new(),
1133        )
1134        .await
1135        .context("failed to get existing interface properties from watcher")?
1136        .into_iter()
1137        .find_map(|(_id, properties_and_state): (u64, _)| {
1138            let fnet_interfaces_ext::PropertiesAndState {
1139                properties: properties @ fnet_interfaces_ext::Properties { port_class, .. },
1140                state: (),
1141            } = properties_and_state;
1142            port_class.is_loopback().then_some(properties)
1143        });
1144        Ok(properties)
1145    }
1146
1147    fn interface_control(&self, id: u64) -> Result<fnet_interfaces_ext::admin::Control> {
1148        let root_control = self
1149            .connect_to_protocol::<fnet_root::InterfacesMarker>()
1150            .context("connect to protocol")?;
1151
1152        let (control, server) = fnet_interfaces_ext::admin::Control::create_endpoints()
1153            .context("create Control proxy")?;
1154        root_control.get_admin(id, server).context("get admin")?;
1155        Ok(control)
1156    }
1157}