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