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