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