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 fidl_fuchsia_component as fcomponent;
13use fidl_fuchsia_net_debug as fnet_debug;
14use fidl_fuchsia_net_dhcp as fnet_dhcp;
15use fidl_fuchsia_net_dhcpv6 as fnet_dhcpv6;
16use fidl_fuchsia_net_filter as fnet_filter;
17use fidl_fuchsia_net_filter_deprecated as fnet_filter_deprecated;
18use fidl_fuchsia_net_interfaces as fnet_interfaces;
19use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
20use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
21use fidl_fuchsia_net_masquerade as fnet_masquerade;
22use fidl_fuchsia_net_multicast_admin as fnet_multicast_admin;
23use fidl_fuchsia_net_name as fnet_name;
24use fidl_fuchsia_net_ndp as fnet_ndp;
25use fidl_fuchsia_net_neighbor as fnet_neighbor;
26use fidl_fuchsia_net_policy_properties as fnp_properties;
27use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
28use fidl_fuchsia_net_policy_testing as fnp_testing;
29use fidl_fuchsia_net_power as fnet_power;
30use fidl_fuchsia_net_reachability as fnet_reachability;
31use fidl_fuchsia_net_root as fnet_root;
32use fidl_fuchsia_net_routes as fnet_routes;
33use fidl_fuchsia_net_routes_admin as fnet_routes_admin;
34use fidl_fuchsia_net_settings as fnet_settings;
35use fidl_fuchsia_net_sockets as fnet_sockets;
36use fidl_fuchsia_net_stack as fnet_stack;
37use fidl_fuchsia_net_test_realm as fntr;
38use fidl_fuchsia_net_virtualization as fnet_virtualization;
39use fidl_fuchsia_netemul as fnetemul;
40use fidl_fuchsia_posix_socket as fposix_socket;
41use fidl_fuchsia_posix_socket_packet as fposix_socket_packet;
42use fidl_fuchsia_posix_socket_raw as fposix_socket_raw;
43use fidl_fuchsia_scheduler as fscheduler;
44use fidl_fuchsia_stash as fstash;
45use fidl_fuchsia_update_verify as fupdate_verify;
46
47use anyhow::Context as _;
48use async_trait::async_trait;
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::NetemulDevfs(fnetemul::DevfsDep {
611                                    name: Some(constants::netcfg::DEV_CLASS_NETWORK.to_string()),
612                                    subdir: Some(constants::netcfg::CLASS_NETWORK_PATH.to_string()),
613                                    ..Default::default()
614                                }),
615                                fnetemul::Capability::StorageDep(fnetemul::StorageDep {
616                                    variant: Some(fnetemul::StorageVariant::Data),
617                                    path: Some("/data".to_string()),
618                                    ..Default::default()
619                                }),
620                            ]
621                            .into_iter(),
622                        )
623                        .collect(),
624                    )),
625                    eager: Some(true),
626                    ..Default::default()
627                }
628            }
629            KnownServiceProvider::SecureStash => fnetemul::ChildDef {
630                name: Some(constants::secure_stash::COMPONENT_NAME.to_string()),
631                source: Some(fnetemul::ChildSource::Component(
632                    constants::secure_stash::COMPONENT_URL.to_string(),
633                )),
634                exposes: Some(vec![fstash::SecureStoreMarker::PROTOCOL_NAME.to_string()]),
635                uses: Some(fnetemul::ChildUses::Capabilities(vec![
636                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
637                    fnetemul::Capability::StorageDep(fnetemul::StorageDep {
638                        variant: Some(fnetemul::StorageVariant::Data),
639                        path: Some("/data".to_string()),
640                        ..Default::default()
641                    }),
642                ])),
643                ..Default::default()
644            },
645            KnownServiceProvider::DhcpServer { persistent } => fnetemul::ChildDef {
646                name: Some(constants::dhcp_server::COMPONENT_NAME.to_string()),
647                source: Some(fnetemul::ChildSource::Component(
648                    constants::dhcp_server::COMPONENT_URL.to_string(),
649                )),
650                exposes: Some(vec![fnet_dhcp::Server_Marker::PROTOCOL_NAME.to_string()]),
651                uses: Some(fnetemul::ChildUses::Capabilities(
652                    [
653                        fnetemul::Capability::LogSink(fnetemul::Empty {}),
654                        fnetemul::Capability::ChildDep(protocol_dep::<
655                            fnet_neighbor::ControllerMarker,
656                        >(
657                            constants::netstack::COMPONENT_NAME
658                        )),
659                        fnetemul::Capability::ChildDep(
660                            protocol_dep::<fposix_socket::ProviderMarker>(
661                                constants::netstack::COMPONENT_NAME,
662                            ),
663                        ),
664                        fnetemul::Capability::ChildDep(protocol_dep::<
665                            fposix_socket_packet::ProviderMarker,
666                        >(
667                            constants::netstack::COMPONENT_NAME
668                        )),
669                    ]
670                    .into_iter()
671                    .chain(persistent.then_some(fnetemul::Capability::ChildDep(protocol_dep::<
672                        fstash::SecureStoreMarker,
673                    >(
674                        constants::secure_stash::COMPONENT_NAME,
675                    ))))
676                    .collect(),
677                )),
678                program_args: if *persistent {
679                    Some(vec![String::from("--persistent")])
680                } else {
681                    None
682                },
683                ..Default::default()
684            },
685            KnownServiceProvider::DhcpClient => fnetemul::ChildDef {
686                name: Some(constants::dhcp_client::COMPONENT_NAME.to_string()),
687                source: Some(fnetemul::ChildSource::Component(
688                    constants::dhcp_client::COMPONENT_URL.to_string(),
689                )),
690                exposes: Some(vec![fnet_dhcp::ClientProviderMarker::PROTOCOL_NAME.to_string()]),
691                uses: Some(fnetemul::ChildUses::Capabilities(vec![
692                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
693                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
694                        constants::netstack::COMPONENT_NAME,
695                    )),
696                    fnetemul::Capability::ChildDep(protocol_dep::<
697                        fposix_socket_packet::ProviderMarker,
698                    >(
699                        constants::netstack::COMPONENT_NAME
700                    )),
701                ])),
702                program_args: None,
703                ..Default::default()
704            },
705            KnownServiceProvider::Dhcpv6Client => fnetemul::ChildDef {
706                name: Some(constants::dhcpv6_client::COMPONENT_NAME.to_string()),
707                source: Some(fnetemul::ChildSource::Component(
708                    constants::dhcpv6_client::COMPONENT_URL.to_string(),
709                )),
710                exposes: Some(vec![fnet_dhcpv6::ClientProviderMarker::PROTOCOL_NAME.to_string()]),
711                uses: Some(fnetemul::ChildUses::Capabilities(vec![
712                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
713                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
714                        constants::netstack::COMPONENT_NAME,
715                    )),
716                ])),
717                ..Default::default()
718            },
719            KnownServiceProvider::DnsResolver => fnetemul::ChildDef {
720                name: Some(constants::dns_resolver::COMPONENT_NAME.to_string()),
721                source: Some(fnetemul::ChildSource::Component(
722                    constants::dns_resolver::COMPONENT_URL.to_string(),
723                )),
724                exposes: Some(vec![
725                    fnet_name::LookupAdminMarker::PROTOCOL_NAME.to_string(),
726                    fnet_name::LookupMarker::PROTOCOL_NAME.to_string(),
727                ]),
728                uses: Some(fnetemul::ChildUses::Capabilities(vec![
729                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
730                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateMarker>(
731                        constants::netstack::COMPONENT_NAME,
732                    )),
733                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
734                        constants::netstack::COMPONENT_NAME,
735                    )),
736                    fnetemul::Capability::ChildDep(protocol_dep::<
737                        fidl_fuchsia_testing::FakeClockMarker,
738                    >(
739                        constants::fake_clock::COMPONENT_NAME
740                    )),
741                    fnetemul::Capability::ChildDep(void_protocol_dep::<
742                        fscheduler::RoleManagerMarker,
743                    >()),
744                ])),
745                ..Default::default()
746            },
747            KnownServiceProvider::Reachability { eager } => fnetemul::ChildDef {
748                name: Some(constants::reachability::COMPONENT_NAME.to_string()),
749                source: Some(fnetemul::ChildSource::Component(
750                    constants::reachability::COMPONENT_URL.to_string(),
751                )),
752                exposes: Some(vec![fnet_reachability::MonitorMarker::PROTOCOL_NAME.to_string()]),
753                uses: Some(fnetemul::ChildUses::Capabilities(vec![
754                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
755                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_interfaces::StateMarker>(
756                        constants::netstack::COMPONENT_NAME,
757                    )),
758                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
759                        constants::netstack::COMPONENT_NAME,
760                    )),
761                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_name::LookupMarker>(
762                        constants::dns_resolver::COMPONENT_NAME,
763                    )),
764                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_neighbor::ViewMarker>(
765                        constants::netstack::COMPONENT_NAME,
766                    )),
767                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_debug::InterfacesMarker>(
768                        constants::netstack::COMPONENT_NAME,
769                    )),
770                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_root::InterfacesMarker>(
771                        constants::netstack::COMPONENT_NAME,
772                    )),
773                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateV4Marker>(
774                        constants::netstack::COMPONENT_NAME,
775                    )),
776                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateV6Marker>(
777                        constants::netstack::COMPONENT_NAME,
778                    )),
779                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_debug::DiagnosticsMarker>(
780                        constants::netstack::COMPONENT_NAME,
781                    )),
782                    fnetemul::Capability::ChildDep(protocol_dep::<
783                        fidl_fuchsia_testing::FakeClockMarker,
784                    >(
785                        constants::fake_clock::COMPONENT_NAME
786                    )),
787                ])),
788                eager: Some(*eager),
789                ..Default::default()
790            },
791            KnownServiceProvider::SocketProxy => fnetemul::ChildDef {
792                name: Some(constants::socket_proxy::COMPONENT_NAME.to_string()),
793                source: Some(fnetemul::ChildSource::Component(
794                    constants::socket_proxy::COMPONENT_URL.to_string(),
795                )),
796                exposes: Some(vec![
797                    fposix_socket::ProviderMarker::PROTOCOL_NAME.to_string(),
798                    fposix_socket_raw::ProviderMarker::PROTOCOL_NAME.to_string(),
799                    fnp_socketproxy::StarnixNetworksMarker::PROTOCOL_NAME.to_string(),
800                    fnp_socketproxy::FuchsiaNetworksMarker::PROTOCOL_NAME.to_string(),
801                    fnp_socketproxy::DnsServerWatcherMarker::PROTOCOL_NAME.to_string(),
802                ]),
803                uses: Some(fnetemul::ChildUses::Capabilities(vec![
804                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
805                        constants::netstack::COMPONENT_NAME,
806                    )),
807                    fnetemul::Capability::ChildDep(
808                        protocol_dep::<fposix_socket_raw::ProviderMarker>(
809                            constants::netstack::COMPONENT_NAME,
810                        ),
811                    ),
812                    fnetemul::Capability::ChildDep(fnetemul::ChildDep {
813                        is_weak: Some(true),
814                        ..protocol_dep::<fnp_socketproxy::NetworkRegistryMarker>(
815                            constants::netcfg::COMPONENT_NAME,
816                        )
817                    }),
818                ])),
819                ..Default::default()
820            },
821            KnownServiceProvider::NetworkTestRealm { require_outer_netstack } => {
822                fnetemul::ChildDef {
823                    name: Some(constants::network_test_realm::COMPONENT_NAME.to_string()),
824                    source: Some(fnetemul::ChildSource::Component(
825                        constants::network_test_realm::COMPONENT_URL.to_string(),
826                    )),
827                    exposes: Some(vec![
828                        fntr::ControllerMarker::PROTOCOL_NAME.to_string(),
829                        fcomponent::RealmMarker::PROTOCOL_NAME.to_string(),
830                    ]),
831                    uses: Some(fnetemul::ChildUses::Capabilities(
832                        std::iter::once(fnetemul::Capability::LogSink(fnetemul::Empty {}))
833                            .chain(
834                                require_outer_netstack
835                                    .then_some([
836                                        fnetemul::Capability::ChildDep(protocol_dep::<
837                                            fnet_stack::StackMarker,
838                                        >(
839                                            constants::netstack::COMPONENT_NAME,
840                                        )),
841                                        fnetemul::Capability::ChildDep(protocol_dep::<
842                                            fnet_debug::InterfacesMarker,
843                                        >(
844                                            constants::netstack::COMPONENT_NAME,
845                                        )),
846                                        fnetemul::Capability::ChildDep(protocol_dep::<
847                                            fnet_root::InterfacesMarker,
848                                        >(
849                                            constants::netstack::COMPONENT_NAME,
850                                        )),
851                                        fnetemul::Capability::ChildDep(protocol_dep::<
852                                            fnet_interfaces::StateMarker,
853                                        >(
854                                            constants::netstack::COMPONENT_NAME,
855                                        )),
856                                    ])
857                                    .into_iter()
858                                    .flatten(),
859                            )
860                            .collect::<Vec<_>>(),
861                    )),
862                    ..Default::default()
863                }
864            }
865            KnownServiceProvider::FakeClock => fnetemul::ChildDef {
866                name: Some(constants::fake_clock::COMPONENT_NAME.to_string()),
867                source: Some(fnetemul::ChildSource::Component(
868                    constants::fake_clock::COMPONENT_URL.to_string(),
869                )),
870                exposes: Some(vec![
871                    fidl_fuchsia_testing::FakeClockMarker::PROTOCOL_NAME.to_string(),
872                    fidl_fuchsia_testing::FakeClockControlMarker::PROTOCOL_NAME.to_string(),
873                ]),
874                uses: Some(fnetemul::ChildUses::Capabilities(vec![fnetemul::Capability::LogSink(
875                    fnetemul::Empty {},
876                )])),
877                ..Default::default()
878            },
879            KnownServiceProvider::FakeSocketProxy => fnetemul::ChildDef {
880                name: Some(constants::fake_socket_proxy::COMPONENT_NAME.to_string()),
881                source: Some(fnetemul::ChildSource::Component(
882                    constants::fake_socket_proxy::COMPONENT_URL.to_string(),
883                )),
884                exposes: Some(vec![
885                    fnp_socketproxy::DnsServerWatcherMarker::PROTOCOL_NAME.to_string(),
886                    fnp_socketproxy::FuchsiaNetworksMarker::PROTOCOL_NAME.to_string(),
887                    fnp_socketproxy::NetworkRegistryMarker::PROTOCOL_NAME.to_string(),
888                    fnp_testing::FakeSocketProxy_Marker::PROTOCOL_NAME.to_string(),
889                ]),
890                uses: Some(fnetemul::ChildUses::Capabilities(vec![
891                    fnetemul::Capability::ChildDep(fnetemul::ChildDep {
892                        is_weak: Some(true),
893                        ..protocol_dep::<fnp_socketproxy::NetworkRegistryMarker>(
894                            constants::netcfg::COMPONENT_NAME,
895                        )
896                    }),
897                ])),
898                ..Default::default()
899            },
900            KnownServiceProvider::FakeNetcfg => fnetemul::ChildDef {
901                name: Some(constants::netcfg::COMPONENT_NAME.to_string()),
902                source: Some(fnetemul::ChildSource::Component(
903                    constants::netcfg::fake::COMPONENT_URL.to_string(),
904                )),
905                exposes: Some(vec![
906                    fnp_properties::NetworksMarker::PROTOCOL_NAME.to_string(),
907                    fnp_socketproxy::NetworkRegistryMarker::PROTOCOL_NAME.to_string(),
908                    fnp_testing::FakeNetcfgMarker::PROTOCOL_NAME.to_string(),
909                ]),
910                ..Default::default()
911            },
912        }
913    }
914}
915
916/// Set the `opaque_iids` structured configuration value for Netstack3.
917pub fn set_netstack3_opaque_iids(netstack: &mut fnetemul::ChildDef, value: bool) {
918    const KEY: &str = "opaque_iids";
919    set_structured_config_value(netstack, KEY.to_owned(), cm_rust::ConfigValue::from(value));
920}
921
922/// Set the `suspend_enabled` structured configuration value for Netstack3.
923pub fn set_netstack3_suspend_enabled(netstack: &mut fnetemul::ChildDef, value: bool) {
924    const KEY: &str = "suspend_enabled";
925    set_structured_config_value(netstack, KEY.to_owned(), cm_rust::ConfigValue::from(value));
926}
927
928/// Set a structured configuration value for the provided component.
929fn set_structured_config_value(
930    component: &mut fnetemul::ChildDef,
931    key: String,
932    value: cm_rust::ConfigValue,
933) {
934    component
935        .config_values
936        .get_or_insert_default()
937        .push(fnetemul::ChildConfigValue { key, value: value.native_into_fidl() });
938}
939
940/// Abstraction for a Fuchsia component which offers network stack services.
941pub trait Netstack: Copy + Clone {
942    /// The Netstack version.
943    const VERSION: NetstackVersion;
944}
945
946/// Uninstantiable type that represents Netstack2's implementation of a
947/// network stack.
948#[derive(Copy, Clone)]
949pub enum Netstack2 {}
950
951impl Netstack for Netstack2 {
952    const VERSION: NetstackVersion = NetstackVersion::Netstack2 { tracing: false, fast_udp: false };
953}
954
955/// Uninstantiable type that represents Netstack2's production implementation of
956/// a network stack.
957#[derive(Copy, Clone)]
958pub enum ProdNetstack2 {}
959
960impl Netstack for ProdNetstack2 {
961    const VERSION: NetstackVersion = NetstackVersion::ProdNetstack2;
962}
963
964/// Uninstantiable type that represents Netstack3's implementation of a
965/// network stack.
966#[derive(Copy, Clone)]
967pub enum Netstack3 {}
968
969impl Netstack for Netstack3 {
970    const VERSION: NetstackVersion = NetstackVersion::Netstack3;
971}
972
973/// Uninstantiable type that represents Netstack3's production implementation of
974/// a network stack.
975#[derive(Copy, Clone)]
976pub enum ProdNetstack3 {}
977
978impl Netstack for ProdNetstack3 {
979    const VERSION: NetstackVersion = NetstackVersion::ProdNetstack3;
980}
981
982/// Abstraction for a Fuchsia component which offers network configuration services.
983pub trait Manager: Copy + Clone {
984    /// The management agent to be used.
985    const MANAGEMENT_AGENT: ManagementAgent;
986}
987
988/// Uninstantiable type that represents netcfg_basic's implementation of a network manager.
989#[derive(Copy, Clone)]
990pub enum NetCfgBasic {}
991
992impl Manager for NetCfgBasic {
993    const MANAGEMENT_AGENT: ManagementAgent = ManagementAgent::NetCfg(NetCfgVersion::Basic);
994}
995
996/// Uninstantiable type that represents netcfg_advanced's implementation of a
997/// network manager.
998#[derive(Copy, Clone)]
999pub enum NetCfgAdvanced {}
1000
1001impl Manager for NetCfgAdvanced {
1002    const MANAGEMENT_AGENT: ManagementAgent = ManagementAgent::NetCfg(NetCfgVersion::Advanced);
1003}
1004
1005pub use netemul::{DhcpClient, DhcpClientVersion, InStack, OutOfStack};
1006
1007/// A combination of Netstack and DhcpClient guaranteed to be compatible with
1008/// each other.
1009pub trait NetstackAndDhcpClient: Copy + Clone {
1010    /// The netstack to be used.
1011    type Netstack: Netstack;
1012    /// The DHCP client to be used.
1013    type DhcpClient: DhcpClient;
1014}
1015
1016/// Netstack2 with the in-stack DHCP client.
1017#[derive(Copy, Clone)]
1018pub enum Netstack2AndInStackDhcpClient {}
1019
1020impl NetstackAndDhcpClient for Netstack2AndInStackDhcpClient {
1021    type Netstack = Netstack2;
1022    type DhcpClient = InStack;
1023}
1024
1025/// Netstack2 with the out-of-stack DHCP client.
1026#[derive(Copy, Clone)]
1027pub enum Netstack2AndOutOfStackDhcpClient {}
1028
1029impl NetstackAndDhcpClient for Netstack2AndOutOfStackDhcpClient {
1030    type Netstack = Netstack2;
1031    type DhcpClient = OutOfStack;
1032}
1033
1034/// Netstack3 with the out-of-stack DHCP client.
1035#[derive(Copy, Clone)]
1036pub enum Netstack3AndOutOfStackDhcpClient {}
1037
1038impl NetstackAndDhcpClient for Netstack3AndOutOfStackDhcpClient {
1039    type Netstack = Netstack3;
1040    type DhcpClient = OutOfStack;
1041}
1042
1043/// Helpers for `netemul::TestSandbox`.
1044#[async_trait]
1045pub trait TestSandboxExt {
1046    /// Creates a realm with Netstack services.
1047    fn create_netstack_realm<'a, N, S>(&'a self, name: S) -> Result<netemul::TestRealm<'a>>
1048    where
1049        N: Netstack,
1050        S: Into<Cow<'a, str>>;
1051
1052    /// Creates a realm with the base Netstack services plus additional ones in
1053    /// `children`.
1054    fn create_netstack_realm_with<'a, N, S, I>(
1055        &'a self,
1056        name: S,
1057        children: I,
1058    ) -> Result<netemul::TestRealm<'a>>
1059    where
1060        S: Into<Cow<'a, str>>,
1061        N: Netstack,
1062        I: IntoIterator,
1063        I::Item: Into<fnetemul::ChildDef>;
1064}
1065
1066#[async_trait]
1067impl TestSandboxExt for netemul::TestSandbox {
1068    fn create_netstack_realm<'a, N, S>(&'a self, name: S) -> Result<netemul::TestRealm<'a>>
1069    where
1070        N: Netstack,
1071        S: Into<Cow<'a, str>>,
1072    {
1073        self.create_netstack_realm_with::<N, _, _>(name, std::iter::empty::<fnetemul::ChildDef>())
1074    }
1075
1076    fn create_netstack_realm_with<'a, N, S, I>(
1077        &'a self,
1078        name: S,
1079        children: I,
1080    ) -> Result<netemul::TestRealm<'a>>
1081    where
1082        S: Into<Cow<'a, str>>,
1083        N: Netstack,
1084        I: IntoIterator,
1085        I::Item: Into<fnetemul::ChildDef>,
1086    {
1087        self.create_realm(
1088            name,
1089            [KnownServiceProvider::Netstack(N::VERSION)]
1090                .iter()
1091                .map(fnetemul::ChildDef::from)
1092                .chain(children.into_iter().map(Into::into)),
1093        )
1094    }
1095}
1096
1097/// Helpers for `netemul::TestRealm`.
1098#[async_trait]
1099pub trait TestRealmExt {
1100    /// Returns the properties of the loopback interface, or `None` if there is no
1101    /// loopback interface.
1102    async fn loopback_properties(
1103        &self,
1104    ) -> Result<Option<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>>>;
1105
1106    /// Get a `fuchsia.net.interfaces.admin/Control` client proxy for the
1107    /// interface identified by [`id`] via `fuchsia.net.root`.
1108    ///
1109    /// Note that one should prefer to operate on a `TestInterface` if it is
1110    /// available; but this method exists in order to obtain a Control channel
1111    /// for interfaces such as loopback.
1112    fn interface_control(&self, id: u64) -> Result<fnet_interfaces_ext::admin::Control>;
1113}
1114
1115#[async_trait]
1116impl TestRealmExt for netemul::TestRealm<'_> {
1117    async fn loopback_properties(
1118        &self,
1119    ) -> Result<Option<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>>> {
1120        let interface_state = self
1121            .connect_to_protocol::<fnet_interfaces::StateMarker>()
1122            .context("failed to connect to fuchsia.net.interfaces/State")?;
1123
1124        let properties = fnet_interfaces_ext::existing(
1125            fnet_interfaces_ext::event_stream_from_state(&interface_state, Default::default())
1126                .expect("create watcher event stream"),
1127            HashMap::<u64, fnet_interfaces_ext::PropertiesAndState<(), _>>::new(),
1128        )
1129        .await
1130        .context("failed to get existing interface properties from watcher")?
1131        .into_iter()
1132        .find_map(|(_id, properties_and_state): (u64, _)| {
1133            let fnet_interfaces_ext::PropertiesAndState {
1134                properties: properties @ fnet_interfaces_ext::Properties { port_class, .. },
1135                state: (),
1136            } = properties_and_state;
1137            port_class.is_loopback().then_some(properties)
1138        });
1139        Ok(properties)
1140    }
1141
1142    fn interface_control(&self, id: u64) -> Result<fnet_interfaces_ext::admin::Control> {
1143        let root_control = self
1144            .connect_to_protocol::<fnet_root::InterfacesMarker>()
1145            .context("connect to protocol")?;
1146
1147        let (control, server) = fnet_interfaces_ext::admin::Control::create_endpoints()
1148            .context("create Control proxy")?;
1149        root_control.get_admin(id, server).context("get admin")?;
1150        Ok(control)
1151    }
1152}