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::{TelemetryEvent, TelemetrySender};
21use anyhow::anyhow;
22use fuchsia_inspect::{Inspector, Node as InspectNode};
23use futures::channel::mpsc;
24use inspect::InspectInfo;
25use itertools::Itertools;
26use log::{debug, error, info};
27use named_timer::DeadlineId;
28use net_declare::{fidl_subnet, std_ip};
29use net_types::ScopeableAddress as _;
30use num_derive::FromPrimitive;
31use std::collections::hash_map::{Entry, HashMap};
32use {
33 fidl_fuchsia_net_ext as fnet_ext, fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
34 fuchsia_async as fasync,
35};
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)]
271struct 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 {
335 other
336 } else {
337 self
338 }
339 }
340}
341
342impl StateEq for SystemState {
343 fn compare_state(&self, &Self { id, state: StateEvent { state, time: _ } }: &Self) -> bool {
344 self.id == id && self.state.state == state
345 }
346}
347
348#[derive(Debug, Default, Clone)]
352#[cfg_attr(test, derive(PartialEq))]
353pub struct StateInfo {
354 per_interface: HashMap<Id, IpVersions<StateEvent>>,
356 system: IpVersions<Option<Id>>,
358}
359
360impl StateInfo {
361 fn get(&self, id: Id) -> Option<&IpVersions<StateEvent>> {
363 self.per_interface.get(&id)
364 }
365
366 fn get_system_ipv4(&self) -> Option<SystemState> {
368 self.system.ipv4.map(|id| SystemState {
369 id,
370 state: self
371 .get(id)
372 .unwrap_or_else(|| {
373 panic!("inconsistent system IPv4 state: no interface with ID {:?}", id)
374 })
375 .ipv4,
376 })
377 }
378
379 fn get_system_ipv6(&self) -> Option<SystemState> {
381 self.system.ipv6.map(|id| SystemState {
382 id,
383 state: self
384 .get(id)
385 .unwrap_or_else(|| {
386 panic!("inconsistent system IPv6 state: no interface with ID {:?}", id)
387 })
388 .ipv6,
389 })
390 }
391
392 fn get_system(&self) -> IpVersions<Option<SystemState>> {
393 IpVersions { ipv4: self.get_system_ipv4(), ipv6: self.get_system_ipv6() }
394 }
395
396 pub fn system_has_internet(&self) -> bool {
397 self.get_system().state().has_internet()
398 }
399
400 pub fn system_has_gateway(&self) -> bool {
401 self.get_system().state().has_gateway()
402 }
403
404 pub fn system_has_dns(&self) -> bool {
405 self.get_system().state().has_dns()
406 }
407
408 pub fn system_has_http(&self) -> bool {
409 self.get_system().state().has_http()
410 }
411
412 fn report(&self) {
414 let time = fasync::MonotonicInstant::now();
415 debug!("system reachability state IPv4 {:?}", self.get_system_ipv4());
416 debug!("system reachability state IPv6 {:?}", self.get_system_ipv6());
417 for (id, IpVersions { ipv4, ipv6 }) in self.per_interface.iter() {
418 debug!(
419 "reachability state {:?} IPv4 {:?} with duration {:?}",
420 id,
421 ipv4,
422 time - ipv4.time
423 );
424 debug!(
425 "reachability state {:?} IPv6 {:?} with duration {:?}",
426 id,
427 ipv6,
428 time - ipv6.time
429 );
430 }
431 }
432
433 fn update(&mut self, id: Id, new_reachability: IpVersions<StateEvent>) -> StateDelta {
437 let previous_system_ipv4 = self.get_system_ipv4();
438 let previous_system_ipv6 = self.get_system_ipv6();
439 let port = match self.per_interface.entry(id) {
440 Entry::Occupied(mut occupied) => {
441 let IpVersions { ipv4, ipv6 } = occupied.get_mut();
442 let IpVersions { ipv4: new_ipv4, ipv6: new_ipv6 } = new_reachability;
443
444 IpVersions { ipv4: ipv4.update(new_ipv4), ipv6: ipv6.update(new_ipv6) }
445 }
446 Entry::Vacant(vacant) => {
447 let IpVersions { ipv4, ipv6 } = vacant.insert(new_reachability);
448 IpVersions {
449 ipv4: Delta { previous: None, current: *ipv4 },
450 ipv6: Delta { previous: None, current: *ipv6 },
451 }
452 }
453 };
454
455 let IpVersions { ipv4: system_ipv4, ipv6: system_ipv6 } = self.per_interface.iter().fold(
456 {
457 let IpVersions {
458 ipv4: Delta { previous: _, current: ipv4 },
459 ipv6: Delta { previous: _, current: ipv6 },
460 } = port;
461 IpVersions {
462 ipv4: SystemState { id, state: ipv4 },
463 ipv6: SystemState { id, state: ipv6 },
464 }
465 },
466 |IpVersions { ipv4: system_ipv4, ipv6: system_ipv6 },
467 (&id, &IpVersions { ipv4, ipv6 })| {
468 IpVersions {
469 ipv4: system_ipv4.max(SystemState { id, state: ipv4 }),
470 ipv6: system_ipv6.max(SystemState { id, state: ipv6 }),
471 }
472 },
473 );
474
475 self.system = IpVersions { ipv4: Some(system_ipv4.id), ipv6: Some(system_ipv6.id) };
476
477 StateDelta {
478 port,
479 system: IpVersions {
480 ipv4: Delta { previous: previous_system_ipv4, current: system_ipv4 },
481 ipv6: Delta { previous: previous_system_ipv6, current: system_ipv6 },
482 },
483 }
484 }
485}
486
487#[derive(Copy, Clone, Debug)]
489pub struct InterfaceView<'a> {
490 pub properties: &'a fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
491 pub routes: &'a RouteTable,
492 pub neighbors: Option<&'a InterfaceNeighborCache>,
493}
494
495pub enum NetworkCheckerOutcome {
498 MustResume,
500 Complete,
503}
504
505pub trait NetworkChecker {
508 fn begin(&mut self, view: InterfaceView<'_>) -> Result<NetworkCheckerOutcome, anyhow::Error>;
512
513 fn resume(
515 &mut self,
516 cookie: NetworkCheckCookie,
517 result: NetworkCheckResult,
518 ) -> Result<NetworkCheckerOutcome, anyhow::Error>;
519}
520
521#[derive(Debug, Default)]
523enum NetworkCheckState {
524 #[default]
529 Begin,
530 PingGateway,
535 PingInternet,
538 ResolveDns,
542 FetchHttp,
545 Idle,
548}
549impl std::fmt::Display for NetworkCheckState {
550 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
551 match self {
552 NetworkCheckState::Begin => write!(f, "Begin"),
553 NetworkCheckState::PingGateway => write!(f, "Ping Gateway"),
554 NetworkCheckState::PingInternet => write!(f, "Ping Internet"),
555 NetworkCheckState::ResolveDns => write!(f, "Resolve DNS"),
556 NetworkCheckState::FetchHttp => write!(f, "Fetch URL"),
557 NetworkCheckState::Idle => write!(f, "Idle"),
558 }
559 }
560}
561
562#[derive(Debug, Clone, Default)]
563pub struct ResolvedIps {
564 v4: Vec<std::net::Ipv4Addr>,
565 v6: Vec<std::net::Ipv6Addr>,
566}
567
568struct PersistentNetworkCheckContext {
569 resolved_addrs: HashMap<String, ResolvedIps>,
571 resolved_time: zx::MonotonicInstant,
573}
574
575impl Default for PersistentNetworkCheckContext {
576 fn default() -> Self {
577 Self {
578 resolved_addrs: Default::default(),
579 resolved_time: zx::MonotonicInstant::INFINITE_PAST,
580 }
581 }
582}
583
584struct NetworkCheckContext {
586 checker_state: NetworkCheckState,
588 ping_addrs: Vec<std::net::SocketAddr>,
590 pings_expected: usize,
592 pings_completed: usize,
594 fetches_expected: usize,
596 fetches_completed: usize,
598 discovered_state_v4: State,
600 discovered_state_v6: State,
602 always_ping_internet: bool,
604 router_discoverable: bool,
606 gateway_pingable: bool,
608 persistent_context: PersistentNetworkCheckContext,
610 }
615
616impl NetworkCheckContext {
617 fn set_global_link_state(&mut self, link: LinkState) {
618 self.discovered_state_v4.set_link_state(link);
619 self.discovered_state_v6.set_link_state(link);
620 }
621
622 fn initiate_ping(
623 &mut self,
624 id: Id,
625 interface_name: &str,
626 network_check_sender: &mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
627 new_state: NetworkCheckState,
628 addrs: Vec<std::net::SocketAddr>,
629 ) {
630 self.checker_state = new_state;
631 self.ping_addrs = addrs;
632 self.pings_expected = self.ping_addrs.len();
633 self.pings_completed = 0;
634 self.ping_addrs
635 .iter()
636 .map(|addr| {
637 let action = NetworkCheckAction::Ping(PingParameters {
638 interface_name: interface_name.to_string(),
639 addr: addr.clone(),
640 });
641 (action, NetworkCheckCookie { id })
642 })
643 .for_each(|message| match network_check_sender.unbounded_send(message) {
644 Ok(()) => {}
645 Err(e) => {
646 debug!("unable to send network check internet msg: {:?}", e)
647 }
648 });
649 }
650}
651
652impl Default for NetworkCheckContext {
653 fn default() -> Self {
655 NetworkCheckContext {
656 checker_state: Default::default(),
657 ping_addrs: Vec::new(),
658 pings_expected: 0usize,
659 pings_completed: 0usize,
660 fetches_expected: 0usize,
661 fetches_completed: 0usize,
662 discovered_state_v4: State { link: LinkState::None, ..Default::default() },
663 discovered_state_v6: State { link: LinkState::None, ..Default::default() },
664 always_ping_internet: true,
665 router_discoverable: false,
666 gateway_pingable: false,
667 persistent_context: Default::default(),
668 }
669 }
670}
671
672#[derive(Clone)]
674pub struct NetworkCheckCookie {
675 id: Id,
677}
678
679#[derive(Debug, Clone)]
680pub enum NetworkCheckResult {
681 Ping { parameters: PingParameters, success: bool },
682 ResolveDns { parameters: ResolveDnsParameters, ips: Option<ResolvedIps> },
683 Fetch { parameters: FetchParameters, status: Option<u16> },
684}
685
686#[derive(Debug, Clone)]
687pub struct PingParameters {
688 pub interface_name: std::string::String,
690 pub addr: std::net::SocketAddr,
692}
693
694#[derive(Debug, Clone)]
695pub struct ResolveDnsParameters {
696 pub interface_name: std::string::String,
698 pub domain: String,
700}
701
702#[derive(Debug, Clone)]
703pub struct FetchParameters {
704 pub interface_name: std::string::String,
706 pub domain: std::string::String,
708 pub ip: std::net::IpAddr,
710 pub path: String,
712 pub expected_statuses: Vec<u16>,
714}
715
716impl NetworkCheckResult {
717 fn interface_name(&self) -> &str {
718 match self {
719 NetworkCheckResult::Ping {
720 parameters: PingParameters { interface_name, .. }, ..
721 } => interface_name,
722 NetworkCheckResult::ResolveDns {
723 parameters: ResolveDnsParameters { interface_name, .. },
724 ..
725 } => interface_name,
726 NetworkCheckResult::Fetch {
727 parameters: FetchParameters { interface_name, .. },
728 ..
729 } => interface_name,
730 }
731 }
732
733 fn ping_result(self) -> Option<(PingParameters, bool)> {
734 match self {
735 NetworkCheckResult::Ping { parameters, success } => Some((parameters, success)),
736 _ => None,
737 }
738 }
739
740 fn resolve_dns_result(self) -> Option<(ResolveDnsParameters, Option<ResolvedIps>)> {
741 match self {
742 NetworkCheckResult::ResolveDns { parameters, ips } => Some((parameters, ips)),
743 _ => None,
744 }
745 }
746
747 fn fetch_result(self) -> Option<(FetchParameters, Option<u16>)> {
748 match self {
749 NetworkCheckResult::Fetch { parameters, status } => Some((parameters, status)),
750 _ => None,
751 }
752 }
753}
754
755#[derive(Debug, Clone)]
757pub enum NetworkCheckAction {
758 Ping(PingParameters),
759 ResolveDns(ResolveDnsParameters),
760 Fetch(FetchParameters),
761}
762
763pub trait TimeProvider {
764 fn now(&mut self) -> zx::MonotonicInstant;
765}
766
767#[derive(Debug, Default)]
768pub struct MonotonicInstant;
769impl TimeProvider for MonotonicInstant {
770 fn now(&mut self) -> zx::MonotonicInstant {
771 zx::MonotonicInstant::get()
772 }
773}
774
775pub struct Monitor<Time = MonotonicInstant> {
777 state: StateInfo,
778 stats: Stats,
779 inspector: Option<&'static Inspector>,
780 system_node: Option<InspectInfo>,
781 nodes: HashMap<Id, InspectInfo>,
782 telemetry_sender: Option<TelemetrySender>,
783 network_check_sender: mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
787 interface_context: HashMap<Id, NetworkCheckContext>,
788 time_provider: Time,
789}
790
791impl<Time: TimeProvider + Default> Monitor<Time> {
792 pub fn new(
794 network_check_sender: mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
795 ) -> anyhow::Result<Self> {
796 Ok(Monitor {
797 state: Default::default(),
798 stats: Default::default(),
799 inspector: None,
800 system_node: None,
801 nodes: HashMap::new(),
802 telemetry_sender: None,
803 network_check_sender,
804 interface_context: HashMap::new(),
805 time_provider: Default::default(),
806 })
807 }
808}
809
810impl<Time> Monitor<Time> {
811 pub fn new_with_time_provider(
813 network_check_sender: mpsc::UnboundedSender<(NetworkCheckAction, NetworkCheckCookie)>,
814 time_provider: Time,
815 ) -> anyhow::Result<Self> {
816 Ok(Monitor {
817 state: Default::default(),
818 stats: Default::default(),
819 inspector: None,
820 system_node: None,
821 nodes: HashMap::new(),
822 telemetry_sender: None,
823 network_check_sender,
824 interface_context: HashMap::new(),
825 time_provider,
826 })
827 }
828}
829
830impl<Time: TimeProvider> Monitor<Time> {
831 pub fn state(&self) -> &StateInfo {
832 &self.state
833 }
834
835 pub fn report_state(&self) {
837 self.state.report();
838 debug!("reachability stats {:?}", self.stats);
839 }
840
841 pub fn set_inspector(&mut self, inspector: &'static Inspector) {
843 self.inspector = Some(inspector);
844
845 let system_node = InspectInfo::new(inspector.root(), "system", "");
846 self.system_node = Some(system_node);
847
848 LinkState::log_state_vals_inspect(inspector.root(), "state_vals");
849 }
850
851 pub fn set_telemetry_sender(&mut self, telemetry_sender: TelemetrySender) {
852 self.telemetry_sender = Some(telemetry_sender);
853 }
854
855 fn interface_node(&mut self, id: Id, name: &str) -> Option<&mut InspectInfo> {
856 self.inspector.map(move |inspector| {
857 self.nodes.entry(id).or_insert_with_key(|id| {
858 InspectInfo::new(inspector.root(), &format!("{:?}", id), name)
859 })
860 })
861 }
862
863 fn update_state_from_context(
864 &mut self,
865 id: Id,
866 name: &str,
867 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
868 let ctx = self.interface_context.get_mut(&id).ok_or_else(|| {
869 anyhow!(
870 "attempting to update state with context but context for id {} does not exist",
871 id
872 )
873 })?;
874
875 ctx.checker_state = NetworkCheckState::Idle;
876 let info = IpVersions {
877 ipv4: StateEvent {
878 state: ctx.discovered_state_v4,
879 time: fasync::MonotonicInstant::now(),
880 },
881 ipv6: StateEvent {
882 state: ctx.discovered_state_v6,
883 time: fasync::MonotonicInstant::now(),
884 },
885 };
886
887 let gateway_event_v4 = TelemetryEvent::GatewayProbe {
888 gateway_discoverable: ctx.router_discoverable,
889 gateway_pingable: ctx.gateway_pingable,
890 internet_available: ctx.discovered_state_v4.has_internet(),
891 };
892 let gateway_event_v6 = TelemetryEvent::GatewayProbe {
893 gateway_discoverable: ctx.router_discoverable,
894 gateway_pingable: ctx.gateway_pingable,
895 internet_available: ctx.discovered_state_v6.has_internet(),
896 };
897
898 if let Some(telemetry_sender) = &mut self.telemetry_sender {
899 telemetry_sender.send(gateway_event_v4);
900 telemetry_sender.send(gateway_event_v6);
901 telemetry_sender.send(TelemetryEvent::SystemStateUpdate {
902 update: telemetry::SystemStateUpdate {
903 system_state: self.state.get_system().state(),
904 },
905 });
906 }
907
908 let () = self.update_state(id, &name, info);
909 Ok(NetworkCheckerOutcome::Complete)
910 }
911
912 fn update_state(&mut self, id: Id, name: &str, reachability: IpVersions<StateEvent>) {
914 let StateDelta { port, system } = self.state.update(id, reachability);
915
916 let () = port.with_version(|proto, delta| {
917 if delta.change_observed() {
918 let &Delta { previous, current } = delta;
919 if let Some(previous) = previous {
920 info!(
921 "interface updated {:?} {:?} current: {:?} previous: {:?}",
922 id, proto, current, previous
923 );
924 } else {
925 info!("new interface {:?} {:?}: {:?}", id, proto, current);
926 }
927 let () = log_state(self.interface_node(id, name), proto, current.state);
928 *self.stats.state_updates.entry(id).or_insert(0) += 1;
929 }
930 });
931
932 let () = system.with_version(|proto, delta| {
933 if delta.change_observed() {
934 let &Delta { previous, current } = delta;
935 if let Some(previous) = previous {
936 info!(
937 "system updated {:?} current: {:?}, previous: {:?}",
938 proto, current, previous,
939 );
940 } else {
941 info!("initial system state {:?}: {:?}", proto, current);
942 }
943 let () = log_state(self.system_node.as_mut(), proto, current.state.state);
944 }
945 });
946 }
947
948 pub fn handle_interface_removed(
950 &mut self,
951 fnet_interfaces_ext::Properties { id, name, .. }: fnet_interfaces_ext::Properties<
952 fnet_interfaces_ext::DefaultInterest,
953 >,
954 ) {
955 let time = fasync::MonotonicInstant::now();
956 if let Some(mut reachability) = self.state.get(id.into()).cloned() {
957 reachability.ipv4 = StateEvent {
958 state: State { link: LinkState::Removed, ..Default::default() },
959 time,
960 };
961 reachability.ipv6 = StateEvent {
962 state: State { link: LinkState::Removed, ..Default::default() },
963 time,
964 };
965 let () = self.update_state(id.into(), &name, reachability);
966 }
967 }
968
969 fn handle_fetch_success(ctx: &mut NetworkCheckContext, ip: std::net::IpAddr) {
970 match ctx.checker_state {
971 NetworkCheckState::FetchHttp => match ip {
972 IpAddr::V4(_) => {
973 ctx.discovered_state_v4.application.http_fetch_succeeded = true;
974 }
975 IpAddr::V6(_) => {
976 ctx.discovered_state_v6.application.http_fetch_succeeded = true;
977 }
978 },
979 NetworkCheckState::PingGateway
980 | NetworkCheckState::PingInternet
981 | NetworkCheckState::Begin
982 | NetworkCheckState::Idle
983 | NetworkCheckState::ResolveDns => {
984 panic!("continue check had an invalid state")
985 }
986 }
987 }
988
989 fn handle_ping_success(ctx: &mut NetworkCheckContext, addr: &std::net::SocketAddr) {
990 match ctx.checker_state {
991 NetworkCheckState::PingGateway => {
992 ctx.gateway_pingable = true;
993 match addr {
994 std::net::SocketAddr::V4 { .. } => {
995 ctx.discovered_state_v4.set_link_state(LinkState::Gateway)
996 }
997 std::net::SocketAddr::V6 { .. } => {
998 ctx.discovered_state_v6.set_link_state(LinkState::Gateway)
999 }
1000 }
1001 }
1002 NetworkCheckState::PingInternet => match addr {
1003 std::net::SocketAddr::V4 { .. } => {
1004 ctx.discovered_state_v4.set_link_state(LinkState::Internet)
1005 }
1006 std::net::SocketAddr::V6 { .. } => {
1007 ctx.discovered_state_v6.set_link_state(LinkState::Internet)
1008 }
1009 },
1010 NetworkCheckState::FetchHttp
1011 | NetworkCheckState::Begin
1012 | NetworkCheckState::Idle
1013 | NetworkCheckState::ResolveDns => {
1014 panic!("continue check had an invalid state")
1015 }
1016 }
1017 }
1018}
1019
1020impl<Time: TimeProvider> NetworkChecker for Monitor<Time> {
1021 fn begin(
1022 &mut self,
1023 InterfaceView {
1024 properties:
1025 &fnet_interfaces_ext::Properties {
1026 id,
1027 ref name,
1028 port_class: _,
1029 online,
1030 addresses: _,
1031 has_default_ipv4_route: _,
1032 has_default_ipv6_route: _,
1033 },
1034 routes,
1035 neighbors,
1036 }: InterfaceView<'_>,
1037 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
1038 let id = Id::from(id);
1039 let ctx = self.interface_context.entry(id).or_insert(Default::default());
1047
1048 match ctx.checker_state {
1049 NetworkCheckState::Begin => {}
1050 NetworkCheckState::Idle => {
1051 let mut new_ctx = NetworkCheckContext::default();
1052 std::mem::swap(&mut new_ctx.persistent_context, &mut ctx.persistent_context);
1054 *ctx = new_ctx;
1055 }
1056 NetworkCheckState::PingGateway
1057 | NetworkCheckState::PingInternet
1058 | NetworkCheckState::FetchHttp
1059 | NetworkCheckState::ResolveDns => {
1060 return Err(anyhow!("skipped, non-idle state found on interface {id}"));
1061 }
1062 }
1063
1064 if !online {
1065 ctx.set_global_link_state(LinkState::Down);
1066 return self.update_state_from_context(id, name);
1067 }
1068
1069 let device_routes: Vec<_> = routes.device_routes(id).collect();
1072
1073 let (discovered_online_neighbor, discovered_online_router) =
1074 scan_neighbor_health(neighbors, &device_routes);
1075
1076 if !discovered_online_neighbor && discovered_online_router {
1077 return Err(anyhow!(
1078 "invalid state: cannot have router discovered while neighbors are undiscovered"
1079 ));
1080 }
1081
1082 if !discovered_online_neighbor && !discovered_online_router {
1083 let discovered_state =
1084 if device_routes.is_empty() { LinkState::Up } else { LinkState::Local };
1085 ctx.set_global_link_state(discovered_state);
1086
1087 if discovered_state == LinkState::Up {
1088 return self.update_state_from_context(id, name);
1089 } else {
1090 ctx.always_ping_internet = false;
1093 }
1094 }
1095
1096 let relevant_routes: Vec<_> = device_routes
1097 .iter()
1098 .filter(|Route { destination, outbound_interface: _, next_hop: _ }| {
1099 *destination == UNSPECIFIED_V4 || *destination == UNSPECIFIED_V6
1100 })
1101 .collect();
1102
1103 let gateway_ping_addrs = relevant_routes
1104 .iter()
1105 .filter_map(move |Route { destination: _, outbound_interface, next_hop }| {
1106 next_hop.and_then(|next_hop| {
1107 let fnet_ext::IpAddress(next_hop) = next_hop.into();
1108 match next_hop.into() {
1109 std::net::IpAddr::V4(v4) => {
1110 Some(std::net::SocketAddr::V4(std::net::SocketAddrV4::new(v4, 0)))
1111 }
1112 std::net::IpAddr::V6(v6) => match (*outbound_interface).try_into() {
1113 Err(std::num::TryFromIntError { .. }) => {
1114 error!("device id {} doesn't fit in u32", outbound_interface);
1115 None
1116 }
1117 Ok(device_id) => {
1118 if device_id == 0
1119 && net_types::ip::Ipv6Addr::from_bytes(v6.octets()).scope()
1120 != net_types::ip::Ipv6Scope::Global
1121 {
1122 None
1123 } else {
1124 Some(std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
1125 v6, 0, 0, device_id,
1126 )))
1127 }
1128 }
1129 },
1130 }
1131 })
1132 })
1133 .map(|next_hop| next_hop)
1134 .collect::<Vec<_>>();
1135
1136 ctx.router_discoverable = discovered_online_router;
1138 if gateway_ping_addrs.is_empty() {
1139 if discovered_online_router {
1151 ctx.initiate_ping(
1156 id,
1157 name,
1158 &self.network_check_sender,
1159 NetworkCheckState::PingInternet,
1160 [
1161 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1162 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1163 ]
1164 .into_iter()
1165 .map(|ip| std::net::SocketAddr::new(ip, 0))
1166 .collect(),
1167 );
1168 } else {
1169 ctx.set_global_link_state(LinkState::Local);
1173 return self.update_state_from_context(id, name);
1174 }
1175 } else {
1176 ctx.set_global_link_state(if discovered_online_router {
1178 LinkState::Gateway
1179 } else {
1180 LinkState::Local
1181 });
1182 ctx.initiate_ping(
1183 id,
1184 name,
1185 &self.network_check_sender,
1186 NetworkCheckState::PingGateway,
1187 gateway_ping_addrs,
1188 );
1189 }
1190 Ok(NetworkCheckerOutcome::MustResume)
1191 }
1192
1193 fn resume(
1194 &mut self,
1195 cookie: NetworkCheckCookie,
1196 result: NetworkCheckResult,
1197 ) -> Result<NetworkCheckerOutcome, anyhow::Error> {
1198 let ctx = self.interface_context.get_mut(&cookie.id).ok_or_else(|| {
1199 anyhow!("resume: interface id {} should already exist in map", cookie.id)
1200 })?;
1201 let interface_name = result.interface_name().to_string();
1202 match ctx.checker_state {
1203 NetworkCheckState::Begin | NetworkCheckState::Idle => {
1204 return Err(anyhow!(
1205 "skipped, idle state found in resume for interface {}",
1206 cookie.id
1207 ));
1208 }
1209 NetworkCheckState::PingGateway | NetworkCheckState::PingInternet => {
1210 let (PingParameters { interface_name, addr }, success) =
1211 result.ping_result().ok_or_else(|| {
1212 anyhow!(
1213 "resume: mismatched state and result {interface_name} ({})",
1214 cookie.id
1215 )
1216 })?;
1217 ctx.pings_completed = ctx.pings_completed + 1;
1218
1219 if success {
1220 let () = Self::handle_ping_success(ctx, &addr);
1221 }
1222
1223 if ctx.pings_completed == ctx.pings_expected {
1224 if let NetworkCheckState::PingGateway = ctx.checker_state {
1225 ctx.initiate_ping(
1226 cookie.id,
1227 &interface_name,
1228 &self.network_check_sender,
1229 NetworkCheckState::PingInternet,
1230 [
1231 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1232 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1233 ]
1234 .into_iter()
1235 .map(|ip| std::net::SocketAddr::new(ip, 0))
1236 .collect(),
1237 );
1238 } else {
1239 let parameters = ResolveDnsParameters {
1240 interface_name: interface_name.to_string(),
1241 domain: GSTATIC.into(),
1242 };
1243 ctx.checker_state = NetworkCheckState::ResolveDns;
1244
1245 if self.time_provider.now() - ctx.persistent_context.resolved_time
1246 < DNS_PROBE_PERIOD
1247 {
1248 debug!(
1249 "Skipping ResolveDns since it has not yet been {} seconds",
1250 DNS_PROBE_PERIOD.clone().into_seconds()
1251 );
1252 if let Some(ips) = ctx.persistent_context.resolved_addrs.get(GSTATIC) {
1253 if !ips.v4.is_empty() {
1254 ctx.discovered_state_v4.application.dns_resolved = true;
1255 }
1256 if !ips.v6.is_empty() {
1257 ctx.discovered_state_v6.application.dns_resolved = true;
1258 }
1259 }
1260 return self.resume(
1261 cookie,
1262 NetworkCheckResult::ResolveDns { parameters, ips: None },
1263 );
1264 }
1265
1266 let action = NetworkCheckAction::ResolveDns(parameters);
1267 match self
1268 .network_check_sender
1269 .unbounded_send((action, NetworkCheckCookie { id: cookie.id }))
1270 {
1271 Ok(()) => {}
1272 Err(e) => {
1273 debug!("unable to send network check internet msg: {e:?}")
1274 }
1275 }
1276 }
1277 }
1278 }
1279 NetworkCheckState::ResolveDns => {
1280 let (ResolveDnsParameters { interface_name, domain }, ips) =
1281 result.resolve_dns_result().ok_or_else(|| {
1282 anyhow!(
1283 "resume: mismatched state and result {interface_name} ({})",
1284 cookie.id
1285 )
1286 })?;
1287
1288 if let Some(ips) = ips {
1289 if !ips.v4.is_empty() {
1290 ctx.discovered_state_v4.application.dns_resolved = true;
1291 }
1292 if !ips.v6.is_empty() {
1293 ctx.discovered_state_v6.application.dns_resolved = true;
1294 }
1295 ctx.persistent_context.resolved_time = self.time_provider.now();
1296 let _: Option<ResolvedIps> =
1297 ctx.persistent_context.resolved_addrs.insert(domain.clone(), ips);
1298 }
1299
1300 ctx.checker_state = NetworkCheckState::FetchHttp;
1301 ctx.fetches_expected = 0;
1302
1303 let mut add_fetch = |ip: IpAddr| {
1304 ctx.fetches_expected += 1;
1305 let action = NetworkCheckAction::Fetch(FetchParameters {
1306 interface_name: interface_name.clone(),
1307 domain: domain.clone(),
1308 ip,
1309 path: GENERATE_204.into(),
1310 expected_statuses: vec![204],
1311 });
1312 match self
1313 .network_check_sender
1314 .unbounded_send((action, NetworkCheckCookie { id: cookie.id }))
1315 {
1316 Ok(()) => {}
1317 Err(e) => debug!("unable to send network check internet message: {e:?}"),
1318 }
1319 };
1320
1321 if let Some(v4) =
1322 ctx.persistent_context.resolved_addrs.get(&domain).and_then(|ips| ips.v4.get(0))
1323 {
1324 add_fetch(IpAddr::V4(*v4));
1325 }
1326 if let Some(v6) =
1327 ctx.persistent_context.resolved_addrs.get(&domain).and_then(|ips| ips.v6.get(0))
1328 {
1329 add_fetch(IpAddr::V6(*v6));
1330 }
1331
1332 if ctx.fetches_expected == 0 {
1333 return self.update_state_from_context(cookie.id, &interface_name);
1334 }
1335 }
1336 NetworkCheckState::FetchHttp => {
1337 let (FetchParameters { interface_name, ip, expected_statuses, .. }, status) =
1338 result.fetch_result().ok_or_else(|| {
1339 anyhow!(
1340 "resume: mismatched state and result {interface_name} ({})",
1341 cookie.id
1342 )
1343 })?;
1344 ctx.fetches_completed += 1;
1345
1346 if let Some(status) = status {
1347 if expected_statuses.contains(&status) {
1348 let () = Self::handle_fetch_success(ctx, ip);
1349 }
1350 }
1351
1352 if ctx.fetches_completed == ctx.fetches_expected {
1353 return self.update_state_from_context(cookie.id, &interface_name);
1354 }
1355 }
1356 }
1357 Ok(NetworkCheckerOutcome::MustResume)
1358 }
1359}
1360
1361fn log_state(info: Option<&mut InspectInfo>, proto: Proto, state: State) {
1362 info.into_iter().for_each(|info| info.log_link_state(proto, state.link))
1363}
1364
1365fn scan_neighbor_health(
1368 neighbors: Option<&InterfaceNeighborCache>,
1369 device_routes: &Vec<route_table::Route>,
1370) -> (bool, bool) {
1371 match neighbors {
1372 None => (false, false),
1373 Some(neighbors) => {
1374 neighbors
1375 .iter_health()
1376 .fold_while(
1377 (false, false),
1378 |(discovered_online_neighbor, _discovered_online_router),
1379 (neighbor, health)| {
1380 let is_router = device_routes.iter().any(
1381 |Route { destination: _, outbound_interface: _, next_hop }| {
1382 next_hop.map(|next_hop| *neighbor == next_hop).unwrap_or(false)
1383 },
1384 );
1385 match health {
1386 neighbor_cache::NeighborHealth::Unhealthy { .. }
1389 | neighbor_cache::NeighborHealth::Unknown => {
1390 itertools::FoldWhile::Continue((discovered_online_neighbor, false))
1391 }
1392 neighbor_cache::NeighborHealth::Healthy { .. } => {
1396 if !is_router {
1397 return itertools::FoldWhile::Continue((true, false));
1398 }
1399 itertools::FoldWhile::Done((true, true))
1400 }
1401 }
1402 },
1403 )
1404 .into_inner()
1405 }
1406 }
1407}
1408
1409#[cfg(test)]
1410mod tests {
1411 use crate::fetch::FetchAddr;
1412
1413 use super::*;
1414 use crate::dig::Dig;
1415 use crate::fetch::Fetch;
1416 use crate::neighbor_cache::{NeighborHealth, NeighborState};
1417 use crate::ping::Ping;
1418 use async_trait::async_trait;
1419 use diagnostics_assertions::assert_data_tree;
1420 use futures::StreamExt as _;
1421 use net_declare::{fidl_ip, fidl_subnet, std_ip, std_socket_addr};
1422 use net_types::ip;
1423 use std::pin::pin;
1424 use std::task::Poll;
1425 use test_case::test_case;
1426 use {
1427 fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as fnet_interfaces,
1428 fuchsia_async as fasync,
1429 };
1430
1431 const ETHERNET_INTERFACE_NAME: &str = "eth1";
1432 const ID1: u64 = 1;
1433 const ID2: u64 = 2;
1434
1435 trait Construct<T> {
1440 fn construct(_: T) -> Self;
1441 }
1442
1443 impl<S: Into<State>> Construct<S> for StateEvent {
1444 fn construct(link: S) -> Self {
1445 Self { state: link.into(), time: fasync::MonotonicInstant::INFINITE }
1446 }
1447 }
1448
1449 impl Construct<(LinkState, bool, bool)> for StateEvent {
1450 fn construct((link, dns_resolved, http_fetch_succeeded): (LinkState, bool, bool)) -> Self {
1451 Self {
1452 state: State {
1453 link,
1454 application: ApplicationState { dns_resolved, http_fetch_succeeded },
1455 },
1456 time: fasync::MonotonicInstant::INFINITE,
1457 }
1458 }
1459 }
1460
1461 impl Construct<StateEvent> for IpVersions<StateEvent> {
1462 fn construct(state: StateEvent) -> Self {
1463 Self { ipv4: state, ipv6: state }
1464 }
1465 }
1466
1467 struct FakeTime {
1468 increment: zx::MonotonicDuration,
1469 time: zx::MonotonicInstant,
1470 }
1471
1472 impl TimeProvider for FakeTime {
1473 fn now(&mut self) -> zx::MonotonicInstant {
1474 let result = self.time;
1475 self.time += self.increment;
1476 result
1477 }
1478 }
1479
1480 #[test]
1481 fn test_log_state_vals_inspect() {
1482 let inspector = Inspector::default();
1483 LinkState::log_state_vals_inspect(inspector.root(), "state_vals");
1484 assert_data_tree!(inspector, root: {
1485 state_vals: {
1486 "1": "None",
1487 "5": "Removed",
1488 "10": "Down",
1489 "15": "Up",
1490 "20": "Local",
1491 "25": "Gateway",
1492 "30": "Internet",
1493 }
1494 })
1495 }
1496
1497 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("1.2.3.0:8080")];
1498 "gateway ping on ipv4")]
1499 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("[123::]:0")];
1500 "gateway ping on ipv6")]
1501 #[test_case(NetworkCheckState::PingGateway, &[std_socket_addr!("1.2.3.0:8080"),
1502 std_socket_addr!("[123::]:0")]; "gateway ping on ipv4/ipv6")]
1503 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("8.8.8.8:0")];
1504 "internet ping on ipv4")]
1505 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("[2001:4860:4860::8888]:0")];
1506 "internet ping on ipv6")]
1507 #[test_case(NetworkCheckState::PingInternet, &[std_socket_addr!("8.8.8.8:0"),
1508 std_socket_addr!("[2001:4860:4860::8888]:0")]; "internet ping on ipv4/ipv6")]
1509 fn test_handle_ping_success(checker_state: NetworkCheckState, addrs: &[std::net::SocketAddr]) {
1510 let mut expected_state_v4: State = Default::default();
1511 let mut expected_state_v6: State = Default::default();
1512
1513 let mut ctx = NetworkCheckContext { checker_state, ..Default::default() };
1514 assert_eq!(ctx.discovered_state_v4, expected_state_v4);
1516 assert_eq!(ctx.discovered_state_v6, expected_state_v6);
1517
1518 let expected_state = match ctx.checker_state {
1519 NetworkCheckState::PingGateway => LinkState::Gateway.into(),
1520 NetworkCheckState::PingInternet => LinkState::Internet.into(),
1521 NetworkCheckState::ResolveDns => LinkState::Internet.into(),
1522 NetworkCheckState::FetchHttp => State {
1523 link: LinkState::Internet,
1524 application: ApplicationState { dns_resolved: true, http_fetch_succeeded: true },
1525 },
1526 NetworkCheckState::Begin | NetworkCheckState::Idle => Default::default(),
1527 };
1528
1529 addrs.iter().for_each(|addr| {
1530 let () = Monitor::<FakeTime>::handle_ping_success(&mut ctx, addr);
1532 match addr {
1534 std::net::SocketAddr::V4 { .. } => {
1535 expected_state_v4 = expected_state;
1536 }
1537 std::net::SocketAddr::V6 { .. } => {
1538 expected_state_v6 = expected_state;
1539 }
1540 }
1541 });
1542 assert_eq!(ctx.discovered_state_v4, expected_state_v4);
1544 assert_eq!(ctx.discovered_state_v6, expected_state_v6);
1545 }
1546
1547 #[derive(Default, Clone)]
1548 struct FakePing {
1549 gateway_addrs: std::collections::HashSet<std::net::IpAddr>,
1550 gateway_response: bool,
1551 internet_response: bool,
1552 }
1553
1554 #[async_trait]
1555 impl Ping for FakePing {
1556 async fn ping(&self, _interface_name: &str, addr: std::net::SocketAddr) -> bool {
1557 let Self { gateway_addrs, gateway_response, internet_response } = self;
1558 let ip = addr.ip();
1559 if [IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS, IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS]
1560 .contains(&ip)
1561 {
1562 *internet_response
1563 } else if gateway_addrs.contains(&ip) {
1564 *gateway_response
1565 } else {
1566 false
1567 }
1568 }
1569 }
1570
1571 #[derive(Default)]
1572 struct FakeDig {
1573 response: Option<ResolvedIps>,
1574 }
1575
1576 impl FakeDig {
1577 fn new(ips: Vec<std::net::IpAddr>) -> Self {
1578 let mut ips_out = ResolvedIps::default();
1579 for ip in ips {
1580 match ip {
1581 IpAddr::V4(v4) => ips_out.v4.push(v4),
1582 IpAddr::V6(v6) => ips_out.v6.push(v6),
1583 }
1584 }
1585 FakeDig { response: Some(ips_out) }
1586 }
1587 }
1588
1589 #[async_trait]
1590 impl Dig for FakeDig {
1591 async fn dig(&self, _interface_name: &str, _domain: &str) -> Option<ResolvedIps> {
1592 self.response.clone()
1593 }
1594 }
1595
1596 #[derive(Default, Copy, Clone)]
1597 struct FakeFetch {
1598 expected_url: Option<&'static str>,
1599 response: Option<u16>,
1600 }
1601
1602 #[async_trait]
1603 impl Fetch for FakeFetch {
1604 async fn fetch<FA: FetchAddr + std::marker::Sync>(
1605 &self,
1606 _interface_name: &str,
1607 domain: &str,
1608 path: &str,
1609 _addr: &FA,
1610 ) -> Option<u16> {
1611 if let Some(expected) = self.expected_url {
1612 assert_eq!(
1613 format!("http://{domain}{path}"),
1614 expected,
1615 "Did not receive expected URL"
1616 );
1617 }
1618 self.response
1619 }
1620 }
1621
1622 struct NetworkCheckTestResponder {
1623 receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
1624 }
1625
1626 impl NetworkCheckTestResponder {
1627 fn new(
1628 receiver: mpsc::UnboundedReceiver<(NetworkCheckAction, NetworkCheckCookie)>,
1629 ) -> Self {
1630 Self { receiver }
1631 }
1632
1633 async fn respond_to_messages<P: Ping, D: Dig, F: Fetch, Time: TimeProvider>(
1634 &mut self,
1635 monitor: &mut Monitor<Time>,
1636 p: P,
1637 d: D,
1638 f: F,
1639 ) {
1640 loop {
1641 if let Some((action, cookie)) = self.receiver.next().await {
1642 match action {
1643 NetworkCheckAction::Ping(parameters) => {
1644 let success = p.ping(¶meters.interface_name, parameters.addr).await;
1645 match monitor
1646 .resume(cookie, NetworkCheckResult::Ping { parameters, success })
1647 {
1648 Ok(NetworkCheckerOutcome::Complete) => return,
1650 _ => {}
1651 }
1652 }
1653 NetworkCheckAction::ResolveDns(parameters) => {
1654 let ips = d.dig(¶meters.interface_name, ¶meters.domain).await;
1655 match monitor
1656 .resume(cookie, NetworkCheckResult::ResolveDns { parameters, ips })
1657 {
1658 Ok(NetworkCheckerOutcome::Complete) => return,
1660 _ => {}
1661 }
1662 }
1663 NetworkCheckAction::Fetch(parameters) => {
1664 let status = f
1665 .fetch(
1666 ¶meters.interface_name,
1667 ¶meters.domain,
1668 ¶meters.path,
1669 ¶meters.ip,
1670 )
1671 .await;
1672 match monitor
1673 .resume(cookie, NetworkCheckResult::Fetch { parameters, status })
1674 {
1675 Ok(NetworkCheckerOutcome::Complete) => return,
1677 _ => {}
1678 }
1679 }
1680 }
1681 }
1682 }
1683 }
1684 }
1685
1686 fn run_network_check_partial_properties_repeated<P: Ping, D: Dig, F: Fetch>(
1687 exec: &mut fasync::TestExecutor,
1688 name: &str,
1689 interface_id: u64,
1690 routes: &RouteTable,
1691 mocks: Vec<(P, D, F)>,
1692 neighbors: Option<&InterfaceNeighborCache>,
1693 internet_ping_address: std::net::IpAddr,
1694 sleep_between: Option<zx::MonotonicDuration>,
1695 ) -> Vec<State> {
1696 let properties = &fnet_interfaces_ext::Properties {
1697 id: interface_id.try_into().expect("should be nonzero"),
1698 name: name.to_string(),
1699 port_class: fnet_interfaces_ext::PortClass::Ethernet,
1700 online: true,
1701 addresses: Default::default(),
1702 has_default_ipv4_route: Default::default(),
1703 has_default_ipv6_route: Default::default(),
1704 };
1705
1706 let mock_count = mocks.len();
1707 match run_network_check_repeated(exec, properties, routes, neighbors, mocks, sleep_between)
1708 {
1709 Ok(Some(events)) => {
1710 events
1714 .into_iter()
1715 .map(|event| match internet_ping_address {
1716 std::net::IpAddr::V4 { .. } => event.ipv4.state,
1717 std::net::IpAddr::V6 { .. } => event.ipv6.state,
1718 })
1719 .collect()
1720 }
1721 Ok(None) => {
1722 error!("id for interface unexpectedly did not exist after network check");
1723 std::iter::repeat(LinkState::None.into()).take(mock_count).collect()
1724 }
1725 Err(e) => {
1726 error!("network check had an issue calculating state: {:?}", e);
1727 std::iter::repeat(LinkState::None.into()).take(mock_count).collect()
1728 }
1729 }
1730 }
1731
1732 fn run_network_check_partial_properties<P: Ping, D: Dig, F: Fetch>(
1733 exec: &mut fasync::TestExecutor,
1734 name: &str,
1735 interface_id: u64,
1736 routes: &RouteTable,
1737 pinger: P,
1738 digger: D,
1739 fetcher: F,
1740 neighbors: Option<&InterfaceNeighborCache>,
1741 internet_ping_address: std::net::IpAddr,
1742 ) -> State {
1743 run_network_check_partial_properties_repeated(
1744 exec,
1745 name,
1746 interface_id,
1747 routes,
1748 vec![(pinger, digger, fetcher)],
1749 neighbors,
1750 internet_ping_address,
1751 None,
1752 )
1753 .pop()
1754 .unwrap_or_else(|| {
1755 error!("network check returned no states");
1756 LinkState::None.into()
1757 })
1758 }
1759
1760 fn run_network_check_repeated<P: Ping, D: Dig, F: Fetch>(
1761 exec: &mut fasync::TestExecutor,
1762 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1763 routes: &RouteTable,
1764 neighbors: Option<&InterfaceNeighborCache>,
1765 mocks: Vec<(P, D, F)>,
1766 sleep_between: Option<zx::MonotonicDuration>,
1767 ) -> Result<Option<Vec<IpVersions<StateEvent>>>, anyhow::Error> {
1768 let (sender, receiver) = mpsc::unbounded::<(NetworkCheckAction, NetworkCheckCookie)>();
1769 let mut monitor = Monitor::new_with_time_provider(
1770 sender,
1771 FakeTime {
1772 increment: sleep_between.unwrap_or(zx::MonotonicDuration::from_nanos(10)),
1773 time: zx::MonotonicInstant::get(),
1774 },
1775 )
1776 .unwrap();
1777 let mut network_check_responder = NetworkCheckTestResponder::new(receiver);
1778
1779 let view = InterfaceView { properties, routes, neighbors };
1780 let network_check_fut = async {
1781 let mut states = Vec::new();
1782 for (pinger, digger, fetcher) in mocks {
1783 match monitor.begin(view) {
1784 Ok(NetworkCheckerOutcome::Complete) => {}
1785 Ok(NetworkCheckerOutcome::MustResume) => {
1786 let () = network_check_responder
1787 .respond_to_messages(&mut monitor, pinger, digger, fetcher)
1788 .await;
1789 }
1790 Err(e) => {
1791 error!("begin had an issue calculating state: {:?}", e)
1792 }
1793 }
1794 states.push(monitor.state().get(properties.id.get()).map(Clone::clone));
1795 }
1796 states
1797 };
1798
1799 let mut network_check_fut = pin!(network_check_fut);
1800 match exec.run_until_stalled(&mut network_check_fut) {
1801 Poll::Ready(got) => Ok(got.into_iter().collect()),
1802 Poll::Pending => Err(anyhow::anyhow!("network_check blocked unexpectedly")),
1803 }
1804 }
1805
1806 fn run_network_check<P: Ping, D: Dig, F: Fetch>(
1807 exec: &mut fasync::TestExecutor,
1808 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1809 routes: &RouteTable,
1810 neighbors: Option<&InterfaceNeighborCache>,
1811 pinger: P,
1812 digger: D,
1813 fetcher: F,
1814 ) -> Result<Option<IpVersions<StateEvent>>, anyhow::Error> {
1815 run_network_check_repeated(
1816 exec,
1817 properties,
1818 routes,
1819 neighbors,
1820 vec![(pinger, digger, fetcher)],
1821 None,
1822 )
1823 .map(|res| res.and_then(|mut v| v.pop()))
1824 }
1825
1826 #[test]
1827 fn test_network_check_ethernet_ipv4() {
1828 test_network_check_ethernet::<ip::Ipv4>(
1829 fidl_ip!("1.2.3.0"),
1830 fidl_ip!("1.2.3.4"),
1831 fidl_ip!("1.2.3.1"),
1832 fidl_ip!("2.2.3.0"),
1833 fidl_ip!("2.2.3.1"),
1834 UNSPECIFIED_V4,
1835 fidl_subnet!("0.0.0.0/1"),
1836 IPV4_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1837 24,
1838 );
1839 }
1840
1841 #[test]
1842 fn test_network_check_ethernet_ipv6() {
1843 test_network_check_ethernet::<ip::Ipv6>(
1844 fidl_ip!("123::"),
1845 fidl_ip!("123::4"),
1846 fidl_ip!("123::1"),
1847 fidl_ip!("223::"),
1848 fidl_ip!("223::1"),
1849 UNSPECIFIED_V6,
1850 fidl_subnet!("::/1"),
1851 IPV6_INTERNET_CONNECTIVITY_CHECK_ADDRESS,
1852 64,
1853 );
1854 }
1855
1856 fn test_network_check_ethernet<I: ip::Ip>(
1857 net1: fnet::IpAddress,
1858 _net1_addr: fnet::IpAddress,
1859 net1_gateway: fnet::IpAddress,
1860 net2: fnet::IpAddress,
1861 net2_gateway: fnet::IpAddress,
1862 unspecified_addr: fnet::Subnet,
1863 non_default_addr: fnet::Subnet,
1864 ping_internet_addr: std::net::IpAddr,
1865 prefix_len: u8,
1866 ) {
1867 let route_table = testutil::build_route_table_from_flattened_routes([
1868 Route {
1869 destination: unspecified_addr,
1870 outbound_interface: ID1,
1871 next_hop: Some(net1_gateway),
1872 },
1873 Route {
1874 destination: fnet::Subnet { addr: net1, prefix_len },
1875 outbound_interface: ID1,
1876 next_hop: None,
1877 },
1878 ]);
1879 let route_table_2 = testutil::build_route_table_from_flattened_routes([
1880 Route {
1881 destination: unspecified_addr,
1882 outbound_interface: ID1,
1883 next_hop: Some(net2_gateway),
1884 },
1885 Route {
1886 destination: fnet::Subnet { addr: net1, prefix_len },
1887 outbound_interface: ID1,
1888 next_hop: None,
1889 },
1890 Route {
1891 destination: fnet::Subnet { addr: net2, prefix_len },
1892 outbound_interface: ID1,
1893 next_hop: None,
1894 },
1895 ]);
1896 let route_table_3 = testutil::build_route_table_from_flattened_routes([
1897 Route {
1898 destination: unspecified_addr,
1899 outbound_interface: ID2,
1900 next_hop: Some(net1_gateway),
1901 },
1902 Route {
1903 destination: fnet::Subnet { addr: net1, prefix_len },
1904 outbound_interface: ID2,
1905 next_hop: None,
1906 },
1907 ]);
1908 let route_table_4 = testutil::build_route_table_from_flattened_routes([
1909 Route {
1910 destination: non_default_addr,
1911 outbound_interface: ID1,
1912 next_hop: Some(net1_gateway),
1913 },
1914 Route {
1915 destination: fnet::Subnet { addr: net1, prefix_len },
1916 outbound_interface: ID1,
1917 next_hop: None,
1918 },
1919 ]);
1920
1921 let fnet_ext::IpAddress(net1_gateway_ext) = net1_gateway.into();
1922 let mut exec = fasync::TestExecutor::new();
1923
1924 assert_eq!(
1926 run_network_check_partial_properties(
1927 &mut exec,
1928 ETHERNET_INTERFACE_NAME,
1929 ID1,
1930 &route_table,
1931 FakePing {
1932 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
1933 gateway_response: true,
1934 internet_response: true,
1935 },
1936 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]),
1937 FakeFetch {
1938 expected_url: Some("http://www.gstatic.com/generate_204"),
1939 response: Some(204)
1940 },
1941 None,
1942 ping_internet_addr,
1943 ),
1944 State {
1945 link: LinkState::Internet,
1946 application: ApplicationState { dns_resolved: true, http_fetch_succeeded: true },
1947 },
1948 "All is good. Can reach internet"
1949 );
1950
1951 assert_eq!(
1952 run_network_check_partial_properties(
1953 &mut exec,
1954 ETHERNET_INTERFACE_NAME,
1955 ID1,
1956 &route_table,
1957 FakePing {
1958 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
1959 gateway_response: true,
1960 internet_response: true,
1961 },
1962 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]),
1963 FakeFetch::default(),
1964 None,
1965 ping_internet_addr,
1966 ),
1967 State {
1968 link: LinkState::Internet,
1969 application: ApplicationState { dns_resolved: true, ..Default::default() },
1970 },
1971 "HTTP Fetch fails"
1972 );
1973
1974 assert_eq!(
1975 run_network_check_partial_properties(
1976 &mut exec,
1977 ETHERNET_INTERFACE_NAME,
1978 ID1,
1979 &route_table,
1980 FakePing {
1981 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
1982 gateway_response: true,
1983 internet_response: true,
1984 },
1985 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("1.2.4.0")]),
1986 FakeFetch::default(),
1987 None,
1988 ping_internet_addr,
1989 ),
1990 State {
1991 link: LinkState::Internet,
1992 application: ApplicationState {
1993 dns_resolved: ping_internet_addr.is_ipv4(),
1994 ..Default::default()
1995 },
1996 },
1997 "DNS Resolves only IPV4",
1998 );
1999
2000 assert_eq!(
2001 run_network_check_partial_properties(
2002 &mut exec,
2003 ETHERNET_INTERFACE_NAME,
2004 ID1,
2005 &route_table,
2006 FakePing {
2007 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2008 gateway_response: true,
2009 internet_response: true,
2010 },
2011 FakeDig::new(vec![std_ip!("123::"), std_ip!("124::")]),
2012 FakeFetch::default(),
2013 None,
2014 ping_internet_addr,
2015 ),
2016 State {
2017 link: LinkState::Internet,
2018 application: ApplicationState {
2019 dns_resolved: ping_internet_addr.is_ipv6(),
2020 ..Default::default()
2021 },
2022 },
2023 "DNS Resolves only IPV6",
2024 );
2025
2026 assert_eq!(
2027 run_network_check_partial_properties(
2028 &mut exec,
2029 ETHERNET_INTERFACE_NAME,
2030 ID1,
2031 &route_table,
2032 FakePing {
2033 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2034 gateway_response: false,
2035 internet_response: true,
2036 },
2037 FakeDig::default(),
2038 FakeFetch::default(),
2039 Some(&InterfaceNeighborCache {
2040 neighbors: [(
2041 net1_gateway,
2042 NeighborState::new(NeighborHealth::Healthy {
2043 last_observed: zx::MonotonicInstant::default(),
2044 })
2045 )]
2046 .iter()
2047 .cloned()
2048 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2049 }),
2050 ping_internet_addr,
2051 ),
2052 LinkState::Internet.into(),
2053 "Can reach internet, gateway responding via ARP/ND"
2054 );
2055
2056 assert_eq!(
2057 run_network_check_partial_properties(
2058 &mut exec,
2059 ETHERNET_INTERFACE_NAME,
2060 ID1,
2061 &route_table,
2062 FakePing {
2063 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2064 gateway_response: false,
2065 internet_response: true,
2066 },
2067 FakeDig::default(),
2068 FakeFetch::default(),
2069 Some(&InterfaceNeighborCache {
2070 neighbors: [(
2071 net1,
2072 NeighborState::new(NeighborHealth::Healthy {
2073 last_observed: zx::MonotonicInstant::default(),
2074 })
2075 )]
2076 .iter()
2077 .cloned()
2078 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2079 }),
2080 ping_internet_addr,
2081 ),
2082 LinkState::Internet.into(),
2083 "Gateway not responding via ping or ARP/ND. Can reach internet"
2084 );
2085
2086 assert_eq!(
2087 run_network_check_partial_properties(
2088 &mut exec,
2089 ETHERNET_INTERFACE_NAME,
2090 ID1,
2091 &route_table_4,
2092 FakePing {
2093 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2094 gateway_response: true,
2095 internet_response: true,
2096 },
2097 FakeDig::default(),
2098 FakeFetch::default(),
2099 Some(&InterfaceNeighborCache {
2100 neighbors: [(
2101 net1_gateway,
2102 NeighborState::new(NeighborHealth::Healthy {
2103 last_observed: zx::MonotonicInstant::default(),
2104 })
2105 )]
2106 .iter()
2107 .cloned()
2108 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2109 }),
2110 ping_internet_addr,
2111 ),
2112 LinkState::Internet.into(),
2113 "No default route, but healthy gateway with internet/gateway response"
2114 );
2115
2116 assert_eq!(
2117 run_network_check_partial_properties(
2118 &mut exec,
2119 ETHERNET_INTERFACE_NAME,
2120 ID1,
2121 &route_table,
2122 FakePing {
2123 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2124 gateway_response: true,
2125 internet_response: false,
2126 },
2127 FakeDig::default(),
2128 FakeFetch::default(),
2129 None,
2130 ping_internet_addr,
2131 ),
2132 LinkState::Gateway.into(),
2133 "Can reach gateway via ping"
2134 );
2135
2136 assert_eq!(
2137 run_network_check_partial_properties(
2138 &mut exec,
2139 ETHERNET_INTERFACE_NAME,
2140 ID1,
2141 &route_table,
2142 FakePing::default(),
2143 FakeDig::default(),
2144 FakeFetch::default(),
2145 Some(&InterfaceNeighborCache {
2146 neighbors: [(
2147 net1_gateway,
2148 NeighborState::new(NeighborHealth::Healthy {
2149 last_observed: zx::MonotonicInstant::default(),
2150 })
2151 )]
2152 .iter()
2153 .cloned()
2154 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2155 }),
2156 ping_internet_addr,
2157 ),
2158 LinkState::Gateway.into(),
2159 "Can reach gateway via ARP/ND"
2160 );
2161
2162 assert_eq!(
2163 run_network_check_partial_properties(
2164 &mut exec,
2165 ETHERNET_INTERFACE_NAME,
2166 ID1,
2167 &route_table,
2168 FakePing {
2169 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2170 gateway_response: false,
2171 internet_response: false,
2172 },
2173 FakeDig::default(),
2174 FakeFetch::default(),
2175 None,
2176 ping_internet_addr,
2177 ),
2178 LinkState::Local.into(),
2179 "Local only, Cannot reach gateway"
2180 );
2181
2182 assert_eq!(
2183 run_network_check_partial_properties(
2184 &mut exec,
2185 ETHERNET_INTERFACE_NAME,
2186 ID1,
2187 &route_table_2,
2188 FakePing::default(),
2189 FakeDig::default(),
2190 FakeFetch::default(),
2191 None,
2192 ping_internet_addr,
2193 ),
2194 LinkState::Local.into(),
2195 "No default route"
2196 );
2197
2198 assert_eq!(
2199 run_network_check_partial_properties(
2200 &mut exec,
2201 ETHERNET_INTERFACE_NAME,
2202 ID1,
2203 &route_table_4,
2204 FakePing {
2205 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2206 gateway_response: true,
2207 internet_response: false,
2208 },
2209 FakeDig::default(),
2210 FakeFetch::default(),
2211 None,
2212 ping_internet_addr,
2213 ),
2214 LinkState::Local.into(),
2215 "No default route, with only gateway response"
2216 );
2217
2218 assert_eq!(
2219 run_network_check_partial_properties(
2220 &mut exec,
2221 ETHERNET_INTERFACE_NAME,
2222 ID1,
2223 &route_table_2,
2224 FakePing::default(),
2225 FakeDig::default(),
2226 FakeFetch::default(),
2227 Some(&InterfaceNeighborCache {
2228 neighbors: [(
2229 net1,
2230 NeighborState::new(NeighborHealth::Healthy {
2231 last_observed: zx::MonotonicInstant::default(),
2232 })
2233 )]
2234 .iter()
2235 .cloned()
2236 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2237 }),
2238 ping_internet_addr,
2239 ),
2240 LinkState::Local.into(),
2241 "Local only, neighbors responsive with no default route"
2242 );
2243
2244 assert_eq!(
2245 run_network_check_partial_properties(
2246 &mut exec,
2247 ETHERNET_INTERFACE_NAME,
2248 ID1,
2249 &route_table,
2250 FakePing::default(),
2251 FakeDig::default(),
2252 FakeFetch::default(),
2253 Some(&InterfaceNeighborCache {
2254 neighbors: [(
2255 net1,
2256 NeighborState::new(NeighborHealth::Healthy {
2257 last_observed: zx::MonotonicInstant::default(),
2258 })
2259 )]
2260 .iter()
2261 .cloned()
2262 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2263 }),
2264 ping_internet_addr
2265 ),
2266 LinkState::Local.into(),
2267 "Local only, neighbors responsive with a default route"
2268 );
2269
2270 assert_eq!(
2271 run_network_check_partial_properties(
2272 &mut exec,
2273 ETHERNET_INTERFACE_NAME,
2274 ID1,
2275 &route_table_3,
2276 FakePing::default(),
2277 FakeDig::default(),
2278 FakeFetch::default(),
2279 Some(&InterfaceNeighborCache {
2280 neighbors: [(
2281 net1,
2282 NeighborState::new(NeighborHealth::Healthy {
2283 last_observed: zx::MonotonicInstant::default(),
2284 })
2285 )]
2286 .iter()
2287 .cloned()
2288 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2289 }),
2290 ping_internet_addr,
2291 ),
2292 LinkState::Local.into(),
2293 "Local only, neighbors responsive with no routes"
2294 );
2295
2296 assert_eq!(
2297 run_network_check_partial_properties(
2298 &mut exec,
2299 ETHERNET_INTERFACE_NAME,
2300 ID1,
2301 &route_table,
2302 FakePing::default(),
2303 FakeDig::default(),
2304 FakeFetch::default(),
2305 Some(&InterfaceNeighborCache {
2306 neighbors: [
2307 (
2308 net1,
2309 NeighborState::new(NeighborHealth::Healthy {
2310 last_observed: zx::MonotonicInstant::default(),
2311 })
2312 ),
2313 (
2314 net1_gateway,
2315 NeighborState::new(NeighborHealth::Unhealthy { last_healthy: None })
2316 )
2317 ]
2318 .iter()
2319 .cloned()
2320 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2321 }),
2322 ping_internet_addr,
2323 ),
2324 LinkState::Local.into(),
2325 "Local only, gateway unhealthy with healthy neighbor"
2326 );
2327
2328 assert_eq!(
2329 run_network_check_partial_properties(
2330 &mut exec,
2331 ETHERNET_INTERFACE_NAME,
2332 ID1,
2333 &route_table_3,
2334 FakePing::default(),
2335 FakeDig::default(),
2336 FakeFetch::default(),
2337 Some(&InterfaceNeighborCache {
2338 neighbors: [(
2339 net1_gateway,
2340 NeighborState::new(NeighborHealth::Unhealthy { last_healthy: None })
2341 )]
2342 .iter()
2343 .cloned()
2344 .collect::<HashMap<fnet::IpAddress, NeighborState>>()
2345 }),
2346 ping_internet_addr,
2347 ),
2348 LinkState::Up.into(),
2349 "No routes and unhealthy gateway"
2350 );
2351
2352 assert_eq!(
2353 run_network_check_partial_properties(
2354 &mut exec,
2355 ETHERNET_INTERFACE_NAME,
2356 ID1,
2357 &route_table_3,
2358 FakePing::default(),
2359 FakeDig::default(),
2360 FakeFetch::default(),
2361 None,
2362 ping_internet_addr,
2363 ),
2364 LinkState::Up.into(),
2365 "No routes",
2366 );
2367
2368 assert_eq!(
2369 run_network_check_partial_properties_repeated(
2370 &mut exec,
2371 ETHERNET_INTERFACE_NAME,
2372 ID1,
2373 &route_table,
2374 vec![
2375 (
2376 FakePing {
2377 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2378 gateway_response: true,
2379 internet_response: true,
2380 },
2381 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2383 expected_url: Some("http://www.gstatic.com/generate_204"),
2384 response: Some(204)
2385 },
2386 ),
2387 (
2388 FakePing {
2389 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2390 gateway_response: true,
2391 internet_response: true,
2392 },
2393 FakeDig { response: None }, FakeFetch {
2395 expected_url: Some("http://www.gstatic.com/generate_204"),
2396 response: Some(204)
2397 },
2398 ),
2399 ],
2400 None,
2401 ping_internet_addr,
2402 None,
2403 ),
2404 vec![
2405 State {
2406 link: LinkState::Internet,
2407 application: ApplicationState {
2408 dns_resolved: true,
2409 http_fetch_succeeded: true
2410 }
2411 },
2412 State {
2413 link: LinkState::Internet,
2414 application: ApplicationState {
2415 dns_resolved: true,
2416 http_fetch_succeeded: true
2417 }
2418 }
2419 ],
2420 "Fail DNS on second check; fetch succeeds; no pause"
2421 );
2422
2423 assert_eq!(
2424 run_network_check_partial_properties_repeated(
2425 &mut exec,
2426 ETHERNET_INTERFACE_NAME,
2427 ID1,
2428 &route_table,
2429 vec![
2430 (
2431 FakePing {
2432 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2433 gateway_response: true,
2434 internet_response: true,
2435 },
2436 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2438 expected_url: Some("http://www.gstatic.com/generate_204"),
2439 response: Some(204)
2440 },
2441 ),
2442 (
2443 FakePing {
2444 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2445 gateway_response: true,
2446 internet_response: true,
2447 },
2448 FakeDig { response: None }, FakeFetch {
2450 expected_url: Some("http://www.gstatic.com/generate_204"),
2451 response: Some(204)
2452 },
2453 ),
2454 ],
2455 None,
2456 ping_internet_addr,
2457 Some(DNS_PROBE_PERIOD),
2458 ),
2459 vec![
2460 State {
2461 link: LinkState::Internet,
2462 application: ApplicationState {
2463 dns_resolved: true,
2464 http_fetch_succeeded: true
2465 }
2466 },
2467 State {
2468 link: LinkState::Internet,
2469 application: ApplicationState {
2470 dns_resolved: false,
2471 http_fetch_succeeded: true
2472 }
2473 }
2474 ],
2475 "Fail DNS on second check; fetch succeeds"
2476 );
2477
2478 assert_eq!(
2479 run_network_check_partial_properties_repeated(
2480 &mut exec,
2481 ETHERNET_INTERFACE_NAME,
2482 ID1,
2483 &route_table,
2484 vec![
2485 (
2486 FakePing {
2487 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2488 gateway_response: true,
2489 internet_response: true,
2490 },
2491 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2493 expected_url: Some("http://www.gstatic.com/generate_204"),
2494 response: None
2495 },
2496 ),
2497 (
2498 FakePing {
2499 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2500 gateway_response: true,
2501 internet_response: true,
2502 },
2503 FakeDig { response: None }, FakeFetch {
2505 expected_url: Some("http://www.gstatic.com/generate_204"),
2506 response: None,
2507 },
2508 ),
2509 ],
2510 None,
2511 ping_internet_addr,
2512 None,
2513 ),
2514 vec![
2515 State {
2516 link: LinkState::Internet,
2517 application: ApplicationState { dns_resolved: true, ..Default::default() }
2518 },
2519 State {
2520 link: LinkState::Internet,
2521 application: ApplicationState { dns_resolved: true, ..Default::default() }
2522 }
2523 ],
2524 "Fail DNS on second check; fetch fails; no pause"
2525 );
2526
2527 assert_eq!(
2528 run_network_check_partial_properties_repeated(
2529 &mut exec,
2530 ETHERNET_INTERFACE_NAME,
2531 ID1,
2532 &route_table,
2533 vec![
2534 (
2535 FakePing {
2536 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2537 gateway_response: true,
2538 internet_response: true,
2539 },
2540 FakeDig::new(vec![std_ip!("1.2.3.0"), std_ip!("123::")]), FakeFetch {
2542 expected_url: Some("http://www.gstatic.com/generate_204"),
2543 response: None
2544 },
2545 ),
2546 (
2547 FakePing {
2548 gateway_addrs: std::iter::once(net1_gateway_ext).collect(),
2549 gateway_response: true,
2550 internet_response: true,
2551 },
2552 FakeDig { response: None }, FakeFetch {
2554 expected_url: Some("http://www.gstatic.com/generate_204"),
2555 response: None
2556 },
2557 ),
2558 ],
2559 None,
2560 ping_internet_addr,
2561 Some(DNS_PROBE_PERIOD),
2562 ),
2563 vec![
2564 State {
2565 link: LinkState::Internet,
2566 application: ApplicationState { dns_resolved: true, ..Default::default() }
2567 },
2568 State {
2569 link: LinkState::Internet,
2570 application: ApplicationState { dns_resolved: false, ..Default::default() }
2571 }
2572 ],
2573 "Fail DNS on second check; fetch fails"
2574 );
2575 }
2576
2577 #[test]
2578 fn test_network_check_varying_properties() {
2579 let properties = fnet_interfaces_ext::Properties {
2580 id: ID1.try_into().expect("should be nonzero"),
2581 name: ETHERNET_INTERFACE_NAME.to_string(),
2582 port_class: fnet_interfaces_ext::PortClass::Ethernet,
2583 has_default_ipv4_route: true,
2584 has_default_ipv6_route: true,
2585 online: true,
2586 addresses: vec![
2587 fnet_interfaces_ext::Address {
2588 addr: fidl_subnet!("1.2.3.0/24"),
2589 valid_until: fnet_interfaces_ext::NoInterest,
2590 preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
2591 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
2592 },
2593 fnet_interfaces_ext::Address {
2594 addr: fidl_subnet!("123::4/64"),
2595 valid_until: fnet_interfaces_ext::NoInterest,
2596 preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
2597 assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
2598 },
2599 ],
2600 };
2601 let local_routes = testutil::build_route_table_from_flattened_routes([Route {
2602 destination: fidl_subnet!("1.2.3.0/24"),
2603 outbound_interface: ID1,
2604 next_hop: None,
2605 }]);
2606 let route_table = testutil::build_route_table_from_flattened_routes([
2607 Route {
2608 destination: fidl_subnet!("0.0.0.0/0"),
2609 outbound_interface: ID1,
2610 next_hop: Some(fidl_ip!("1.2.3.1")),
2611 },
2612 Route {
2613 destination: fidl_subnet!("::0/0"),
2614 outbound_interface: ID1,
2615 next_hop: Some(fidl_ip!("123::1")),
2616 },
2617 ]);
2618 let route_table2 = testutil::build_route_table_from_flattened_routes([
2619 Route {
2620 destination: fidl_subnet!("0.0.0.0/0"),
2621 outbound_interface: ID1,
2622 next_hop: Some(fidl_ip!("2.2.3.1")),
2623 },
2624 Route {
2625 destination: fidl_subnet!("::0/0"),
2626 outbound_interface: ID1,
2627 next_hop: Some(fidl_ip!("223::1")),
2628 },
2629 ]);
2630
2631 const NON_ETHERNET_INTERFACE_NAME: &str = "test01";
2632
2633 let mut exec = fasync::TestExecutor::new_with_fake_time();
2634 let time = fasync::MonotonicInstant::from_nanos(1_000_000_000);
2635 let () = exec.set_fake_time(time.into());
2636
2637 let got = run_network_check(
2638 &mut exec,
2639 &fnet_interfaces_ext::Properties {
2640 id: ID1.try_into().expect("should be nonzero"),
2641 name: NON_ETHERNET_INTERFACE_NAME.to_string(),
2642 port_class: fnet_interfaces_ext::PortClass::Virtual,
2643 online: false,
2644 has_default_ipv4_route: false,
2645 has_default_ipv6_route: false,
2646 addresses: vec![],
2647 },
2648 &Default::default(),
2649 None,
2650 FakePing::default(),
2651 FakeDig::default(),
2652 FakeFetch::default(),
2653 )
2654 .expect(
2655 "error calling network check with non-ethernet interface, no addresses, interface down",
2656 );
2657 assert_eq!(
2658 got,
2659 Some(IpVersions::construct(StateEvent {
2660 state: State { link: LinkState::Down, ..Default::default() },
2661 time
2662 }))
2663 );
2664
2665 let got = run_network_check(
2666 &mut exec,
2667 &fnet_interfaces_ext::Properties { online: false, ..properties.clone() },
2668 &Default::default(),
2669 None,
2670 FakePing::default(),
2671 FakeDig::default(),
2672 FakeFetch::default(),
2673 )
2674 .expect("error calling network check, want Down state");
2675 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2676 state: State { link: LinkState::Down, ..Default::default() },
2677 time,
2678 }));
2679 assert_eq!(got, want);
2680
2681 let got = run_network_check(
2682 &mut exec,
2683 &fnet_interfaces_ext::Properties {
2684 has_default_ipv4_route: false,
2685 has_default_ipv6_route: false,
2686 ..properties.clone()
2687 },
2688 &local_routes,
2689 None,
2690 FakePing::default(),
2691 FakeDig::default(),
2692 FakeFetch::default(),
2693 )
2694 .expect("error calling network check, want Local state due to no default routes");
2695 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2696 state: State { link: LinkState::Local, ..Default::default() },
2697 time,
2698 }));
2699 assert_eq!(got, want);
2700
2701 let got = run_network_check(
2702 &mut exec,
2703 &properties,
2704 &route_table2,
2705 None,
2706 FakePing::default(),
2707 FakeDig::default(),
2708 FakeFetch::default(),
2709 )
2710 .expect("error calling network check, want Local state due to no matching default route");
2711 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2712 state: State { link: LinkState::Local, ..Default::default() },
2713 time,
2714 }));
2715 assert_eq!(got, want);
2716
2717 let got = run_network_check(
2718 &mut exec,
2719 &properties,
2720 &route_table,
2721 None,
2722 FakePing {
2723 gateway_addrs: [std_ip!("1.2.3.1"), std_ip!("123::1")].iter().cloned().collect(),
2724 gateway_response: true,
2725 internet_response: false,
2726 },
2727 FakeDig::default(),
2728 FakeFetch::default(),
2729 )
2730 .expect("error calling network check, want Gateway state");
2731 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2732 state: State { link: LinkState::Gateway, ..Default::default() },
2733 time,
2734 }));
2735 assert_eq!(got, want);
2736
2737 let got = run_network_check(
2738 &mut exec,
2739 &properties,
2740 &route_table,
2741 None,
2742 FakePing {
2743 gateway_addrs: [std_ip!("1.2.3.1"), std_ip!("123::1")].iter().cloned().collect(),
2744 gateway_response: true,
2745 internet_response: true,
2746 },
2747 FakeDig::default(),
2748 FakeFetch::default(),
2749 )
2750 .expect("error calling network check, want Internet state");
2751 let want = Some(IpVersions::<StateEvent>::construct(StateEvent {
2752 state: State { link: LinkState::Internet, ..Default::default() },
2753 time,
2754 }));
2755 assert_eq!(got, want);
2756 }
2757
2758 #[test]
2759 fn test_state_info_update() {
2760 fn update_delta(port: Delta<StateEvent>, system: Delta<SystemState>) -> StateDelta {
2761 StateDelta {
2762 port: IpVersions { ipv4: port.clone(), ipv6: port },
2763 system: IpVersions { ipv4: system.clone(), ipv6: system },
2764 }
2765 }
2766
2767 let if1_local_event = StateEvent::construct(LinkState::Local);
2768 let if1_local = IpVersions::<StateEvent>::construct(if1_local_event);
2769 let mut state = StateInfo::default();
2771 let want = update_delta(
2772 Delta { previous: None, current: if1_local_event },
2773 Delta { previous: None, current: SystemState { id: ID1, state: if1_local_event } },
2774 );
2775 assert_eq!(state.update(ID1, if1_local.clone()), want);
2776 let want_state = StateInfo {
2777 per_interface: std::iter::once((ID1, if1_local.clone())).collect::<HashMap<_, _>>(),
2778 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
2779 };
2780 assert_eq!(state, want_state);
2781
2782 let if2_gateway_event = StateEvent::construct(LinkState::Gateway);
2783 let if2_gateway = IpVersions::<StateEvent>::construct(if2_gateway_event);
2784 let want = update_delta(
2787 Delta { previous: None, current: if2_gateway_event },
2788 Delta {
2789 previous: Some(SystemState { id: ID1, state: if1_local_event }),
2790 current: SystemState { id: ID2, state: if2_gateway_event },
2791 },
2792 );
2793 assert_eq!(state.update(ID2, if2_gateway.clone()), want);
2794 let want_state = StateInfo {
2795 per_interface: [(ID1, if1_local.clone()), (ID2, if2_gateway.clone())]
2796 .iter()
2797 .cloned()
2798 .collect::<HashMap<_, _>>(),
2799 system: IpVersions { ipv4: Some(ID2), ipv6: Some(ID2) },
2800 };
2801 assert_eq!(state, want_state);
2802
2803 let if2_removed_event = StateEvent::construct(LinkState::Removed);
2804 let if2_removed = IpVersions::<StateEvent>::construct(if2_removed_event);
2805 let want = update_delta(
2808 Delta { previous: Some(if2_gateway_event), current: if2_removed_event },
2809 Delta {
2810 previous: Some(SystemState { id: ID2, state: if2_gateway_event }),
2811 current: SystemState { id: ID1, state: if1_local_event },
2812 },
2813 );
2814 assert_eq!(state.update(ID2, if2_removed.clone()), want);
2815 let want_state = StateInfo {
2816 per_interface: [(ID1, if1_local.clone()), (ID2, if2_removed.clone())]
2817 .iter()
2818 .cloned()
2819 .collect::<HashMap<_, _>>(),
2820 system: IpVersions { ipv4: Some(ID1), ipv6: Some(ID1) },
2821 };
2822 assert_eq!(state, want_state);
2823 }
2824
2825 #[test_case(None::<LinkState>, None::<LinkState>, false, false, false, false;
2826 "no interfaces available")]
2827 #[test_case(Some(LinkState::Local), Some(LinkState::Local), false, false, false, false;
2828 "no interfaces with gateway or internet state")]
2829 #[test_case(Some(LinkState::Local), Some(LinkState::Gateway), false, false, false, true;
2830 "only one interface with gateway state or above")]
2831 #[test_case(Some(LinkState::Local), Some(LinkState::Internet), false, false, true, true;
2832 "only one interface with internet state")]
2833 #[test_case(Some(LinkState::Internet), Some(LinkState::Internet), false, false, true, true;
2834 "all interfaces with internet")]
2835 #[test_case(Some(LinkState::Internet), None::<LinkState>, false, false, true, true;
2836 "only one interface available, has internet state")]
2837 #[test_case(Some(LinkState::Local), Some((LinkState::Internet, true, false)), false, true, true, true;
2838 "only one interface with DNS resolved state")]
2839 #[test_case(Some((LinkState::Internet, true, false)), Some((LinkState::Internet, true, false)), false, true, true, true;
2840 "all interfaces with DNS resolved state")]
2841 #[test_case(Some((LinkState::Internet, true, false)), None::<LinkState>, false, true, true, true;
2842 "only one interface available, has DNS resolved state")]
2843 #[test_case(Some(LinkState::Local), Some((LinkState::Internet, true, true)), true, true, true, true;
2844 "only one interface with HTTP resolved state")]
2845 #[test_case(Some((LinkState::Internet, true, true)), Some((LinkState::Internet, true, true)), true, true, true, true;
2846 "all interfaces with HTTP resolved state")]
2847 #[test_case(Some((LinkState::Internet, true, true)), None::<LinkState>, true, true, true, true;
2848 "only one interface available, has HTTP resolved state")]
2849 #[test_case(Some((LinkState::Internet, false, true)), None::<LinkState>, true, false, true, true;
2850 "only one interface available, has HTTP resolved state, but no DNS")]
2851 fn test_system_has_state<S1, S2>(
2852 ipv4_state: Option<S1>,
2853 ipv6_state: Option<S2>,
2854 expect_http: bool,
2855 expect_dns: bool,
2856 expect_internet: bool,
2857 expect_gateway: bool,
2858 ) where
2859 StateEvent: Construct<S1>,
2860 StateEvent: Construct<S2>,
2861 {
2862 let if1 = ipv4_state
2863 .map(|state| IpVersions::<StateEvent>::construct(StateEvent::construct(state)));
2864 let if2 = ipv6_state
2865 .map(|state| IpVersions::<StateEvent>::construct(StateEvent::construct(state)));
2866
2867 let mut system_interfaces: HashMap<u64, IpVersions<StateEvent>> = HashMap::new();
2868
2869 let system_interface_ipv4 = if1.map(|interface| {
2870 let _ = system_interfaces.insert(ID1, interface);
2871 ID1
2872 });
2873
2874 let system_interface_ipv6 = if2.map(|interface| {
2875 let _ = system_interfaces.insert(ID2, interface);
2876 ID2
2877 });
2878
2879 let state = StateInfo {
2880 per_interface: system_interfaces,
2881 system: IpVersions { ipv4: system_interface_ipv4, ipv6: system_interface_ipv6 },
2882 };
2883
2884 assert_eq!(state.system_has_http(), expect_http);
2885 assert_eq!(state.system_has_dns(), expect_dns);
2886 assert_eq!(state.system_has_internet(), expect_internet);
2887 assert_eq!(state.system_has_gateway(), expect_gateway);
2888 }
2889}