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