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(protocol_dep::<
523                                        fnet_filter::ControlMarker,
524                                    >(
525                                        constants::netstack::COMPONENT_NAME,
526                                    )),
527                                    fnetemul::Capability::ChildDep(protocol_dep::<
528                                        fnet_filter_deprecated::FilterMarker,
529                                    >(
530                                        constants::netstack::COMPONENT_NAME,
531                                    )),
532                                    fnetemul::Capability::ChildDep(protocol_dep::<
533                                        fnet_interfaces::StateMarker,
534                                    >(
535                                        constants::netstack::COMPONENT_NAME,
536                                    )),
537                                    fnetemul::Capability::ChildDep(protocol_dep::<
538                                        fnet_interfaces_admin::InstallerMarker,
539                                    >(
540                                        constants::netstack::COMPONENT_NAME,
541                                    )),
542                                    fnetemul::Capability::ChildDep(protocol_dep::<
543                                        fnet_stack::StackMarker,
544                                    >(
545                                        constants::netstack::COMPONENT_NAME,
546                                    )),
547                                    fnetemul::Capability::ChildDep(protocol_dep::<
548                                        fnet_routes_admin::RouteTableV4Marker,
549                                    >(
550                                        constants::netstack::COMPONENT_NAME,
551                                    )),
552                                    fnetemul::Capability::ChildDep(protocol_dep::<
553                                        fnet_routes_admin::RouteTableV6Marker,
554                                    >(
555                                        constants::netstack::COMPONENT_NAME,
556                                    )),
557                                    fnetemul::Capability::ChildDep(protocol_dep::<
558                                        fnet_routes_admin::RuleTableV4Marker,
559                                    >(
560                                        constants::netstack::COMPONENT_NAME,
561                                    )),
562                                    fnetemul::Capability::ChildDep(protocol_dep::<
563                                        fnet_routes_admin::RuleTableV6Marker,
564                                    >(
565                                        constants::netstack::COMPONENT_NAME,
566                                    )),
567                                    fnetemul::Capability::ChildDep(protocol_dep::<
568                                        fnet_name::DnsServerWatcherMarker,
569                                    >(
570                                        constants::netstack::COMPONENT_NAME,
571                                    )),
572                                    fnetemul::Capability::ChildDep(protocol_dep::<
573                                        fnet_name::LookupAdminMarker,
574                                    >(
575                                        constants::dns_resolver::COMPONENT_NAME,
576                                    )),
577                                    fnetemul::Capability::ChildDep(protocol_dep::<
578                                        fnet_ndp::RouterAdvertisementOptionWatcherProviderMarker,
579                                    >(
580                                        constants::netstack::COMPONENT_NAME,
581                                    )),
582                                    fnetemul::Capability::NetemulDevfs(fnetemul::DevfsDep {
583                                        name: Some(
584                                            constants::netcfg::DEV_CLASS_NETWORK.to_string(),
585                                        ),
586                                        subdir: Some(
587                                            constants::netcfg::CLASS_NETWORK_PATH.to_string(),
588                                        ),
589                                        ..Default::default()
590                                    }),
591                                    fnetemul::Capability::StorageDep(fnetemul::StorageDep {
592                                        variant: Some(fnetemul::StorageVariant::Data),
593                                        path: Some("/data".to_string()),
594                                        ..Default::default()
595                                    }),
596                                ]
597                                .into_iter(),
598                            )
599                            .collect(),
600                    )),
601                    eager: Some(true),
602                    ..Default::default()
603                }
604            }
605            KnownServiceProvider::SecureStash => fnetemul::ChildDef {
606                name: Some(constants::secure_stash::COMPONENT_NAME.to_string()),
607                source: Some(fnetemul::ChildSource::Component(
608                    constants::secure_stash::COMPONENT_URL.to_string(),
609                )),
610                exposes: Some(vec![fstash::SecureStoreMarker::PROTOCOL_NAME.to_string()]),
611                uses: Some(fnetemul::ChildUses::Capabilities(vec![
612                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
613                    fnetemul::Capability::StorageDep(fnetemul::StorageDep {
614                        variant: Some(fnetemul::StorageVariant::Data),
615                        path: Some("/data".to_string()),
616                        ..Default::default()
617                    }),
618                ])),
619                ..Default::default()
620            },
621            KnownServiceProvider::DhcpServer { persistent } => fnetemul::ChildDef {
622                name: Some(constants::dhcp_server::COMPONENT_NAME.to_string()),
623                source: Some(fnetemul::ChildSource::Component(
624                    constants::dhcp_server::COMPONENT_URL.to_string(),
625                )),
626                exposes: Some(vec![fnet_dhcp::Server_Marker::PROTOCOL_NAME.to_string()]),
627                uses: Some(fnetemul::ChildUses::Capabilities(
628                    [
629                        fnetemul::Capability::LogSink(fnetemul::Empty {}),
630                        fnetemul::Capability::ChildDep(protocol_dep::<
631                            fnet_neighbor::ControllerMarker,
632                        >(
633                            constants::netstack::COMPONENT_NAME
634                        )),
635                        fnetemul::Capability::ChildDep(
636                            protocol_dep::<fposix_socket::ProviderMarker>(
637                                constants::netstack::COMPONENT_NAME,
638                            ),
639                        ),
640                        fnetemul::Capability::ChildDep(protocol_dep::<
641                            fposix_socket_packet::ProviderMarker,
642                        >(
643                            constants::netstack::COMPONENT_NAME
644                        )),
645                    ]
646                    .into_iter()
647                    .chain(persistent.then_some(fnetemul::Capability::ChildDep(protocol_dep::<
648                        fstash::SecureStoreMarker,
649                    >(
650                        constants::secure_stash::COMPONENT_NAME,
651                    ))))
652                    .collect(),
653                )),
654                program_args: if *persistent {
655                    Some(vec![String::from("--persistent")])
656                } else {
657                    None
658                },
659                ..Default::default()
660            },
661            KnownServiceProvider::DhcpClient => fnetemul::ChildDef {
662                name: Some(constants::dhcp_client::COMPONENT_NAME.to_string()),
663                source: Some(fnetemul::ChildSource::Component(
664                    constants::dhcp_client::COMPONENT_URL.to_string(),
665                )),
666                exposes: Some(vec![fnet_dhcp::ClientProviderMarker::PROTOCOL_NAME.to_string()]),
667                uses: Some(fnetemul::ChildUses::Capabilities(vec![
668                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
669                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
670                        constants::netstack::COMPONENT_NAME,
671                    )),
672                    fnetemul::Capability::ChildDep(protocol_dep::<
673                        fposix_socket_packet::ProviderMarker,
674                    >(
675                        constants::netstack::COMPONENT_NAME
676                    )),
677                ])),
678                program_args: None,
679                ..Default::default()
680            },
681            KnownServiceProvider::Dhcpv6Client => fnetemul::ChildDef {
682                name: Some(constants::dhcpv6_client::COMPONENT_NAME.to_string()),
683                source: Some(fnetemul::ChildSource::Component(
684                    constants::dhcpv6_client::COMPONENT_URL.to_string(),
685                )),
686                exposes: Some(vec![fnet_dhcpv6::ClientProviderMarker::PROTOCOL_NAME.to_string()]),
687                uses: Some(fnetemul::ChildUses::Capabilities(vec![
688                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
689                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
690                        constants::netstack::COMPONENT_NAME,
691                    )),
692                ])),
693                ..Default::default()
694            },
695            KnownServiceProvider::DnsResolver => fnetemul::ChildDef {
696                name: Some(constants::dns_resolver::COMPONENT_NAME.to_string()),
697                source: Some(fnetemul::ChildSource::Component(
698                    constants::dns_resolver::COMPONENT_URL.to_string(),
699                )),
700                exposes: Some(vec![
701                    fnet_name::LookupAdminMarker::PROTOCOL_NAME.to_string(),
702                    fnet_name::LookupMarker::PROTOCOL_NAME.to_string(),
703                ]),
704                uses: Some(fnetemul::ChildUses::Capabilities(vec![
705                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
706                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateMarker>(
707                        constants::netstack::COMPONENT_NAME,
708                    )),
709                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
710                        constants::netstack::COMPONENT_NAME,
711                    )),
712                    fnetemul::Capability::ChildDep(protocol_dep::<
713                        fidl_fuchsia_testing::FakeClockMarker,
714                    >(
715                        constants::fake_clock::COMPONENT_NAME
716                    )),
717                ])),
718                ..Default::default()
719            },
720            KnownServiceProvider::Reachability { eager } => fnetemul::ChildDef {
721                name: Some(constants::reachability::COMPONENT_NAME.to_string()),
722                source: Some(fnetemul::ChildSource::Component(
723                    constants::reachability::COMPONENT_URL.to_string(),
724                )),
725                exposes: Some(vec![fnet_reachability::MonitorMarker::PROTOCOL_NAME.to_string()]),
726                uses: Some(fnetemul::ChildUses::Capabilities(vec![
727                    fnetemul::Capability::LogSink(fnetemul::Empty {}),
728                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_interfaces::StateMarker>(
729                        constants::netstack::COMPONENT_NAME,
730                    )),
731                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
732                        constants::netstack::COMPONENT_NAME,
733                    )),
734                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_name::LookupMarker>(
735                        constants::dns_resolver::COMPONENT_NAME,
736                    )),
737                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_neighbor::ViewMarker>(
738                        constants::netstack::COMPONENT_NAME,
739                    )),
740                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_debug::InterfacesMarker>(
741                        constants::netstack::COMPONENT_NAME,
742                    )),
743                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_root::InterfacesMarker>(
744                        constants::netstack::COMPONENT_NAME,
745                    )),
746                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateV4Marker>(
747                        constants::netstack::COMPONENT_NAME,
748                    )),
749                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_routes::StateV6Marker>(
750                        constants::netstack::COMPONENT_NAME,
751                    )),
752                    fnetemul::Capability::ChildDep(protocol_dep::<fnet_debug::DiagnosticsMarker>(
753                        constants::netstack::COMPONENT_NAME,
754                    )),
755                    fnetemul::Capability::ChildDep(protocol_dep::<
756                        fidl_fuchsia_testing::FakeClockMarker,
757                    >(
758                        constants::fake_clock::COMPONENT_NAME
759                    )),
760                ])),
761                eager: Some(*eager),
762                ..Default::default()
763            },
764            KnownServiceProvider::SocketProxy => fnetemul::ChildDef {
765                name: Some(constants::socket_proxy::COMPONENT_NAME.to_string()),
766                source: Some(fnetemul::ChildSource::Component(
767                    constants::socket_proxy::COMPONENT_URL.to_string(),
768                )),
769                exposes: Some(vec![
770                    fposix_socket::ProviderMarker::PROTOCOL_NAME.to_string(),
771                    fposix_socket_raw::ProviderMarker::PROTOCOL_NAME.to_string(),
772                    fnp_socketproxy::StarnixNetworksMarker::PROTOCOL_NAME.to_string(),
773                    fnp_socketproxy::FuchsiaNetworksMarker::PROTOCOL_NAME.to_string(),
774                    fnp_socketproxy::DnsServerWatcherMarker::PROTOCOL_NAME.to_string(),
775                    fnp_properties::DefaultNetworkWatcherMarker::PROTOCOL_NAME.to_string(),
776                ]),
777                uses: Some(fnetemul::ChildUses::Capabilities(vec![
778                    fnetemul::Capability::ChildDep(protocol_dep::<fposix_socket::ProviderMarker>(
779                        constants::netstack::COMPONENT_NAME,
780                    )),
781                    fnetemul::Capability::ChildDep(
782                        protocol_dep::<fposix_socket_raw::ProviderMarker>(
783                            constants::netstack::COMPONENT_NAME,
784                        ),
785                    ),
786                ])),
787                ..Default::default()
788            },
789            KnownServiceProvider::NetworkTestRealm { require_outer_netstack } => {
790                fnetemul::ChildDef {
791                    name: Some(constants::network_test_realm::COMPONENT_NAME.to_string()),
792                    source: Some(fnetemul::ChildSource::Component(
793                        constants::network_test_realm::COMPONENT_URL.to_string(),
794                    )),
795                    exposes: Some(vec![
796                        fntr::ControllerMarker::PROTOCOL_NAME.to_string(),
797                        fcomponent::RealmMarker::PROTOCOL_NAME.to_string(),
798                    ]),
799                    uses: Some(fnetemul::ChildUses::Capabilities(
800                        std::iter::once(fnetemul::Capability::LogSink(fnetemul::Empty {}))
801                            .chain(
802                                require_outer_netstack
803                                    .then_some([
804                                        fnetemul::Capability::ChildDep(protocol_dep::<
805                                            fnet_stack::StackMarker,
806                                        >(
807                                            constants::netstack::COMPONENT_NAME,
808                                        )),
809                                        fnetemul::Capability::ChildDep(protocol_dep::<
810                                            fnet_debug::InterfacesMarker,
811                                        >(
812                                            constants::netstack::COMPONENT_NAME,
813                                        )),
814                                        fnetemul::Capability::ChildDep(protocol_dep::<
815                                            fnet_root::InterfacesMarker,
816                                        >(
817                                            constants::netstack::COMPONENT_NAME,
818                                        )),
819                                        fnetemul::Capability::ChildDep(protocol_dep::<
820                                            fnet_interfaces::StateMarker,
821                                        >(
822                                            constants::netstack::COMPONENT_NAME,
823                                        )),
824                                    ])
825                                    .into_iter()
826                                    .flatten(),
827                            )
828                            .collect::<Vec<_>>(),
829                    )),
830                    ..Default::default()
831                }
832            }
833            KnownServiceProvider::FakeClock => fnetemul::ChildDef {
834                name: Some(constants::fake_clock::COMPONENT_NAME.to_string()),
835                source: Some(fnetemul::ChildSource::Component(
836                    constants::fake_clock::COMPONENT_URL.to_string(),
837                )),
838                exposes: Some(vec![
839                    fidl_fuchsia_testing::FakeClockMarker::PROTOCOL_NAME.to_string(),
840                    fidl_fuchsia_testing::FakeClockControlMarker::PROTOCOL_NAME.to_string(),
841                ]),
842                uses: Some(fnetemul::ChildUses::Capabilities(vec![fnetemul::Capability::LogSink(
843                    fnetemul::Empty {},
844                )])),
845                ..Default::default()
846            },
847            KnownServiceProvider::FakeSocketProxy => fnetemul::ChildDef {
848                name: Some(constants::fake_socket_proxy::COMPONENT_NAME.to_string()),
849                source: Some(fnetemul::ChildSource::Component(
850                    constants::fake_socket_proxy::COMPONENT_URL.to_string(),
851                )),
852                exposes: Some(vec![
853                    fnp_properties::DefaultNetworkWatcherMarker::PROTOCOL_NAME.to_string(),
854                    fnp_socketproxy::DnsServerWatcherMarker::PROTOCOL_NAME.to_string(),
855                    fnp_socketproxy::FuchsiaNetworksMarker::PROTOCOL_NAME.to_string(),
856                    fnp_testing::FakeSocketProxy_Marker::PROTOCOL_NAME.to_string(),
857                ]),
858                ..Default::default()
859            },
860            KnownServiceProvider::FakeNetcfg => fnetemul::ChildDef {
861                name: Some(constants::netcfg::COMPONENT_NAME.to_string()),
862                source: Some(fnetemul::ChildSource::Component(
863                    constants::netcfg::fake::COMPONENT_URL.to_string(),
864                )),
865                exposes: Some(vec![
866                    fnp_properties::NetworksMarker::PROTOCOL_NAME.to_string(),
867                    fnp_testing::FakeNetcfgMarker::PROTOCOL_NAME.to_string(),
868                ]),
869                ..Default::default()
870            },
871        }
872    }
873}
874
875/// Set the `opaque_iids` structured configuration value for Netstack3.
876pub fn set_netstack3_opaque_iids(netstack: &mut fnetemul::ChildDef, value: bool) {
877    const KEY: &str = "opaque_iids";
878    set_structured_config_value(netstack, KEY.to_owned(), cm_rust::ConfigValue::from(value));
879}
880
881/// Set the `suspend_enabled` structured configuration value for Netstack3.
882pub fn set_netstack3_suspend_enabled(netstack: &mut fnetemul::ChildDef, value: bool) {
883    const KEY: &str = "suspend_enabled";
884    set_structured_config_value(netstack, KEY.to_owned(), cm_rust::ConfigValue::from(value));
885}
886
887/// Set a structured configuration value for the provided component.
888fn set_structured_config_value(
889    component: &mut fnetemul::ChildDef,
890    key: String,
891    value: cm_rust::ConfigValue,
892) {
893    component
894        .config_values
895        .get_or_insert_default()
896        .push(fnetemul::ChildConfigValue { key, value: value.native_into_fidl() });
897}
898
899/// Abstraction for a Fuchsia component which offers network stack services.
900pub trait Netstack: Copy + Clone {
901    /// The Netstack version.
902    const VERSION: NetstackVersion;
903}
904
905/// Uninstantiable type that represents Netstack2's implementation of a
906/// network stack.
907#[derive(Copy, Clone)]
908pub enum Netstack2 {}
909
910impl Netstack for Netstack2 {
911    const VERSION: NetstackVersion = NetstackVersion::Netstack2 { tracing: false, fast_udp: false };
912}
913
914/// Uninstantiable type that represents Netstack2's production implementation of
915/// a network stack.
916#[derive(Copy, Clone)]
917pub enum ProdNetstack2 {}
918
919impl Netstack for ProdNetstack2 {
920    const VERSION: NetstackVersion = NetstackVersion::ProdNetstack2;
921}
922
923/// Uninstantiable type that represents Netstack3's implementation of a
924/// network stack.
925#[derive(Copy, Clone)]
926pub enum Netstack3 {}
927
928impl Netstack for Netstack3 {
929    const VERSION: NetstackVersion = NetstackVersion::Netstack3;
930}
931
932/// Uninstantiable type that represents Netstack3's production implementation of
933/// a network stack.
934#[derive(Copy, Clone)]
935pub enum ProdNetstack3 {}
936
937impl Netstack for ProdNetstack3 {
938    const VERSION: NetstackVersion = NetstackVersion::ProdNetstack3;
939}
940
941/// Abstraction for a Fuchsia component which offers network configuration services.
942pub trait Manager: Copy + Clone {
943    /// The management agent to be used.
944    const MANAGEMENT_AGENT: ManagementAgent;
945}
946
947/// Uninstantiable type that represents netcfg_basic's implementation of a network manager.
948#[derive(Copy, Clone)]
949pub enum NetCfgBasic {}
950
951impl Manager for NetCfgBasic {
952    const MANAGEMENT_AGENT: ManagementAgent = ManagementAgent::NetCfg(NetCfgVersion::Basic);
953}
954
955/// Uninstantiable type that represents netcfg_advanced's implementation of a
956/// network manager.
957#[derive(Copy, Clone)]
958pub enum NetCfgAdvanced {}
959
960impl Manager for NetCfgAdvanced {
961    const MANAGEMENT_AGENT: ManagementAgent = ManagementAgent::NetCfg(NetCfgVersion::Advanced);
962}
963
964pub use netemul::{DhcpClient, DhcpClientVersion, InStack, OutOfStack};
965
966/// A combination of Netstack and DhcpClient guaranteed to be compatible with
967/// each other.
968pub trait NetstackAndDhcpClient: Copy + Clone {
969    /// The netstack to be used.
970    type Netstack: Netstack;
971    /// The DHCP client to be used.
972    type DhcpClient: DhcpClient;
973}
974
975/// Netstack2 with the in-stack DHCP client.
976#[derive(Copy, Clone)]
977pub enum Netstack2AndInStackDhcpClient {}
978
979impl NetstackAndDhcpClient for Netstack2AndInStackDhcpClient {
980    type Netstack = Netstack2;
981    type DhcpClient = InStack;
982}
983
984/// Netstack2 with the out-of-stack DHCP client.
985#[derive(Copy, Clone)]
986pub enum Netstack2AndOutOfStackDhcpClient {}
987
988impl NetstackAndDhcpClient for Netstack2AndOutOfStackDhcpClient {
989    type Netstack = Netstack2;
990    type DhcpClient = OutOfStack;
991}
992
993/// Netstack3 with the out-of-stack DHCP client.
994#[derive(Copy, Clone)]
995pub enum Netstack3AndOutOfStackDhcpClient {}
996
997impl NetstackAndDhcpClient for Netstack3AndOutOfStackDhcpClient {
998    type Netstack = Netstack3;
999    type DhcpClient = OutOfStack;
1000}
1001
1002/// Helpers for `netemul::TestSandbox`.
1003#[async_trait]
1004pub trait TestSandboxExt {
1005    /// Creates a realm with Netstack services.
1006    fn create_netstack_realm<'a, N, S>(&'a self, name: S) -> Result<netemul::TestRealm<'a>>
1007    where
1008        N: Netstack,
1009        S: Into<Cow<'a, str>>;
1010
1011    /// Creates a realm with the base Netstack services plus additional ones in
1012    /// `children`.
1013    fn create_netstack_realm_with<'a, N, S, I>(
1014        &'a self,
1015        name: S,
1016        children: I,
1017    ) -> Result<netemul::TestRealm<'a>>
1018    where
1019        S: Into<Cow<'a, str>>,
1020        N: Netstack,
1021        I: IntoIterator,
1022        I::Item: Into<fnetemul::ChildDef>;
1023}
1024
1025#[async_trait]
1026impl TestSandboxExt for netemul::TestSandbox {
1027    fn create_netstack_realm<'a, N, S>(&'a self, name: S) -> Result<netemul::TestRealm<'a>>
1028    where
1029        N: Netstack,
1030        S: Into<Cow<'a, str>>,
1031    {
1032        self.create_netstack_realm_with::<N, _, _>(name, std::iter::empty::<fnetemul::ChildDef>())
1033    }
1034
1035    fn create_netstack_realm_with<'a, N, S, I>(
1036        &'a self,
1037        name: S,
1038        children: I,
1039    ) -> Result<netemul::TestRealm<'a>>
1040    where
1041        S: Into<Cow<'a, str>>,
1042        N: Netstack,
1043        I: IntoIterator,
1044        I::Item: Into<fnetemul::ChildDef>,
1045    {
1046        self.create_realm(
1047            name,
1048            [KnownServiceProvider::Netstack(N::VERSION)]
1049                .iter()
1050                .map(fnetemul::ChildDef::from)
1051                .chain(children.into_iter().map(Into::into)),
1052        )
1053    }
1054}
1055
1056/// Helpers for `netemul::TestRealm`.
1057#[async_trait]
1058pub trait TestRealmExt {
1059    /// Returns the properties of the loopback interface, or `None` if there is no
1060    /// loopback interface.
1061    async fn loopback_properties(
1062        &self,
1063    ) -> Result<Option<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>>>;
1064
1065    /// Get a `fuchsia.net.interfaces.admin/Control` client proxy for the
1066    /// interface identified by [`id`] via `fuchsia.net.root`.
1067    ///
1068    /// Note that one should prefer to operate on a `TestInterface` if it is
1069    /// available; but this method exists in order to obtain a Control channel
1070    /// for interfaces such as loopback.
1071    fn interface_control(&self, id: u64) -> Result<fnet_interfaces_ext::admin::Control>;
1072}
1073
1074#[async_trait]
1075impl TestRealmExt for netemul::TestRealm<'_> {
1076    async fn loopback_properties(
1077        &self,
1078    ) -> Result<Option<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>>> {
1079        let interface_state = self
1080            .connect_to_protocol::<fnet_interfaces::StateMarker>()
1081            .context("failed to connect to fuchsia.net.interfaces/State")?;
1082
1083        let properties = fnet_interfaces_ext::existing(
1084            fnet_interfaces_ext::event_stream_from_state(&interface_state, Default::default())
1085                .expect("create watcher event stream"),
1086            HashMap::<u64, fnet_interfaces_ext::PropertiesAndState<(), _>>::new(),
1087        )
1088        .await
1089        .context("failed to get existing interface properties from watcher")?
1090        .into_iter()
1091        .find_map(|(_id, properties_and_state): (u64, _)| {
1092            let fnet_interfaces_ext::PropertiesAndState {
1093                properties: properties @ fnet_interfaces_ext::Properties { port_class, .. },
1094                state: (),
1095            } = properties_and_state;
1096            port_class.is_loopback().then_some(properties)
1097        });
1098        Ok(properties)
1099    }
1100
1101    fn interface_control(&self, id: u64) -> Result<fnet_interfaces_ext::admin::Control> {
1102        let root_control = self
1103            .connect_to_protocol::<fnet_root::InterfacesMarker>()
1104            .context("connect to protocol")?;
1105
1106        let (control, server) = fnet_interfaces_ext::admin::Control::create_endpoints()
1107            .context("create Control proxy")?;
1108        let () = root_control.get_admin(id, server).context("get admin")?;
1109        Ok(control)
1110    }
1111}