policy_testing_common/
lib.rs

1// Copyright 2024 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
5use std::pin::pin;
6
7use fidl::endpoints::ProtocolMarker;
8use fidl_fuchsia_net_routes_ext::admin::FidlRouteAdminIpExt;
9use fidl_fuchsia_net_routes_ext::{self as fnet_routes_ext, FidlRouteIpExt};
10use futures::future::{FutureExt as _, LocalBoxFuture};
11use net_types::ip::{self as net_types_ip, Ip};
12use netemul::{TestEndpoint, TestNetwork, TestRealm};
13use netstack_testing_common::realms::{
14    KnownServiceProvider, Manager, ManagerConfig, Netstack, SocketProxyType, TestRealmExt,
15    TestSandboxExt, constants,
16};
17use netstack_testing_common::{
18    ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT, interfaces, wait_for_component_stopped,
19};
20use {
21    fidl_fuchsia_net_interfaces as fnet_interfaces, fidl_fuchsia_net_resources as fnet_resources,
22    fidl_fuchsia_net_routes as fnet_routes, fidl_fuchsia_netemul_network as fnetemul_network,
23};
24
25#[derive(Default)]
26pub struct NetcfgOwnedDeviceArgs {
27    // Whether to use the out of stack DHCP client.
28    pub use_out_of_stack_dhcp_client: bool,
29    // Whether to include the socketproxy protocols in netcfg.
30    pub socket_proxy_type: SocketProxyType,
31    // Additional service providers to include in the realm.
32    pub extra_known_service_providers: Vec<KnownServiceProvider>,
33}
34
35/// Initialize a realm with a device that is owned by netcfg.
36/// The device is discovered through devfs and installed into
37/// the Netstack via netcfg. `after_interface_up` is called
38/// once the interface has been discovered via the Netstack
39/// interfaces watcher.
40pub async fn with_netcfg_owned_device<
41    M: Manager,
42    N: Netstack,
43    F: for<'a> FnOnce(
44        u64,
45        &'a netemul::TestNetwork<'a>,
46        &'a fnet_interfaces::StateProxy,
47        &'a netemul::TestRealm<'a>,
48        &'a netemul::TestSandbox,
49    ) -> LocalBoxFuture<'a, ()>,
50>(
51    name: &str,
52    manager_config: ManagerConfig,
53    additional_args: NetcfgOwnedDeviceArgs,
54    after_interface_up: F,
55) -> String {
56    let NetcfgOwnedDeviceArgs {
57        use_out_of_stack_dhcp_client,
58        socket_proxy_type,
59        extra_known_service_providers,
60    } = additional_args;
61    let sandbox = netemul::TestSandbox::new().expect("create sandbox");
62    let realm = sandbox
63        .create_netstack_realm_with::<N, _, _>(
64            name,
65            [
66                KnownServiceProvider::Manager {
67                    agent: M::MANAGEMENT_AGENT,
68                    use_dhcp_server: false,
69                    use_out_of_stack_dhcp_client,
70                    socket_proxy_type,
71                    config: manager_config,
72                },
73                KnownServiceProvider::DnsResolver,
74                KnownServiceProvider::FakeClock,
75            ]
76            .into_iter()
77            .chain(extra_known_service_providers)
78            // If the client requested an out of stack DHCP client or to use
79            // the socket proxy, add them to the list of service providers.
80            .chain(
81                use_out_of_stack_dhcp_client
82                    .then_some(KnownServiceProvider::DhcpClient)
83                    .into_iter(),
84            )
85            .chain(socket_proxy_type.known_service_provider().into_iter()),
86        )
87        .expect("create netstack realm");
88    // Add a device to the realm.
89    let network = sandbox.create_network(name).await.expect("create network");
90    let _endpoint = add_device_to_devfs::<M>(&network, &realm, name.to_string(), None).await;
91
92    // Make sure the Netstack got the new device added.
93    let (interface_state, if_id, if_name) = verify_interface_added::<M>(&realm).await;
94
95    after_interface_up(if_id, &network, &interface_state, &realm, &sandbox).await;
96
97    // Wait for orderly shutdown of the test realm to complete before allowing
98    // test interfaces to be cleaned up.
99    //
100    // This is necessary to prevent test interfaces from being removed while
101    // NetCfg is still in the process of configuring them after adding them to
102    // the Netstack, which causes spurious errors.
103    realm.shutdown().await.expect("failed to shutdown realm");
104
105    if_name
106}
107
108/// Add a device into devfs to be fully managed by netcfg. Netcfg will discover
109/// and install the device into the Netstack. We cannot assume that netcfg has
110/// observed and installed the device as a result of this function, that
111/// condition must be observed via the interfaces watcher.
112///
113/// Returns the new endpoint that was added to the realm.
114pub async fn add_device_to_devfs<'a, M: Manager>(
115    network: &'a TestNetwork<'a>,
116    realm: &'a TestRealm<'a>,
117    name: String,
118    endpoint_config: Option<fnetemul_network::EndpointConfig>,
119) -> TestEndpoint<'a> {
120    let endpoint = match endpoint_config {
121        Some(config) => network.create_endpoint_with(name, config).await,
122        None => network.create_endpoint(name).await,
123    }
124    .expect("create endpoint");
125    endpoint.set_link_up(true).await.expect("set link up");
126    let endpoint_mount_path = netemul::devfs_device_path("ep");
127    let endpoint_mount_path = endpoint_mount_path.as_path();
128    realm.add_virtual_device(&endpoint, endpoint_mount_path).await.unwrap_or_else(|e| {
129        panic!("add virtual device {}: {:?}", endpoint_mount_path.display(), e)
130    });
131    endpoint
132}
133
134/// Observe a new non-loopback interface via Netstack's interface watcher.
135///
136/// Returns the interface state proxy and the interface's id and name.
137pub async fn verify_interface_added<'a, M: Manager>(
138    realm: &'a TestRealm<'a>,
139) -> (fnet_interfaces::StateProxy, u64, String) {
140    let interface_state = realm
141        .connect_to_protocol::<fnet_interfaces::StateMarker>()
142        .expect("connect to fuchsia.net.interfaces/State service");
143    let wait_for_netmgr =
144        wait_for_component_stopped(&realm, constants::netcfg::COMPONENT_NAME, None).fuse();
145    let mut wait_for_netmgr = pin!(wait_for_netmgr);
146    let (if_id, if_name): (u64, String) = interfaces::wait_for_non_loopback_interface_up(
147        &interface_state,
148        &mut wait_for_netmgr,
149        None,
150        ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT,
151    )
152    .await
153    .expect("wait for non loopback interface");
154    (interface_state, if_id, if_name.clone())
155}
156
157/// Add a default route to the provided interface by id. Must not be called
158/// more than once for the same interface.
159///
160/// Returns whether the route was added to the RouteSet.
161pub async fn add_default_route<'a, I>(
162    realm: &'a TestRealm<'a>,
163    if_id: u64,
164    route_set: &<I::RouteSetMarker as ProtocolMarker>::Proxy,
165) -> bool
166where
167    I: Ip + FidlRouteAdminIpExt + FidlRouteIpExt,
168{
169    // Acquire the control for the interface so we can directly add a default route to the
170    // interface without going through DHCP. This meets the condition for the interface to
171    // be added to Fuchsia's NetworkRegistry and set as default if it is Locally provisioned.
172    let client_interface_control =
173        realm.interface_control(if_id).expect("get client interface Control");
174
175    let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } =
176        client_interface_control
177            .get_authorization_for_interface()
178            .await
179            .expect("get authorization");
180
181    fnet_routes_ext::admin::authenticate_for_interface::<I>(
182        route_set,
183        fnet_resources::ProofOfInterfaceAuthorization { interface_id, token },
184    )
185    .await
186    .expect("authenticate for interface should not see FIDL error")
187    .expect("authenticate for interface should succeed");
188
189    let default_route = fnet_routes_ext::Route {
190        destination: net_types_ip::Subnet::new(I::UNSPECIFIED_ADDRESS, 0).expect("invalid subnet"),
191        action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget::<I> {
192            outbound_interface: interface_id,
193            next_hop: None,
194        }),
195        properties: fnet_routes_ext::RouteProperties {
196            specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
197                metric: fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
198            },
199        },
200    };
201
202    fnet_routes_ext::admin::add_route::<I>(
203        route_set,
204        &default_route.try_into().expect("convert into fidl route"),
205    )
206    .await
207    .expect("should not see RouteSet FIDL error")
208    .expect("should not see RouteSet error")
209}