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