netstack_testing_common/
interfaces.rs

1// Copyright 2021 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#![warn(missing_docs)]
6
7//! Provides utilities for using `fuchsia.net.interfaces` and
8//! `fuchsia.net.interfaces.admin` in Netstack integration tests.
9
10use super::Result;
11
12use anyhow::Context as _;
13use fuchsia_async::{DurationExt as _, TimeoutExt as _};
14
15use futures::future::{FusedFuture, Future, FutureExt as _, TryFutureExt as _};
16use std::collections::{HashMap, HashSet};
17use std::pin::pin;
18
19/// Waits for a non-loopback interface to come up with an ID not in `exclude_ids`.
20///
21/// Useful when waiting for an interface to be discovered and brought up by a
22/// network manager.
23///
24/// Returns the interface's ID and name.
25pub async fn wait_for_non_loopback_interface_up<
26    F: Unpin + FusedFuture + Future<Output = Result<component_events::events::Stopped>>,
27>(
28    interface_state: &fidl_fuchsia_net_interfaces::StateProxy,
29    mut wait_for_netmgr: &mut F,
30    exclude_ids: Option<&HashSet<u64>>,
31    timeout: zx::MonotonicDuration,
32) -> Result<(u64, String)> {
33    let mut if_map =
34        HashMap::<u64, fidl_fuchsia_net_interfaces_ext::PropertiesAndState<(), _>>::new();
35    let mut wait_for_interface = pin!(
36        fidl_fuchsia_net_interfaces_ext::wait_interface(
37            fidl_fuchsia_net_interfaces_ext::event_stream_from_state::<
38                fidl_fuchsia_net_interfaces_ext::DefaultInterest,
39            >(interface_state, Default::default(),)?,
40            &mut if_map,
41            |if_map| {
42                if_map.iter().find_map(
43                    |(
44                        id,
45                        fidl_fuchsia_net_interfaces_ext::PropertiesAndState {
46                            properties:
47                                fidl_fuchsia_net_interfaces_ext::Properties {
48                                    name,
49                                    port_class,
50                                    online,
51                                    ..
52                                },
53                            state: _,
54                        },
55                    )| {
56                        (*port_class != fidl_fuchsia_net_interfaces_ext::PortClass::Loopback
57                            && *online
58                            && exclude_ids.map_or(true, |ids| !ids.contains(id)))
59                        .then(|| (*id, name.clone()))
60                    },
61                )
62            },
63        )
64        .map_err(anyhow::Error::from)
65        .on_timeout(timeout.after_now(), || Err(anyhow::anyhow!("timed out")))
66        .map(|r| r.context("failed to wait for non-loopback interface up"))
67        .fuse()
68    );
69    futures::select! {
70        wait_for_interface_res = wait_for_interface => {
71            wait_for_interface_res
72        }
73        stopped_event = wait_for_netmgr => {
74            Err(anyhow::anyhow!("the network manager unexpectedly stopped with event = {:?}", stopped_event))
75        }
76    }
77}
78
79/// Add an address, returning once the assignment state is `Assigned`.
80pub async fn add_address_wait_assigned(
81    control: &fidl_fuchsia_net_interfaces_ext::admin::Control,
82    address: fidl_fuchsia_net::Subnet,
83    address_parameters: fidl_fuchsia_net_interfaces_admin::AddressParameters,
84) -> std::result::Result<
85    fidl_fuchsia_net_interfaces_admin::AddressStateProviderProxy,
86    fidl_fuchsia_net_interfaces_ext::admin::AddressStateProviderError,
87> {
88    let (address_state_provider, server) = fidl::endpoints::create_proxy::<
89        fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker,
90    >();
91    let () = control
92        .add_address(&address, &address_parameters, server)
93        .expect("Control.AddAddress FIDL error");
94
95    fidl_fuchsia_net_interfaces_ext::admin::wait_for_address_added_event(
96        &mut address_state_provider.take_event_stream(),
97    )
98    .await?;
99
100    {
101        let mut state_stream =
102            pin!(fidl_fuchsia_net_interfaces_ext::admin::assignment_state_stream(
103                address_state_provider.clone(),
104            ));
105        let () = fidl_fuchsia_net_interfaces_ext::admin::wait_assignment_state(
106            &mut state_stream,
107            fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned,
108        )
109        .await?;
110    }
111    Ok(address_state_provider)
112}
113
114/// Remove a subnet address and route, returning true if the address was removed.
115pub async fn remove_subnet_address_and_route<'a>(
116    iface: &'a netemul::TestInterface<'a>,
117    subnet: fidl_fuchsia_net::Subnet,
118) -> Result<bool> {
119    iface.del_address_and_subnet_route(subnet).await
120}
121
122/// Wait until there is an IPv4 and an IPv6 link-local address assigned to the
123/// interface identified by `id`.
124///
125/// If there are multiple IPv4 or multiple IPv6 link-local addresses assigned,
126/// the choice of which particular address to return is arbitrary and should
127/// not be relied upon.
128///
129/// Note that if a `netemul::TestInterface` is available, helpers on said type
130/// should be preferred over using this function.
131pub async fn wait_for_v4_and_v6_ll(
132    interfaces_state: &fidl_fuchsia_net_interfaces::StateProxy,
133    id: u64,
134) -> Result<(net_types::ip::Ipv4Addr, net_types::ip::Ipv6Addr)> {
135    wait_for_addresses(interfaces_state, id, |addresses| {
136        let (v4, v6) = addresses.into_iter().fold(
137            (None, None),
138            |(v4, v6),
139             &fidl_fuchsia_net_interfaces_ext::Address {
140                 addr: fidl_fuchsia_net::Subnet { addr, prefix_len: _ },
141                 valid_until: _,
142                 preferred_lifetime_info: _,
143                 assignment_state,
144             }| {
145                assert_eq!(
146                    assignment_state,
147                    fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned
148                );
149                match addr {
150                    fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr }) => {
151                        (Some(net_types::ip::Ipv4Addr::from(addr)), v6)
152                    }
153                    fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
154                        let v6_addr = net_types::ip::Ipv6Addr::from_bytes(addr);
155                        (v4, if v6_addr.is_unicast_link_local() { Some(v6_addr) } else { v6 })
156                    }
157                }
158            },
159        );
160        match (v4, v6) {
161            (Some(v4), Some(v6)) => Some((v4, v6)),
162            _ => None,
163        }
164    })
165    .await
166    .context("wait for addresses")
167}
168
169/// Wait until there is an IPv6 link-local address assigned to the interface
170/// identified by `id`.
171///
172/// If there are multiple IPv6 link-local addresses assigned, the choice
173/// of which particular address to return is arbitrary and should not be
174/// relied upon.
175///
176/// Note that if a `netemul::TestInterface` is available, helpers on said type
177/// should be preferred over using this function.
178pub async fn wait_for_v6_ll(
179    interfaces_state: &fidl_fuchsia_net_interfaces::StateProxy,
180    id: u64,
181) -> Result<net_types::ip::Ipv6Addr> {
182    wait_for_addresses(interfaces_state, id, |addresses| {
183        addresses.into_iter().find_map(
184            |&fidl_fuchsia_net_interfaces_ext::Address {
185                 addr: fidl_fuchsia_net::Subnet { addr, prefix_len: _ },
186                 valid_until: _,
187                 preferred_lifetime_info: _,
188                 assignment_state,
189             }| {
190                assert_eq!(
191                    assignment_state,
192                    fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned
193                );
194                match addr {
195                    fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
196                        addr: _,
197                    }) => None,
198                    fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
199                        let v6_addr = net_types::ip::Ipv6Addr::from_bytes(addr);
200                        v6_addr.is_unicast_link_local().then(|| v6_addr)
201                    }
202                }
203            },
204        )
205    })
206    .await
207    .context("wait for IPv6 link-local address")
208}
209
210/// Wait until the given interface has a set of assigned addresses that matches
211/// the given predicate.
212pub async fn wait_for_addresses<T, F>(
213    interfaces_state: &fidl_fuchsia_net_interfaces::StateProxy,
214    id: u64,
215    mut predicate: F,
216) -> Result<T>
217where
218    F: FnMut(
219        &[fidl_fuchsia_net_interfaces_ext::Address<fidl_fuchsia_net_interfaces_ext::AllInterest>],
220    ) -> Option<T>,
221{
222    let mut state =
223        fidl_fuchsia_net_interfaces_ext::InterfaceState::<(), _>::Unknown(u64::from(id));
224    fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
225        fidl_fuchsia_net_interfaces_ext::event_stream_from_state::<
226            fidl_fuchsia_net_interfaces_ext::AllInterest,
227        >(&interfaces_state, Default::default())
228        .context("get interface event stream")?,
229        &mut state,
230        |properties_and_state| predicate(&properties_and_state.properties.addresses),
231    )
232    .await
233    .context("wait for address")
234}
235
236/// Wait until the interface's online property matches `want_online`.
237pub async fn wait_for_online(
238    interfaces_state: &fidl_fuchsia_net_interfaces::StateProxy,
239    id: u64,
240    want_online: bool,
241) -> Result<()> {
242    let mut state =
243        fidl_fuchsia_net_interfaces_ext::InterfaceState::<(), _>::Unknown(u64::from(id));
244    fidl_fuchsia_net_interfaces_ext::wait_interface_with_id(
245        fidl_fuchsia_net_interfaces_ext::event_stream_from_state::<
246            fidl_fuchsia_net_interfaces_ext::DefaultInterest,
247        >(&interfaces_state, Default::default())
248        .context("get interface event stream")?,
249        &mut state,
250        |properties_and_state| {
251            (properties_and_state.properties.online == want_online).then_some(())
252        },
253    )
254    .await
255    .with_context(|| format!("wait for online {}", want_online))
256}
257
258/// Helpers for `netemul::TestInterface`.
259#[async_trait::async_trait]
260pub trait TestInterfaceExt {
261    /// Calls [`crate::nud::apply_nud_flake_workaround`] for this interface.
262    async fn apply_nud_flake_workaround(&self) -> Result;
263}
264
265#[async_trait::async_trait]
266impl<'a> TestInterfaceExt for netemul::TestInterface<'a> {
267    async fn apply_nud_flake_workaround(&self) -> Result {
268        crate::nud::apply_nud_flake_workaround(self.control()).await
269    }
270}