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