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