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