1use reachability_handler::ReachabilityState;
11
12use anyhow::{Context as _, anyhow};
13use fidl_fuchsia_net_interfaces_ext::{self as fnet_interfaces_ext, Update as _};
14use fuchsia_async::{self as fasync};
15use fuchsia_inspect::Inspector;
16use fuchsia_inspect::health::Reporter;
17use futures::channel::mpsc;
18use futures::prelude::*;
19use futures::select;
20use log::{debug, error, info, warn};
21use named_timer::NamedTimeoutExt;
22use reachability_core::dig::Dig;
23use reachability_core::fetch::Fetch;
24use reachability_core::ping::Ping;
25use reachability_core::route_table::RouteTable;
26use reachability_core::telemetry::{self, TelemetryEvent, TelemetrySender};
27use reachability_core::{
28 FIDL_TIMEOUT_ID, InterfaceView, Monitor, NeighborCache, NetworkCheckAction, NetworkCheckCookie,
29 NetworkCheckResult, NetworkChecker, NetworkCheckerOutcome, watchdog,
30};
31use reachability_handler::ReachabilityHandler;
32use std::collections::{HashMap, HashSet};
33use std::pin::pin;
34use {
35 fidl_fuchsia_hardware_network as fhardware_network, fidl_fuchsia_net_debug as fnet_debug,
36 fidl_fuchsia_net_interfaces as fnet_interfaces, fidl_fuchsia_net_neighbor as fnet_neighbor,
37 fidl_fuchsia_net_routes as fnet_routes, fidl_fuchsia_net_routes_ext as fnet_routes_ext,
38};
39
40const REPORT_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(60);
41const PROBE_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(60);
42const FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(90);
43
44struct SystemDispatcher;
45
46#[async_trait::async_trait]
47impl watchdog::SystemDispatcher for SystemDispatcher {
48 type DeviceDiagnostics = DeviceDiagnosticsProvider;
49
50 async fn log_debug_info(&self) -> Result<(), watchdog::Error> {
51 let diagnostics =
52 fuchsia_component::client::connect_to_protocol::<fnet_debug::DiagnosticsMarker>()
53 .map_err(|e| {
54 error!(e:? = e; "failed to connect to protocol");
55 watchdog::Error::NotSupported
56 })?;
57 diagnostics
58 .log_debug_info_to_syslog()
59 .map_err(watchdog::Error::Fidl)
60 .on_timeout_named(&FIDL_TIMEOUT_ID, FIDL_TIMEOUT, || Err(watchdog::Error::Timeout))
61 .await
62 }
63
64 fn get_device_diagnostics(
65 &self,
66 interface: u64,
67 ) -> Result<Self::DeviceDiagnostics, watchdog::Error> {
68 let interfaces_debug =
69 fuchsia_component::client::connect_to_protocol::<fnet_debug::InterfacesMarker>()
70 .map_err(|e| {
71 error!(e:? = e; "failed to connect to protocol");
72 watchdog::Error::NotSupported
73 })?;
74 let (port, server_end) = fidl::endpoints::create_proxy();
75 interfaces_debug.get_port(interface, server_end)?;
76
77 let (diagnostics, server_end) = fidl::endpoints::create_proxy();
78 port.get_diagnostics(server_end)?;
79
80 Ok(DeviceDiagnosticsProvider { interface, diagnostics, port })
81 }
82}
83
84struct DeviceDiagnosticsProvider {
85 interface: u64,
86 diagnostics: fhardware_network::DiagnosticsProxy,
87 port: fhardware_network::PortProxy,
88}
89
90#[async_trait::async_trait]
91impl watchdog::DeviceDiagnosticsProvider for DeviceDiagnosticsProvider {
92 async fn get_counters(&self) -> Result<watchdog::DeviceCounters, watchdog::Error> {
93 self.port
94 .get_counters()
95 .map_err(watchdog::Error::Fidl)
96 .and_then(
97 |fhardware_network::PortGetCountersResponse { rx_frames, tx_frames, .. }| match (
98 rx_frames, tx_frames,
99 ) {
100 (Some(rx_frames), Some(tx_frames)) => {
101 futures::future::ok(watchdog::DeviceCounters { rx_frames, tx_frames })
102 }
103 (None, Some(_)) | (Some(_), None) | (None, None) => {
104 error!(iface = self.interface; "missing information from port counters");
105 futures::future::err(watchdog::Error::NotSupported)
106 }
107 },
108 )
109 .on_timeout_named(&FIDL_TIMEOUT_ID, FIDL_TIMEOUT, || Err(watchdog::Error::Timeout))
110 .await
111 }
112
113 async fn log_debug_info(&self) -> Result<(), watchdog::Error> {
114 self.diagnostics
115 .log_debug_info_to_syslog()
116 .map_err(watchdog::Error::Fidl)
117 .on_timeout_named(&FIDL_TIMEOUT_ID, FIDL_TIMEOUT, || Err(watchdog::Error::Timeout))
118 .await
119 }
120}
121
122type Watchdog = watchdog::Watchdog<SystemDispatcher>;
123
124async fn handle_network_check_message<'a>(
125 netcheck_futures: &mut futures::stream::FuturesUnordered<
126 futures::future::BoxFuture<'a, (NetworkCheckCookie, NetworkCheckResult)>,
127 >,
128 msg: Option<(NetworkCheckAction, NetworkCheckCookie)>,
129) {
130 let (action, cookie) = msg
131 .context("network check receiver unexpectedly closed")
132 .unwrap_or_else(|err| exit_with_anyhow_error(err));
133 match action {
134 NetworkCheckAction::Ping(parameters) => {
135 netcheck_futures.push(Box::pin(async move {
136 let success = reachability_core::ping::Pinger
137 .ping(¶meters.interface_name, parameters.addr)
138 .await;
139 (cookie, NetworkCheckResult::Ping { parameters, success })
140 }));
141 }
142 NetworkCheckAction::ResolveDns(parameters) => {
143 netcheck_futures.push(Box::pin(async move {
144 let ips = reachability_core::dig::Digger::new()
145 .dig(¶meters.interface_name, ¶meters.domain)
146 .await;
147 (cookie, NetworkCheckResult::ResolveDns { parameters, ips })
148 }));
149 }
150 NetworkCheckAction::Fetch(parameters) => {
151 netcheck_futures.push(Box::pin(async move {
152 let status = reachability_core::fetch::Fetcher
153 .fetch(
154 ¶meters.interface_name,
155 ¶meters.domain,
156 ¶meters.path,
157 ¶meters.ip,
158 )
159 .await;
160 (cookie, NetworkCheckResult::Fetch { parameters, status })
161 }));
162 }
163 }
164}
165
166pub struct EventLoop {
168 monitor: Monitor,
169 handler: ReachabilityHandler,
170 watchdog: Watchdog,
171 interface_properties: HashMap<
172 u64,
173 fnet_interfaces_ext::PropertiesAndState<(), fnet_interfaces_ext::DefaultInterest>,
174 >,
175 neighbor_cache: NeighborCache,
176 routes: RouteTable,
177 telemetry_sender: Option<TelemetrySender>,
178 network_check_receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
179 inspector: &'static Inspector,
180}
181
182impl EventLoop {
183 pub fn new(
185 monitor: Monitor,
186 handler: ReachabilityHandler,
187 network_check_receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
188 inspector: &'static Inspector,
189 ) -> Self {
190 fuchsia_inspect::component::health().set_starting_up();
191 EventLoop {
192 monitor,
193 handler,
194 watchdog: Watchdog::new(),
195 interface_properties: Default::default(),
196 neighbor_cache: Default::default(),
197 routes: Default::default(),
198 telemetry_sender: None,
199 network_check_receiver,
200 inspector,
201 }
202 }
203
204 pub async fn run(&mut self) -> Result<(), anyhow::Error> {
206 use fuchsia_component::client::connect_to_protocol;
207
208 let cobalt_svc = fuchsia_component::client::connect_to_protocol::<
209 fidl_fuchsia_metrics::MetricEventLoggerFactoryMarker,
210 >()
211 .context("connect to metrics service")
212 .unwrap_or_else(|err| exit_with_anyhow_error(err));
213
214 let cobalt_proxy = match telemetry::create_metrics_logger(cobalt_svc).await {
215 Ok(proxy) => proxy,
216 Err(e) => {
217 warn!("Metrics logging is unavailable: {}", e);
218
219 let (proxy, _) = fidl::endpoints::create_proxy::<
222 fidl_fuchsia_metrics::MetricEventLoggerMarker,
223 >();
224 proxy
225 }
226 };
227
228 let telemetry_inspect_node = self.inspector.root().create_child("telemetry");
229 let (telemetry_sender, telemetry_fut) =
230 telemetry::serve_telemetry(cobalt_proxy, telemetry_inspect_node);
231 let telemetry_fut = telemetry_fut.fuse();
232 self.telemetry_sender = Some(telemetry_sender.clone());
233 self.monitor.set_telemetry_sender(telemetry_sender);
234
235 let if_watcher_stream = {
236 let interface_state = connect_to_protocol::<fnet_interfaces::StateMarker>()
237 .context("network_manager failed to connect to interface state")
238 .unwrap_or_else(|err| exit_with_anyhow_error(err));
239 fnet_interfaces_ext::event_stream_from_state(
240 &interface_state,
241 fnet_interfaces_ext::IncludedAddresses::OnlyAssigned,
242 )
243 .context("get interface event stream")
244 .unwrap_or_else(|err| exit_with_anyhow_error(err))
245 .fuse()
246 };
247
248 let neigh_watcher_stream = {
249 let view = connect_to_protocol::<fnet_neighbor::ViewMarker>()
250 .context("failed to connect to neighbor view")
251 .unwrap_or_else(|err| exit_with_anyhow_error(err));
252 let (proxy, server_end) =
253 fidl::endpoints::create_proxy::<fnet_neighbor::EntryIteratorMarker>();
254 let () = view
255 .open_entry_iterator(server_end, &fnet_neighbor::EntryIteratorOptions::default())
256 .context("failed to open EntryIterator")
257 .unwrap_or_else(|err| exit_with_anyhow_error(err));
258 futures::stream::try_unfold(proxy, |proxy| {
259 proxy.get_next().map_ok(|e| {
260 Some((
261 futures::stream::iter(e.into_iter().map(Result::<_, fidl::Error>::Ok)),
262 proxy,
263 ))
264 })
265 })
266 .try_flatten()
267 .fuse()
268 };
269
270 let ipv4_route_event_stream = {
271 let state_v4 = connect_to_protocol::<fnet_routes::StateV4Marker>()
272 .context("failed to connect to fuchsia.net.routes/StateV4")
273 .unwrap_or_else(|err| exit_with_anyhow_error(err));
274 fnet_routes_ext::event_stream_from_state(&state_v4)
275 .context("failed to initialize a `WatcherV4` client")
276 .unwrap_or_else(|err| exit_with_anyhow_error(err))
277 .fuse()
278 };
279 let ipv6_route_event_stream = {
280 let state_v6 = connect_to_protocol::<fnet_routes::StateV6Marker>()
281 .context("failed to connect to fuchsia.net.routes/StateV6")
282 .unwrap_or_else(|err| exit_with_anyhow_error(err));
283 fnet_routes_ext::event_stream_from_state(&state_v6)
284 .context("failed to initialize a `WatcherV6` client")
285 .unwrap_or_else(|err| exit_with_anyhow_error(err))
286 .fuse()
287 };
288
289 let mut probe_futures = futures::stream::FuturesUnordered::new();
290 let mut netcheck_futures = futures::stream::FuturesUnordered::new();
291 let report_stream = fasync::Interval::new(REPORT_PERIOD).fuse();
292
293 let mut if_watcher_stream = pin!(if_watcher_stream);
294 let mut report_stream = pin!(report_stream);
295 let mut neigh_watcher_stream = pin!(neigh_watcher_stream);
296 let mut ipv4_route_event_stream = pin!(ipv4_route_event_stream);
297 let mut ipv6_route_event_stream = pin!(ipv6_route_event_stream);
298 let mut telemetry_fut = pin!(telemetry_fut);
299
300 let (v4_routes, v6_routes) = futures::join!(
302 fnet_routes_ext::collect_routes_until_idle::<_, HashSet<_>>(
303 ipv4_route_event_stream.by_ref(),
304 ),
305 fnet_routes_ext::collect_routes_until_idle::<_, HashSet<_>>(
306 ipv6_route_event_stream.by_ref(),
307 )
308 );
309 self.routes = RouteTable::new_with_existing_routes(
310 v4_routes
311 .context("get existing IPv4 routes")
312 .unwrap_or_else(|err| exit_with_anyhow_error(err)),
313 v6_routes
314 .context("get existing IPv6 routes")
315 .unwrap_or_else(|err| exit_with_anyhow_error(err)),
316 );
317
318 debug!("starting event loop");
319
320 fuchsia_inspect::component::health().set_ok();
321
322 loop {
323 select! {
324 if_watcher_res = if_watcher_stream.try_next() => {
325 if let Some(id) = self.handle_interface_watcher_result(if_watcher_res).await {
326 probe_futures.push(fasync::Interval::new(PROBE_PERIOD).map(move |()| id).into_future());
327 }
328 },
329 neigh_res = neigh_watcher_stream.try_next() => {
330 let event = neigh_res
331 .unwrap_or_else(|err| exit_with_anyhow_error(
332 anyhow!("neighbor watcher event stream fidl error: {err}")
333 ))
334 .unwrap_or_else(|| exit_with_anyhow_error(
335 anyhow!("neighbor event stream ended")
336 ));
337 self.neighbor_cache.process_neighbor_event(event);
338 }
339 route_v4_res = ipv4_route_event_stream.try_next() => {
340 let event = route_v4_res
341 .unwrap_or_else(|err| exit_with_anyhow_error(
342 anyhow!("ipv4 route watch error: {err}")
343 ))
344 .unwrap_or_else(|| exit_with_anyhow_error(
345 anyhow!("ipv4 route event stream ended")
346 ));
347 self.handle_route_watcher_event(event);
348 }
349 route_v6_res = ipv6_route_event_stream.try_next() => {
350 let event = route_v6_res
351 .unwrap_or_else(|err| exit_with_anyhow_error(
352 anyhow!("ipv6 route watch error: {err}")
353 ))
354 .unwrap_or_else(|| exit_with_anyhow_error(
355 anyhow!("ipv6 route event stream ended")
356 ));
357 self.handle_route_watcher_event(event);
358 }
359 report = report_stream.next() => {
360 let () = report
361 .context("periodic timer for reporting unexpectedly ended")
362 .unwrap_or_else(|err| exit_with_anyhow_error(err));
363 let () = self.monitor.report_state();
364 },
365 probe = probe_futures.select_next_some() => {
366 match probe {
367 (Some(id), stream) => {
368 if let Some(fnet_interfaces_ext::PropertiesAndState { properties, state: _ }) = self.interface_properties.get(&id) {
369 let () = Self::begin_network_check(
370 &mut self.monitor,
371 &mut self.watchdog,
372 &mut self.handler,
373 properties,
374 &self.routes,
375 &self.neighbor_cache
376 ).await;
377
378 let () = probe_futures.push(stream.into_future());
379 }
380 }
381 (None, _) => {
382 exit_with_anyhow_error(anyhow!(
383 "probe timer for probing reachability unexpectedly ended"
384 ));
385 }
386 }
387 },
388 msg = self.network_check_receiver.next() => {
389 handle_network_check_message(&mut netcheck_futures, msg).await;
390 },
391 netcheck_res = netcheck_futures.select_next_some() => {
392 self.handle_netcheck_response(netcheck_res).await;
393 },
394 telemetry_res = telemetry_fut => exit_with_anyhow_error(
395 anyhow!("unexpectedly stopped serving telemetry: {telemetry_res:?}")
396 ),
397 }
398 }
399 }
400
401 async fn handle_interface_watcher_result(
402 &mut self,
403 if_watcher_res: Result<
404 Option<fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>>,
405 fidl::Error,
406 >,
407 ) -> Option<u64> {
408 let event = if_watcher_res
409 .unwrap_or_else(|err| {
410 exit_with_anyhow_error(anyhow!("interface watcher event stream fidl error: {err}"))
411 })
412 .unwrap_or_else(|| {
413 exit_with_anyhow_error(anyhow!("interface watcher stream unexpectedly ended"))
414 });
415
416 let should_monitor_interface = match event.inner() {
419 fnet_interfaces::Event::Existing(properties)
420 | fnet_interfaces::Event::Added(properties) => Self::should_monitor_interface(
421 properties
422 .port_class
423 .clone()
424 .expect("non-Changed Events should have port_class present"),
425 ),
426 fnet_interfaces::Event::Changed(fnet_interfaces::Properties { id, .. }) => self
431 .interface_properties
432 .contains_key(&id.expect("Changed events must include the interface id")),
433 fnet_interfaces::Event::Removed(id) => self.interface_properties.contains_key(id),
434 fnet_interfaces::Event::Idle(_) => true,
435 };
436 if !should_monitor_interface {
437 return None;
438 }
439
440 let discovered_id = self
441 .handle_interface_watcher_event(event)
442 .await
443 .unwrap_or_else(|err| exit_with_anyhow_error(err));
444 if let Some(id) = discovered_id {
445 if let Some(telemetry_sender) = &self.telemetry_sender {
446 let has_default_ipv4_route =
447 self.interface_properties.values().any(|p| p.properties.has_default_ipv4_route);
448 let has_default_ipv6_route =
449 self.interface_properties.values().any(|p| p.properties.has_default_ipv6_route);
450 telemetry_sender.send(TelemetryEvent::NetworkConfig {
451 has_default_ipv4_route,
452 has_default_ipv6_route,
453 });
454 }
455 return Some(id);
456 }
457 None
458 }
459
460 async fn handle_interface_watcher_event(
461 &mut self,
462 event: fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
463 ) -> Result<Option<u64>, anyhow::Error> {
464 match self
465 .interface_properties
466 .update(event)
467 .context("failed to update interface properties map with watcher event")?
468 {
469 fnet_interfaces_ext::UpdateResult::Added { properties, state: _ }
470 | fnet_interfaces_ext::UpdateResult::Existing { properties, state: _ } => {
471 let id = properties.id;
472 debug!("setting timer for interface {}", id);
473
474 let () = Self::begin_network_check(
475 &mut self.monitor,
476 &mut self.watchdog,
477 &mut self.handler,
478 properties,
479 &self.routes,
480 &self.neighbor_cache,
481 )
482 .await;
483
484 return Ok(Some(id.get()));
485 }
486 fnet_interfaces_ext::UpdateResult::Changed {
487 previous:
488 fnet_interfaces::Properties {
489 online,
490 addresses,
491 has_default_ipv4_route,
492 has_default_ipv6_route,
493 ..
494 },
495 current:
496 properties @ fnet_interfaces_ext::Properties {
497 addresses: current_addresses,
498 id: _,
499 name: _,
500 port_class: _,
501 online: _,
502 has_default_ipv4_route: _,
503 has_default_ipv6_route: _,
504 port_identity_koid: _,
505 },
506 state: _,
507 } => {
508 if online.is_some()
513 || has_default_ipv4_route.is_some()
514 || has_default_ipv6_route.is_some()
515 || addresses.is_some_and(|addresses| {
516 let previous = addresses
517 .iter()
518 .filter_map(|fnet_interfaces::Address { addr, .. }| addr.as_ref());
519 let current = current_addresses.iter().map(
520 |fnet_interfaces_ext::Address {
521 addr,
522 valid_until: _,
523 preferred_lifetime_info: _,
524 assignment_state,
525 }| {
526 assert_eq!(
527 *assignment_state,
528 fnet_interfaces::AddressAssignmentState::Assigned
529 );
530 addr
531 },
532 );
533 previous.ne(current)
534 })
535 {
536 let () = Self::begin_network_check(
537 &mut self.monitor,
538 &mut self.watchdog,
539 &mut self.handler,
540 properties,
541 &self.routes,
542 &self.neighbor_cache,
543 )
544 .await;
545 }
546 }
547 fnet_interfaces_ext::UpdateResult::Removed(
548 fnet_interfaces_ext::PropertiesAndState { properties, state: () },
549 ) => {
550 self.watchdog.handle_interface_removed(properties.id.get());
551 self.monitor.handle_interface_removed(properties);
552 }
553 fnet_interfaces_ext::UpdateResult::NoChange => {}
554 }
555 Ok(None)
556 }
557
558 fn should_monitor_interface(port_class: fnet_interfaces::PortClass) -> bool {
560 match port_class {
561 fnet_interfaces::PortClass::Loopback(_)
562 | fnet_interfaces::PortClass::Blackhole(_)
563 | fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Lowpan) => false,
564 fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Virtual)
565 | fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Ethernet)
566 | fnet_interfaces::PortClass::Device(fhardware_network::PortClass::WlanClient)
567 | fnet_interfaces::PortClass::Device(fhardware_network::PortClass::WlanAp)
568 | fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Ppp)
569 | fnet_interfaces::PortClass::Device(fhardware_network::PortClass::Bridge) => true,
570 fnet_interfaces::PortClass::Device(
571 fhardware_network::PortClass::__SourceBreaking { unknown_ordinal },
572 ) => {
573 log::warn!("unknown fhardware_network::PortClass ordinal {unknown_ordinal:?}");
574 false
575 }
576 fnet_interfaces::PortClass::__SourceBreaking { unknown_ordinal } => {
577 log::warn!("unknown fnet_interfaces::PortClass ordinal {unknown_ordinal:?}");
578 false
579 }
580 }
581 }
582
583 pub fn handle_route_watcher_event<I: net_types::ip::Ip>(
590 &mut self,
591 event: fnet_routes_ext::Event<I>,
592 ) {
593 match event {
594 fnet_routes_ext::Event::Added(route) => {
595 if !self.routes.add_route(route) {
596 error!("Received add event for already existing route: {:?}", route)
597 }
598 }
599 fnet_routes_ext::Event::Removed(route) => {
600 if !self.routes.remove_route(&route) {
601 error!("Received removed event for non-existing route: {:?}", route)
602 }
603 }
604 fnet_routes_ext::Event::Existing(_)
608 | fnet_routes_ext::Event::Idle
609 | fnet_routes_ext::Event::Unknown => {
610 panic!("route watcher observed unexpected event: {:?}", event)
611 }
612 }
613 }
614
615 async fn begin_network_check(
616 monitor: &mut Monitor,
617 watchdog: &mut Watchdog,
618 handler: &mut ReachabilityHandler,
619 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
620 routes: &RouteTable,
621 neighbor_cache: &NeighborCache,
622 ) {
623 let view = InterfaceView {
624 properties,
625 routes,
626 neighbors: neighbor_cache.get_interface_neighbors(properties.id.get()),
627 };
628
629 let () = watchdog
632 .check_interface_state(zx::MonotonicInstant::get(), &SystemDispatcher {}, view)
633 .await;
634
635 let (system_internet, system_gateway, system_dns, system_http) = {
636 match monitor.begin(view) {
637 Ok(NetworkCheckerOutcome::MustResume) => return,
638 Ok(NetworkCheckerOutcome::Complete) => {
639 let monitor_state = monitor.state();
640 (
641 monitor_state.system_has_internet(),
642 monitor_state.system_has_gateway(),
643 monitor_state.system_has_dns(),
644 monitor_state.system_has_http(),
645 )
646 }
647 Err(e) => {
648 info!("begin network check error: {:?}", e);
649 return;
650 }
651 }
652 };
653
654 handler
655 .replace_state(ReachabilityState {
656 internet_available: system_internet,
657 gateway_reachable: system_gateway,
658 dns_active: system_dns,
659 http_active: system_http,
660 })
661 .await;
662 }
663
664 async fn handle_netcheck_response(
667 &mut self,
668 (cookie, result): (NetworkCheckCookie, NetworkCheckResult),
669 ) {
670 match self.monitor.resume(cookie, result) {
671 Ok(NetworkCheckerOutcome::MustResume) => {}
672 Ok(NetworkCheckerOutcome::Complete) => {
673 let (system_internet, system_gateway, system_dns, system_http) = {
674 let monitor_state = self.monitor.state();
675 (
676 monitor_state.system_has_internet(),
677 monitor_state.system_has_gateway(),
678 monitor_state.system_has_dns(),
679 monitor_state.system_has_http(),
680 )
681 };
682
683 self.handler
684 .replace_state(ReachabilityState {
685 internet_available: system_internet,
686 gateway_reachable: system_gateway,
687 dns_active: system_dns,
688 http_active: system_http,
689 })
690 .await;
691 }
692 Err(e) => error!("resume network check error: {:?}", e),
693 }
694 }
695}
696
697fn exit_with_anyhow_error(cause: anyhow::Error) -> ! {
701 error!(cause:%; "exiting due to error");
702 std::process::exit(1);
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708 #[allow(unused)]
709 use assert_matches::assert_matches as _;
710 use fidl_fuchsia_net::{IpAddress, Ipv4Address, Ipv6Address, Subnet};
711 use fidl_fuchsia_net_interfaces::{Address, AddressAssignmentState, Event, Properties};
712 use net_declare::{std_ip_v4, std_ip_v6};
713
714 fn create_eventloop() -> EventLoop {
715 let handler = ReachabilityHandler::new();
716 let inspector = fuchsia_inspect::component::inspector();
717 let (sender, receiver) =
718 futures::channel::mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
719 let mut monitor = Monitor::new(sender).expect("failed to create reachability monitor");
720 let () = monitor.set_inspector(inspector);
721
722 return EventLoop::new(monitor, handler, receiver, inspector);
723 }
724
725 #[fuchsia::test]
726 async fn test_handle_interface_watcher_result_ipv4() {
727 let mut event_loop = create_eventloop();
728
729 let v4_subnet = Subnet {
730 addr: IpAddress::Ipv4(Ipv4Address { addr: std_ip_v4!("192.0.2.1").octets() }),
731 prefix_len: 16,
732 };
733
734 let addr = Address {
735 addr: Some(v4_subnet),
736 assignment_state: Some(AddressAssignmentState::Assigned),
737 ..Default::default()
738 };
739
740 let mut props = Properties {
741 id: Some(12345),
742 addresses: Some(vec![addr]),
743 online: Some(true),
744 port_class: Some(fnet_interfaces::PortClass::Device(
745 fidl_fuchsia_hardware_network::PortClass::Ethernet,
746 )),
747 has_default_ipv4_route: Some(true),
748 has_default_ipv6_route: Some(true),
749 name: Some("IPv4 Reachability Test Interface".to_string()),
750 ..Default::default()
751 };
752
753 let event_res = Ok(Some(Event::Existing(props.clone()).into()));
754 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(12345));
755
756 props.id = Some(54321);
757 let event_res = Ok(Some(Event::Added(props.clone()).into()));
758 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(54321));
759 }
760
761 #[fuchsia::test]
762 async fn test_handle_interface_watcher_result_ipv6() {
763 let mut event_loop = create_eventloop();
764
765 let v6_subnet = Subnet {
766 addr: IpAddress::Ipv6(Ipv6Address { addr: std_ip_v6!("2001:db8::1").octets() }),
767 prefix_len: 16,
768 };
769
770 let addr = Address {
771 addr: Some(v6_subnet),
772 assignment_state: Some(AddressAssignmentState::Assigned),
773 ..Default::default()
774 };
775
776 let mut props = Properties {
777 id: Some(12345),
778 addresses: Some(vec![addr]),
779 online: Some(true),
780 port_class: Some(fnet_interfaces::PortClass::Device(
781 fidl_fuchsia_hardware_network::PortClass::Ethernet,
782 )),
783 has_default_ipv4_route: Some(true),
784 has_default_ipv6_route: Some(true),
785 name: Some("IPv6 Reachability Test Interface".to_string()),
786 ..Default::default()
787 };
788
789 let event_res = Ok(Some(Event::Existing(props.clone()).into()));
790 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(12345));
791
792 props.id = Some(54321);
793 let event_res = Ok(Some(Event::Added(props.clone()).into()));
794 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(54321));
795 }
796}