1use crate::{
6 Address, EventWithInterest, FieldInterests, PortClass, Properties, PropertiesAndState, Update,
7 UpdateResult, WatcherOperationError,
8};
9
10use fidl_fuchsia_net as fnet;
11use fidl_fuchsia_net_interfaces as fnet_interfaces;
12use futures::{Stream, TryStreamExt};
13use net_types::{LinkLocalAddress as _, ScopeableAddress as _};
14use std::collections::{HashMap, HashSet};
15use thiserror::Error;
16
17pub fn is_globally_routable<I: FieldInterests>(
23 &Properties {
24 ref port_class,
25 online,
26 ref addresses,
27 has_default_ipv4_route,
28 has_default_ipv6_route,
29 ..
30 }: &Properties<I>,
31) -> bool {
32 match port_class {
33 PortClass::Loopback | PortClass::Blackhole => return false,
37 PortClass::Virtual
38 | PortClass::Ethernet
39 | PortClass::WlanClient
40 | PortClass::WlanAp
41 | PortClass::Ppp
42 | PortClass::Bridge
43 | PortClass::Lowpan => {}
44 }
45 if !online {
46 return false;
47 }
48 if !has_default_ipv4_route && !has_default_ipv6_route {
49 return false;
50 }
51 addresses.iter().any(
52 |Address {
53 addr: fnet::Subnet { addr, prefix_len: _ },
54 valid_until: _,
55 preferred_lifetime_info: _,
56 assignment_state,
57 }| {
58 let assigned = match assignment_state {
59 fnet_interfaces::AddressAssignmentState::Assigned => true,
60 fnet_interfaces::AddressAssignmentState::Tentative
61 | fnet_interfaces::AddressAssignmentState::Unavailable => false,
62 };
63 assigned
64 && match addr {
65 fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr }) => {
66 has_default_ipv4_route
67 && !net_types::ip::Ipv4Addr::new(*addr).is_link_local()
68 }
69 fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr }) => {
70 has_default_ipv6_route
71 && net_types::ip::Ipv6Addr::from_bytes(*addr).scope()
72 == net_types::ip::Ipv6Scope::Global
73 }
74 }
75 },
76 )
77}
78
79pub fn to_reachability_stream<I: FieldInterests>(
89 event_stream: impl Stream<Item = Result<EventWithInterest<I>, fidl::Error>>,
90) -> impl Stream<Item = Result<bool, WatcherOperationError<(), HashMap<u64, PropertiesAndState<(), I>>>>>
91{
92 let mut if_map = HashMap::<u64, _>::new();
93 let mut reachable = None;
94 let mut reachable_ids = HashSet::new();
95 event_stream.map_err(WatcherOperationError::EventStream).try_filter_map(move |event| {
96 futures::future::ready(if_map.update(event).map_err(WatcherOperationError::Update).map(
97 |changed: UpdateResult<'_, (), _>| {
98 let reachable_ids_changed = match changed {
99 UpdateResult::Existing { properties, state: _ }
100 | UpdateResult::Added { properties, state: _ }
101 | UpdateResult::Changed { previous: _, current: properties, state: _ }
102 if is_globally_routable(properties) =>
103 {
104 reachable_ids.insert(properties.id)
105 }
106 UpdateResult::Existing { .. } | UpdateResult::Added { .. } => false,
107 UpdateResult::Changed { previous: _, current: properties, state: _ } => {
108 reachable_ids.remove(&properties.id)
109 }
110 UpdateResult::Removed(PropertiesAndState { properties, state: _ }) => {
111 reachable_ids.remove(&properties.id)
112 }
113 UpdateResult::NoChange => return None,
114 };
115 if reachable.is_none() {
118 reachable = Some(!reachable_ids.is_empty());
119 return reachable;
120 } else if reachable_ids_changed {
121 let new_reachable = Some(!reachable_ids.is_empty());
122 if reachable != new_reachable {
123 reachable = new_reachable;
124 return reachable;
125 }
126 }
127 None
128 },
129 ))
130 })
131}
132
133#[derive(Error, Debug)]
135pub enum OperationError<S: std::fmt::Debug, B: Update<S> + std::fmt::Debug> {
136 #[error("watcher operation error: {0}")]
137 Watcher(WatcherOperationError<S, B>),
138 #[error("reachability status stream ended unexpectedly")]
139 UnexpectedEnd,
140}
141
142pub async fn wait_for_reachability<I: FieldInterests>(
145 event_stream: impl Stream<Item = Result<EventWithInterest<I>, fidl::Error>>,
146) -> Result<(), OperationError<(), HashMap<u64, PropertiesAndState<(), I>>>> {
147 futures::pin_mut!(event_stream);
148 let rtn = to_reachability_stream(event_stream)
149 .map_err(OperationError::Watcher)
150 .try_filter_map(|reachable| futures::future::ok(if reachable { Some(()) } else { None }))
151 .try_next()
152 .await
153 .and_then(|item| item.ok_or_else(|| OperationError::UnexpectedEnd));
154 rtn
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 use crate::{AllInterest, PositiveMonotonicInstant, PreferredLifetimeInfo};
162
163 use anyhow::Context as _;
164 use fidl_fuchsia_hardware_network as fnetwork;
165 use futures::FutureExt as _;
166 use net_declare::fidl_subnet;
167 use std::convert::TryInto as _;
168 use zx_types as zx;
169
170 const IPV4_LINK_LOCAL: fnet::Subnet = fidl_subnet!("169.254.0.1/16");
171 const IPV6_LINK_LOCAL: fnet::Subnet = fidl_subnet!("fe80::1/64");
172 const IPV4_GLOBAL: fnet::Subnet = fidl_subnet!("192.168.0.1/16");
173 const IPV6_GLOBAL: fnet::Subnet = fidl_subnet!("100::1/64");
174
175 fn valid_interface(id: u64) -> fnet_interfaces::Properties {
176 fnet_interfaces::Properties {
177 id: Some(id),
178 name: Some("test1".to_string()),
179 port_class: Some(fnet_interfaces::PortClass::Device(fnetwork::PortClass::Ethernet)),
180 online: Some(true),
181 addresses: Some(vec![
182 fnet_interfaces::Address {
183 addr: Some(IPV4_GLOBAL),
184 valid_until: Some(zx::ZX_TIME_INFINITE),
185 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
186 preferred_lifetime_info: Some(
187 PreferredLifetimeInfo::preferred_forever().into(),
188 ),
189 __source_breaking: Default::default(),
190 },
191 fnet_interfaces::Address {
192 addr: Some(IPV4_LINK_LOCAL),
193 valid_until: Some(zx::ZX_TIME_INFINITE),
194 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
195 preferred_lifetime_info: Some(
196 PreferredLifetimeInfo::preferred_forever().into(),
197 ),
198 __source_breaking: Default::default(),
199 },
200 fnet_interfaces::Address {
201 addr: Some(IPV6_GLOBAL),
202 valid_until: Some(zx::ZX_TIME_INFINITE),
203 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
204 preferred_lifetime_info: Some(
205 PreferredLifetimeInfo::preferred_forever().into(),
206 ),
207 __source_breaking: Default::default(),
208 },
209 fnet_interfaces::Address {
210 addr: Some(IPV6_LINK_LOCAL),
211 valid_until: Some(zx::ZX_TIME_INFINITE),
212 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
213 preferred_lifetime_info: Some(
214 PreferredLifetimeInfo::preferred_forever().into(),
215 ),
216 __source_breaking: Default::default(),
217 },
218 ]),
219 has_default_ipv4_route: Some(true),
220 has_default_ipv6_route: Some(true),
221 ..Default::default()
222 }
223 }
224
225 #[test]
226 fn test_is_globally_routable() -> Result<(), anyhow::Error> {
227 const ID: u64 = 1;
228 const ASSIGNED_ADDR: Address<AllInterest> = Address {
229 addr: IPV4_GLOBAL,
230 valid_until: PositiveMonotonicInstant::INFINITE_FUTURE,
231 preferred_lifetime_info: PreferredLifetimeInfo::preferred_forever(),
232 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
233 };
234 assert!(!is_globally_routable(&Properties::<AllInterest> {
236 port_class: PortClass::Loopback,
237 ..valid_interface(ID).try_into()?
238 }));
239 assert!(!is_globally_routable(&Properties::<AllInterest> {
240 online: false,
241 ..valid_interface(ID).try_into()?
242 }));
243 assert!(!is_globally_routable(&Properties::<AllInterest> {
244 addresses: vec![],
245 ..valid_interface(ID).try_into()?
246 }));
247 assert!(!is_globally_routable(&Properties::<AllInterest> {
248 has_default_ipv4_route: false,
249 has_default_ipv6_route: false,
250 ..valid_interface(ID).try_into()?
251 }));
252 assert!(!is_globally_routable(&Properties::<AllInterest> {
253 addresses: vec![Address { addr: IPV4_GLOBAL, ..ASSIGNED_ADDR }],
254 has_default_ipv4_route: false,
255 ..valid_interface(ID).try_into()?
256 }));
257 assert!(!is_globally_routable(&Properties::<AllInterest> {
258 addresses: vec![Address { addr: IPV6_GLOBAL, ..ASSIGNED_ADDR }],
259 has_default_ipv6_route: false,
260 ..valid_interface(ID).try_into()?
261 }));
262 assert!(!is_globally_routable(&Properties::<AllInterest> {
263 addresses: vec![Address { addr: IPV6_LINK_LOCAL, ..ASSIGNED_ADDR }],
264 has_default_ipv6_route: true,
265 ..valid_interface(ID).try_into()?
266 }));
267 assert!(!is_globally_routable(&Properties::<AllInterest> {
268 addresses: vec![Address { addr: IPV4_LINK_LOCAL, ..ASSIGNED_ADDR }],
269 has_default_ipv4_route: true,
270 ..valid_interface(ID).try_into()?
271 }));
272
273 assert!(is_globally_routable::<AllInterest>(&valid_interface(ID).try_into()?));
275 assert!(is_globally_routable::<AllInterest>(&Properties {
276 addresses: vec![Address { addr: IPV4_GLOBAL, ..ASSIGNED_ADDR }],
277 has_default_ipv4_route: true,
278 has_default_ipv6_route: false,
279 ..valid_interface(ID).try_into()?
280 }));
281 assert!(is_globally_routable::<AllInterest>(&Properties {
282 addresses: vec![Address { addr: IPV6_GLOBAL, ..ASSIGNED_ADDR }],
283 has_default_ipv4_route: false,
284 has_default_ipv6_route: true,
285 ..valid_interface(ID).try_into()?
286 }));
287 Ok(())
288 }
289
290 #[test]
291 fn test_to_reachability_stream() -> Result<(), anyhow::Error> {
292 let (sender, receiver) = futures::channel::mpsc::unbounded();
293 let mut reachability_stream = to_reachability_stream::<AllInterest>(receiver);
294 for (event, want) in vec![
295 (fnet_interfaces::Event::Idle(fnet_interfaces::Empty {}), None),
296 (
298 fnet_interfaces::Event::Added(fnet_interfaces::Properties {
299 online: Some(false),
300 ..valid_interface(1)
301 }),
302 Some(false),
303 ),
304 (fnet_interfaces::Event::Added(valid_interface(2)), Some(true)),
305 (
306 fnet_interfaces::Event::Added(fnet_interfaces::Properties {
307 online: Some(false),
308 ..valid_interface(3)
309 }),
310 None,
311 ),
312 (
314 fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
315 id: Some(2),
316 online: Some(false),
317 ..Default::default()
318 }),
319 Some(false),
320 ),
321 (
322 fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
323 id: Some(1),
324 online: Some(true),
325 ..Default::default()
326 }),
327 Some(true),
328 ),
329 (
330 fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
331 id: Some(3),
332 online: Some(true),
333 ..Default::default()
334 }),
335 None,
336 ),
337 (fnet_interfaces::Event::Removed(1), None),
339 (fnet_interfaces::Event::Removed(3), Some(false)),
340 (fnet_interfaces::Event::Removed(2), None),
341 ] {
342 let () =
343 sender.unbounded_send(Ok(event.clone().into())).context("failed to send event")?;
344 let got = reachability_stream.try_next().now_or_never();
345 if let Some(want_reachable) = want {
346 let r = got.ok_or_else(|| {
347 anyhow::anyhow!("reachability status stream unexpectedly yielded nothing")
348 })?;
349 let item = r.context("reachability status stream error")?;
350 let got_reachable = item.ok_or_else(|| {
351 anyhow::anyhow!("reachability status stream ended unexpectedly")
352 })?;
353 assert_eq!(got_reachable, want_reachable);
354 } else {
355 if got.is_some() {
356 panic!(
357 "got {:?} from reachability stream after event {:?}, \
358 want None as reachability status should not have changed",
359 got, event
360 );
361 }
362 }
363 }
364 Ok(())
365 }
366}