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::LinkProperties;
21use crate::telemetry::{TelemetryEvent, TelemetrySender};
22use anyhow::anyhow;
23use fidl_fuchsia_net as fnet;
24use fidl_fuchsia_net_ext::{self as fnet_ext, IpExt};
25use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
26use fuchsia_async as fasync;
27use fuchsia_inspect::{Inspector, Node as InspectNode};
28use futures::channel::mpsc;
29use inspect::InspectInfo;
30use log::{debug, error, info};
31use named_timer::DeadlineId;
32use net_declare::{fidl_subnet, std_ip};
33use net_types::ScopeableAddress as _;
34use num_derive::FromPrimitive;
35use std::collections::HashSet;
36use std::collections::hash_map::{Entry, HashMap};
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<telemetry::processors::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 = telemetry::processors::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)]
765pub enum NetworkCheckResult {
766 Ping { parameters: PingParameters, result: Result<(), ping::PingError> },
767 ResolveDns { parameters: ResolveDnsParameters, ips: Option<ResolvedIps> },
768 Fetch { parameters: FetchParameters, result: Result<u16, fetch::FetchError> },
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, Result<(), ping::PingError>)> {
819 match self {
820 NetworkCheckResult::Ping { parameters, result } => Some((parameters, result)),
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, Result<u16, fetch::FetchError>)> {
833 match self {
834 NetworkCheckResult::Fetch { parameters, result } => Some((parameters, result)),
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 port_identity_koid: _,
1152 },
1153 routes,
1154 neighbors,
1155 }: InterfaceView<'_>,
1156 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
1157 let id = Id::from(id);
1158 let telemetry_context = TelemetryContext::new(
1166 port_class,
1167 &addresses,
1168 has_default_ipv4_route,
1169 has_default_ipv6_route,
1170 );
1171 let ctx = self
1172 .interface_context
1173 .entry(id)
1174 .or_insert_with(|| NetworkCheckContext::from(telemetry_context.clone()));
1175
1176 match ctx.checker_state {
1177 NetworkCheckState::Begin => {}
1178 NetworkCheckState::Idle => {
1179 let mut new_ctx = NetworkCheckContext::default();
1180 std::mem::swap(&mut new_ctx.persistent_context, &mut ctx.persistent_context);
1182 new_ctx.persistent_context.telemetry = telemetry_context;
1184 *ctx = new_ctx;
1185 }
1186 NetworkCheckState::PingGateway
1187 | NetworkCheckState::PingInternet
1188 | NetworkCheckState::FetchHttp
1189 | NetworkCheckState::ResolveDns => {
1190 ctx.persistent_context.telemetry = telemetry_context;
1193 return Err(anyhow!("skipped, non-idle state found on interface {id}"));
1194 }
1195 }
1196
1197 if !online {
1198 ctx.set_global_link_state(LinkState::Down);
1199 return self.update_state_from_context(id, name);
1200 }
1201
1202 ctx.set_global_link_state(LinkState::Up);
1203
1204 let device_routes: Vec<_> = routes.device_routes(id).collect();
1207
1208 let neighbor_scan_health = scan_neighbor_health(neighbors, &device_routes);
1209
1210 let has_route = IpVersions {
1211 ipv4: device_routes
1212 .iter()
1213 .any(|route| matches!(route.destination.addr, fnet::IpAddress::Ipv4(_))),
1214 ipv6: device_routes
1215 .iter()
1216 .any(|route| matches!(route.destination.addr, fnet::IpAddress::Ipv6(_))),
1217 };
1218
1219 if neighbor_scan_health.ipv4 == NeighborHealthScanResult::NoneHealthy
1220 && neighbor_scan_health.ipv6 == NeighborHealthScanResult::NoneHealthy
1221 {
1222 if !has_route.ipv4 && !has_route.ipv6 {
1223 return self.update_state_from_context(id, name);
1225 }
1226
1227 ctx.always_ping_internet = false;
1230 }
1231 if has_route.ipv4 || neighbor_scan_health.ipv4.is_healthy() {
1232 ctx.discovered_state.ipv4.set_link_state(LinkState::Local);
1233 }
1234 if has_route.ipv6 || neighbor_scan_health.ipv6.is_healthy() {
1235 ctx.discovered_state.ipv6.set_link_state(LinkState::Local);
1236 }
1237
1238 let gateway_ping_addrs = device_routes
1239 .iter()
1240 .filter_map(move |Route { destination, outbound_interface, next_hop }| {
1241 if *destination != UNSPECIFIED_V4 && *destination != UNSPECIFIED_V6 {
1242 return None;
1243 }
1244 next_hop.and_then(|next_hop| {
1245 let fnet_ext::IpAddress(next_hop) = next_hop.into();
1246 match next_hop.into() {
1247 std::net::IpAddr::V4(v4) => {
1248 Some(std::net::SocketAddr::V4(std::net::SocketAddrV4::new(v4, 0)))
1249 }
1250 std::net::IpAddr::V6(v6) => match (*outbound_interface).try_into() {
1251 Err(std::num::TryFromIntError { .. }) => {
1252 error!("device id {} doesn't fit in u32", outbound_interface);
1253 None
1254 }
1255 Ok(device_id) => {
1256 if device_id == 0
1257 && net_types::ip::Ipv6Addr::from_bytes(v6.octets()).scope()
1258 != net_types::ip::Ipv6Scope::Global
1259 {
1260 None
1261 } else {
1262 Some(std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
1263 v6, 0, 0, device_id,
1264 )))
1265 }
1266 }
1267 },
1268 }
1269 })
1270 })
1271 .map(|next_hop| next_hop)
1272 .collect::<Vec<_>>();
1273
1274 ctx.router_discoverable = IpVersions {
1276 ipv4: neighbor_scan_health.ipv4 == NeighborHealthScanResult::HealthyRouter,
1277 ipv6: neighbor_scan_health.ipv6 == NeighborHealthScanResult::HealthyRouter,
1278 };
1279 if gateway_ping_addrs.is_empty() {
1280 if neighbor_scan_health.ipv4 == NeighborHealthScanResult::HealthyRouter
1292 || neighbor_scan_health.ipv6 == NeighborHealthScanResult::HealthyRouter
1293 {
1294 ctx.initiate_ping(
1299 id,
1300 name,
1301 &self.network_check_sender,
1302 NetworkCheckState::PingInternet,
1303 [
1304 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1305 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1306 ]
1307 .into_iter()
1308 .map(|ip| std::net::SocketAddr::new(ip, 0))
1309 .collect(),
1310 );
1311 } else {
1312 return self.update_state_from_context(id, name);
1316 }
1317 } else {
1318 if neighbor_scan_health.ipv4.is_healthy_router() {
1320 ctx.discovered_state.ipv4.set_link_state(LinkState::Gateway);
1321 }
1322 if neighbor_scan_health.ipv6.is_healthy_router() {
1323 ctx.discovered_state.ipv6.set_link_state(LinkState::Gateway);
1324 }
1325 ctx.initiate_ping(
1326 id,
1327 name,
1328 &self.network_check_sender,
1329 NetworkCheckState::PingGateway,
1330 gateway_ping_addrs,
1331 );
1332 }
1333 Ok(NetworkCheckerOutcome::MustResume)
1334 }
1335
1336 fn resume(
1337 &mut self,
1338 cookie: NetworkCheckCookie,
1339 result: NetworkCheckResult,
1340 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
1341 let ctx = self.interface_context.get_mut(&cookie.id).ok_or_else(|| {
1342 anyhow!("resume: interface id {} should already exist in map", cookie.id)
1343 })?;
1344 let interface_name = result.interface_name().to_string();
1345 match ctx.checker_state {
1346 NetworkCheckState::Begin | NetworkCheckState::Idle => {
1347 return Err(anyhow!(
1348 "skipped, idle state found in resume for interface {}",
1349 cookie.id
1350 ));
1351 }
1352 NetworkCheckState::PingGateway | NetworkCheckState::PingInternet => {
1353 let (parameters, result) = result.ping_result().ok_or_else(|| {
1354 anyhow!("resume: mismatched state and result {interface_name} ({})", cookie.id)
1355 })?;
1356 ctx.pings_completed = ctx.pings_completed + 1;
1357
1358 let ping_is_ok = result.is_ok();
1360
1361 if let Some(telemetry_sender) = &mut self.telemetry_sender {
1362 let interface_identifiers =
1363 ctx.persistent_context.telemetry.interface_identifiers.clone();
1364 if let NetworkCheckState::PingInternet = ctx.checker_state {
1365 telemetry_sender.send(TelemetryEvent::InternetPingResult {
1366 interface_identifiers,
1367 ping_parameters: parameters.clone(),
1368 internet_ping_result: result,
1369 });
1370 } else {
1371 telemetry_sender.send(TelemetryEvent::GatewayPingResult {
1372 interface_identifiers,
1373 ping_parameters: parameters.clone(),
1374 gateway_ping_result: result,
1375 });
1376 }
1377 }
1378
1379 let PingParameters { interface_name, addr, .. } = parameters;
1380 if ping_is_ok {
1381 let () = Self::handle_ping_success(ctx, &addr);
1382 }
1383
1384 if ctx.pings_completed == ctx.pings_expected {
1385 if let NetworkCheckState::PingGateway = ctx.checker_state {
1386 ctx.initiate_ping(
1387 cookie.id,
1388 &interface_name,
1389 &self.network_check_sender,
1390 NetworkCheckState::PingInternet,
1391 [
1392 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1393 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1394 ]
1395 .into_iter()
1396 .map(|ip| std::net::SocketAddr::new(ip, 0))
1397 .collect(),
1398 );
1399 } else {
1400 let parameters = ResolveDnsParameters {
1401 interface_name: interface_name.to_string(),
1402 domain: GSTATIC.into(),
1403 };
1404 ctx.checker_state = NetworkCheckState::ResolveDns;
1405
1406 if self.time_provider.now() - ctx.persistent_context.resolved_time
1407 < DNS_PROBE_PERIOD
1408 {
1409 debug!(
1410 "Skipping ResolveDns since it has not yet been {} seconds",
1411 DNS_PROBE_PERIOD.clone().into_seconds()
1412 );
1413 if let Some(ips) = ctx.persistent_context.resolved_addrs.get(GSTATIC) {
1414 if !ips.v4.is_empty() {
1415 ctx.discovered_state.ipv4.application.dns_resolved = true;
1416 }
1417 if !ips.v6.is_empty() {
1418 ctx.discovered_state.ipv6.application.dns_resolved = true;
1419 }
1420 }
1421 return self.resume(
1422 cookie,
1423 NetworkCheckResult::ResolveDns { parameters, ips: None },
1424 );
1425 }
1426
1427 let action = NetworkCheckAction::ResolveDns(parameters);
1428 match self
1429 .network_check_sender
1430 .unbounded_send((action, NetworkCheckCookie { id: cookie.id }))
1431 {
1432 Ok(()) => {}
1433 Err(e) => {
1434 debug!("unable to send network check internet msg: {e:?}")
1435 }
1436 }
1437 }
1438 }
1439 }
1440 NetworkCheckState::ResolveDns => {
1441 let (ResolveDnsParameters { interface_name, domain }, ips) =
1442 result.resolve_dns_result().ok_or_else(|| {
1443 anyhow!(
1444 "resume: mismatched state and result {interface_name} ({})",
1445 cookie.id
1446 )
1447 })?;
1448
1449 if let Some(ips) = ips {
1450 if !ips.v4.is_empty() {
1451 ctx.discovered_state.ipv4.application.dns_resolved = true;
1452 }
1453 if !ips.v6.is_empty() {
1454 ctx.discovered_state.ipv6.application.dns_resolved = true;
1455 }
1456 ctx.persistent_context.resolved_time = self.time_provider.now();
1457 let _: Option<ResolvedIps> =
1458 ctx.persistent_context.resolved_addrs.insert(domain.clone(), ips);
1459 }
1460
1461 ctx.checker_state = NetworkCheckState::FetchHttp;
1462 ctx.fetches_expected = 0;
1463
1464 let mut add_fetch = |ip: IpAddr| {
1465 ctx.fetches_expected += 1;
1466 let action = NetworkCheckAction::Fetch(FetchParameters {
1467 interface_name: interface_name.clone(),
1468 domain: domain.clone(),
1469 ip,
1470 path: GENERATE_204.into(),
1471 expected_statuses: vec![204],
1472 });
1473 match self
1474 .network_check_sender
1475 .unbounded_send((action, NetworkCheckCookie { id: cookie.id }))
1476 {
1477 Ok(()) => {}
1478 Err(e) => debug!("unable to send network check internet message: {e:?}"),
1479 }
1480 };
1481
1482 if let Some(v4) =
1483 ctx.persistent_context.resolved_addrs.get(&domain).and_then(|ips| ips.v4.get(0))
1484 {
1485 add_fetch(IpAddr::V4(*v4));
1486 }
1487 if let Some(v6) =
1488 ctx.persistent_context.resolved_addrs.get(&domain).and_then(|ips| ips.v6.get(0))
1489 {
1490 add_fetch(IpAddr::V6(*v6));
1491 }
1492
1493 if ctx.fetches_expected == 0 {
1494 return self.update_state_from_context(cookie.id, &interface_name);
1495 }
1496 }
1497 NetworkCheckState::FetchHttp => {
1498 let (parameters, result) = result.fetch_result().ok_or_else(|| {
1499 anyhow!("resume: mismatched state and result {interface_name} ({})", cookie.id)
1500 })?;
1501 ctx.fetches_completed += 1;
1502
1503 let fetch_ok_status = result.as_ref().copied().ok();
1505
1506 if let Some(telemetry_sender) = &mut self.telemetry_sender {
1507 telemetry_sender.send(TelemetryEvent::FetchResult {
1508 interface_identifiers: ctx
1509 .persistent_context
1510 .telemetry
1511 .interface_identifiers
1512 .clone(),
1513 fetch_parameters: parameters.clone(),
1514 fetch_result: result,
1515 });
1516 }
1517
1518 let FetchParameters { interface_name, ip, expected_statuses, .. } = parameters;
1519 if let Some(status) = fetch_ok_status {
1520 if expected_statuses.contains(&status) {
1521 let () = Self::handle_fetch_success(ctx, ip);
1522 }
1523 }
1524
1525 if ctx.fetches_completed == ctx.fetches_expected {
1526 return self.update_state_from_context(cookie.id, &interface_name);
1527 }
1528 }
1529 }
1530 Ok(NetworkCheckerOutcome::MustResume)
1531 }
1532}
1533
1534fn log_state(info: Option<&mut InspectInfo>, proto: Proto, state: State) {
1535 info.into_iter().for_each(|info| info.log_link_state(proto, state.link))
1536}
1537
1538#[derive(Default, PartialEq)]
1539enum NeighborHealthScanResult {
1540 #[default]
1542 NoneHealthy,
1543 HealthyNeighbor,
1545 HealthyRouter,
1549}
1550
1551impl NeighborHealthScanResult {
1552 fn update_scan_result(&mut self, is_router: bool) {
1555 *self = match (&self, is_router) {
1556 (_, true) | (Self::HealthyRouter, _) => Self::HealthyRouter,
1558 _ => Self::HealthyNeighbor,
1559 }
1560 }
1561
1562 fn is_healthy(&self) -> bool {
1563 match self {
1564 Self::NoneHealthy => false,
1565 Self::HealthyNeighbor | Self::HealthyRouter => true,
1566 }
1567 }
1568
1569 fn is_healthy_router(&self) -> bool {
1570 match self {
1571 Self::NoneHealthy | Self::HealthyNeighbor => false,
1572 Self::HealthyRouter => true,
1573 }
1574 }
1575}
1576
1577fn scan_neighbor_health(
1580 neighbors: Option<&InterfaceNeighborCache>,
1581 device_routes: &Vec<route_table::Route>,
1582) -> IpVersions<NeighborHealthScanResult> {
1583 match neighbors {
1584 None => Default::default(),
1585 Some(neighbors) => {
1586 let router_next_hops: HashSet<_> =
1587 device_routes.iter().filter_map(|Route { next_hop, .. }| *next_hop).collect();
1588 neighbors.iter_health().fold(
1589 Default::default(),
1590 |mut neighbor_health_scan, (neighbor, health)| {
1591 match health {
1592 neighbor_cache::NeighborHealth::Unhealthy { .. }
1595 | neighbor_cache::NeighborHealth::Unknown => neighbor_health_scan,
1596 neighbor_cache::NeighborHealth::Healthy { .. } => {
1600 let scan = match neighbor {
1601 fnet::IpAddress::Ipv4(..) => &mut neighbor_health_scan.ipv4,
1602 fnet::IpAddress::Ipv6(..) => &mut neighbor_health_scan.ipv6,
1603 };
1604
1605 scan.update_scan_result(router_next_hops.contains(neighbor));
1606 neighbor_health_scan
1607 }
1608 }
1609 },
1610 )
1611 }
1612 }
1613}
1614
1615#[cfg(test)]
1616mod tests {
1617 use crate::fetch::FetchAddr;
1618
1619 use super::*;
1620 use crate::dig::Dig;
1621 use crate::fetch::Fetch;
1622 use crate::neighbor_cache::{NeighborHealth, NeighborState};
1623 use crate::ping::Ping;
1624 use async_trait::async_trait;
1625 use diagnostics_assertions::assert_data_tree;
1626 use fidl_fuchsia_net as fnet;
1627 use fidl_fuchsia_net_interfaces as fnet_interfaces;
1628 use fuchsia_async as fasync;
1629 use futures::StreamExt as _;
1630 use net_declare::{fidl_ip, fidl_subnet, std_ip, std_socket_addr};
1631 use net_types::ip;
1632 use std::pin::pin;
1633 use std::task::Poll;
1634 use test_case::test_case;
1635
1636 const ETHERNET_INTERFACE_NAME: &str = "eth1";
1637 const ID1: u64 = 1;
1638 const ID2: u64 = 2;
1639 const IPV4_ADDR: fnet::IpAddress = fidl_ip!("192.168.0.1");
1641 const IPV6_ADDR: fnet::IpAddress = fidl_ip!("2001:db8::");
1643
1644 trait Construct<T> {
1649 fn construct(_: T) -> Self;
1650 }
1651
1652 impl<S: Into<State>> Construct<S> for StateEvent {
1653 fn construct(link: S) -> Self {
1654 Self { state: link.into(), time: fasync::MonotonicInstant::INFINITE }
1655 }
1656 }
1657
1658 impl Construct<(LinkState, bool, bool)> for StateEvent {
1659 fn construct((link, dns_resolved, http_fetch_succeeded): (LinkState, bool, bool)) -> Self {
1660 Self {
1661 state: State {
1662 link,
1663 application: ApplicationState { dns_resolved, http_fetch_succeeded },
1664 },
1665 time: fasync::MonotonicInstant::INFINITE,
1666 }
1667 }
1668 }
1669
1670 impl Construct<StateEvent> for IpVersions<StateEvent> {
1671 fn construct(state: StateEvent) -> Self {
1672 Self { ipv4: state, ipv6: state }
1673 }
1674 }
1675
1676 struct FakeTime {
1677 increment: zx::MonotonicDuration,
1678 time: zx::MonotonicInstant,
1679 }
1680
1681 impl TimeProvider for FakeTime {
1682 fn now(&mut self) -> zx::MonotonicInstant {
1683 let result = self.time;
1684 self.time += self.increment;
1685 result
1686 }
1687 }
1688
1689 #[fuchsia::test]
1690 async fn test_log_state_vals_inspect() {
1691 let inspector = Inspector::default();
1692 LinkState::log_state_vals_inspect(inspector.root(), "state_vals");
1693 assert_data_tree!(inspector, root: {
1694 state_vals: {
1695 "1": "None",
1696 "5": "Removed",
1697 "10": "Down",
1698 "15": "Up",
1699 "20": "Local",
1700 "25": "Gateway",
1701 "30": "Internet",
1702 }
1703 })
1704 }
1705
1706 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("1.2.3.0:8080")];
1707 "gateway ping on ipv4")]
1708 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("[123::]:0")];
1709 "gateway ping on ipv6")]
1710 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("1.2.3.0:8080"),
1711 std_socket_addr!("[123::]:0")]; "gateway ping on ipv4/ipv6")]
1712 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("8.8.8.8:0")];
1713 "internet ping on ipv4")]
1714 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("[2001:4860:4860::8888]:0")];
1715 "internet ping on ipv6")]
1716 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("8.8.8.8:0"),
1717 std_socket_addr!("[2001:4860:4860::8888]:0")]; "internet ping on ipv4/ipv6")]
1718 fn test_handle_ping_success(checker_state: NetworkCheckState, addrs: &[std::net::SocketAddr]) {
1719 let mut expected_state_v4: State = Default::default();
1720 let mut expected_state_v6: State = Default::default();
1721
1722 let mut ctx = NetworkCheckContext { checker_state, ..Default::default() };
1723 assert_eq!(ctx.discovered_state.ipv4, expected_state_v4);
1725 assert_eq!(ctx.discovered_state.ipv6, expected_state_v6);
1726
1727 let expected_state = match ctx.checker_state {
1728 NetworkCheckState::PingGateway => LinkState::Gateway.into(),
1729 NetworkCheckState::PingInternet => LinkState::Internet.into(),
1730 NetworkCheckState::ResolveDns => LinkState::Internet.into(),
1731 NetworkCheckState::FetchHttp => State {
1732 link: LinkState::Internet,
1733 application: ApplicationState { dns_resolved: true, http_fetch_succeeded: true },
1734 },
1735 NetworkCheckState::Begin | NetworkCheckState::Idle => Default::default(),
1736 };
1737
1738 addrs.iter().for_each(|addr| {
1739 let () = Monitor::<FakeTime>::handle_ping_success(&mut ctx, addr);
1741 match addr {
1743 std::net::SocketAddr::V4 { .. } => {
1744 expected_state_v4 = expected_state;
1745 }
1746 std::net::SocketAddr::V6 { .. } => {
1747 expected_state_v6 = expected_state;
1748 }
1749 }
1750 });
1751 assert_eq!(ctx.discovered_state.ipv4, expected_state_v4);
1753 assert_eq!(ctx.discovered_state.ipv6, expected_state_v6);
1754 }
1755
1756 #[derive(Default, Clone)]
1757 struct FakePing {
1758 gateway_addrs: std::collections::HashSet<std::net::IpAddr>,
1759 gateway_response: bool,
1760 internet_response: bool,
1761 }
1762
1763 #[async_trait]
1764 impl Ping for FakePing {
1765 async fn ping(
1766 &self,
1767 _interface_name: &str,
1768 addr: std::net::SocketAddr,
1769 ) -> Result<(), crate::ping::PingError> {
1770 let Self { gateway_addrs, gateway_response, internet_response } = self;
1771 let ip = addr.ip();
1772 let success = if [
1773 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1774 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1775 ]
1776 .contains(&ip)
1777 {
1778 *internet_response
1779 } else if gateway_addrs.contains(&ip) {
1780 *gateway_response
1781 } else {
1782 false
1783 };
1784 if success { Ok(()) } else { Err(crate::ping::PingError::NoReply) }
1785 }
1786 }
1787
1788 #[derive(Default)]
1789 struct FakeDig {
1790 response: Option<ResolvedIps>,
1791 }
1792
1793 impl FakeDig {
1794 fn new(ips: Vec<std::net::IpAddr>) -> Self {
1795 let mut ips_out = ResolvedIps::default();
1796 for ip in ips {
1797 match ip {
1798 IpAddr::V4(v4) => ips_out.v4.push(v4),
1799 IpAddr::V6(v6) => ips_out.v6.push(v6),
1800 }
1801 }
1802 FakeDig { response: Some(ips_out) }
1803 }
1804 }
1805
1806 #[async_trait]
1807 impl Dig for FakeDig {
1808 async fn dig(&self, _interface_name: &str, _domain: &str) -> Option<ResolvedIps> {
1809 self.response.clone()
1810 }
1811 }
1812
1813 #[derive(Default)]
1814 struct FakeFetch {
1815 expected_url: Option<&'static str>,
1816 response: Option<Box<dyn Fn() -> Result<u16, fetch::FetchError> + Send + Sync>>,
1817 }
1818
1819 #[async_trait]
1820 impl Fetch for FakeFetch {
1821 async fn fetch<FA: FetchAddr + std::marker::Sync>(
1822 &self,
1823 _interface_name: &str,
1824 domain: &str,
1825 path: &str,
1826 _addr: &FA,
1827 ) -> Result<u16, fetch::FetchError> {
1828 if let Some(expected) = self.expected_url {
1829 assert_eq!(
1830 format!("http://{domain}{path}"),
1831 expected,
1832 "Did not receive expected URL"
1833 );
1834 }
1835 if let Some(response) = &self.response {
1836 response()
1837 } else {
1838 Err(fetch::FetchError::ReadTcpStreamTimeout)
1839 }
1840 }
1841 }
1842
1843 struct NetworkCheckTestResponder {
1844 receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
1845 }
1846
1847 impl NetworkCheckTestResponder {
1848 fn new(
1849 receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
1850 ) -> Self {
1851 Self { receiver }
1852 }
1853
1854 async fn respond_to_messages<P: Ping, D: Dig, F: Fetch, Time: TimeProvider>(
1855 &mut self,
1856 monitor: &mut Monitor<Time>,
1857 p: P,
1858 d: D,
1859 f: F,
1860 ) {
1861 loop {
1862 if let Some((action, cookie)) = self.receiver.next().await {
1863 match action {
1864 NetworkCheckAction::Ping(parameters) => {
1865 let result = p.ping(¶meters.interface_name, parameters.addr).await;
1866 match monitor
1867 .resume(cookie, NetworkCheckResult::Ping { parameters, result })
1868 {
1869 Ok(NetworkCheckerOutcome::Complete) => return,
1871 _ => {}
1872 }
1873 }
1874 NetworkCheckAction::ResolveDns(parameters) => {
1875 let ips = d.dig(¶meters.interface_name, ¶meters.domain).await;
1876 match monitor
1877 .resume(cookie, NetworkCheckResult::ResolveDns { parameters, ips })
1878 {
1879 Ok(NetworkCheckerOutcome::Complete) => return,
1881 _ => {}
1882 }
1883 }
1884 NetworkCheckAction::Fetch(parameters) => {
1885 let result = f
1886 .fetch(
1887 ¶meters.interface_name,
1888 ¶meters.domain,
1889 ¶meters.path,
1890 ¶meters.ip,
1891 )
1892 .await;
1893 match monitor
1894 .resume(cookie, NetworkCheckResult::Fetch { parameters, result })
1895 {
1896 Ok(NetworkCheckerOutcome::Complete) => return,
1898 _ => {}
1899 }
1900 }
1901 }
1902 }
1903 }
1904 }
1905 }
1906
1907 fn run_network_check_partial_properties_repeated<P: Ping, D: Dig, F: Fetch>(
1908 exec: &mut fasync::TestExecutor,
1909 name: &str,
1910 interface_id: u64,
1911 routes: &RouteTable,
1912 mocks: Vec<(P, D, F)>,
1913 neighbors: Option<&InterfaceNeighborCache>,
1914 internet_ping_address: std::net::IpAddr,
1915 sleep_between: Option<zx::MonotonicDuration>,
1916 ) -> Vec<State> {
1917 let properties = &fnet_interfaces_ext::Properties {
1918 id: interface_id.try_into().expect("should be nonzero"),
1919 name: name.to_string(),
1920 port_class: fnet_interfaces_ext::PortClass::Ethernet,
1921 online: true,
1922 addresses: Default::default(),
1923 has_default_ipv4_route: Default::default(),
1924 has_default_ipv6_route: Default::default(),
1925 port_identity_koid: Default::default(),
1926 };
1927
1928 let mock_count = mocks.len();
1929 match run_network_check_repeated(exec, properties, routes, neighbors, mocks, sleep_between)
1930 {
1931 Ok(Some(events)) => {
1932 events
1936 .into_iter()
1937 .map(|event| match internet_ping_address {
1938 std::net::IpAddr::V4 { .. } => event.ipv4.state,
1939 std::net::IpAddr::V6 { .. } => event.ipv6.state,
1940 })
1941 .collect()
1942 }
1943 Ok(None) => {
1944 error!("id for interface unexpectedly did not exist after network check");
1945 std::iter::repeat(LinkState::None.into()).take(mock_count).collect()
1946 }
1947 Err(e) => {
1948 error!("network check had an issue calculating state: {:?}", e);
1949 std::iter::repeat(LinkState::None.into()).take(mock_count).collect()
1950 }
1951 }
1952 }
1953
1954 fn run_network_check_partial_properties<P: Ping, D: Dig, F: Fetch>(
1955 exec: &mut fasync::TestExecutor,
1956 name: &str,
1957 interface_id: u64,
1958 routes: &RouteTable,
1959 pinger: P,
1960 digger: D,
1961 fetcher: F,
1962 neighbors: Option<&InterfaceNeighborCache>,
1963 internet_ping_address: std::net::IpAddr,
1964 ) -> State {
1965 run_network_check_partial_properties_repeated(
1966 exec,
1967 name,
1968 interface_id,
1969 routes,
1970 vec![(pinger, digger, fetcher)],
1971 neighbors,
1972 internet_ping_address,
1973 None,
1974 )
1975 .pop()
1976 .unwrap_or_else(|| {
1977 error!("network check returned no states");
1978 LinkState::None.into()
1979 })
1980 }
1981
1982 fn run_network_check_repeated<P: Ping, D: Dig, F: Fetch>(
1983 exec: &mut fasync::TestExecutor,
1984 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1985 routes: &RouteTable,
1986 neighbors: Option<&InterfaceNeighborCache>,
1987 mocks: Vec<(P, D, F)>,
1988 sleep_between: Option<zx::MonotonicDuration>,
1989 ) -> Result<Option<Vec<IpVersions<StateEvent>>>, anyhow::Error> {
1990 let (sender, receiver) = mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
1991 let mut monitor = Monitor::new_with_time_provider(
1992 sender,
1993 FakeTime {
1994 increment: sleep_between.unwrap_or(zx::MonotonicDuration::from_nanos(10)),
1995 time: zx::MonotonicInstant::get(),
1996 },
1997 )
1998 .unwrap();
1999 let mut network_check_responder = NetworkCheckTestResponder::new(receiver);
2000
2001 let view = InterfaceView { properties, routes, neighbors };
2002 let network_check_fut = async {
2003 let mut states = Vec::new();
2004 for (pinger, digger, fetcher) in mocks {
2005 match monitor.begin(view) {
2006 Ok(NetworkCheckerOutcome::Complete) => {}
2007 Ok(NetworkCheckerOutcome::MustResume) => {
2008 let () = network_check_responder
2009 .respond_to_messages(&mut monitor, pinger, digger, fetcher)
2010 .await;
2011 }
2012 Err(e) => {
2013 error!("begin had an issue calculating state: {:?}", e)
2014 }
2015 }
2016 states.push(monitor.state().get(properties.id.get()).map(Clone::clone));
2017 }
2018 states
2019 };
2020
2021 let mut network_check_fut = pin!(network_check_fut);
2022 match exec.run_until_stalled(&mut network_check_fut) {
2023 Poll::Ready(got) => Ok(got.into_iter().collect()),
2024 Poll::Pending => Err(anyhow::anyhow!("network_check blocked unexpectedly")),
2025 }
2026 }
2027
2028 fn run_network_check<P: Ping, D: Dig, F: Fetch>(
2029 exec: &mut fasync::TestExecutor,
2030 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
2031 routes: &RouteTable,
2032 neighbors: Option<&InterfaceNeighborCache>,
2033 pinger: P,
2034 digger: D,
2035 fetcher: F,
2036 ) -> Result<Option<IpVersions<StateEvent>>, anyhow::Error> {
2037 run_network_check_repeated(
2038 exec,
2039 properties,
2040 routes,
2041 neighbors,
2042 vec![(pinger, digger, fetcher)],
2043 None,
2044 )
2045 .map(|res| res.and_then(|mut v| v.pop()))
2046 }
2047
2048 #[test]
2049 fn test_network_check_ipv6_local_only() {
2050 let mut exec = fasync::TestExecutor::new_with_fake_time();
2051 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
2052 let () = exec.set_fake_time(time.into());
2053
2054 let routes = testutil::build_route_table_from_flattened_routes([Route {
2057 destination: UNSPECIFIED_V6,
2058 outbound_interface: ID1,
2059 next_hop: Some(IPV6_ADDR),
2060 }]);
2061 let properties = &fnet_interfaces_ext::Properties {
2062 id: ID1.try_into().expect("should be nonzero"),
2063 name: ETHERNET_INTERFACE_NAME.to_string(),
2064 port_class: fnet_interfaces_ext::PortClass::Ethernet,
2065 online: true,
2066 addresses: vec![],
2067 has_default_ipv4_route: false,
2068 has_default_ipv6_route: true,
2069 port_identity_koid: Default::default(),
2070 };
2071 let neighbors = InterfaceNeighborCache::default();
2072
2073 let got = run_network_check(
2074 &mut exec,
2075 properties,
2076 &routes,
2077 Some(&neighbors),
2078 FakePing::default(),
2079 FakeDig::default(),
2080 FakeFetch::default(),
2081 )
2082 .expect("run_network_check failed")
2083 .expect("interface state not found");
2084
2085 let want_ipv4 =
2086 StateEvent { state: State { link: LinkState::Up, ..Default::default() }, time };
2087 let want_ipv6 =
2088 StateEvent { state: State { link: LinkState::Local, ..Default::default() }, time };
2089 assert_eq!(got.ipv4, want_ipv4);
2090 assert_eq!(got.ipv6, want_ipv6);
2091 }
2092
2093 #[test]
2094 fn test_network_check_ipv6_local_only_not_default_route() {
2095 let mut exec = fasync::TestExecutor::new_with_fake_time();
2096 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
2097 let () = exec.set_fake_time(time.into());
2098
2099 let routes = testutil::build_route_table_from_flattened_routes([Route {
2102 destination: fidl_subnet!("::/1"),
2103 outbound_interface: ID1,
2104 next_hop: Some(IPV6_ADDR),
2105 }]);
2106 let properties = &fnet_interfaces_ext::Properties {
2107 id: ID1.try_into().expect("should be nonzero"),
2108 name: ETHERNET_INTERFACE_NAME.to_string(),
2109 port_class: fnet_interfaces_ext::PortClass::Ethernet,
2110 online: true,
2111 addresses: vec![],
2112 has_default_ipv4_route: false,
2113 has_default_ipv6_route: true,
2114 port_identity_koid: Default::default(),
2115 };
2116 let neighbors = InterfaceNeighborCache::default();
2117
2118 let got = run_network_check(
2119 &mut exec,
2120 properties,
2121 &routes,
2122 Some(&neighbors),
2123 FakePing::default(),
2124 FakeDig::default(),
2125 FakeFetch::default(),
2126 )
2127 .expect("run_network_check failed")
2128 .expect("interface state not found");
2129
2130 let want_ipv4 =
2131 StateEvent { state: State { link: LinkState::Up, ..Default::default() }, time };
2132 let want_ipv6 =
2133 StateEvent { state: State { link: LinkState::Local, ..Default::default() }, time };
2134 assert_eq!(got.ipv4, want_ipv4);
2135 assert_eq!(got.ipv6, want_ipv6);
2136 }
2137
2138 #[test]
2139 fn test_network_check_ipv6_gateway_only() {
2140 let mut exec = fasync::TestExecutor::new_with_fake_time();
2141 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
2142 let () = exec.set_fake_time(time.into());
2143
2144 let routes = testutil::build_route_table_from_flattened_routes([Route {
2147 destination: UNSPECIFIED_V6,
2148 outbound_interface: ID1,
2149 next_hop: Some(IPV6_ADDR),
2150 }]);
2151 let properties = &fnet_interfaces_ext::Properties {
2152 id: ID1.try_into().expect("should be nonzero"),
2153 name: ETHERNET_INTERFACE_NAME.to_string(),
2154 port_class: fnet_interfaces_ext::PortClass::Ethernet,
2155 online: true,
2156 addresses: vec![],
2157 has_default_ipv4_route: false,
2158 has_default_ipv6_route: true,
2159 port_identity_koid: Default::default(),
2160 };
2161 let neighbors = InterfaceNeighborCache {
2162 neighbors: [(
2163 IPV6_ADDR,
2164 NeighborState::new(NeighborHealth::Healthy {
2165 last_observed: zx::MonotonicInstant::default(),
2166 }),
2167 )]
2168 .into_iter()
2169 .collect::<HashMap<fnet::IpAddress, NeighborState>>(),
2170 };
2171
2172 let got = run_network_check(
2173 &mut exec,
2174 properties,
2175 &routes,
2176 Some(&neighbors),
2177 FakePing::default(),
2178 FakeDig::default(),
2179 FakeFetch::default(),
2180 )
2181 .expect("run_network_check failed")
2182 .expect("interface state not found");
2183
2184 let want_ipv4 =
2185 StateEvent { state: State { link: LinkState::Up, ..Default::default() }, time };
2186 let want_ipv6 =
2187 StateEvent { state: State { link: LinkState::Gateway, ..Default::default() }, time };
2188 assert_eq!(got.ipv4, want_ipv4);
2189 assert_eq!(got.ipv6, want_ipv6);
2190 }
2191
2192 #[fuchsia::test]
2193 fn test_network_check_ipv4_and_ipv6_gateway() {
2194 let mut exec = fasync::TestExecutor::new_with_fake_time();
2195 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
2196 let () = exec.set_fake_time(time.into());
2197
2198 let routes = testutil::build_route_table_from_flattened_routes([
2201 Route {
2202 destination: UNSPECIFIED_V4,
2203 outbound_interface: ID1,
2204 next_hop: Some(IPV4_ADDR),
2205 },
2206 Route {
2207 destination: UNSPECIFIED_V6,
2208 outbound_interface: ID1,
2209 next_hop: Some(IPV6_ADDR),
2210 },
2211 ]);
2212 let properties = &fnet_interfaces_ext::Properties {
2213 id: ID1.try_into().expect("should be nonzero"),
2214 name: ETHERNET_INTERFACE_NAME.to_string(),
2215 port_class: fnet_interfaces_ext::PortClass::Ethernet,
2216 online: true,
2217 addresses: vec![],
2218 has_default_ipv4_route: true,
2219 has_default_ipv6_route: true,
2220 port_identity_koid: Default::default(),
2221 };
2222 let neighbors = InterfaceNeighborCache {
2223 neighbors: [
2224 (
2225 IPV4_ADDR,
2226 NeighborState::new(NeighborHealth::Healthy {
2227 last_observed: zx::MonotonicInstant::default(),
2228 }),
2229 ),
2230 (
2231 IPV6_ADDR,
2232 NeighborState::new(NeighborHealth::Healthy {
2233 last_observed: zx::MonotonicInstant::default(),
2234 }),
2235 ),
2236 ]
2237 .into_iter()
2238 .collect::<HashMap<fnet::IpAddress, NeighborState>>(),
2239 };
2240
2241 let got = run_network_check(
2242 &mut exec,
2243 properties,
2244 &routes,
2245 Some(&neighbors),
2246 FakePing::default(),
2247 FakeDig::default(),
2248 FakeFetch::default(),
2249 )
2250 .expect("run_network_check failed")
2251 .expect("interface state not found");
2252
2253 assert_eq!(
2254 got,
2255 IpVersions::construct(StateEvent {
2256 state: State { link: LinkState::Gateway, ..Default::default() },
2257 time
2258 })
2259 );
2260 }
2261
2262 #[test]
2263 fn test_network_check_ethernet_ipv4() {
2264 test_network_check_ethernet::<ip::Ipv4>(
2265 fidl_ip!("1.2.3.0"),
2266 fidl_ip!("1.2.3.4"),
2267 fidl_ip!("1.2.3.1"),
2268 fidl_ip!("2.2.3.0"),
2269 fidl_ip!("2.2.3.1"),
2270 UNSPECIFIED_V4,
2271 fidl_subnet!("0.0.0.0/1"),
2272 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
2273 24,
2274 );
2275 }
2276
2277 #[test]
2278 fn test_network_check_ethernet_ipv6() {
2279 test_network_check_ethernet::<ip::Ipv6>(
2280 fidl_ip!("123::"),
2281 fidl_ip!("123::4"),
2282 fidl_ip!("123::1"),
2283 fidl_ip!("223::"),
2284 fidl_ip!("223::1"),
2285 UNSPECIFIED_V6,
2286 fidl_subnet!("::/1"),
2287 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
2288 64,
2289 );
2290 }
2291
2292 fn test_network_check_ethernet<I: ip::Ip>(
2293 net1: fnet::IpAddress,
2294 _net1_addr: fnet::IpAddress,
2295 net1_gateway: fnet::IpAddress,
2296 net2: fnet::IpAddress,
2297 net2_gateway: fnet::IpAddress,
2298 unspecified_addr: fnet::Subnet,
2299 non_default_addr: fnet::Subnet,
2300 ping_internet_addr: std::net::IpAddr,
2301 prefix_len: u8,
2302 ) {
2303 let route_table = testutil::build_route_table_from_flattened_routes([
2304 Route {
2305 destination: unspecified_addr,
2306 outbound_interface: ID1,
2307 next_hop: Some(net1_gateway),
2308 },
2309 Route {
2310 destination: fnet::Subnet { addr: net1, prefix_len },
2311 outbound_interface: ID1,
2312 next_hop: None,
2313 },
2314 ]);
2315 let route_table_2 = testutil::build_route_table_from_flattened_routes([
2316 Route {
2317 destination: unspecified_addr,
2318 outbound_interface: ID1,
2319 next_hop: Some(net2_gateway),
2320 },
2321 Route {
2322 destination: fnet::Subnet { addr: net1, prefix_len },
2323 outbound_interface: ID1,
2324 next_hop: None,
2325 },
2326 Route {
2327 destination: fnet::Subnet { addr: net2, prefix_len },
2328 outbound_interface: ID1,
2329 next_hop: None,
2330 },
2331 ]);
2332 let route_table_3 = testutil::build_route_table_from_flattened_routes([
2333 Route {
2334 destination: unspecified_addr,
2335 outbound_interface: ID2,
2336 next_hop: Some(net1_gateway),
2337 },
2338 Route {
2339 destination: fnet::Subnet { addr: net1, prefix_len },
2340 outbound_interface: ID2,
2341 next_hop: None,
2342 },
2343 ]);
2344 let route_table_4 = testutil::build_route_table_from_flattened_routes([
2345 Route {
2346 destination: non_default_addr,
2347 outbound_interface: ID1,
2348 next_hop: Some(net1_gateway),
2349 },
2350 Route {
2351 destination: fnet::Subnet { addr: net1, prefix_len },
2352 outbound_interface: ID1,
2353 next_hop: None,
2354 },
2355 ]);
2356
2357 let fnet_ext::IpAddress(net1_gateway_ext) = net1_gateway.into();
2358 let mut exec = fasync::TestExecutor::new();
2359
2360 assert_eq!(
2362 run_network_check_partial_properties(
2363 &mut exec,
2364 ETHERNET_INTERFACE_NAME,
2365 ID1,
2366 &route_table,
2367 FakePing {
2368 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2369 gateway_response: true,
2370 internet_response: true,
2371 },
2372 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]),
2373 FakeFetch {
2374 expected_url: Some("http://www.gstatic.com/generate_204"),
2375 response: Some(Box::new(|| Ok(204))),
2376 },
2377 None,
2378 ping_internet_addr,
2379 ),
2380 State {
2381 link: LinkState::Internet,
2382 application: ApplicationState { dns_resolved: true, http_fetch_succeeded: true },
2383 },
2384 "All is good. Can reach internet"
2385 );
2386
2387 assert_eq!(
2388 run_network_check_partial_properties(
2389 &mut exec,
2390 ETHERNET_INTERFACE_NAME,
2391 ID1,
2392 &route_table,
2393 FakePing {
2394 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2395 gateway_response: true,
2396 internet_response: true,
2397 },
2398 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]),
2399 FakeFetch::default(),
2400 None,
2401 ping_internet_addr,
2402 ),
2403 State {
2404 link: LinkState::Internet,
2405 application: ApplicationState { dns_resolved: true, ..Default::default() },
2406 },
2407 "HTTP Fetch fails"
2408 );
2409
2410 assert_eq!(
2411 run_network_check_partial_properties(
2412 &mut exec,
2413 ETHERNET_INTERFACE_NAME,
2414 ID1,
2415 &route_table,
2416 FakePing {
2417 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2418 gateway_response: true,
2419 internet_response: true,
2420 },
2421 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("1.2.4.0")]),
2422 FakeFetch::default(),
2423 None,
2424 ping_internet_addr,
2425 ),
2426 State {
2427 link: LinkState::Internet,
2428 application: ApplicationState {
2429 dns_resolved: ping_internet_addr.is_ipv4(),
2430 ..Default::default()
2431 },
2432 },
2433 "DNS Resolves only IPV4",
2434 );
2435
2436 assert_eq!(
2437 run_network_check_partial_properties(
2438 &mut exec,
2439 ETHERNET_INTERFACE_NAME,
2440 ID1,
2441 &route_table,
2442 FakePing {
2443 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2444 gateway_response: true,
2445 internet_response: true,
2446 },
2447 FakeDig::new(vec![std_ip!("123::"), std_ip!("124::")]),
2448 FakeFetch::default(),
2449 None,
2450 ping_internet_addr,
2451 ),
2452 State {
2453 link: LinkState::Internet,
2454 application: ApplicationState {
2455 dns_resolved: ping_internet_addr.is_ipv6(),
2456 ..Default::default()
2457 },
2458 },
2459 "DNS Resolves only IPV6",
2460 );
2461
2462 assert_eq!(
2463 run_network_check_partial_properties(
2464 &mut exec,
2465 ETHERNET_INTERFACE_NAME,
2466 ID1,
2467 &route_table,
2468 FakePing {
2469 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2470 gateway_response: false,
2471 internet_response: true,
2472 },
2473 FakeDig::default(),
2474 FakeFetch::default(),
2475 Some(&InterfaceNeighborCache {
2476 neighbors: [(
2477 net1_gateway,
2478 NeighborState::new(NeighborHealth::Healthy {
2479 last_observed: zx::MonotonicInstant::default(),
2480 })
2481 )]
2482 .into_iter()
2483 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2484 }),
2485 ping_internet_addr,
2486 ),
2487 LinkState::Internet.into(),
2488 "Can reach internet, gateway responding via ARP/ND"
2489 );
2490
2491 assert_eq!(
2492 run_network_check_partial_properties(
2493 &mut exec,
2494 ETHERNET_INTERFACE_NAME,
2495 ID1,
2496 &route_table,
2497 FakePing {
2498 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2499 gateway_response: false,
2500 internet_response: true,
2501 },
2502 FakeDig::default(),
2503 FakeFetch::default(),
2504 Some(&InterfaceNeighborCache {
2505 neighbors: [(
2506 net1,
2507 NeighborState::new(NeighborHealth::Healthy {
2508 last_observed: zx::MonotonicInstant::default(),
2509 })
2510 )]
2511 .into_iter()
2512 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2513 }),
2514 ping_internet_addr,
2515 ),
2516 LinkState::Internet.into(),
2517 "Gateway not responding via ping or ARP/ND. Can reach internet"
2518 );
2519
2520 assert_eq!(
2521 run_network_check_partial_properties(
2522 &mut exec,
2523 ETHERNET_INTERFACE_NAME,
2524 ID1,
2525 &route_table_4,
2526 FakePing {
2527 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2528 gateway_response: true,
2529 internet_response: true,
2530 },
2531 FakeDig::default(),
2532 FakeFetch::default(),
2533 Some(&InterfaceNeighborCache {
2534 neighbors: [(
2535 net1_gateway,
2536 NeighborState::new(NeighborHealth::Healthy {
2537 last_observed: zx::MonotonicInstant::default(),
2538 })
2539 )]
2540 .into_iter()
2541 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2542 }),
2543 ping_internet_addr,
2544 ),
2545 LinkState::Internet.into(),
2546 "No default route, but healthy gateway with internet/gateway response"
2547 );
2548
2549 assert_eq!(
2550 run_network_check_partial_properties(
2551 &mut exec,
2552 ETHERNET_INTERFACE_NAME,
2553 ID1,
2554 &route_table,
2555 FakePing {
2556 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2557 gateway_response: true,
2558 internet_response: false,
2559 },
2560 FakeDig::default(),
2561 FakeFetch::default(),
2562 None,
2563 ping_internet_addr,
2564 ),
2565 LinkState::Gateway.into(),
2566 "Can reach gateway via ping"
2567 );
2568
2569 assert_eq!(
2570 run_network_check_partial_properties(
2571 &mut exec,
2572 ETHERNET_INTERFACE_NAME,
2573 ID1,
2574 &route_table,
2575 FakePing::default(),
2576 FakeDig::default(),
2577 FakeFetch::default(),
2578 Some(&InterfaceNeighborCache {
2579 neighbors: [(
2580 net1_gateway,
2581 NeighborState::new(NeighborHealth::Healthy {
2582 last_observed: zx::MonotonicInstant::default(),
2583 })
2584 )]
2585 .into_iter()
2586 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2587 }),
2588 ping_internet_addr,
2589 ),
2590 LinkState::Gateway.into(),
2591 "Can reach gateway via ARP/ND"
2592 );
2593
2594 assert_eq!(
2595 run_network_check_partial_properties(
2596 &mut exec,
2597 ETHERNET_INTERFACE_NAME,
2598 ID1,
2599 &route_table,
2600 FakePing {
2601 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2602 gateway_response: false,
2603 internet_response: false,
2604 },
2605 FakeDig::default(),
2606 FakeFetch::default(),
2607 None,
2608 ping_internet_addr,
2609 ),
2610 LinkState::Local.into(),
2611 "Local only, Cannot reach gateway"
2612 );
2613
2614 assert_eq!(
2615 run_network_check_partial_properties(
2616 &mut exec,
2617 ETHERNET_INTERFACE_NAME,
2618 ID1,
2619 &route_table_2,
2620 FakePing::default(),
2621 FakeDig::default(),
2622 FakeFetch::default(),
2623 None,
2624 ping_internet_addr,
2625 ),
2626 LinkState::Local.into(),
2627 "No default route"
2628 );
2629
2630 assert_eq!(
2631 run_network_check_partial_properties(
2632 &mut exec,
2633 ETHERNET_INTERFACE_NAME,
2634 ID1,
2635 &route_table_4,
2636 FakePing {
2637 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2638 gateway_response: true,
2639 internet_response: false,
2640 },
2641 FakeDig::default(),
2642 FakeFetch::default(),
2643 None,
2644 ping_internet_addr,
2645 ),
2646 LinkState::Local.into(),
2647 "No default route, with only gateway response"
2648 );
2649
2650 assert_eq!(
2651 run_network_check_partial_properties(
2652 &mut exec,
2653 ETHERNET_INTERFACE_NAME,
2654 ID1,
2655 &route_table_2,
2656 FakePing::default(),
2657 FakeDig::default(),
2658 FakeFetch::default(),
2659 Some(&InterfaceNeighborCache {
2660 neighbors: [(
2661 net1,
2662 NeighborState::new(NeighborHealth::Healthy {
2663 last_observed: zx::MonotonicInstant::default(),
2664 })
2665 )]
2666 .into_iter()
2667 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2668 }),
2669 ping_internet_addr,
2670 ),
2671 LinkState::Local.into(),
2672 "Local only, neighbors responsive with no default route"
2673 );
2674
2675 assert_eq!(
2676 run_network_check_partial_properties(
2677 &mut exec,
2678 ETHERNET_INTERFACE_NAME,
2679 ID1,
2680 &route_table,
2681 FakePing::default(),
2682 FakeDig::default(),
2683 FakeFetch::default(),
2684 Some(&InterfaceNeighborCache {
2685 neighbors: [(
2686 net1,
2687 NeighborState::new(NeighborHealth::Healthy {
2688 last_observed: zx::MonotonicInstant::default(),
2689 })
2690 )]
2691 .into_iter()
2692 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2693 }),
2694 ping_internet_addr
2695 ),
2696 LinkState::Local.into(),
2697 "Local only, neighbors responsive with a default route"
2698 );
2699
2700 assert_eq!(
2701 run_network_check_partial_properties(
2702 &mut exec,
2703 ETHERNET_INTERFACE_NAME,
2704 ID1,
2705 &route_table_3,
2706 FakePing::default(),
2707 FakeDig::default(),
2708 FakeFetch::default(),
2709 Some(&InterfaceNeighborCache {
2710 neighbors: [(
2711 net1,
2712 NeighborState::new(NeighborHealth::Healthy {
2713 last_observed: zx::MonotonicInstant::default(),
2714 })
2715 )]
2716 .into_iter()
2717 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2718 }),
2719 ping_internet_addr,
2720 ),
2721 LinkState::Local.into(),
2722 "Local only, neighbors responsive with no routes"
2723 );
2724
2725 assert_eq!(
2726 run_network_check_partial_properties(
2727 &mut exec,
2728 ETHERNET_INTERFACE_NAME,
2729 ID1,
2730 &route_table,
2731 FakePing::default(),
2732 FakeDig::default(),
2733 FakeFetch::default(),
2734 Some(&InterfaceNeighborCache {
2735 neighbors: [
2736 (
2737 net1,
2738 NeighborState::new(NeighborHealth::Healthy {
2739 last_observed: zx::MonotonicInstant::default(),
2740 })
2741 ),
2742 (
2743 net1_gateway,
2744 NeighborState::new(NeighborHealth::Unhealthy { last_healthy: None })
2745 )
2746 ]
2747 .into_iter()
2748 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2749 }),
2750 ping_internet_addr,
2751 ),
2752 LinkState::Local.into(),
2753 "Local only, gateway unhealthy with healthy neighbor"
2754 );
2755
2756 assert_eq!(
2757 run_network_check_partial_properties(
2758 &mut exec,
2759 ETHERNET_INTERFACE_NAME,
2760 ID1,
2761 &route_table_3,
2762 FakePing::default(),
2763 FakeDig::default(),
2764 FakeFetch::default(),
2765 Some(&InterfaceNeighborCache {
2766 neighbors: [(
2767 net1_gateway,
2768 NeighborState::new(NeighborHealth::Unhealthy { last_healthy: None })
2769 )]
2770 .into_iter()
2771 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2772 }),
2773 ping_internet_addr,
2774 ),
2775 LinkState::Up.into(),
2776 "No routes and unhealthy gateway"
2777 );
2778
2779 assert_eq!(
2780 run_network_check_partial_properties(
2781 &mut exec,
2782 ETHERNET_INTERFACE_NAME,
2783 ID1,
2784 &route_table_3,
2785 FakePing::default(),
2786 FakeDig::default(),
2787 FakeFetch::default(),
2788 None,
2789 ping_internet_addr,
2790 ),
2791 LinkState::Up.into(),
2792 "No routes",
2793 );
2794
2795 assert_eq!(
2796 run_network_check_partial_properties_repeated(
2797 &mut exec,
2798 ETHERNET_INTERFACE_NAME,
2799 ID1,
2800 &route_table,
2801 vec![
2802 (
2803 FakePing {
2804 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2805 gateway_response: true,
2806 internet_response: true,
2807 },
2808 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2810 expected_url: Some("http://www.gstatic.com/generate_204"),
2811 response: Some(Box::new(|| Ok(204))),
2812 },
2813 ),
2814 (
2815 FakePing {
2816 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2817 gateway_response: true,
2818 internet_response: true,
2819 },
2820 FakeDig { response: None }, FakeFetch {
2822 expected_url: Some("http://www.gstatic.com/generate_204"),
2823 response: Some(Box::new(|| Ok(204))),
2824 },
2825 ),
2826 ],
2827 None,
2828 ping_internet_addr,
2829 None,
2830 ),
2831 vec![
2832 State {
2833 link: LinkState::Internet,
2834 application: ApplicationState {
2835 dns_resolved: true,
2836 http_fetch_succeeded: true
2837 }
2838 },
2839 State {
2840 link: LinkState::Internet,
2841 application: ApplicationState {
2842 dns_resolved: true,
2843 http_fetch_succeeded: true
2844 }
2845 }
2846 ],
2847 "Fail DNS on second check; fetch succeeds; no pause"
2848 );
2849
2850 assert_eq!(
2851 run_network_check_partial_properties_repeated(
2852 &mut exec,
2853 ETHERNET_INTERFACE_NAME,
2854 ID1,
2855 &route_table,
2856 vec![
2857 (
2858 FakePing {
2859 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2860 gateway_response: true,
2861 internet_response: true,
2862 },
2863 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2865 expected_url: Some("http://www.gstatic.com/generate_204"),
2866 response: Some(Box::new(|| Ok(204))),
2867 },
2868 ),
2869 (
2870 FakePing {
2871 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2872 gateway_response: true,
2873 internet_response: true,
2874 },
2875 FakeDig { response: None }, FakeFetch {
2877 expected_url: Some("http://www.gstatic.com/generate_204"),
2878 response: Some(Box::new(|| Ok(204))),
2879 },
2880 ),
2881 ],
2882 None,
2883 ping_internet_addr,
2884 Some(DNS_PROBE_PERIOD),
2885 ),
2886 vec![
2887 State {
2888 link: LinkState::Internet,
2889 application: ApplicationState {
2890 dns_resolved: true,
2891 http_fetch_succeeded: true
2892 }
2893 },
2894 State {
2895 link: LinkState::Internet,
2896 application: ApplicationState {
2897 dns_resolved: false,
2898 http_fetch_succeeded: true
2899 }
2900 }
2901 ],
2902 "Fail DNS on second check; fetch succeeds"
2903 );
2904
2905 assert_eq!(
2906 run_network_check_partial_properties_repeated(
2907 &mut exec,
2908 ETHERNET_INTERFACE_NAME,
2909 ID1,
2910 &route_table,
2911 vec![
2912 (
2913 FakePing {
2914 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2915 gateway_response: true,
2916 internet_response: true,
2917 },
2918 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2920 expected_url: Some("http://www.gstatic.com/generate_204"),
2921 response: Some(Box::new(|| Err(
2922 fetch::FetchError::ReadTcpStreamTimeout
2923 ))),
2924 },
2925 ),
2926 (
2927 FakePing {
2928 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2929 gateway_response: true,
2930 internet_response: true,
2931 },
2932 FakeDig { response: None }, FakeFetch {
2934 expected_url: Some("http://www.gstatic.com/generate_204"),
2935 response: Some(Box::new(|| Err(
2936 fetch::FetchError::ReadTcpStreamTimeout
2937 ))),
2938 },
2939 ),
2940 ],
2941 None,
2942 ping_internet_addr,
2943 None,
2944 ),
2945 vec![
2946 State {
2947 link: LinkState::Internet,
2948 application: ApplicationState { dns_resolved: true, ..Default::default() }
2949 },
2950 State {
2951 link: LinkState::Internet,
2952 application: ApplicationState { dns_resolved: true, ..Default::default() }
2953 }
2954 ],
2955 "Fail DNS on second check; fetch fails; no pause"
2956 );
2957
2958 assert_eq!(
2959 run_network_check_partial_properties_repeated(
2960 &mut exec,
2961 ETHERNET_INTERFACE_NAME,
2962 ID1,
2963 &route_table,
2964 vec![
2965 (
2966 FakePing {
2967 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2968 gateway_response: true,
2969 internet_response: true,
2970 },
2971 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2973 expected_url: Some("http://www.gstatic.com/generate_204"),
2974 response: Some(Box::new(|| Err(
2975 fetch::FetchError::ReadTcpStreamTimeout
2976 ))),
2977 },
2978 ),
2979 (
2980 FakePing {
2981 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2982 gateway_response: true,
2983 internet_response: true,
2984 },
2985 FakeDig { response: None }, FakeFetch {
2987 expected_url: Some("http://www.gstatic.com/generate_204"),
2988 response: Some(Box::new(|| Err(
2989 fetch::FetchError::ReadTcpStreamTimeout
2990 ))),
2991 },
2992 ),
2993 ],
2994 None,
2995 ping_internet_addr,
2996 Some(DNS_PROBE_PERIOD),
2997 ),
2998 vec![
2999 State {
3000 link: LinkState::Internet,
3001 application: ApplicationState { dns_resolved: true, ..Default::default() }
3002 },
3003 State {
3004 link: LinkState::Internet,
3005 application: ApplicationState { dns_resolved: false, ..Default::default() }
3006 }
3007 ],
3008 "Fail DNS on second check; fetch fails"
3009 );
3010 }
3011
3012 #[test]
3013 fn test_network_check_varying_properties() {
3014 let properties = fnet_interfaces_ext::Properties {
3015 id: ID1.try_into().expect("should be nonzero"),
3016 name: ETHERNET_INTERFACE_NAME.to_string(),
3017 port_class: fnet_interfaces_ext::PortClass::Ethernet,
3018 has_default_ipv4_route: true,
3019 has_default_ipv6_route: true,
3020 online: true,
3021 addresses: vec![
3022 fnet_interfaces_ext::Address {
3023 addr: fidl_subnet!("1.2.3.0/24"),
3024 valid_until: fnet_interfaces_ext::NoInterest,
3025 preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
3026 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
3027 },
3028 fnet_interfaces_ext::Address {
3029 addr: fidl_subnet!("123::4/64"),
3030 valid_until: fnet_interfaces_ext::NoInterest,
3031 preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
3032 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
3033 },
3034 ],
3035 port_identity_koid: Default::default(),
3036 };
3037 let local_routes = testutil::build_route_table_from_flattened_routes([
3038 Route {
3039 destination: fidl_subnet!("1.2.3.0/24"),
3040 outbound_interface: ID1,
3041 next_hop: None,
3042 },
3043 Route {
3044 destination: fidl_subnet!("123::/64"),
3045 outbound_interface: ID1,
3046 next_hop: None,
3047 },
3048 ]);
3049 let route_table = testutil::build_route_table_from_flattened_routes([
3050 Route {
3051 destination: fidl_subnet!("0.0.0.0/0"),
3052 outbound_interface: ID1,
3053 next_hop: Some(fidl_ip!("1.2.3.1")),
3054 },
3055 Route {
3056 destination: fidl_subnet!("::0/0"),
3057 outbound_interface: ID1,
3058 next_hop: Some(fidl_ip!("123::1")),
3059 },
3060 ]);
3061 let route_table2 = testutil::build_route_table_from_flattened_routes([
3062 Route {
3063 destination: fidl_subnet!("0.0.0.0/0"),
3064 outbound_interface: ID1,
3065 next_hop: Some(fidl_ip!("2.2.3.1")),
3066 },
3067 Route {
3068 destination: fidl_subnet!("::0/0"),
3069 outbound_interface: ID1,
3070 next_hop: Some(fidl_ip!("223::1")),
3071 },
3072 ]);
3073
3074 const NON_ETHERNET_INTERFACE_NAME: &str = "test01";
3075
3076 let mut exec = fasync::TestExecutor::new_with_fake_time();
3077 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
3078 let () = exec.set_fake_time(time.into());
3079
3080 let got = run_network_check(
3081 &mut exec,
3082 &fnet_interfaces_ext::Properties {
3083 id: ID1.try_into().expect("should be nonzero"),
3084 name: NON_ETHERNET_INTERFACE_NAME.to_string(),
3085 port_class: fnet_interfaces_ext::PortClass::Virtual,
3086 online: false,
3087 has_default_ipv4_route: false,
3088 has_default_ipv6_route: false,
3089 addresses: vec![],
3090 port_identity_koid: Default::default(),
3091 },
3092 &Default::default(),
3093 None,
3094 FakePing::default(),
3095 FakeDig::default(),
3096 FakeFetch::default(),
3097 )
3098 .expect(
3099 "error calling network check with non-ethernet interface, no addresses, interface down",
3100 );
3101 assert_eq!(
3102 got,
3103 Some(IpVersions::construct(StateEvent {
3104 state: State { link: LinkState::Down, ..Default::default() },
3105 time
3106 }))
3107 );
3108
3109 let got = run_network_check(
3110 &mut exec,
3111 &fnet_interfaces_ext::Properties { online: false, ..properties.clone() },
3112 &Default::default(),
3113 None,
3114 FakePing::default(),
3115 FakeDig::default(),
3116 FakeFetch::default(),
3117 )
3118 .expect("error calling network check, want Down state");
3119 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
3120 state: State { link: LinkState::Down, ..Default::default() },
3121 time,
3122 }));
3123 assert_eq!(got, want);
3124
3125 let got = run_network_check(
3126 &mut exec,
3127 &fnet_interfaces_ext::Properties {
3128 has_default_ipv4_route: false,
3129 has_default_ipv6_route: false,
3130 ..properties.clone()
3131 },
3132 &local_routes,
3133 None,
3134 FakePing::default(),
3135 FakeDig::default(),
3136 FakeFetch::default(),
3137 )
3138 .expect("error calling network check, want Local state due to no default routes");
3139 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
3140 state: State { link: LinkState::Local, ..Default::default() },
3141 time,
3142 }));
3143 assert_eq!(got, want);
3144
3145 let got = run_network_check(
3146 &mut exec,
3147 &properties,
3148 &route_table2,
3149 None,
3150 FakePing::default(),
3151 FakeDig::default(),
3152 FakeFetch::default(),
3153 )
3154 .expect("error calling network check, want Local state due to no matching default route");
3155 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
3156 state: State { link: LinkState::Local, ..Default::default() },
3157 time,
3158 }));
3159 assert_eq!(got, want);
3160
3161 let got = run_network_check(
3162 &mut exec,
3163 &properties,
3164 &route_table,
3165 None,
3166 FakePing {
3167 gateway_addrs: [std_ip!("1.2.3.1"), std_ip!("123::1")].into_iter().collect(),
3168 gateway_response: true,
3169 internet_response: false,
3170 },
3171 FakeDig::default(),
3172 FakeFetch::default(),
3173 )
3174 .expect("error calling network check, want Gateway state");
3175 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
3176 state: State { link: LinkState::Gateway, ..Default::default() },
3177 time,
3178 }));
3179 assert_eq!(got, want);
3180
3181 let got = run_network_check(
3182 &mut exec,
3183 &properties,
3184 &route_table,
3185 None,
3186 FakePing {
3187 gateway_addrs: [std_ip!("1.2.3.1"), std_ip!("123::1")].into_iter().collect(),
3188 gateway_response: true,
3189 internet_response: true,
3190 },
3191 FakeDig::default(),
3192 FakeFetch::default(),
3193 )
3194 .expect("error calling network check, want Internet state");
3195 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
3196 state: State { link: LinkState::Internet, ..Default::default() },
3197 time,
3198 }));
3199 assert_eq!(got, want);
3200 }
3201
3202 fn update_delta(port: Delta<StateEvent>, system: Delta<SystemState>) -> StateDelta {
3203 StateDelta {
3204 port: IpVersions { ipv4: port.clone(), ipv6: port },
3205 system: IpVersions { ipv4: system.clone(), ipv6: system },
3206 }
3207 }
3208
3209 #[test]
3210 fn test_state_info_update() {
3211 let if1_local_event = StateEvent::construct(LinkState::Local);
3212 let if1_local = IpVersions::<StateEvent>::construct(if1_local_event);
3213 let mut state = StateInfo::default();
3215 let want = update_delta(
3216 Delta { previous: None, current: if1_local_event },
3217 Delta { previous: None, current: SystemState { id: ID1, state: if1_local_event } },
3218 );
3219 assert_eq!(state.update(ID1, if1_local.clone()), want);
3220 let want_state = StateInfo {
3221 per_interface: std::iter::once((ID1, if1_local.clone())).collect::<HashMap<_, _>>(),
3222 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
3223 };
3224 assert_eq!(state, want_state);
3225
3226 let if2_gateway_event = StateEvent::construct(LinkState::Gateway);
3227 let if2_gateway = IpVersions::<StateEvent>::construct(if2_gateway_event);
3228 let want = update_delta(
3231 Delta { previous: None, current: if2_gateway_event },
3232 Delta {
3233 previous: Some(SystemState { id: ID1, state: if1_local_event }),
3234 current: SystemState { id: ID2, state: if2_gateway_event },
3235 },
3236 );
3237 assert_eq!(state.update(ID2, if2_gateway.clone()), want);
3238 let want_state = StateInfo {
3239 per_interface: [(ID1, if1_local.clone()), (ID2, if2_gateway.clone())]
3240 .into_iter()
3241 .collect::<HashMap<_, _>>(),
3242 system: IpVersions { ipv4: Some(ID2), ipv6: Some(ID2) },
3243 };
3244 assert_eq!(state, want_state);
3245
3246 let if2_removed_event = StateEvent::construct(LinkState::Removed);
3247 let if2_removed = IpVersions::<StateEvent>::construct(if2_removed_event);
3248 let want = update_delta(
3251 Delta { previous: Some(if2_gateway_event), current: if2_removed_event },
3252 Delta {
3253 previous: Some(SystemState { id: ID2, state: if2_gateway_event }),
3254 current: SystemState { id: ID1, state: if1_local_event },
3255 },
3256 );
3257 assert_eq!(state.update(ID2, if2_removed.clone()), want);
3258 let want_state = StateInfo {
3259 per_interface: [(ID1, if1_local.clone()), (ID2, if2_removed.clone())]
3260 .into_iter()
3261 .collect::<HashMap<_, _>>(),
3262 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
3263 };
3264 assert_eq!(state, want_state);
3265 }
3266
3267 #[test]
3271 fn test_state_info_update_same_link_state() {
3272 let if_local_event = StateEvent::construct(LinkState::Local);
3273 let if_local = IpVersions::<StateEvent>::construct(if_local_event);
3274 let mut state = StateInfo::default();
3276 let want = update_delta(
3277 Delta { previous: None, current: if_local_event },
3278 Delta { previous: None, current: SystemState { id: ID1, state: if_local_event } },
3279 );
3280 assert_eq!(state.update(ID1, if_local.clone()), want);
3281 let want_state = StateInfo {
3282 per_interface: std::iter::once((ID1, if_local.clone())).collect::<HashMap<_, _>>(),
3283 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
3284 };
3285 assert_eq!(state, want_state);
3286
3287 let want = update_delta(
3290 Delta { previous: None, current: if_local_event },
3291 Delta {
3292 previous: Some(SystemState { id: ID1, state: if_local_event }),
3293 current: SystemState { id: ID1, state: if_local_event },
3294 },
3295 );
3296 assert_eq!(state.update(ID2, if_local.clone()), want);
3297 let want_state = StateInfo {
3298 per_interface: [(ID1, if_local.clone()), (ID2, if_local.clone())]
3299 .into_iter()
3300 .collect::<HashMap<_, _>>(),
3301 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
3302 };
3303 assert_eq!(state, want_state);
3304
3305 let if_removed_event = StateEvent::construct(LinkState::Removed);
3308 let if_removed = IpVersions::<StateEvent>::construct(if_removed_event);
3309 let want = update_delta(
3310 Delta { previous: Some(if_local_event), current: if_removed_event },
3311 Delta {
3312 previous: Some(SystemState { id: ID1, state: if_local_event }),
3313 current: SystemState { id: ID2, state: if_local_event },
3314 },
3315 );
3316 assert_eq!(state.update(ID1, if_removed.clone()), want);
3317 let want_state = StateInfo {
3318 per_interface: [(ID1, if_removed.clone()), (ID2, if_local.clone())]
3319 .into_iter()
3320 .collect::<HashMap<_, _>>(),
3321 system: IpVersions { ipv4: Some(ID2), ipv6: Some(ID2) },
3322 };
3323 assert_eq!(state, want_state);
3324 }
3325
3326 #[test_case(None::<LinkState>, None::<LinkState>, false, false, false, false;
3327 "no interfaces available")]
3328 #[test_case(Some(LinkState::Local), Some(LinkState::Local), false, false, false, false;
3329 "no interfaces with gateway or internet state")]
3330 #[test_case(Some(LinkState::Local), Some(LinkState::Gateway), false, false, false, true;
3331 "only one interface with gateway state or above")]
3332 #[test_case(Some(LinkState::Local), Some(LinkState::Internet), false, false, true, true;
3333 "only one interface with internet state")]
3334 #[test_case(Some(LinkState::Internet), Some(LinkState::Internet), false, false, true, true;
3335 "all interfaces with internet")]
3336 #[test_case(Some(LinkState::Internet), None::<LinkState>, false, false, true, true;
3337 "only one interface available, has internet state")]
3338 #[test_case(Some(LinkState::Local), Some((LinkState::Internet, true, false)), false, true, true, true;
3339 "only one interface with DNS resolved state")]
3340 #[test_case(Some((LinkState::Internet, true, false)), Some((LinkState::Internet, true, false)), false, true, true, true;
3341 "all interfaces with DNS resolved state")]
3342 #[test_case(Some((LinkState::Internet, true, false)), None::<LinkState>, false, true, true, true;
3343 "only one interface available, has DNS resolved state")]
3344 #[test_case(Some(LinkState::Local), Some((LinkState::Internet, true, true)), true, true, true, true;
3345 "only one interface with HTTP resolved state")]
3346 #[test_case(Some((LinkState::Internet, true, true)), Some((LinkState::Internet, true, true)), true, true, true, true;
3347 "all interfaces with HTTP resolved state")]
3348 #[test_case(Some((LinkState::Internet, true, true)), None::<LinkState>, true, true, true, true;
3349 "only one interface available, has HTTP resolved state")]
3350 #[test_case(Some((LinkState::Internet, false, true)), None::<LinkState>, true, false, true, true;
3351 "only one interface available, has HTTP resolved state, but no DNS")]
3352 fn test_system_has_state<S1, S2>(
3353 ipv4_state: Option<S1>,
3354 ipv6_state: Option<S2>,
3355 expect_http: bool,
3356 expect_dns: bool,
3357 expect_internet: bool,
3358 expect_gateway: bool,
3359 ) where
3360 StateEvent: Construct<S1>,
3361 StateEvent: Construct<S2>,
3362 {
3363 let if1 = ipv4_state
3364 .map(|state| IpVersions::<StateEvent>::construct(StateEvent::construct(state)));
3365 let if2 = ipv6_state
3366 .map(|state| IpVersions::<StateEvent>::construct(StateEvent::construct(state)));
3367
3368 let mut system_interfaces: HashMap<u64, IpVersions<StateEvent>> = HashMap::new();
3369
3370 let system_interface_ipv4 = if1.map(|interface| {
3371 let _ = system_interfaces.insert(ID1, interface);
3372 ID1
3373 });
3374
3375 let system_interface_ipv6 = if2.map(|interface| {
3376 let _ = system_interfaces.insert(ID2, interface);
3377 ID2
3378 });
3379
3380 let state = StateInfo {
3381 per_interface: system_interfaces,
3382 system: IpVersions { ipv4: system_interface_ipv4, ipv6: system_interface_ipv6 },
3383 };
3384
3385 assert_eq!(state.system_has_http(), expect_http);
3386 assert_eq!(state.system_has_dns(), expect_dns);
3387 assert_eq!(state.system_has_internet(), expect_internet);
3388 assert_eq!(state.system_has_gateway(), expect_gateway);
3389 }
3390
3391 #[test]
3392 fn test_resume_after_interface_removed() {
3393 use assert_matches::assert_matches;
3394
3395 let _exec = fasync::TestExecutor::new();
3396 let (sender, _receiver) = mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
3397 let mut monitor: Monitor<MonotonicInstant> = Monitor::new(sender).unwrap();
3398
3399 let properties = fnet_interfaces_ext::Properties {
3400 id: ID1.try_into().expect("should be nonzero"),
3401 name: ETHERNET_INTERFACE_NAME.to_string(),
3402 port_class: fnet_interfaces_ext::PortClass::Ethernet,
3403 online: false,
3404 addresses: vec![],
3405 has_default_ipv4_route: false,
3406 has_default_ipv6_route: false,
3407 port_identity_koid: Default::default(),
3408 };
3409
3410 let initial_state = IpVersions {
3412 ipv4: StateEvent {
3413 state: State { link: LinkState::None, ..Default::default() },
3414 time: fasync::MonotonicInstant::now(),
3415 },
3416 ipv6: StateEvent {
3417 state: State { link: LinkState::None, ..Default::default() },
3418 time: fasync::MonotonicInstant::now(),
3419 },
3420 };
3421 monitor.update_state(ID1, ETHERNET_INTERFACE_NAME, initial_state);
3422
3423 monitor.handle_interface_removed(properties.clone());
3426
3427 let removed_state = monitor.state().get(ID1).unwrap();
3429 assert_eq!(removed_state.ipv4.state.link, LinkState::Removed);
3430 assert_eq!(removed_state.ipv6.state.link, LinkState::Removed);
3431
3432 let routes = testutil::build_route_table_from_flattened_routes([]);
3437 let view = InterfaceView { properties: &properties, routes: &routes, neighbors: None };
3438 assert_matches!(monitor.begin(view), Ok(NetworkCheckerOutcome::Complete));
3439
3440 let interface_context = monitor.interface_context.get(&ID1).unwrap();
3443 assert_matches!(interface_context.discovered_state.ipv4.link, LinkState::Down);
3444 assert_matches!(interface_context.discovered_state.ipv6.link, LinkState::Down);
3445
3446 let final_state = monitor.state().get(ID1).unwrap();
3449 assert_eq!(final_state.ipv4.state.link, LinkState::Removed);
3450 assert_eq!(final_state.ipv6.state.link, LinkState::Removed);
3451 }
3452
3453 #[test]
3454 fn test_ping_and_fetch_telemetry_events_sent() {
3455 let mut exec = fasync::TestExecutor::new_with_fake_time();
3456 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
3457 let () = exec.set_fake_time(time.into());
3458
3459 let (action_sender, action_receiver) = mpsc::unbounded();
3460 let mut monitor = Monitor::new_with_time_provider(
3461 action_sender,
3462 FakeTime {
3463 increment: zx::MonotonicDuration::from_nanos(10),
3464 time: zx::MonotonicInstant::get(),
3465 },
3466 )
3467 .unwrap();
3468
3469 let (telemetry_tx, mut telemetry_rx) = mpsc::channel(100);
3470 monitor.set_telemetry_sender(TelemetrySender::new(telemetry_tx));
3471
3472 let properties = &fnet_interfaces_ext::Properties {
3473 id: ID1.try_into().unwrap(),
3474 name: ETHERNET_INTERFACE_NAME.to_string(),
3475 port_class: fnet_interfaces_ext::PortClass::Ethernet,
3476 online: true,
3477 addresses: vec![],
3478 has_default_ipv4_route: true,
3479 has_default_ipv6_route: false,
3480 port_identity_koid: Default::default(),
3481 };
3482
3483 let net_gateway = fidl_ip!("192.168.0.254");
3484 let net_gateway_std = std_ip!("192.168.0.254");
3485
3486 let routes = testutil::build_route_table_from_flattened_routes([Route {
3488 destination: UNSPECIFIED_V4,
3489 outbound_interface: ID1,
3490 next_hop: Some(net_gateway),
3491 }]);
3492
3493 let neighbors_map = [(
3495 net_gateway,
3496 NeighborState::new(NeighborHealth::Healthy {
3497 last_observed: zx::MonotonicInstant::default(),
3498 }),
3499 )]
3500 .into_iter()
3501 .collect::<HashMap<fnet::IpAddress, NeighborState>>();
3502 let neighbors = InterfaceNeighborCache { neighbors: neighbors_map };
3503
3504 let view = InterfaceView { properties, routes: &routes, neighbors: Some(&neighbors) };
3505
3506 let mut network_check_responder = NetworkCheckTestResponder::new(action_receiver);
3507
3508 let pinger = FakePing {
3509 gateway_addrs: vec![net_gateway_std].into_iter().collect(),
3510 gateway_response: true,
3511 internet_response: true,
3512 };
3513 let digger = FakeDig::new(vec![std_ip!("1.2.3.0")]);
3514 let fetcher = FakeFetch::default();
3515
3516 let network_check_fut = async {
3517 match monitor.begin(view) {
3518 Ok(NetworkCheckerOutcome::MustResume) => {
3519 let () = network_check_responder
3520 .respond_to_messages(&mut monitor, pinger, digger, fetcher)
3521 .await;
3522 }
3523 _ => panic!("Expected MustResume"),
3524 }
3525 };
3526
3527 let mut network_check_fut = pin!(network_check_fut);
3528 match exec.run_until_stalled(&mut network_check_fut) {
3529 Poll::Ready(()) => {}
3530 Poll::Pending => panic!("network_check blocked unexpectedly"),
3531 }
3532
3533 let mut events = Vec::new();
3534 while let Poll::Ready(Some(event)) = exec.run_until_stalled(&mut telemetry_rx.next()) {
3535 events.push(event);
3536 }
3537
3538 let has_gateway =
3539 events.iter().any(|e| matches!(e, TelemetryEvent::GatewayPingResult { .. }));
3540 let has_internet =
3541 events.iter().any(|e| matches!(e, TelemetryEvent::InternetPingResult { .. }));
3542 let has_fetch = events.iter().any(|e| matches!(e, TelemetryEvent::FetchResult { .. }));
3543
3544 assert!(has_gateway, "Expected GatewayPingResult telemetry event");
3545 assert!(has_internet, "Expected InternetPingResult telemetry event");
3546 assert!(has_fetch, "Expected FetchResult telemetry event");
3547 }
3548}