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