Skip to main content

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