1use reachability_handler::ReachabilityState;
11
12use anyhow::{anyhow, Context as _};
13use fidl_fuchsia_net_interfaces_ext::{self as fnet_interfaces_ext, Update as _};
14use fuchsia_async::{self as fasync};
15use fuchsia_inspect::health::Reporter;
16use fuchsia_inspect::Inspector;
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 watchdog, InterfaceView, Monitor, NeighborCache, NetworkCheckAction, NetworkCheckCookie,
29 NetworkCheckResult, NetworkChecker, NetworkCheckerOutcome, FIDL_TIMEOUT_ID,
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_fut => exit_with_anyhow_error(anyhow!("unexpectedly stopped serving telemetry")),
395 }
396 }
397 }
398
399 async fn handle_interface_watcher_result(
400 &mut self,
401 if_watcher_res: Result<
402 Option<fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>>,
403 fidl::Error,
404 >,
405 ) -> Option<u64> {
406 let event = if_watcher_res
407 .unwrap_or_else(|err| {
408 exit_with_anyhow_error(anyhow!("interface watcher event stream fidl error: {err}"))
409 })
410 .unwrap_or_else(|| {
411 exit_with_anyhow_error(anyhow!("interface watcher stream unexpectedly ended"))
412 });
413 let discovered_id = self
414 .handle_interface_watcher_event(event)
415 .await
416 .unwrap_or_else(|err| exit_with_anyhow_error(err));
417 if let Some(id) = discovered_id {
418 if let Some(telemetry_sender) = &self.telemetry_sender {
419 let has_default_ipv4_route =
420 self.interface_properties.values().any(|p| p.properties.has_default_ipv4_route);
421 let has_default_ipv6_route =
422 self.interface_properties.values().any(|p| p.properties.has_default_ipv6_route);
423 telemetry_sender.send(TelemetryEvent::NetworkConfig {
424 has_default_ipv4_route,
425 has_default_ipv6_route,
426 });
427 }
428 return Some(id);
429 }
430 None
431 }
432
433 async fn handle_interface_watcher_event(
434 &mut self,
435 event: fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
436 ) -> Result<Option<u64>, anyhow::Error> {
437 match self
438 .interface_properties
439 .update(event)
440 .context("failed to update interface properties map with watcher event")?
441 {
442 fnet_interfaces_ext::UpdateResult::Added { properties, state: _ }
443 | fnet_interfaces_ext::UpdateResult::Existing { properties, state: _ } => {
444 if !Self::should_monitor_interface(&properties) {
445 return Ok(None);
446 }
447
448 let id = properties.id;
449 debug!("setting timer for interface {}", id);
450
451 let () = Self::begin_network_check(
452 &mut self.monitor,
453 &mut self.watchdog,
454 &mut self.handler,
455 properties,
456 &self.routes,
457 &self.neighbor_cache,
458 )
459 .await;
460
461 return Ok(Some(id.get()));
462 }
463 fnet_interfaces_ext::UpdateResult::Changed {
464 previous:
465 fnet_interfaces::Properties {
466 online,
467 addresses,
468 has_default_ipv4_route,
469 has_default_ipv6_route,
470 ..
471 },
472 current:
473 properties @ fnet_interfaces_ext::Properties {
474 addresses: current_addresses,
475 id: _,
476 name: _,
477 port_class: _,
478 online: _,
479 has_default_ipv4_route: _,
480 has_default_ipv6_route: _,
481 },
482 state: _,
483 } => {
484 if online.is_some()
489 || has_default_ipv4_route.is_some()
490 || has_default_ipv6_route.is_some()
491 || addresses.is_some_and(|addresses| {
492 let previous = addresses
493 .iter()
494 .filter_map(|fnet_interfaces::Address { addr, .. }| addr.as_ref());
495 let current = current_addresses.iter().map(
496 |fnet_interfaces_ext::Address {
497 addr,
498 valid_until: _,
499 preferred_lifetime_info: _,
500 assignment_state,
501 }| {
502 assert_eq!(
503 *assignment_state,
504 fnet_interfaces::AddressAssignmentState::Assigned
505 );
506 addr
507 },
508 );
509 previous.ne(current)
510 })
511 {
512 let () = Self::begin_network_check(
513 &mut self.monitor,
514 &mut self.watchdog,
515 &mut self.handler,
516 properties,
517 &self.routes,
518 &self.neighbor_cache,
519 )
520 .await;
521 }
522 }
523 fnet_interfaces_ext::UpdateResult::Removed(
524 fnet_interfaces_ext::PropertiesAndState { properties, state: () },
525 ) => {
526 self.watchdog.handle_interface_removed(properties.id.get());
527 self.monitor.handle_interface_removed(properties);
528 }
529 fnet_interfaces_ext::UpdateResult::NoChange => {}
530 }
531 Ok(None)
532 }
533
534 fn should_monitor_interface(
536 &fnet_interfaces_ext::Properties { port_class, .. }: &fnet_interfaces_ext::Properties<
537 fnet_interfaces_ext::DefaultInterest,
538 >,
539 ) -> bool {
540 return match port_class {
541 fnet_interfaces_ext::PortClass::Loopback
542 | fnet_interfaces_ext::PortClass::Blackhole
543 | fnet_interfaces_ext::PortClass::Lowpan => false,
544 fnet_interfaces_ext::PortClass::Virtual
545 | fnet_interfaces_ext::PortClass::Ethernet
546 | fnet_interfaces_ext::PortClass::WlanClient
547 | fnet_interfaces_ext::PortClass::WlanAp
548 | fnet_interfaces_ext::PortClass::Ppp
549 | fnet_interfaces_ext::PortClass::Bridge => true,
550 };
551 }
552
553 pub fn handle_route_watcher_event<I: net_types::ip::Ip>(
560 &mut self,
561 event: fnet_routes_ext::Event<I>,
562 ) {
563 match event {
564 fnet_routes_ext::Event::Added(route) => {
565 if !self.routes.add_route(route) {
566 error!("Received add event for already existing route: {:?}", route)
567 }
568 }
569 fnet_routes_ext::Event::Removed(route) => {
570 if !self.routes.remove_route(&route) {
571 error!("Received removed event for non-existing route: {:?}", route)
572 }
573 }
574 fnet_routes_ext::Event::Existing(_)
578 | fnet_routes_ext::Event::Idle
579 | fnet_routes_ext::Event::Unknown => {
580 panic!("route watcher observed unexpected event: {:?}", event)
581 }
582 }
583 }
584
585 async fn begin_network_check(
586 monitor: &mut Monitor,
587 watchdog: &mut Watchdog,
588 handler: &mut ReachabilityHandler,
589 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
590 routes: &RouteTable,
591 neighbor_cache: &NeighborCache,
592 ) {
593 let view = InterfaceView {
594 properties,
595 routes,
596 neighbors: neighbor_cache.get_interface_neighbors(properties.id.get()),
597 };
598
599 let () = watchdog
602 .check_interface_state(zx::MonotonicInstant::get(), &SystemDispatcher {}, view)
603 .await;
604
605 let (system_internet, system_gateway, system_dns, system_http) = {
606 match monitor.begin(view) {
607 Ok(NetworkCheckerOutcome::MustResume) => return,
608 Ok(NetworkCheckerOutcome::Complete) => {
609 let monitor_state = monitor.state();
610 (
611 monitor_state.system_has_internet(),
612 monitor_state.system_has_gateway(),
613 monitor_state.system_has_dns(),
614 monitor_state.system_has_http(),
615 )
616 }
617 Err(e) => {
618 info!("begin network check error: {:?}", e);
619 return;
620 }
621 }
622 };
623
624 handler
625 .replace_state(ReachabilityState {
626 internet_available: system_internet,
627 gateway_reachable: system_gateway,
628 dns_active: system_dns,
629 http_active: system_http,
630 })
631 .await;
632 }
633
634 async fn handle_netcheck_response(
637 &mut self,
638 (cookie, result): (NetworkCheckCookie, NetworkCheckResult),
639 ) {
640 match self.monitor.resume(cookie, result) {
641 Ok(NetworkCheckerOutcome::MustResume) => {}
642 Ok(NetworkCheckerOutcome::Complete) => {
643 let (system_internet, system_gateway, system_dns, system_http) = {
644 let monitor_state = self.monitor.state();
645 (
646 monitor_state.system_has_internet(),
647 monitor_state.system_has_gateway(),
648 monitor_state.system_has_dns(),
649 monitor_state.system_has_http(),
650 )
651 };
652
653 self.handler
654 .replace_state(ReachabilityState {
655 internet_available: system_internet,
656 gateway_reachable: system_gateway,
657 dns_active: system_dns,
658 http_active: system_http,
659 })
660 .await;
661 }
662 Err(e) => error!("resume network check error: {:?}", e),
663 }
664 }
665}
666
667fn exit_with_anyhow_error(cause: anyhow::Error) -> ! {
671 error!(cause:%; "exiting due to error");
672 std::process::exit(1);
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678 #[allow(unused)]
679 use assert_matches::assert_matches as _;
680 use fidl_fuchsia_net::{IpAddress, Ipv4Address, Ipv6Address, Subnet};
681 use fidl_fuchsia_net_interfaces::{Address, AddressAssignmentState, Event, Properties};
682 use net_declare::{std_ip_v4, std_ip_v6};
683
684 fn create_eventloop() -> EventLoop {
685 let handler = ReachabilityHandler::new();
686 let inspector = fuchsia_inspect::component::inspector();
687 let (sender, receiver) =
688 futures::channel::mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
689 let mut monitor = Monitor::new(sender).expect("failed to create reachability monitor");
690 let () = monitor.set_inspector(inspector);
691
692 return EventLoop::new(monitor, handler, receiver, inspector);
693 }
694
695 #[fuchsia::test]
696 async fn test_handle_interface_watcher_result_ipv4() {
697 let mut event_loop = create_eventloop();
698
699 let v4_subnet = Subnet {
700 addr: IpAddress::Ipv4(Ipv4Address { addr: std_ip_v4!("192.0.2.1").octets() }),
701 prefix_len: 16,
702 };
703
704 let addr = Address {
705 addr: Some(v4_subnet),
706 assignment_state: Some(AddressAssignmentState::Assigned),
707 ..Default::default()
708 };
709
710 let mut props = Properties {
711 id: Some(12345),
712 addresses: Some(vec![addr]),
713 online: Some(true),
714 port_class: Some(fnet_interfaces::PortClass::Device(
715 fidl_fuchsia_hardware_network::PortClass::Ethernet,
716 )),
717 has_default_ipv4_route: Some(true),
718 has_default_ipv6_route: Some(true),
719 name: Some("IPv4 Reachability Test Interface".to_string()),
720 ..Default::default()
721 };
722
723 let event_res = Ok(Some(Event::Existing(props.clone()).into()));
724 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(12345));
725
726 props.id = Some(54321);
727 let event_res = Ok(Some(Event::Added(props.clone()).into()));
728 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(54321));
729 }
730
731 #[fuchsia::test]
732 async fn test_handle_interface_watcher_result_ipv6() {
733 let mut event_loop = create_eventloop();
734
735 let v6_subnet = Subnet {
736 addr: IpAddress::Ipv6(Ipv6Address { addr: std_ip_v6!("2001:db8::1").octets() }),
737 prefix_len: 16,
738 };
739
740 let addr = Address {
741 addr: Some(v6_subnet),
742 assignment_state: Some(AddressAssignmentState::Assigned),
743 ..Default::default()
744 };
745
746 let mut props = Properties {
747 id: Some(12345),
748 addresses: Some(vec![addr]),
749 online: Some(true),
750 port_class: Some(fnet_interfaces::PortClass::Device(
751 fidl_fuchsia_hardware_network::PortClass::Ethernet,
752 )),
753 has_default_ipv4_route: Some(true),
754 has_default_ipv6_route: Some(true),
755 name: Some("IPv6 Reachability Test Interface".to_string()),
756 ..Default::default()
757 };
758
759 let event_res = Ok(Some(Event::Existing(props.clone()).into()));
760 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(12345));
761
762 props.id = Some(54321);
763 let event_res = Ok(Some(Event::Added(props.clone()).into()));
764 assert_eq!(event_loop.handle_interface_watcher_result(event_res).await, Some(54321));
765 }
766}