1#![deny(clippy::unused_async)]
6
7pub mod dig;
8pub mod fetch;
9mod inspect;
10mod neighbor_cache;
11pub mod ping;
12pub mod route_table;
13pub mod telemetry;
14pub mod watchdog;
15
16#[cfg(test)]
17mod testutil;
18
19use crate::route_table::{Route, RouteTable};
20use crate::telemetry::processors::link_properties_state::{self, LinkProperties};
21use crate::telemetry::{TelemetryEvent, TelemetrySender};
22use anyhow::anyhow;
23use fidl_fuchsia_net_ext::{self as fnet_ext, IpExt};
24use fuchsia_inspect::{Inspector, Node as InspectNode};
25use futures::channel::mpsc;
26use inspect::InspectInfo;
27use itertools::Itertools;
28use log::{debug, error, info};
29use named_timer::DeadlineId;
30use net_declare::{fidl_subnet, std_ip};
31use net_types::ScopeableAddress as _;
32use num_derive::FromPrimitive;
33use std::collections::hash_map::{Entry, HashMap};
34use {
35 fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
36 fuchsia_async as fasync,
37};
38
39use std::net::IpAddr;
40
41pub use neighbor_cache::{InterfaceNeighborCache, NeighborCache};
42
43const IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS: std::net::IpAddr = std_ip!("8.8.8.8");
44const IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS: std::net::IpAddr = std_ip!("2001:4860:4860::8888");
45const UNSPECIFIED_V4: fidl_fuchsia_net::Subnet = fidl_subnet!("0.0.0.0/0");
46const UNSPECIFIED_V6: fidl_fuchsia_net::Subnet = fidl_subnet!("::0/0");
47const GSTATIC: &'static str = "www.gstatic.com";
48const GENERATE_204: &'static str = "/generate_204";
49const DNS_PROBE_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(300);
53
54pub const FIDL_TIMEOUT_ID: DeadlineId<'static> =
57 DeadlineId::new("reachability", "fidl-request-timeout");
58
59#[derive(Debug, Default, Clone)]
61pub struct Stats {
62 pub events: u64,
64 pub state_updates: HashMap<Id, u64>,
66}
67
68#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, FromPrimitive)]
71#[repr(u8)]
72pub enum LinkState {
73 #[default]
75 None = 1,
76 Removed = 5,
78 Down = 10,
80 Up = 15,
82 Local = 20,
84 Gateway = 25,
86 Internet = 30,
88}
89
90impl LinkState {
91 fn log_state_vals_inspect(node: &InspectNode, name: &str) {
92 let child = node.create_child(name);
93 for i in LinkState::None as u32..=LinkState::Internet as u32 {
94 match <LinkState as num_traits::FromPrimitive>::from_u32(i) {
95 Some(state) => child.record_string(i.to_string(), format!("{:?}", state)),
96 None => (),
97 }
98 }
99 node.record(child);
100 }
101}
102
103#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Copy)]
105pub struct ApplicationState {
106 pub dns_resolved: bool,
107 pub http_fetch_succeeded: bool,
108}
109
110#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Copy)]
112pub struct State {
113 pub link: LinkState,
114 pub application: ApplicationState,
115}
116
117impl From<LinkState> for State {
118 fn from(link: LinkState) -> Self {
119 State { link, ..Default::default() }
120 }
121}
122
123impl LinkState {
124 fn has_interface_up(&self) -> bool {
125 match self {
126 LinkState::None | LinkState::Removed | LinkState::Down => false,
127 LinkState::Up | LinkState::Local | LinkState::Gateway | LinkState::Internet => true,
128 }
129 }
130
131 fn has_internet(&self) -> bool {
132 match self {
133 LinkState::None
134 | LinkState::Removed
135 | LinkState::Down
136 | LinkState::Up
137 | LinkState::Local
138 | LinkState::Gateway => false,
139 LinkState::Internet => true,
140 }
141 }
142
143 fn has_gateway(&self) -> bool {
144 match self {
145 LinkState::None
146 | LinkState::Removed
147 | LinkState::Down
148 | LinkState::Up
149 | LinkState::Local => false,
150 LinkState::Gateway | LinkState::Internet => true,
151 }
152 }
153}
154
155impl State {
156 fn set_link_state(&mut self, link: LinkState) {
157 *self = State { link, ..Default::default() };
158 }
159
160 fn has_interface_up(&self) -> bool {
161 self.link.has_interface_up()
162 }
163
164 fn has_internet(&self) -> bool {
165 self.link.has_internet()
166 }
167
168 fn has_gateway(&self) -> bool {
169 self.link.has_gateway()
170 }
171
172 fn has_dns(&self) -> bool {
173 self.application.dns_resolved
174 }
175
176 fn has_http(&self) -> bool {
177 self.application.http_fetch_succeeded
178 }
179}
180
181impl std::str::FromStr for LinkState {
182 type Err = ();
183
184 fn from_str(s: &str) -> Result<Self, Self::Err> {
185 match s {
186 "None" => Ok(Self::None),
187 "Removed" => Ok(Self::Removed),
188 "Down" => Ok(Self::Down),
189 "Up" => Ok(Self::Up),
190 "Local" => Ok(Self::Local),
191 "Gateway" => Ok(Self::Gateway),
192 "Internet" => Ok(Self::Internet),
193 _ => Err(()),
194 }
195 }
196}
197
198#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
199pub enum Proto {
200 IPv4,
201 IPv6,
202}
203impl std::fmt::Display for Proto {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self {
206 Proto::IPv4 => write!(f, "IPv4"),
207 Proto::IPv6 => write!(f, "IPv6"),
208 }
209 }
210}
211
212trait StateEq {
214 fn compare_state(&self, other: &Self) -> bool;
216}
217
218#[derive(Debug, Clone, Copy)]
222#[cfg_attr(test, derive(PartialEq))]
223struct StateEvent {
224 state: State,
226 time: fasync::MonotonicInstant,
228}
229
230impl StateEvent {
231 fn update(&mut self, other: Self) -> Delta<Self> {
234 let previous = Some(*self);
235 if self.state != other.state {
236 *self = other;
237 }
238 Delta { previous, current: *self }
239 }
240}
241
242impl StateEq for StateEvent {
243 fn compare_state(&self, &Self { state, time: _ }: &Self) -> bool {
244 self.state == state
245 }
246}
247
248#[derive(Clone, Debug, PartialEq)]
249struct Delta<T> {
250 current: T,
251 previous: Option<T>,
252}
253
254impl<T: StateEq> Delta<T> {
255 fn change_observed(&self) -> bool {
256 match &self.previous {
257 Some(previous) => !previous.compare_state(&self.current),
258 None => true,
259 }
260 }
261}
262
263#[derive(Debug)]
266#[cfg_attr(test, derive(PartialEq))]
267struct StateDelta {
268 port: IpVersions<Delta<StateEvent>>,
269 system: IpVersions<Delta<SystemState>>,
270}
271
272#[derive(Clone, Default, Debug, PartialEq)]
273pub struct IpVersions<T> {
274 ipv4: T,
275 ipv6: T,
276}
277
278impl<T> IpVersions<T> {
279 fn with_version<F: FnMut(Proto, &T)>(&self, mut f: F) {
280 let () = f(Proto::IPv4, &self.ipv4);
281 let () = f(Proto::IPv6, &self.ipv6);
282 }
283}
284
285impl IpVersions<Option<SystemState>> {
286 fn state(&self) -> IpVersions<Option<State>> {
287 IpVersions {
288 ipv4: self.ipv4.map(|s| s.state.state),
289 ipv6: self.ipv6.map(|s| s.state.state),
290 }
291 }
292}
293
294impl IpVersions<Option<State>> {
295 fn has_interface_up(&self) -> bool {
296 self.satisfies(State::has_interface_up)
297 }
298
299 fn has_internet(&self) -> bool {
300 self.satisfies(State::has_internet)
301 }
302
303 fn has_dns(&self) -> bool {
304 self.satisfies(State::has_dns)
305 }
306
307 fn has_http(&self) -> bool {
308 self.satisfies(State::has_http)
309 }
310
311 fn has_gateway(&self) -> bool {
312 self.satisfies(State::has_gateway)
313 }
314
315 fn satisfies<F>(&self, f: F) -> bool
316 where
317 F: Fn(&State) -> bool,
318 {
319 return [self.ipv4, self.ipv6].iter().filter_map(|state| state.as_ref()).any(f);
320 }
321}
322
323type Id = u64;
324
325#[derive(Copy, Clone, Debug)]
328#[cfg_attr(test, derive(PartialEq))]
329struct SystemState {
330 id: Id,
331 state: StateEvent,
332}
333
334impl SystemState {
335 fn max(self, other: Self) -> Self {
336 if other.state.state > self.state.state { other } else { self }
337 }
338}
339
340impl StateEq for SystemState {
341 fn compare_state(&self, &Self { id, state: StateEvent { state, time: _ } }: &Self) -> bool {
342 self.id == id && self.state.state == state
343 }
344}
345
346#[derive(Debug, Default, Clone)]
350#[cfg_attr(test, derive(PartialEq))]
351pub struct StateInfo {
352 per_interface: HashMap<Id, IpVersions<StateEvent>>,
354 system: IpVersions<Option<Id>>,
356}
357
358impl StateInfo {
359 fn get(&self, id: Id) -> Option<&IpVersions<StateEvent>> {
361 self.per_interface.get(&id)
362 }
363
364 fn get_system_ipv4(&self) -> Option<SystemState> {
366 self.system.ipv4.map(|id| SystemState {
367 id,
368 state: self
369 .get(id)
370 .unwrap_or_else(|| {
371 panic!("inconsistent system IPv4 state: no interface with ID {:?}", id)
372 })
373 .ipv4,
374 })
375 }
376
377 fn get_system_ipv6(&self) -> Option<SystemState> {
379 self.system.ipv6.map(|id| SystemState {
380 id,
381 state: self
382 .get(id)
383 .unwrap_or_else(|| {
384 panic!("inconsistent system IPv6 state: no interface with ID {:?}", id)
385 })
386 .ipv6,
387 })
388 }
389
390 fn get_system(&self) -> IpVersions<Option<SystemState>> {
391 IpVersions { ipv4: self.get_system_ipv4(), ipv6: self.get_system_ipv6() }
392 }
393
394 pub fn system_has_internet(&self) -> bool {
395 self.get_system().state().has_internet()
396 }
397
398 pub fn system_has_gateway(&self) -> bool {
399 self.get_system().state().has_gateway()
400 }
401
402 pub fn system_has_dns(&self) -> bool {
403 self.get_system().state().has_dns()
404 }
405
406 pub fn system_has_http(&self) -> bool {
407 self.get_system().state().has_http()
408 }
409
410 fn report(&self) {
412 let time = fasync::MonotonicInstant::now();
413 debug!("system reachability state IPv4 {:?}", self.get_system_ipv4());
414 debug!("system reachability state IPv6 {:?}", self.get_system_ipv6());
415 for (id, IpVersions { ipv4, ipv6 }) in self.per_interface.iter() {
416 debug!(
417 "reachability state {:?} IPv4 {:?} with duration {:?}",
418 id,
419 ipv4,
420 time - ipv4.time
421 );
422 debug!(
423 "reachability state {:?} IPv6 {:?} with duration {:?}",
424 id,
425 ipv6,
426 time - ipv6.time
427 );
428 }
429 }
430
431 fn update(&mut self, id: Id, new_reachability: IpVersions<StateEvent>) -> StateDelta {
435 let previous_system_ipv4 = self.get_system_ipv4();
436 let previous_system_ipv6 = self.get_system_ipv6();
437 let port = match self.per_interface.entry(id) {
438 Entry::Occupied(mut occupied) => {
439 let IpVersions { ipv4, ipv6 } = occupied.get_mut();
440 let IpVersions { ipv4: new_ipv4, ipv6: new_ipv6 } = new_reachability;
441
442 IpVersions { ipv4: ipv4.update(new_ipv4), ipv6: ipv6.update(new_ipv6) }
443 }
444 Entry::Vacant(vacant) => {
445 let IpVersions { ipv4, ipv6 } = vacant.insert(new_reachability);
446 IpVersions {
447 ipv4: Delta { previous: None, current: *ipv4 },
448 ipv6: Delta { previous: None, current: *ipv6 },
449 }
450 }
451 };
452
453 let IpVersions { ipv4: system_ipv4, ipv6: system_ipv6 } = self.per_interface.iter().fold(
454 {
455 let IpVersions {
456 ipv4: Delta { previous: _, current: curr_ipv4 },
457 ipv6: Delta { previous: _, current: curr_ipv6 },
458 } = port;
459 let ipv4 = previous_system_ipv4
464 .map(|prev| {
465 if prev.id != id {
466 SystemState { id: prev.id, state: prev.state }
467 } else {
468 SystemState { id, state: curr_ipv4 }
469 }
470 })
471 .unwrap_or(SystemState { id, state: curr_ipv4 });
472 let ipv6 = previous_system_ipv6
473 .map(|prev| {
474 if prev.id != id {
475 SystemState { id: prev.id, state: prev.state }
476 } else {
477 SystemState { id, state: curr_ipv6 }
478 }
479 })
480 .unwrap_or(SystemState { id, state: curr_ipv6 });
481 IpVersions { ipv4, ipv6 }
482 },
483 |IpVersions { ipv4: system_ipv4, ipv6: system_ipv6 },
484 (&id, &IpVersions { ipv4, ipv6 })| {
485 IpVersions {
486 ipv4: system_ipv4.max(SystemState { id, state: ipv4 }),
487 ipv6: system_ipv6.max(SystemState { id, state: ipv6 }),
488 }
489 },
490 );
491
492 self.system = IpVersions { ipv4: Some(system_ipv4.id), ipv6: Some(system_ipv6.id) };
493
494 StateDelta {
495 port,
496 system: IpVersions {
497 ipv4: Delta { previous: previous_system_ipv4, current: system_ipv4 },
498 ipv6: Delta { previous: previous_system_ipv6, current: system_ipv6 },
499 },
500 }
501 }
502}
503
504#[derive(Copy, Clone, Debug)]
506pub struct InterfaceView<'a> {
507 pub properties: &'a fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
508 pub routes: &'a RouteTable,
509 pub neighbors: Option<&'a InterfaceNeighborCache>,
510}
511
512#[derive(Debug)]
515pub enum NetworkCheckerOutcome {
516 MustResume,
518 Complete,
521}
522
523pub trait NetworkChecker {
526 fn begin(&mut self, view: InterfaceView<'_>) -> Result<NetworkCheckerOutcome, anyhow::Error>;
530
531 fn resume(
533 &mut self,
534 cookie: NetworkCheckCookie,
535 result: NetworkCheckResult,
536 ) -> Result<NetworkCheckerOutcome, anyhow::Error>;
537}
538
539#[derive(Debug, Default)]
541enum NetworkCheckState {
542 #[default]
547 Begin,
548 PingGateway,
553 PingInternet,
556 ResolveDns,
560 FetchHttp,
563 Idle,
566}
567impl std::fmt::Display for NetworkCheckState {
568 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569 match self {
570 NetworkCheckState::Begin => write!(f, "Begin"),
571 NetworkCheckState::PingGateway => write!(f, "Ping Gateway"),
572 NetworkCheckState::PingInternet => write!(f, "Ping Internet"),
573 NetworkCheckState::ResolveDns => write!(f, "Resolve DNS"),
574 NetworkCheckState::FetchHttp => write!(f, "Fetch URL"),
575 NetworkCheckState::Idle => write!(f, "Idle"),
576 }
577 }
578}
579
580#[derive(Debug, Clone, Default)]
581pub struct ResolvedIps {
582 v4: Vec<std::net::Ipv4Addr>,
583 v6: Vec<std::net::Ipv6Addr>,
584}
585
586struct PersistentNetworkCheckContext {
587 resolved_addrs: HashMap<String, ResolvedIps>,
589 resolved_time: zx::MonotonicInstant,
591 telemetry: TelemetryContext,
593}
594
595impl Default for PersistentNetworkCheckContext {
596 fn default() -> Self {
597 Self {
598 resolved_addrs: Default::default(),
599 resolved_time: zx::MonotonicInstant::INFINITE_PAST,
600 telemetry: Default::default(),
601 }
602 }
603}
604
605impl From<TelemetryContext> for PersistentNetworkCheckContext {
606 fn from(value: TelemetryContext) -> Self {
607 Self {
608 resolved_addrs: Default::default(),
609 resolved_time: zx::MonotonicInstant::INFINITE_PAST,
610 telemetry: value,
611 }
612 }
613}
614
615#[derive(Clone, Default)]
618struct TelemetryContext {
619 interface_identifiers: Vec<link_properties_state::InterfaceIdentifier>,
622 has_v4_address: bool,
623 has_default_ipv4_route: bool,
624 has_v6_address: bool,
625 has_default_ipv6_route: bool,
626}
627
628impl TelemetryContext {
629 fn new(
630 port_class: fnet_interfaces_ext::PortClass,
631 addresses: &Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::DefaultInterest>>,
632 has_default_ipv4_route: bool,
633 has_default_ipv6_route: bool,
634 ) -> Self {
635 let interface_identifiers = link_properties_state::identifiers_from_port_class(port_class);
636 let (has_v4_address, has_v6_address) = {
639 addresses.iter().fold((false, false), |(mut has_v4, mut has_v6), addr| {
640 match addr.addr.addr {
641 fnet::IpAddress::Ipv4(_) => {
642 has_v4 = true;
643 }
644 fnet::IpAddress::Ipv6(v6) => {
645 has_v6 = has_v6 || !v6.is_unicast_link_local();
646 }
647 };
648 (has_v4, has_v6)
649 })
650 };
651 Self {
652 interface_identifiers,
653 has_v4_address,
654 has_default_ipv4_route,
655 has_v6_address,
656 has_default_ipv6_route,
657 }
658 }
659}
660
661struct NetworkCheckContext {
663 checker_state: NetworkCheckState,
665 ping_addrs: Vec<std::net::SocketAddr>,
667 pings_expected: usize,
669 pings_completed: usize,
671 fetches_expected: usize,
673 fetches_completed: usize,
675 discovered_state_v4: State,
677 discovered_state_v6: State,
679 always_ping_internet: bool,
681 router_discoverable: bool,
683 gateway_pingable: bool,
685 persistent_context: PersistentNetworkCheckContext,
687 }
692
693impl NetworkCheckContext {
694 fn set_global_link_state(&mut self, link: LinkState) {
695 self.discovered_state_v4.set_link_state(link);
696 self.discovered_state_v6.set_link_state(link);
697 }
698
699 fn initiate_ping(
700 &mut self,
701 id: Id,
702 interface_name: &str,
703 network_check_sender: &mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
704 new_state: NetworkCheckState,
705 addrs: Vec<std::net::SocketAddr>,
706 ) {
707 self.checker_state = new_state;
708 self.ping_addrs = addrs;
709 self.pings_expected = self.ping_addrs.len();
710 self.pings_completed = 0;
711 self.ping_addrs
712 .iter()
713 .map(|addr| {
714 let action = NetworkCheckAction::Ping(PingParameters {
715 interface_name: interface_name.to_string(),
716 addr: addr.clone(),
717 });
718 (action, NetworkCheckCookie { id })
719 })
720 .for_each(|message| match network_check_sender.unbounded_send(message) {
721 Ok(()) => {}
722 Err(e) => {
723 debug!("unable to send network check internet msg: {:?}", e)
724 }
725 });
726 }
727}
728
729impl Default for NetworkCheckContext {
730 fn default() -> Self {
732 NetworkCheckContext {
733 checker_state: Default::default(),
734 ping_addrs: Vec::new(),
735 pings_expected: 0usize,
736 pings_completed: 0usize,
737 fetches_expected: 0usize,
738 fetches_completed: 0usize,
739 discovered_state_v4: State { link: LinkState::None, ..Default::default() },
740 discovered_state_v6: State { link: LinkState::None, ..Default::default() },
741 always_ping_internet: true,
742 router_discoverable: false,
743 gateway_pingable: false,
744 persistent_context: Default::default(),
745 }
746 }
747}
748
749impl From<TelemetryContext> for NetworkCheckContext {
750 fn from(value: TelemetryContext) -> Self {
751 NetworkCheckContext {
752 persistent_context: PersistentNetworkCheckContext::from(value),
753 ..Default::default()
754 }
755 }
756}
757
758#[derive(Clone)]
760pub struct NetworkCheckCookie {
761 id: Id,
763}
764
765#[derive(Debug, Clone)]
766pub enum NetworkCheckResult {
767 Ping { parameters: PingParameters, success: bool },
768 ResolveDns { parameters: ResolveDnsParameters, ips: Option<ResolvedIps> },
769 Fetch { parameters: FetchParameters, status: Option<u16> },
770}
771
772#[derive(Debug, Clone)]
773pub struct PingParameters {
774 pub interface_name: std::string::String,
776 pub addr: std::net::SocketAddr,
778}
779
780#[derive(Debug, Clone)]
781pub struct ResolveDnsParameters {
782 pub interface_name: std::string::String,
784 pub domain: String,
786}
787
788#[derive(Debug, Clone)]
789pub struct FetchParameters {
790 pub interface_name: std::string::String,
792 pub domain: std::string::String,
794 pub ip: std::net::IpAddr,
796 pub path: String,
798 pub expected_statuses: Vec<u16>,
800}
801
802impl NetworkCheckResult {
803 fn interface_name(&self) -> &str {
804 match self {
805 NetworkCheckResult::Ping {
806 parameters: PingParameters { interface_name, .. }, ..
807 } => interface_name,
808 NetworkCheckResult::ResolveDns {
809 parameters: ResolveDnsParameters { interface_name, .. },
810 ..
811 } => interface_name,
812 NetworkCheckResult::Fetch {
813 parameters: FetchParameters { interface_name, .. },
814 ..
815 } => interface_name,
816 }
817 }
818
819 fn ping_result(self) -> Option<(PingParameters, bool)> {
820 match self {
821 NetworkCheckResult::Ping { parameters, success } => Some((parameters, success)),
822 _ => None,
823 }
824 }
825
826 fn resolve_dns_result(self) -> Option<(ResolveDnsParameters, Option<ResolvedIps>)> {
827 match self {
828 NetworkCheckResult::ResolveDns { parameters, ips } => Some((parameters, ips)),
829 _ => None,
830 }
831 }
832
833 fn fetch_result(self) -> Option<(FetchParameters, Option<u16>)> {
834 match self {
835 NetworkCheckResult::Fetch { parameters, status } => Some((parameters, status)),
836 _ => None,
837 }
838 }
839}
840
841#[derive(Debug, Clone)]
843pub enum NetworkCheckAction {
844 Ping(PingParameters),
845 ResolveDns(ResolveDnsParameters),
846 Fetch(FetchParameters),
847}
848
849pub trait TimeProvider {
850 fn now(&mut self) -> zx::MonotonicInstant;
851}
852
853#[derive(Debug, Default)]
854pub struct MonotonicInstant;
855impl TimeProvider for MonotonicInstant {
856 fn now(&mut self) -> zx::MonotonicInstant {
857 zx::MonotonicInstant::get()
858 }
859}
860
861pub struct Monitor<Time = MonotonicInstant> {
863 state: StateInfo,
864 stats: Stats,
865 inspector: Option<&'static Inspector>,
866 system_node: Option<InspectInfo>,
867 nodes: HashMap<Id, InspectInfo>,
868 telemetry_sender: Option<TelemetrySender>,
869 network_check_sender: mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
873 interface_context: HashMap<Id, NetworkCheckContext>,
874 time_provider: Time,
875}
876
877impl<Time: TimeProvider + Default> Monitor<Time> {
878 pub fn new(
880 network_check_sender: mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
881 ) -> anyhow::Result<Self> {
882 Ok(Monitor {
883 state: Default::default(),
884 stats: Default::default(),
885 inspector: None,
886 system_node: None,
887 nodes: HashMap::new(),
888 telemetry_sender: None,
889 network_check_sender,
890 interface_context: HashMap::new(),
891 time_provider: Default::default(),
892 })
893 }
894}
895
896impl<Time> Monitor<Time> {
897 pub fn new_with_time_provider(
899 network_check_sender: mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
900 time_provider: Time,
901 ) -> anyhow::Result<Self> {
902 Ok(Monitor {
903 state: Default::default(),
904 stats: Default::default(),
905 inspector: None,
906 system_node: None,
907 nodes: HashMap::new(),
908 telemetry_sender: None,
909 network_check_sender,
910 interface_context: HashMap::new(),
911 time_provider,
912 })
913 }
914}
915
916impl<Time: TimeProvider> Monitor<Time> {
917 pub fn state(&self) -> &StateInfo {
918 &self.state
919 }
920
921 pub fn report_state(&self) {
923 self.state.report();
924 debug!("reachability stats {:?}", self.stats);
925 }
926
927 pub fn set_inspector(&mut self, inspector: &'static Inspector) {
929 self.inspector = Some(inspector);
930
931 let system_node = InspectInfo::new(inspector.root(), "system", "");
932 self.system_node = Some(system_node);
933
934 LinkState::log_state_vals_inspect(inspector.root(), "state_vals");
935 }
936
937 pub fn set_telemetry_sender(&mut self, telemetry_sender: TelemetrySender) {
938 self.telemetry_sender = Some(telemetry_sender);
939 }
940
941 fn interface_node(&mut self, id: Id, name: &str) -> Option<&mut InspectInfo> {
942 self.inspector.map(move |inspector| {
943 self.nodes.entry(id).or_insert_with_key(|id| {
944 InspectInfo::new(inspector.root(), &format!("{:?}", id), name)
945 })
946 })
947 }
948
949 fn update_state_from_context(
950 &mut self,
951 id: Id,
952 name: &str,
953 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
954 let ctx = self.interface_context.get_mut(&id).ok_or_else(|| {
955 anyhow!(
956 "attempting to update state with context but context for id {} does not exist",
957 id
958 )
959 })?;
960
961 ctx.checker_state = NetworkCheckState::Idle;
962
963 if let Some(IpVersions { ipv4, ipv6 }) = self.state.get(id) {
964 if ipv4.state.link == LinkState::Removed && ipv6.state.link == LinkState::Removed {
965 debug!("interface {} was removed, skipping state update", id);
966 return Ok(NetworkCheckerOutcome::Complete);
967 }
968 }
969
970 let info = IpVersions {
971 ipv4: StateEvent {
972 state: ctx.discovered_state_v4,
973 time: fasync::MonotonicInstant::now(),
974 },
975 ipv6: StateEvent {
976 state: ctx.discovered_state_v6,
977 time: fasync::MonotonicInstant::now(),
978 },
979 };
980
981 let gateway_event_v4 = TelemetryEvent::GatewayProbe {
982 gateway_discoverable: ctx.router_discoverable,
983 gateway_pingable: ctx.gateway_pingable,
984 internet_available: ctx.discovered_state_v4.has_internet(),
985 };
986 let gateway_event_v6 = TelemetryEvent::GatewayProbe {
987 gateway_discoverable: ctx.router_discoverable,
988 gateway_pingable: ctx.gateway_pingable,
989 internet_available: ctx.discovered_state_v6.has_internet(),
990 };
991
992 if let Some(telemetry_sender) = &mut self.telemetry_sender {
993 telemetry_sender.send(gateway_event_v4);
994 telemetry_sender.send(gateway_event_v6);
995 telemetry_sender.send(TelemetryEvent::SystemStateUpdate {
996 update: telemetry::SystemStateUpdate {
997 system_state: self.state.get_system().state(),
998 },
999 });
1000 let telemetry_context = &ctx.persistent_context.telemetry;
1001 let interface_identifiers = &telemetry_context.interface_identifiers;
1002 telemetry_sender.send(TelemetryEvent::LinkPropertiesUpdate {
1003 interface_identifiers: interface_identifiers.clone(),
1004 link_properties: IpVersions {
1005 ipv4: LinkProperties {
1006 has_address: telemetry_context.has_v4_address,
1007 has_default_route: telemetry_context.has_default_ipv4_route,
1008 has_dns: ctx.discovered_state_v4.has_dns(),
1009 has_http_reachability: ctx.discovered_state_v4.has_http(),
1010 },
1011 ipv6: LinkProperties {
1012 has_address: telemetry_context.has_v6_address,
1013 has_default_route: telemetry_context.has_default_ipv6_route,
1014 has_dns: ctx.discovered_state_v6.has_dns(),
1015 has_http_reachability: ctx.discovered_state_v6.has_http(),
1016 },
1017 },
1018 });
1019 telemetry_sender.send(TelemetryEvent::LinkStateUpdate {
1020 interface_identifiers: interface_identifiers.clone(),
1021 link_state: IpVersions {
1022 ipv4: ctx.discovered_state_v4.link,
1023 ipv6: ctx.discovered_state_v6.link,
1024 },
1025 });
1026 }
1027
1028 let () = self.update_state(id, &name, info);
1029 Ok(NetworkCheckerOutcome::Complete)
1030 }
1031
1032 fn update_state(&mut self, id: Id, name: &str, reachability: IpVersions<StateEvent>) {
1034 let StateDelta { port, system } = self.state.update(id, reachability);
1035
1036 let () = port.with_version(|proto, delta| {
1037 if delta.change_observed() {
1038 let &Delta { previous, current } = delta;
1039 if let Some(previous) = previous {
1040 info!(
1041 "interface updated {:?} {:?} current: {:?} previous: {:?}",
1042 id, proto, current, previous
1043 );
1044 } else {
1045 info!("new interface {:?} {:?}: {:?}", id, proto, current);
1046 }
1047 let () = log_state(self.interface_node(id, name), proto, current.state);
1048 *self.stats.state_updates.entry(id).or_insert(0) += 1;
1049 }
1050 });
1051
1052 let () = system.with_version(|proto, delta| {
1053 if delta.change_observed() {
1054 let &Delta { previous, current } = delta;
1055 if let Some(previous) = previous {
1056 info!(
1057 "system updated {:?} current: {:?}, previous: {:?}",
1058 proto, current, previous,
1059 );
1060 } else {
1061 info!("initial system state {:?}: {:?}", proto, current);
1062 }
1063 let () = log_state(self.system_node.as_mut(), proto, current.state.state);
1064 }
1065 });
1066 }
1067
1068 pub fn handle_interface_removed(
1070 &mut self,
1071 fnet_interfaces_ext::Properties { id, name, .. }: fnet_interfaces_ext::Properties<
1072 fnet_interfaces_ext::DefaultInterest,
1073 >,
1074 ) {
1075 let time = fasync::MonotonicInstant::now();
1076 if let Some(mut reachability) = self.state.get(id.into()).cloned() {
1077 reachability.ipv4 = StateEvent {
1078 state: State { link: LinkState::Removed, ..Default::default() },
1079 time,
1080 };
1081 reachability.ipv6 = StateEvent {
1082 state: State { link: LinkState::Removed, ..Default::default() },
1083 time,
1084 };
1085 let () = self.update_state(id.into(), &name, reachability);
1086 }
1087 }
1088
1089 fn handle_fetch_success(ctx: &mut NetworkCheckContext, ip: std::net::IpAddr) {
1090 match ctx.checker_state {
1091 NetworkCheckState::FetchHttp => match ip {
1092 IpAddr::V4(_) => {
1093 ctx.discovered_state_v4.application.http_fetch_succeeded = true;
1094 }
1095 IpAddr::V6(_) => {
1096 ctx.discovered_state_v6.application.http_fetch_succeeded = true;
1097 }
1098 },
1099 NetworkCheckState::PingGateway
1100 | NetworkCheckState::PingInternet
1101 | NetworkCheckState::Begin
1102 | NetworkCheckState::Idle
1103 | NetworkCheckState::ResolveDns => {
1104 panic!("continue check had an invalid state")
1105 }
1106 }
1107 }
1108
1109 fn handle_ping_success(ctx: &mut NetworkCheckContext, addr: &std::net::SocketAddr) {
1110 match ctx.checker_state {
1111 NetworkCheckState::PingGateway => {
1112 ctx.gateway_pingable = true;
1113 match addr {
1114 std::net::SocketAddr::V4 { .. } => {
1115 ctx.discovered_state_v4.set_link_state(LinkState::Gateway)
1116 }
1117 std::net::SocketAddr::V6 { .. } => {
1118 ctx.discovered_state_v6.set_link_state(LinkState::Gateway)
1119 }
1120 }
1121 }
1122 NetworkCheckState::PingInternet => match addr {
1123 std::net::SocketAddr::V4 { .. } => {
1124 ctx.discovered_state_v4.set_link_state(LinkState::Internet)
1125 }
1126 std::net::SocketAddr::V6 { .. } => {
1127 ctx.discovered_state_v6.set_link_state(LinkState::Internet)
1128 }
1129 },
1130 NetworkCheckState::FetchHttp
1131 | NetworkCheckState::Begin
1132 | NetworkCheckState::Idle
1133 | NetworkCheckState::ResolveDns => {
1134 panic!("continue check had an invalid state")
1135 }
1136 }
1137 }
1138}
1139
1140impl<Time: TimeProvider> NetworkChecker for Monitor<Time> {
1141 fn begin(
1142 &mut self,
1143 InterfaceView {
1144 properties:
1145 &fnet_interfaces_ext::Properties {
1146 id,
1147 ref name,
1148 port_class,
1149 online,
1150 ref addresses,
1151 has_default_ipv4_route: _,
1152 has_default_ipv6_route: _,
1153 },
1154 routes,
1155 neighbors,
1156 }: InterfaceView<'_>,
1157 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
1158 let id = Id::from(id);
1159 let device_routes: Vec<_> = routes.device_routes(id).collect();
1167 let relevant_routes: Vec<_> = device_routes
1168 .iter()
1169 .filter(|Route { destination, outbound_interface: _, next_hop: _ }| {
1170 *destination == UNSPECIFIED_V4 || *destination == UNSPECIFIED_V6
1171 })
1172 .collect();
1173 let has_default_ipv4_route =
1177 relevant_routes.iter().any(|Route { destination, .. }| *destination == UNSPECIFIED_V4);
1178 let has_default_ipv6_route =
1179 relevant_routes.iter().any(|Route { destination, .. }| *destination == UNSPECIFIED_V6);
1180 let telemetry_context = TelemetryContext::new(
1181 port_class,
1182 &addresses,
1183 has_default_ipv4_route,
1184 has_default_ipv6_route,
1185 );
1186 let ctx = self
1187 .interface_context
1188 .entry(id)
1189 .or_insert_with(|| NetworkCheckContext::from(telemetry_context.clone()));
1190
1191 match ctx.checker_state {
1192 NetworkCheckState::Begin => {}
1193 NetworkCheckState::Idle => {
1194 let mut new_ctx = NetworkCheckContext::default();
1195 std::mem::swap(&mut new_ctx.persistent_context, &mut ctx.persistent_context);
1197 new_ctx.persistent_context.telemetry = telemetry_context;
1199 *ctx = new_ctx;
1200 }
1201 NetworkCheckState::PingGateway
1202 | NetworkCheckState::PingInternet
1203 | NetworkCheckState::FetchHttp
1204 | NetworkCheckState::ResolveDns => {
1205 ctx.persistent_context.telemetry = telemetry_context;
1208 return Err(anyhow!("skipped, non-idle state found on interface {id}"));
1209 }
1210 }
1211
1212 if !online {
1213 ctx.set_global_link_state(LinkState::Down);
1214 return self.update_state_from_context(id, name);
1215 }
1216
1217 let (discovered_online_neighbor, discovered_online_router) =
1220 scan_neighbor_health(neighbors, &device_routes);
1221
1222 if !discovered_online_neighbor && discovered_online_router {
1223 return Err(anyhow!(
1224 "invalid state: cannot have router discovered while neighbors are undiscovered"
1225 ));
1226 }
1227
1228 if !discovered_online_neighbor && !discovered_online_router {
1229 let discovered_state =
1230 if device_routes.is_empty() { LinkState::Up } else { LinkState::Local };
1231 ctx.set_global_link_state(discovered_state);
1232
1233 if discovered_state == LinkState::Up {
1234 return self.update_state_from_context(id, name);
1235 } else {
1236 ctx.always_ping_internet = false;
1239 }
1240 }
1241
1242 let gateway_ping_addrs = relevant_routes
1243 .iter()
1244 .filter_map(move |Route { destination: _, outbound_interface, next_hop }| {
1245 next_hop.and_then(|next_hop| {
1246 let fnet_ext::IpAddress(next_hop) = next_hop.into();
1247 match next_hop.into() {
1248 std::net::IpAddr::V4(v4) => {
1249 Some(std::net::SocketAddr::V4(std::net::SocketAddrV4::new(v4, 0)))
1250 }
1251 std::net::IpAddr::V6(v6) => match (*outbound_interface).try_into() {
1252 Err(std::num::TryFromIntError { .. }) => {
1253 error!("device id {} doesn't fit in u32", outbound_interface);
1254 None
1255 }
1256 Ok(device_id) => {
1257 if device_id == 0
1258 && net_types::ip::Ipv6Addr::from_bytes(v6.octets()).scope()
1259 != net_types::ip::Ipv6Scope::Global
1260 {
1261 None
1262 } else {
1263 Some(std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
1264 v6, 0, 0, device_id,
1265 )))
1266 }
1267 }
1268 },
1269 }
1270 })
1271 })
1272 .map(|next_hop| next_hop)
1273 .collect::<Vec<_>>();
1274
1275 ctx.router_discoverable = discovered_online_router;
1277 if gateway_ping_addrs.is_empty() {
1278 if discovered_online_router {
1290 ctx.initiate_ping(
1295 id,
1296 name,
1297 &self.network_check_sender,
1298 NetworkCheckState::PingInternet,
1299 [
1300 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1301 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1302 ]
1303 .into_iter()
1304 .map(|ip| std::net::SocketAddr::new(ip, 0))
1305 .collect(),
1306 );
1307 } else {
1308 ctx.set_global_link_state(LinkState::Local);
1312 return self.update_state_from_context(id, name);
1313 }
1314 } else {
1315 ctx.set_global_link_state(if discovered_online_router {
1317 LinkState::Gateway
1318 } else {
1319 LinkState::Local
1320 });
1321 ctx.initiate_ping(
1322 id,
1323 name,
1324 &self.network_check_sender,
1325 NetworkCheckState::PingGateway,
1326 gateway_ping_addrs,
1327 );
1328 }
1329 Ok(NetworkCheckerOutcome::MustResume)
1330 }
1331
1332 fn resume(
1333 &mut self,
1334 cookie: NetworkCheckCookie,
1335 result: NetworkCheckResult,
1336 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
1337 let ctx = self.interface_context.get_mut(&cookie.id).ok_or_else(|| {
1338 anyhow!("resume: interface id {} should already exist in map", cookie.id)
1339 })?;
1340 let interface_name = result.interface_name().to_string();
1341 match ctx.checker_state {
1342 NetworkCheckState::Begin | NetworkCheckState::Idle => {
1343 return Err(anyhow!(
1344 "skipped, idle state found in resume for interface {}",
1345 cookie.id
1346 ));
1347 }
1348 NetworkCheckState::PingGateway | NetworkCheckState::PingInternet => {
1349 let (PingParameters { interface_name, addr }, success) =
1350 result.ping_result().ok_or_else(|| {
1351 anyhow!(
1352 "resume: mismatched state and result {interface_name} ({})",
1353 cookie.id
1354 )
1355 })?;
1356 ctx.pings_completed = ctx.pings_completed + 1;
1357
1358 if success {
1359 let () = Self::handle_ping_success(ctx, &addr);
1360 }
1361
1362 if ctx.pings_completed == ctx.pings_expected {
1363 if let NetworkCheckState::PingGateway = ctx.checker_state {
1364 ctx.initiate_ping(
1365 cookie.id,
1366 &interface_name,
1367 &self.network_check_sender,
1368 NetworkCheckState::PingInternet,
1369 [
1370 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1371 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1372 ]
1373 .into_iter()
1374 .map(|ip| std::net::SocketAddr::new(ip, 0))
1375 .collect(),
1376 );
1377 } else {
1378 let parameters = ResolveDnsParameters {
1379 interface_name: interface_name.to_string(),
1380 domain: GSTATIC.into(),
1381 };
1382 ctx.checker_state = NetworkCheckState::ResolveDns;
1383
1384 if self.time_provider.now() - ctx.persistent_context.resolved_time
1385 < DNS_PROBE_PERIOD
1386 {
1387 debug!(
1388 "Skipping ResolveDns since it has not yet been {} seconds",
1389 DNS_PROBE_PERIOD.clone().into_seconds()
1390 );
1391 if let Some(ips) = ctx.persistent_context.resolved_addrs.get(GSTATIC) {
1392 if !ips.v4.is_empty() {
1393 ctx.discovered_state_v4.application.dns_resolved = true;
1394 }
1395 if !ips.v6.is_empty() {
1396 ctx.discovered_state_v6.application.dns_resolved = true;
1397 }
1398 }
1399 return self.resume(
1400 cookie,
1401 NetworkCheckResult::ResolveDns { parameters, ips: None },
1402 );
1403 }
1404
1405 let action = NetworkCheckAction::ResolveDns(parameters);
1406 match self
1407 .network_check_sender
1408 .unbounded_send((action, NetworkCheckCookie { id: cookie.id }))
1409 {
1410 Ok(()) => {}
1411 Err(e) => {
1412 debug!("unable to send network check internet msg: {e:?}")
1413 }
1414 }
1415 }
1416 }
1417 }
1418 NetworkCheckState::ResolveDns => {
1419 let (ResolveDnsParameters { interface_name, domain }, ips) =
1420 result.resolve_dns_result().ok_or_else(|| {
1421 anyhow!(
1422 "resume: mismatched state and result {interface_name} ({})",
1423 cookie.id
1424 )
1425 })?;
1426
1427 if let Some(ips) = ips {
1428 if !ips.v4.is_empty() {
1429 ctx.discovered_state_v4.application.dns_resolved = true;
1430 }
1431 if !ips.v6.is_empty() {
1432 ctx.discovered_state_v6.application.dns_resolved = true;
1433 }
1434 ctx.persistent_context.resolved_time = self.time_provider.now();
1435 let _: Option<ResolvedIps> =
1436 ctx.persistent_context.resolved_addrs.insert(domain.clone(), ips);
1437 }
1438
1439 ctx.checker_state = NetworkCheckState::FetchHttp;
1440 ctx.fetches_expected = 0;
1441
1442 let mut add_fetch = |ip: IpAddr| {
1443 ctx.fetches_expected += 1;
1444 let action = NetworkCheckAction::Fetch(FetchParameters {
1445 interface_name: interface_name.clone(),
1446 domain: domain.clone(),
1447 ip,
1448 path: GENERATE_204.into(),
1449 expected_statuses: vec![204],
1450 });
1451 match self
1452 .network_check_sender
1453 .unbounded_send((action, NetworkCheckCookie { id: cookie.id }))
1454 {
1455 Ok(()) => {}
1456 Err(e) => debug!("unable to send network check internet message: {e:?}"),
1457 }
1458 };
1459
1460 if let Some(v4) =
1461 ctx.persistent_context.resolved_addrs.get(&domain).and_then(|ips| ips.v4.get(0))
1462 {
1463 add_fetch(IpAddr::V4(*v4));
1464 }
1465 if let Some(v6) =
1466 ctx.persistent_context.resolved_addrs.get(&domain).and_then(|ips| ips.v6.get(0))
1467 {
1468 add_fetch(IpAddr::V6(*v6));
1469 }
1470
1471 if ctx.fetches_expected == 0 {
1472 return self.update_state_from_context(cookie.id, &interface_name);
1473 }
1474 }
1475 NetworkCheckState::FetchHttp => {
1476 let (FetchParameters { interface_name, ip, expected_statuses, .. }, status) =
1477 result.fetch_result().ok_or_else(|| {
1478 anyhow!(
1479 "resume: mismatched state and result {interface_name} ({})",
1480 cookie.id
1481 )
1482 })?;
1483 ctx.fetches_completed += 1;
1484
1485 if let Some(status) = status {
1486 if expected_statuses.contains(&status) {
1487 let () = Self::handle_fetch_success(ctx, ip);
1488 }
1489 }
1490
1491 if ctx.fetches_completed == ctx.fetches_expected {
1492 return self.update_state_from_context(cookie.id, &interface_name);
1493 }
1494 }
1495 }
1496 Ok(NetworkCheckerOutcome::MustResume)
1497 }
1498}
1499
1500fn log_state(info: Option<&mut InspectInfo>, proto: Proto, state: State) {
1501 info.into_iter().for_each(|info| info.log_link_state(proto, state.link))
1502}
1503
1504fn scan_neighbor_health(
1507 neighbors: Option<&InterfaceNeighborCache>,
1508 device_routes: &Vec<route_table::Route>,
1509) -> (bool, bool) {
1510 match neighbors {
1511 None => (false, false),
1512 Some(neighbors) => {
1513 neighbors
1514 .iter_health()
1515 .fold_while(
1516 (false, false),
1517 |(discovered_online_neighbor, _discovered_online_router),
1518 (neighbor, health)| {
1519 let is_router = device_routes.iter().any(
1520 |Route { destination: _, outbound_interface: _, next_hop }| {
1521 next_hop.map(|next_hop| *neighbor == next_hop).unwrap_or(false)
1522 },
1523 );
1524 match health {
1525 neighbor_cache::NeighborHealth::Unhealthy { .. }
1528 | neighbor_cache::NeighborHealth::Unknown => {
1529 itertools::FoldWhile::Continue((discovered_online_neighbor, false))
1530 }
1531 neighbor_cache::NeighborHealth::Healthy { .. } => {
1535 if !is_router {
1536 return itertools::FoldWhile::Continue((true, false));
1537 }
1538 itertools::FoldWhile::Done((true, true))
1539 }
1540 }
1541 },
1542 )
1543 .into_inner()
1544 }
1545 }
1546}
1547
1548#[cfg(test)]
1549mod tests {
1550 use crate::fetch::FetchAddr;
1551
1552 use super::*;
1553 use crate::dig::Dig;
1554 use crate::fetch::Fetch;
1555 use crate::neighbor_cache::{NeighborHealth, NeighborState};
1556 use crate::ping::Ping;
1557 use async_trait::async_trait;
1558 use diagnostics_assertions::assert_data_tree;
1559 use futures::StreamExt as _;
1560 use net_declare::{fidl_ip, fidl_subnet, std_ip, std_socket_addr};
1561 use net_types::ip;
1562 use std::pin::pin;
1563 use std::task::Poll;
1564 use test_case::test_case;
1565 use {
1566 fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as fnet_interfaces,
1567 fuchsia_async as fasync,
1568 };
1569
1570 const ETHERNET_INTERFACE_NAME: &str = "eth1";
1571 const ID1: u64 = 1;
1572 const ID2: u64 = 2;
1573
1574 trait Construct<T> {
1579 fn construct(_: T) -> Self;
1580 }
1581
1582 impl<S: Into<State>> Construct<S> for StateEvent {
1583 fn construct(link: S) -> Self {
1584 Self { state: link.into(), time: fasync::MonotonicInstant::INFINITE }
1585 }
1586 }
1587
1588 impl Construct<(LinkState, bool, bool)> for StateEvent {
1589 fn construct((link, dns_resolved, http_fetch_succeeded): (LinkState, bool, bool)) -> Self {
1590 Self {
1591 state: State {
1592 link,
1593 application: ApplicationState { dns_resolved, http_fetch_succeeded },
1594 },
1595 time: fasync::MonotonicInstant::INFINITE,
1596 }
1597 }
1598 }
1599
1600 impl Construct<StateEvent> for IpVersions<StateEvent> {
1601 fn construct(state: StateEvent) -> Self {
1602 Self { ipv4: state, ipv6: state }
1603 }
1604 }
1605
1606 struct FakeTime {
1607 increment: zx::MonotonicDuration,
1608 time: zx::MonotonicInstant,
1609 }
1610
1611 impl TimeProvider for FakeTime {
1612 fn now(&mut self) -> zx::MonotonicInstant {
1613 let result = self.time;
1614 self.time += self.increment;
1615 result
1616 }
1617 }
1618
1619 #[fuchsia::test]
1620 async fn test_log_state_vals_inspect() {
1621 let inspector = Inspector::default();
1622 LinkState::log_state_vals_inspect(inspector.root(), "state_vals");
1623 assert_data_tree!(inspector, root: {
1624 state_vals: {
1625 "1": "None",
1626 "5": "Removed",
1627 "10": "Down",
1628 "15": "Up",
1629 "20": "Local",
1630 "25": "Gateway",
1631 "30": "Internet",
1632 }
1633 })
1634 }
1635
1636 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("1.2.3.0:8080")];
1637 "gateway ping on ipv4")]
1638 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("[123::]:0")];
1639 "gateway ping on ipv6")]
1640 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("1.2.3.0:8080"),
1641 std_socket_addr!("[123::]:0")]; "gateway ping on ipv4/ipv6")]
1642 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("8.8.8.8:0")];
1643 "internet ping on ipv4")]
1644 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("[2001:4860:4860::8888]:0")];
1645 "internet ping on ipv6")]
1646 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("8.8.8.8:0"),
1647 std_socket_addr!("[2001:4860:4860::8888]:0")]; "internet ping on ipv4/ipv6")]
1648 fn test_handle_ping_success(checker_state: NetworkCheckState, addrs: &[std::net::SocketAddr]) {
1649 let mut expected_state_v4: State = Default::default();
1650 let mut expected_state_v6: State = Default::default();
1651
1652 let mut ctx = NetworkCheckContext { checker_state, ..Default::default() };
1653 assert_eq!(ctx.discovered_state_v4, expected_state_v4);
1655 assert_eq!(ctx.discovered_state_v6, expected_state_v6);
1656
1657 let expected_state = match ctx.checker_state {
1658 NetworkCheckState::PingGateway => LinkState::Gateway.into(),
1659 NetworkCheckState::PingInternet => LinkState::Internet.into(),
1660 NetworkCheckState::ResolveDns => LinkState::Internet.into(),
1661 NetworkCheckState::FetchHttp => State {
1662 link: LinkState::Internet,
1663 application: ApplicationState { dns_resolved: true, http_fetch_succeeded: true },
1664 },
1665 NetworkCheckState::Begin | NetworkCheckState::Idle => Default::default(),
1666 };
1667
1668 addrs.iter().for_each(|addr| {
1669 let () = Monitor::<FakeTime>::handle_ping_success(&mut ctx, addr);
1671 match addr {
1673 std::net::SocketAddr::V4 { .. } => {
1674 expected_state_v4 = expected_state;
1675 }
1676 std::net::SocketAddr::V6 { .. } => {
1677 expected_state_v6 = expected_state;
1678 }
1679 }
1680 });
1681 assert_eq!(ctx.discovered_state_v4, expected_state_v4);
1683 assert_eq!(ctx.discovered_state_v6, expected_state_v6);
1684 }
1685
1686 #[derive(Default, Clone)]
1687 struct FakePing {
1688 gateway_addrs: std::collections::HashSet<std::net::IpAddr>,
1689 gateway_response: bool,
1690 internet_response: bool,
1691 }
1692
1693 #[async_trait]
1694 impl Ping for FakePing {
1695 async fn ping(&self, _interface_name: &str, addr: std::net::SocketAddr) -> bool {
1696 let Self { gateway_addrs, gateway_response, internet_response } = self;
1697 let ip = addr.ip();
1698 if [IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS, IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS]
1699 .contains(&ip)
1700 {
1701 *internet_response
1702 } else if gateway_addrs.contains(&ip) {
1703 *gateway_response
1704 } else {
1705 false
1706 }
1707 }
1708 }
1709
1710 #[derive(Default)]
1711 struct FakeDig {
1712 response: Option<ResolvedIps>,
1713 }
1714
1715 impl FakeDig {
1716 fn new(ips: Vec<std::net::IpAddr>) -> Self {
1717 let mut ips_out = ResolvedIps::default();
1718 for ip in ips {
1719 match ip {
1720 IpAddr::V4(v4) => ips_out.v4.push(v4),
1721 IpAddr::V6(v6) => ips_out.v6.push(v6),
1722 }
1723 }
1724 FakeDig { response: Some(ips_out) }
1725 }
1726 }
1727
1728 #[async_trait]
1729 impl Dig for FakeDig {
1730 async fn dig(&self, _interface_name: &str, _domain: &str) -> Option<ResolvedIps> {
1731 self.response.clone()
1732 }
1733 }
1734
1735 #[derive(Default, Copy, Clone)]
1736 struct FakeFetch {
1737 expected_url: Option<&'static str>,
1738 response: Option<u16>,
1739 }
1740
1741 #[async_trait]
1742 impl Fetch for FakeFetch {
1743 async fn fetch<FA: FetchAddr + std::marker::Sync>(
1744 &self,
1745 _interface_name: &str,
1746 domain: &str,
1747 path: &str,
1748 _addr: &FA,
1749 ) -> Option<u16> {
1750 if let Some(expected) = self.expected_url {
1751 assert_eq!(
1752 format!("http://{domain}{path}"),
1753 expected,
1754 "Did not receive expected URL"
1755 );
1756 }
1757 self.response
1758 }
1759 }
1760
1761 struct NetworkCheckTestResponder {
1762 receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
1763 }
1764
1765 impl NetworkCheckTestResponder {
1766 fn new(
1767 receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
1768 ) -> Self {
1769 Self { receiver }
1770 }
1771
1772 async fn respond_to_messages<P: Ping, D: Dig, F: Fetch, Time: TimeProvider>(
1773 &mut self,
1774 monitor: &mut Monitor<Time>,
1775 p: P,
1776 d: D,
1777 f: F,
1778 ) {
1779 loop {
1780 if let Some((action, cookie)) = self.receiver.next().await {
1781 match action {
1782 NetworkCheckAction::Ping(parameters) => {
1783 let success = p.ping(¶meters.interface_name, parameters.addr).await;
1784 match monitor
1785 .resume(cookie, NetworkCheckResult::Ping { parameters, success })
1786 {
1787 Ok(NetworkCheckerOutcome::Complete) => return,
1789 _ => {}
1790 }
1791 }
1792 NetworkCheckAction::ResolveDns(parameters) => {
1793 let ips = d.dig(¶meters.interface_name, ¶meters.domain).await;
1794 match monitor
1795 .resume(cookie, NetworkCheckResult::ResolveDns { parameters, ips })
1796 {
1797 Ok(NetworkCheckerOutcome::Complete) => return,
1799 _ => {}
1800 }
1801 }
1802 NetworkCheckAction::Fetch(parameters) => {
1803 let status = f
1804 .fetch(
1805 ¶meters.interface_name,
1806 ¶meters.domain,
1807 ¶meters.path,
1808 ¶meters.ip,
1809 )
1810 .await;
1811 match monitor
1812 .resume(cookie, NetworkCheckResult::Fetch { parameters, status })
1813 {
1814 Ok(NetworkCheckerOutcome::Complete) => return,
1816 _ => {}
1817 }
1818 }
1819 }
1820 }
1821 }
1822 }
1823 }
1824
1825 fn run_network_check_partial_properties_repeated<P: Ping, D: Dig, F: Fetch>(
1826 exec: &mut fasync::TestExecutor,
1827 name: &str,
1828 interface_id: u64,
1829 routes: &RouteTable,
1830 mocks: Vec<(P, D, F)>,
1831 neighbors: Option<&InterfaceNeighborCache>,
1832 internet_ping_address: std::net::IpAddr,
1833 sleep_between: Option<zx::MonotonicDuration>,
1834 ) -> Vec<State> {
1835 let properties = &fnet_interfaces_ext::Properties {
1836 id: interface_id.try_into().expect("should be nonzero"),
1837 name: name.to_string(),
1838 port_class: fnet_interfaces_ext::PortClass::Ethernet,
1839 online: true,
1840 addresses: Default::default(),
1841 has_default_ipv4_route: Default::default(),
1842 has_default_ipv6_route: Default::default(),
1843 };
1844
1845 let mock_count = mocks.len();
1846 match run_network_check_repeated(exec, properties, routes, neighbors, mocks, sleep_between)
1847 {
1848 Ok(Some(events)) => {
1849 events
1853 .into_iter()
1854 .map(|event| match internet_ping_address {
1855 std::net::IpAddr::V4 { .. } => event.ipv4.state,
1856 std::net::IpAddr::V6 { .. } => event.ipv6.state,
1857 })
1858 .collect()
1859 }
1860 Ok(None) => {
1861 error!("id for interface unexpectedly did not exist after network check");
1862 std::iter::repeat(LinkState::None.into()).take(mock_count).collect()
1863 }
1864 Err(e) => {
1865 error!("network check had an issue calculating state: {:?}", e);
1866 std::iter::repeat(LinkState::None.into()).take(mock_count).collect()
1867 }
1868 }
1869 }
1870
1871 fn run_network_check_partial_properties<P: Ping, D: Dig, F: Fetch>(
1872 exec: &mut fasync::TestExecutor,
1873 name: &str,
1874 interface_id: u64,
1875 routes: &RouteTable,
1876 pinger: P,
1877 digger: D,
1878 fetcher: F,
1879 neighbors: Option<&InterfaceNeighborCache>,
1880 internet_ping_address: std::net::IpAddr,
1881 ) -> State {
1882 run_network_check_partial_properties_repeated(
1883 exec,
1884 name,
1885 interface_id,
1886 routes,
1887 vec![(pinger, digger, fetcher)],
1888 neighbors,
1889 internet_ping_address,
1890 None,
1891 )
1892 .pop()
1893 .unwrap_or_else(|| {
1894 error!("network check returned no states");
1895 LinkState::None.into()
1896 })
1897 }
1898
1899 fn run_network_check_repeated<P: Ping, D: Dig, F: Fetch>(
1900 exec: &mut fasync::TestExecutor,
1901 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1902 routes: &RouteTable,
1903 neighbors: Option<&InterfaceNeighborCache>,
1904 mocks: Vec<(P, D, F)>,
1905 sleep_between: Option<zx::MonotonicDuration>,
1906 ) -> Result<Option<Vec<IpVersions<StateEvent>>>, anyhow::Error> {
1907 let (sender, receiver) = mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
1908 let mut monitor = Monitor::new_with_time_provider(
1909 sender,
1910 FakeTime {
1911 increment: sleep_between.unwrap_or(zx::MonotonicDuration::from_nanos(10)),
1912 time: zx::MonotonicInstant::get(),
1913 },
1914 )
1915 .unwrap();
1916 let mut network_check_responder = NetworkCheckTestResponder::new(receiver);
1917
1918 let view = InterfaceView { properties, routes, neighbors };
1919 let network_check_fut = async {
1920 let mut states = Vec::new();
1921 for (pinger, digger, fetcher) in mocks {
1922 match monitor.begin(view) {
1923 Ok(NetworkCheckerOutcome::Complete) => {}
1924 Ok(NetworkCheckerOutcome::MustResume) => {
1925 let () = network_check_responder
1926 .respond_to_messages(&mut monitor, pinger, digger, fetcher)
1927 .await;
1928 }
1929 Err(e) => {
1930 error!("begin had an issue calculating state: {:?}", e)
1931 }
1932 }
1933 states.push(monitor.state().get(properties.id.get()).map(Clone::clone));
1934 }
1935 states
1936 };
1937
1938 let mut network_check_fut = pin!(network_check_fut);
1939 match exec.run_until_stalled(&mut network_check_fut) {
1940 Poll::Ready(got) => Ok(got.into_iter().collect()),
1941 Poll::Pending => Err(anyhow::anyhow!("network_check blocked unexpectedly")),
1942 }
1943 }
1944
1945 fn run_network_check<P: Ping, D: Dig, F: Fetch>(
1946 exec: &mut fasync::TestExecutor,
1947 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1948 routes: &RouteTable,
1949 neighbors: Option<&InterfaceNeighborCache>,
1950 pinger: P,
1951 digger: D,
1952 fetcher: F,
1953 ) -> Result<Option<IpVersions<StateEvent>>, anyhow::Error> {
1954 run_network_check_repeated(
1955 exec,
1956 properties,
1957 routes,
1958 neighbors,
1959 vec![(pinger, digger, fetcher)],
1960 None,
1961 )
1962 .map(|res| res.and_then(|mut v| v.pop()))
1963 }
1964
1965 #[test]
1966 fn test_network_check_ethernet_ipv4() {
1967 test_network_check_ethernet::<ip::Ipv4>(
1968 fidl_ip!("1.2.3.0"),
1969 fidl_ip!("1.2.3.4"),
1970 fidl_ip!("1.2.3.1"),
1971 fidl_ip!("2.2.3.0"),
1972 fidl_ip!("2.2.3.1"),
1973 UNSPECIFIED_V4,
1974 fidl_subnet!("0.0.0.0/1"),
1975 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1976 24,
1977 );
1978 }
1979
1980 #[test]
1981 fn test_network_check_ethernet_ipv6() {
1982 test_network_check_ethernet::<ip::Ipv6>(
1983 fidl_ip!("123::"),
1984 fidl_ip!("123::4"),
1985 fidl_ip!("123::1"),
1986 fidl_ip!("223::"),
1987 fidl_ip!("223::1"),
1988 UNSPECIFIED_V6,
1989 fidl_subnet!("::/1"),
1990 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1991 64,
1992 );
1993 }
1994
1995 fn test_network_check_ethernet<I: ip::Ip>(
1996 net1: fnet::IpAddress,
1997 _net1_addr: fnet::IpAddress,
1998 net1_gateway: fnet::IpAddress,
1999 net2: fnet::IpAddress,
2000 net2_gateway: fnet::IpAddress,
2001 unspecified_addr: fnet::Subnet,
2002 non_default_addr: fnet::Subnet,
2003 ping_internet_addr: std::net::IpAddr,
2004 prefix_len: u8,
2005 ) {
2006 let route_table = testutil::build_route_table_from_flattened_routes([
2007 Route {
2008 destination: unspecified_addr,
2009 outbound_interface: ID1,
2010 next_hop: Some(net1_gateway),
2011 },
2012 Route {
2013 destination: fnet::Subnet { addr: net1, prefix_len },
2014 outbound_interface: ID1,
2015 next_hop: None,
2016 },
2017 ]);
2018 let route_table_2 = testutil::build_route_table_from_flattened_routes([
2019 Route {
2020 destination: unspecified_addr,
2021 outbound_interface: ID1,
2022 next_hop: Some(net2_gateway),
2023 },
2024 Route {
2025 destination: fnet::Subnet { addr: net1, prefix_len },
2026 outbound_interface: ID1,
2027 next_hop: None,
2028 },
2029 Route {
2030 destination: fnet::Subnet { addr: net2, prefix_len },
2031 outbound_interface: ID1,
2032 next_hop: None,
2033 },
2034 ]);
2035 let route_table_3 = testutil::build_route_table_from_flattened_routes([
2036 Route {
2037 destination: unspecified_addr,
2038 outbound_interface: ID2,
2039 next_hop: Some(net1_gateway),
2040 },
2041 Route {
2042 destination: fnet::Subnet { addr: net1, prefix_len },
2043 outbound_interface: ID2,
2044 next_hop: None,
2045 },
2046 ]);
2047 let route_table_4 = testutil::build_route_table_from_flattened_routes([
2048 Route {
2049 destination: non_default_addr,
2050 outbound_interface: ID1,
2051 next_hop: Some(net1_gateway),
2052 },
2053 Route {
2054 destination: fnet::Subnet { addr: net1, prefix_len },
2055 outbound_interface: ID1,
2056 next_hop: None,
2057 },
2058 ]);
2059
2060 let fnet_ext::IpAddress(net1_gateway_ext) = net1_gateway.into();
2061 let mut exec = fasync::TestExecutor::new();
2062
2063 assert_eq!(
2065 run_network_check_partial_properties(
2066 &mut exec,
2067 ETHERNET_INTERFACE_NAME,
2068 ID1,
2069 &route_table,
2070 FakePing {
2071 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2072 gateway_response: true,
2073 internet_response: true,
2074 },
2075 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]),
2076 FakeFetch {
2077 expected_url: Some("http://www.gstatic.com/generate_204"),
2078 response: Some(204)
2079 },
2080 None,
2081 ping_internet_addr,
2082 ),
2083 State {
2084 link: LinkState::Internet,
2085 application: ApplicationState { dns_resolved: true, http_fetch_succeeded: true },
2086 },
2087 "All is good. Can reach internet"
2088 );
2089
2090 assert_eq!(
2091 run_network_check_partial_properties(
2092 &mut exec,
2093 ETHERNET_INTERFACE_NAME,
2094 ID1,
2095 &route_table,
2096 FakePing {
2097 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2098 gateway_response: true,
2099 internet_response: true,
2100 },
2101 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]),
2102 FakeFetch::default(),
2103 None,
2104 ping_internet_addr,
2105 ),
2106 State {
2107 link: LinkState::Internet,
2108 application: ApplicationState { dns_resolved: true, ..Default::default() },
2109 },
2110 "HTTP Fetch fails"
2111 );
2112
2113 assert_eq!(
2114 run_network_check_partial_properties(
2115 &mut exec,
2116 ETHERNET_INTERFACE_NAME,
2117 ID1,
2118 &route_table,
2119 FakePing {
2120 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2121 gateway_response: true,
2122 internet_response: true,
2123 },
2124 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("1.2.4.0")]),
2125 FakeFetch::default(),
2126 None,
2127 ping_internet_addr,
2128 ),
2129 State {
2130 link: LinkState::Internet,
2131 application: ApplicationState {
2132 dns_resolved: ping_internet_addr.is_ipv4(),
2133 ..Default::default()
2134 },
2135 },
2136 "DNS Resolves only IPV4",
2137 );
2138
2139 assert_eq!(
2140 run_network_check_partial_properties(
2141 &mut exec,
2142 ETHERNET_INTERFACE_NAME,
2143 ID1,
2144 &route_table,
2145 FakePing {
2146 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2147 gateway_response: true,
2148 internet_response: true,
2149 },
2150 FakeDig::new(vec![std_ip!("123::"), std_ip!("124::")]),
2151 FakeFetch::default(),
2152 None,
2153 ping_internet_addr,
2154 ),
2155 State {
2156 link: LinkState::Internet,
2157 application: ApplicationState {
2158 dns_resolved: ping_internet_addr.is_ipv6(),
2159 ..Default::default()
2160 },
2161 },
2162 "DNS Resolves only IPV6",
2163 );
2164
2165 assert_eq!(
2166 run_network_check_partial_properties(
2167 &mut exec,
2168 ETHERNET_INTERFACE_NAME,
2169 ID1,
2170 &route_table,
2171 FakePing {
2172 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2173 gateway_response: false,
2174 internet_response: true,
2175 },
2176 FakeDig::default(),
2177 FakeFetch::default(),
2178 Some(&InterfaceNeighborCache {
2179 neighbors: [(
2180 net1_gateway,
2181 NeighborState::new(NeighborHealth::Healthy {
2182 last_observed: zx::MonotonicInstant::default(),
2183 })
2184 )]
2185 .into_iter()
2186 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2187 }),
2188 ping_internet_addr,
2189 ),
2190 LinkState::Internet.into(),
2191 "Can reach internet, gateway responding via ARP/ND"
2192 );
2193
2194 assert_eq!(
2195 run_network_check_partial_properties(
2196 &mut exec,
2197 ETHERNET_INTERFACE_NAME,
2198 ID1,
2199 &route_table,
2200 FakePing {
2201 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2202 gateway_response: false,
2203 internet_response: true,
2204 },
2205 FakeDig::default(),
2206 FakeFetch::default(),
2207 Some(&InterfaceNeighborCache {
2208 neighbors: [(
2209 net1,
2210 NeighborState::new(NeighborHealth::Healthy {
2211 last_observed: zx::MonotonicInstant::default(),
2212 })
2213 )]
2214 .into_iter()
2215 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2216 }),
2217 ping_internet_addr,
2218 ),
2219 LinkState::Internet.into(),
2220 "Gateway not responding via ping or ARP/ND. Can reach internet"
2221 );
2222
2223 assert_eq!(
2224 run_network_check_partial_properties(
2225 &mut exec,
2226 ETHERNET_INTERFACE_NAME,
2227 ID1,
2228 &route_table_4,
2229 FakePing {
2230 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2231 gateway_response: true,
2232 internet_response: true,
2233 },
2234 FakeDig::default(),
2235 FakeFetch::default(),
2236 Some(&InterfaceNeighborCache {
2237 neighbors: [(
2238 net1_gateway,
2239 NeighborState::new(NeighborHealth::Healthy {
2240 last_observed: zx::MonotonicInstant::default(),
2241 })
2242 )]
2243 .into_iter()
2244 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2245 }),
2246 ping_internet_addr,
2247 ),
2248 LinkState::Internet.into(),
2249 "No default route, but healthy gateway with internet/gateway response"
2250 );
2251
2252 assert_eq!(
2253 run_network_check_partial_properties(
2254 &mut exec,
2255 ETHERNET_INTERFACE_NAME,
2256 ID1,
2257 &route_table,
2258 FakePing {
2259 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2260 gateway_response: true,
2261 internet_response: false,
2262 },
2263 FakeDig::default(),
2264 FakeFetch::default(),
2265 None,
2266 ping_internet_addr,
2267 ),
2268 LinkState::Gateway.into(),
2269 "Can reach gateway via ping"
2270 );
2271
2272 assert_eq!(
2273 run_network_check_partial_properties(
2274 &mut exec,
2275 ETHERNET_INTERFACE_NAME,
2276 ID1,
2277 &route_table,
2278 FakePing::default(),
2279 FakeDig::default(),
2280 FakeFetch::default(),
2281 Some(&InterfaceNeighborCache {
2282 neighbors: [(
2283 net1_gateway,
2284 NeighborState::new(NeighborHealth::Healthy {
2285 last_observed: zx::MonotonicInstant::default(),
2286 })
2287 )]
2288 .into_iter()
2289 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2290 }),
2291 ping_internet_addr,
2292 ),
2293 LinkState::Gateway.into(),
2294 "Can reach gateway via ARP/ND"
2295 );
2296
2297 assert_eq!(
2298 run_network_check_partial_properties(
2299 &mut exec,
2300 ETHERNET_INTERFACE_NAME,
2301 ID1,
2302 &route_table,
2303 FakePing {
2304 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2305 gateway_response: false,
2306 internet_response: false,
2307 },
2308 FakeDig::default(),
2309 FakeFetch::default(),
2310 None,
2311 ping_internet_addr,
2312 ),
2313 LinkState::Local.into(),
2314 "Local only, Cannot reach gateway"
2315 );
2316
2317 assert_eq!(
2318 run_network_check_partial_properties(
2319 &mut exec,
2320 ETHERNET_INTERFACE_NAME,
2321 ID1,
2322 &route_table_2,
2323 FakePing::default(),
2324 FakeDig::default(),
2325 FakeFetch::default(),
2326 None,
2327 ping_internet_addr,
2328 ),
2329 LinkState::Local.into(),
2330 "No default route"
2331 );
2332
2333 assert_eq!(
2334 run_network_check_partial_properties(
2335 &mut exec,
2336 ETHERNET_INTERFACE_NAME,
2337 ID1,
2338 &route_table_4,
2339 FakePing {
2340 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2341 gateway_response: true,
2342 internet_response: false,
2343 },
2344 FakeDig::default(),
2345 FakeFetch::default(),
2346 None,
2347 ping_internet_addr,
2348 ),
2349 LinkState::Local.into(),
2350 "No default route, with only gateway response"
2351 );
2352
2353 assert_eq!(
2354 run_network_check_partial_properties(
2355 &mut exec,
2356 ETHERNET_INTERFACE_NAME,
2357 ID1,
2358 &route_table_2,
2359 FakePing::default(),
2360 FakeDig::default(),
2361 FakeFetch::default(),
2362 Some(&InterfaceNeighborCache {
2363 neighbors: [(
2364 net1,
2365 NeighborState::new(NeighborHealth::Healthy {
2366 last_observed: zx::MonotonicInstant::default(),
2367 })
2368 )]
2369 .into_iter()
2370 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2371 }),
2372 ping_internet_addr,
2373 ),
2374 LinkState::Local.into(),
2375 "Local only, neighbors responsive with no default route"
2376 );
2377
2378 assert_eq!(
2379 run_network_check_partial_properties(
2380 &mut exec,
2381 ETHERNET_INTERFACE_NAME,
2382 ID1,
2383 &route_table,
2384 FakePing::default(),
2385 FakeDig::default(),
2386 FakeFetch::default(),
2387 Some(&InterfaceNeighborCache {
2388 neighbors: [(
2389 net1,
2390 NeighborState::new(NeighborHealth::Healthy {
2391 last_observed: zx::MonotonicInstant::default(),
2392 })
2393 )]
2394 .into_iter()
2395 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2396 }),
2397 ping_internet_addr
2398 ),
2399 LinkState::Local.into(),
2400 "Local only, neighbors responsive with a default route"
2401 );
2402
2403 assert_eq!(
2404 run_network_check_partial_properties(
2405 &mut exec,
2406 ETHERNET_INTERFACE_NAME,
2407 ID1,
2408 &route_table_3,
2409 FakePing::default(),
2410 FakeDig::default(),
2411 FakeFetch::default(),
2412 Some(&InterfaceNeighborCache {
2413 neighbors: [(
2414 net1,
2415 NeighborState::new(NeighborHealth::Healthy {
2416 last_observed: zx::MonotonicInstant::default(),
2417 })
2418 )]
2419 .into_iter()
2420 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2421 }),
2422 ping_internet_addr,
2423 ),
2424 LinkState::Local.into(),
2425 "Local only, neighbors responsive with no routes"
2426 );
2427
2428 assert_eq!(
2429 run_network_check_partial_properties(
2430 &mut exec,
2431 ETHERNET_INTERFACE_NAME,
2432 ID1,
2433 &route_table,
2434 FakePing::default(),
2435 FakeDig::default(),
2436 FakeFetch::default(),
2437 Some(&InterfaceNeighborCache {
2438 neighbors: [
2439 (
2440 net1,
2441 NeighborState::new(NeighborHealth::Healthy {
2442 last_observed: zx::MonotonicInstant::default(),
2443 })
2444 ),
2445 (
2446 net1_gateway,
2447 NeighborState::new(NeighborHealth::Unhealthy { last_healthy: None })
2448 )
2449 ]
2450 .into_iter()
2451 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2452 }),
2453 ping_internet_addr,
2454 ),
2455 LinkState::Local.into(),
2456 "Local only, gateway unhealthy with healthy neighbor"
2457 );
2458
2459 assert_eq!(
2460 run_network_check_partial_properties(
2461 &mut exec,
2462 ETHERNET_INTERFACE_NAME,
2463 ID1,
2464 &route_table_3,
2465 FakePing::default(),
2466 FakeDig::default(),
2467 FakeFetch::default(),
2468 Some(&InterfaceNeighborCache {
2469 neighbors: [(
2470 net1_gateway,
2471 NeighborState::new(NeighborHealth::Unhealthy { last_healthy: None })
2472 )]
2473 .into_iter()
2474 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2475 }),
2476 ping_internet_addr,
2477 ),
2478 LinkState::Up.into(),
2479 "No routes and unhealthy gateway"
2480 );
2481
2482 assert_eq!(
2483 run_network_check_partial_properties(
2484 &mut exec,
2485 ETHERNET_INTERFACE_NAME,
2486 ID1,
2487 &route_table_3,
2488 FakePing::default(),
2489 FakeDig::default(),
2490 FakeFetch::default(),
2491 None,
2492 ping_internet_addr,
2493 ),
2494 LinkState::Up.into(),
2495 "No routes",
2496 );
2497
2498 assert_eq!(
2499 run_network_check_partial_properties_repeated(
2500 &mut exec,
2501 ETHERNET_INTERFACE_NAME,
2502 ID1,
2503 &route_table,
2504 vec![
2505 (
2506 FakePing {
2507 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2508 gateway_response: true,
2509 internet_response: true,
2510 },
2511 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2513 expected_url: Some("http://www.gstatic.com/generate_204"),
2514 response: Some(204)
2515 },
2516 ),
2517 (
2518 FakePing {
2519 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2520 gateway_response: true,
2521 internet_response: true,
2522 },
2523 FakeDig { response: None }, FakeFetch {
2525 expected_url: Some("http://www.gstatic.com/generate_204"),
2526 response: Some(204)
2527 },
2528 ),
2529 ],
2530 None,
2531 ping_internet_addr,
2532 None,
2533 ),
2534 vec![
2535 State {
2536 link: LinkState::Internet,
2537 application: ApplicationState {
2538 dns_resolved: true,
2539 http_fetch_succeeded: true
2540 }
2541 },
2542 State {
2543 link: LinkState::Internet,
2544 application: ApplicationState {
2545 dns_resolved: true,
2546 http_fetch_succeeded: true
2547 }
2548 }
2549 ],
2550 "Fail DNS on second check; fetch succeeds; no pause"
2551 );
2552
2553 assert_eq!(
2554 run_network_check_partial_properties_repeated(
2555 &mut exec,
2556 ETHERNET_INTERFACE_NAME,
2557 ID1,
2558 &route_table,
2559 vec![
2560 (
2561 FakePing {
2562 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2563 gateway_response: true,
2564 internet_response: true,
2565 },
2566 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2568 expected_url: Some("http://www.gstatic.com/generate_204"),
2569 response: Some(204)
2570 },
2571 ),
2572 (
2573 FakePing {
2574 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2575 gateway_response: true,
2576 internet_response: true,
2577 },
2578 FakeDig { response: None }, FakeFetch {
2580 expected_url: Some("http://www.gstatic.com/generate_204"),
2581 response: Some(204)
2582 },
2583 ),
2584 ],
2585 None,
2586 ping_internet_addr,
2587 Some(DNS_PROBE_PERIOD),
2588 ),
2589 vec![
2590 State {
2591 link: LinkState::Internet,
2592 application: ApplicationState {
2593 dns_resolved: true,
2594 http_fetch_succeeded: true
2595 }
2596 },
2597 State {
2598 link: LinkState::Internet,
2599 application: ApplicationState {
2600 dns_resolved: false,
2601 http_fetch_succeeded: true
2602 }
2603 }
2604 ],
2605 "Fail DNS on second check; fetch succeeds"
2606 );
2607
2608 assert_eq!(
2609 run_network_check_partial_properties_repeated(
2610 &mut exec,
2611 ETHERNET_INTERFACE_NAME,
2612 ID1,
2613 &route_table,
2614 vec![
2615 (
2616 FakePing {
2617 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2618 gateway_response: true,
2619 internet_response: true,
2620 },
2621 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2623 expected_url: Some("http://www.gstatic.com/generate_204"),
2624 response: None
2625 },
2626 ),
2627 (
2628 FakePing {
2629 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2630 gateway_response: true,
2631 internet_response: true,
2632 },
2633 FakeDig { response: None }, FakeFetch {
2635 expected_url: Some("http://www.gstatic.com/generate_204"),
2636 response: None,
2637 },
2638 ),
2639 ],
2640 None,
2641 ping_internet_addr,
2642 None,
2643 ),
2644 vec![
2645 State {
2646 link: LinkState::Internet,
2647 application: ApplicationState { dns_resolved: true, ..Default::default() }
2648 },
2649 State {
2650 link: LinkState::Internet,
2651 application: ApplicationState { dns_resolved: true, ..Default::default() }
2652 }
2653 ],
2654 "Fail DNS on second check; fetch fails; no pause"
2655 );
2656
2657 assert_eq!(
2658 run_network_check_partial_properties_repeated(
2659 &mut exec,
2660 ETHERNET_INTERFACE_NAME,
2661 ID1,
2662 &route_table,
2663 vec![
2664 (
2665 FakePing {
2666 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2667 gateway_response: true,
2668 internet_response: true,
2669 },
2670 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2672 expected_url: Some("http://www.gstatic.com/generate_204"),
2673 response: None
2674 },
2675 ),
2676 (
2677 FakePing {
2678 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2679 gateway_response: true,
2680 internet_response: true,
2681 },
2682 FakeDig { response: None }, FakeFetch {
2684 expected_url: Some("http://www.gstatic.com/generate_204"),
2685 response: None
2686 },
2687 ),
2688 ],
2689 None,
2690 ping_internet_addr,
2691 Some(DNS_PROBE_PERIOD),
2692 ),
2693 vec![
2694 State {
2695 link: LinkState::Internet,
2696 application: ApplicationState { dns_resolved: true, ..Default::default() }
2697 },
2698 State {
2699 link: LinkState::Internet,
2700 application: ApplicationState { dns_resolved: false, ..Default::default() }
2701 }
2702 ],
2703 "Fail DNS on second check; fetch fails"
2704 );
2705 }
2706
2707 #[test]
2708 fn test_network_check_varying_properties() {
2709 let properties = fnet_interfaces_ext::Properties {
2710 id: ID1.try_into().expect("should be nonzero"),
2711 name: ETHERNET_INTERFACE_NAME.to_string(),
2712 port_class: fnet_interfaces_ext::PortClass::Ethernet,
2713 has_default_ipv4_route: true,
2714 has_default_ipv6_route: true,
2715 online: true,
2716 addresses: vec![
2717 fnet_interfaces_ext::Address {
2718 addr: fidl_subnet!("1.2.3.0/24"),
2719 valid_until: fnet_interfaces_ext::NoInterest,
2720 preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
2721 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
2722 },
2723 fnet_interfaces_ext::Address {
2724 addr: fidl_subnet!("123::4/64"),
2725 valid_until: fnet_interfaces_ext::NoInterest,
2726 preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
2727 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
2728 },
2729 ],
2730 };
2731 let local_routes = testutil::build_route_table_from_flattened_routes([Route {
2732 destination: fidl_subnet!("1.2.3.0/24"),
2733 outbound_interface: ID1,
2734 next_hop: None,
2735 }]);
2736 let route_table = testutil::build_route_table_from_flattened_routes([
2737 Route {
2738 destination: fidl_subnet!("0.0.0.0/0"),
2739 outbound_interface: ID1,
2740 next_hop: Some(fidl_ip!("1.2.3.1")),
2741 },
2742 Route {
2743 destination: fidl_subnet!("::0/0"),
2744 outbound_interface: ID1,
2745 next_hop: Some(fidl_ip!("123::1")),
2746 },
2747 ]);
2748 let route_table2 = testutil::build_route_table_from_flattened_routes([
2749 Route {
2750 destination: fidl_subnet!("0.0.0.0/0"),
2751 outbound_interface: ID1,
2752 next_hop: Some(fidl_ip!("2.2.3.1")),
2753 },
2754 Route {
2755 destination: fidl_subnet!("::0/0"),
2756 outbound_interface: ID1,
2757 next_hop: Some(fidl_ip!("223::1")),
2758 },
2759 ]);
2760
2761 const NON_ETHERNET_INTERFACE_NAME: &str = "test01";
2762
2763 let mut exec = fasync::TestExecutor::new_with_fake_time();
2764 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
2765 let () = exec.set_fake_time(time.into());
2766
2767 let got = run_network_check(
2768 &mut exec,
2769 &fnet_interfaces_ext::Properties {
2770 id: ID1.try_into().expect("should be nonzero"),
2771 name: NON_ETHERNET_INTERFACE_NAME.to_string(),
2772 port_class: fnet_interfaces_ext::PortClass::Virtual,
2773 online: false,
2774 has_default_ipv4_route: false,
2775 has_default_ipv6_route: false,
2776 addresses: vec![],
2777 },
2778 &Default::default(),
2779 None,
2780 FakePing::default(),
2781 FakeDig::default(),
2782 FakeFetch::default(),
2783 )
2784 .expect(
2785 "error calling network check with non-ethernet interface, no addresses, interface down",
2786 );
2787 assert_eq!(
2788 got,
2789 Some(IpVersions::construct(StateEvent {
2790 state: State { link: LinkState::Down, ..Default::default() },
2791 time
2792 }))
2793 );
2794
2795 let got = run_network_check(
2796 &mut exec,
2797 &fnet_interfaces_ext::Properties { online: false, ..properties.clone() },
2798 &Default::default(),
2799 None,
2800 FakePing::default(),
2801 FakeDig::default(),
2802 FakeFetch::default(),
2803 )
2804 .expect("error calling network check, want Down state");
2805 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2806 state: State { link: LinkState::Down, ..Default::default() },
2807 time,
2808 }));
2809 assert_eq!(got, want);
2810
2811 let got = run_network_check(
2812 &mut exec,
2813 &fnet_interfaces_ext::Properties {
2814 has_default_ipv4_route: false,
2815 has_default_ipv6_route: false,
2816 ..properties.clone()
2817 },
2818 &local_routes,
2819 None,
2820 FakePing::default(),
2821 FakeDig::default(),
2822 FakeFetch::default(),
2823 )
2824 .expect("error calling network check, want Local state due to no default routes");
2825 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2826 state: State { link: LinkState::Local, ..Default::default() },
2827 time,
2828 }));
2829 assert_eq!(got, want);
2830
2831 let got = run_network_check(
2832 &mut exec,
2833 &properties,
2834 &route_table2,
2835 None,
2836 FakePing::default(),
2837 FakeDig::default(),
2838 FakeFetch::default(),
2839 )
2840 .expect("error calling network check, want Local state due to no matching default route");
2841 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2842 state: State { link: LinkState::Local, ..Default::default() },
2843 time,
2844 }));
2845 assert_eq!(got, want);
2846
2847 let got = run_network_check(
2848 &mut exec,
2849 &properties,
2850 &route_table,
2851 None,
2852 FakePing {
2853 gateway_addrs: [std_ip!("1.2.3.1"), std_ip!("123::1")].into_iter().collect(),
2854 gateway_response: true,
2855 internet_response: false,
2856 },
2857 FakeDig::default(),
2858 FakeFetch::default(),
2859 )
2860 .expect("error calling network check, want Gateway state");
2861 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2862 state: State { link: LinkState::Gateway, ..Default::default() },
2863 time,
2864 }));
2865 assert_eq!(got, want);
2866
2867 let got = run_network_check(
2868 &mut exec,
2869 &properties,
2870 &route_table,
2871 None,
2872 FakePing {
2873 gateway_addrs: [std_ip!("1.2.3.1"), std_ip!("123::1")].into_iter().collect(),
2874 gateway_response: true,
2875 internet_response: true,
2876 },
2877 FakeDig::default(),
2878 FakeFetch::default(),
2879 )
2880 .expect("error calling network check, want Internet state");
2881 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2882 state: State { link: LinkState::Internet, ..Default::default() },
2883 time,
2884 }));
2885 assert_eq!(got, want);
2886 }
2887
2888 fn update_delta(port: Delta<StateEvent>, system: Delta<SystemState>) -> StateDelta {
2889 StateDelta {
2890 port: IpVersions { ipv4: port.clone(), ipv6: port },
2891 system: IpVersions { ipv4: system.clone(), ipv6: system },
2892 }
2893 }
2894
2895 #[test]
2896 fn test_state_info_update() {
2897 let if1_local_event = StateEvent::construct(LinkState::Local);
2898 let if1_local = IpVersions::<StateEvent>::construct(if1_local_event);
2899 let mut state = StateInfo::default();
2901 let want = update_delta(
2902 Delta { previous: None, current: if1_local_event },
2903 Delta { previous: None, current: SystemState { id: ID1, state: if1_local_event } },
2904 );
2905 assert_eq!(state.update(ID1, if1_local.clone()), want);
2906 let want_state = StateInfo {
2907 per_interface: std::iter::once((ID1, if1_local.clone())).collect::<HashMap<_, _>>(),
2908 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
2909 };
2910 assert_eq!(state, want_state);
2911
2912 let if2_gateway_event = StateEvent::construct(LinkState::Gateway);
2913 let if2_gateway = IpVersions::<StateEvent>::construct(if2_gateway_event);
2914 let want = update_delta(
2917 Delta { previous: None, current: if2_gateway_event },
2918 Delta {
2919 previous: Some(SystemState { id: ID1, state: if1_local_event }),
2920 current: SystemState { id: ID2, state: if2_gateway_event },
2921 },
2922 );
2923 assert_eq!(state.update(ID2, if2_gateway.clone()), want);
2924 let want_state = StateInfo {
2925 per_interface: [(ID1, if1_local.clone()), (ID2, if2_gateway.clone())]
2926 .into_iter()
2927 .collect::<HashMap<_, _>>(),
2928 system: IpVersions { ipv4: Some(ID2), ipv6: Some(ID2) },
2929 };
2930 assert_eq!(state, want_state);
2931
2932 let if2_removed_event = StateEvent::construct(LinkState::Removed);
2933 let if2_removed = IpVersions::<StateEvent>::construct(if2_removed_event);
2934 let want = update_delta(
2937 Delta { previous: Some(if2_gateway_event), current: if2_removed_event },
2938 Delta {
2939 previous: Some(SystemState { id: ID2, state: if2_gateway_event }),
2940 current: SystemState { id: ID1, state: if1_local_event },
2941 },
2942 );
2943 assert_eq!(state.update(ID2, if2_removed.clone()), want);
2944 let want_state = StateInfo {
2945 per_interface: [(ID1, if1_local.clone()), (ID2, if2_removed.clone())]
2946 .into_iter()
2947 .collect::<HashMap<_, _>>(),
2948 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
2949 };
2950 assert_eq!(state, want_state);
2951 }
2952
2953 #[test]
2957 fn test_state_info_update_same_link_state() {
2958 let if_local_event = StateEvent::construct(LinkState::Local);
2959 let if_local = IpVersions::<StateEvent>::construct(if_local_event);
2960 let mut state = StateInfo::default();
2962 let want = update_delta(
2963 Delta { previous: None, current: if_local_event },
2964 Delta { previous: None, current: SystemState { id: ID1, state: if_local_event } },
2965 );
2966 assert_eq!(state.update(ID1, if_local.clone()), want);
2967 let want_state = StateInfo {
2968 per_interface: std::iter::once((ID1, if_local.clone())).collect::<HashMap<_, _>>(),
2969 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
2970 };
2971 assert_eq!(state, want_state);
2972
2973 let want = update_delta(
2976 Delta { previous: None, current: if_local_event },
2977 Delta {
2978 previous: Some(SystemState { id: ID1, state: if_local_event }),
2979 current: SystemState { id: ID1, state: if_local_event },
2980 },
2981 );
2982 assert_eq!(state.update(ID2, if_local.clone()), want);
2983 let want_state = StateInfo {
2984 per_interface: [(ID1, if_local.clone()), (ID2, if_local.clone())]
2985 .into_iter()
2986 .collect::<HashMap<_, _>>(),
2987 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
2988 };
2989 assert_eq!(state, want_state);
2990
2991 let if_removed_event = StateEvent::construct(LinkState::Removed);
2994 let if_removed = IpVersions::<StateEvent>::construct(if_removed_event);
2995 let want = update_delta(
2996 Delta { previous: Some(if_local_event), current: if_removed_event },
2997 Delta {
2998 previous: Some(SystemState { id: ID1, state: if_local_event }),
2999 current: SystemState { id: ID2, state: if_local_event },
3000 },
3001 );
3002 assert_eq!(state.update(ID1, if_removed.clone()), want);
3003 let want_state = StateInfo {
3004 per_interface: [(ID1, if_removed.clone()), (ID2, if_local.clone())]
3005 .into_iter()
3006 .collect::<HashMap<_, _>>(),
3007 system: IpVersions { ipv4: Some(ID2), ipv6: Some(ID2) },
3008 };
3009 assert_eq!(state, want_state);
3010 }
3011
3012 #[test_case(None::<LinkState>, None::<LinkState>, false, false, false, false;
3013 "no interfaces available")]
3014 #[test_case(Some(LinkState::Local), Some(LinkState::Local), false, false, false, false;
3015 "no interfaces with gateway or internet state")]
3016 #[test_case(Some(LinkState::Local), Some(LinkState::Gateway), false, false, false, true;
3017 "only one interface with gateway state or above")]
3018 #[test_case(Some(LinkState::Local), Some(LinkState::Internet), false, false, true, true;
3019 "only one interface with internet state")]
3020 #[test_case(Some(LinkState::Internet), Some(LinkState::Internet), false, false, true, true;
3021 "all interfaces with internet")]
3022 #[test_case(Some(LinkState::Internet), None::<LinkState>, false, false, true, true;
3023 "only one interface available, has internet state")]
3024 #[test_case(Some(LinkState::Local), Some((LinkState::Internet, true, false)), false, true, true, true;
3025 "only one interface with DNS resolved state")]
3026 #[test_case(Some((LinkState::Internet, true, false)), Some((LinkState::Internet, true, false)), false, true, true, true;
3027 "all interfaces with DNS resolved state")]
3028 #[test_case(Some((LinkState::Internet, true, false)), None::<LinkState>, false, true, true, true;
3029 "only one interface available, has DNS resolved state")]
3030 #[test_case(Some(LinkState::Local), Some((LinkState::Internet, true, true)), true, true, true, true;
3031 "only one interface with HTTP resolved state")]
3032 #[test_case(Some((LinkState::Internet, true, true)), Some((LinkState::Internet, true, true)), true, true, true, true;
3033 "all interfaces with HTTP resolved state")]
3034 #[test_case(Some((LinkState::Internet, true, true)), None::<LinkState>, true, true, true, true;
3035 "only one interface available, has HTTP resolved state")]
3036 #[test_case(Some((LinkState::Internet, false, true)), None::<LinkState>, true, false, true, true;
3037 "only one interface available, has HTTP resolved state, but no DNS")]
3038 fn test_system_has_state<S1, S2>(
3039 ipv4_state: Option<S1>,
3040 ipv6_state: Option<S2>,
3041 expect_http: bool,
3042 expect_dns: bool,
3043 expect_internet: bool,
3044 expect_gateway: bool,
3045 ) where
3046 StateEvent: Construct<S1>,
3047 StateEvent: Construct<S2>,
3048 {
3049 let if1 = ipv4_state
3050 .map(|state| IpVersions::<StateEvent>::construct(StateEvent::construct(state)));
3051 let if2 = ipv6_state
3052 .map(|state| IpVersions::<StateEvent>::construct(StateEvent::construct(state)));
3053
3054 let mut system_interfaces: HashMap<u64, IpVersions<StateEvent>> = HashMap::new();
3055
3056 let system_interface_ipv4 = if1.map(|interface| {
3057 let _ = system_interfaces.insert(ID1, interface);
3058 ID1
3059 });
3060
3061 let system_interface_ipv6 = if2.map(|interface| {
3062 let _ = system_interfaces.insert(ID2, interface);
3063 ID2
3064 });
3065
3066 let state = StateInfo {
3067 per_interface: system_interfaces,
3068 system: IpVersions { ipv4: system_interface_ipv4, ipv6: system_interface_ipv6 },
3069 };
3070
3071 assert_eq!(state.system_has_http(), expect_http);
3072 assert_eq!(state.system_has_dns(), expect_dns);
3073 assert_eq!(state.system_has_internet(), expect_internet);
3074 assert_eq!(state.system_has_gateway(), expect_gateway);
3075 }
3076
3077 #[test]
3078 fn test_resume_after_interface_removed() {
3079 use assert_matches::assert_matches;
3080
3081 let _exec = fasync::TestExecutor::new();
3082 let (sender, _receiver) = mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
3083 let mut monitor: Monitor<MonotonicInstant> = Monitor::new(sender).unwrap();
3084
3085 let properties = fnet_interfaces_ext::Properties {
3086 id: ID1.try_into().expect("should be nonzero"),
3087 name: ETHERNET_INTERFACE_NAME.to_string(),
3088 port_class: fnet_interfaces_ext::PortClass::Ethernet,
3089 online: false,
3090 addresses: vec![],
3091 has_default_ipv4_route: false,
3092 has_default_ipv6_route: false,
3093 };
3094
3095 let initial_state = IpVersions {
3097 ipv4: StateEvent {
3098 state: State { link: LinkState::None, ..Default::default() },
3099 time: fasync::MonotonicInstant::now(),
3100 },
3101 ipv6: StateEvent {
3102 state: State { link: LinkState::None, ..Default::default() },
3103 time: fasync::MonotonicInstant::now(),
3104 },
3105 };
3106 monitor.update_state(ID1, ETHERNET_INTERFACE_NAME, initial_state);
3107
3108 monitor.handle_interface_removed(properties.clone());
3111
3112 let removed_state = monitor.state().get(ID1).unwrap();
3114 assert_eq!(removed_state.ipv4.state.link, LinkState::Removed);
3115 assert_eq!(removed_state.ipv6.state.link, LinkState::Removed);
3116
3117 let routes = testutil::build_route_table_from_flattened_routes([]);
3122 let view = InterfaceView { properties: &properties, routes: &routes, neighbors: None };
3123 assert_matches!(monitor.begin(view), Ok(NetworkCheckerOutcome::Complete));
3124
3125 let interface_context = monitor.interface_context.get(&ID1).unwrap();
3128 assert_matches!(interface_context.discovered_state_v4.link, LinkState::Down);
3129 assert_matches!(interface_context.discovered_state_v6.link, LinkState::Down);
3130
3131 let final_state = monitor.state().get(ID1).unwrap();
3134 assert_eq!(final_state.ipv4.state.link, LinkState::Removed);
3135 assert_eq!(final_state.ipv6.state.link, LinkState::Removed);
3136 }
3137}