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