1use assert_matches::assert_matches;
6use std::collections::HashMap;
7use std::collections::hash_map::Entry;
8
9use fidl::endpoints::{ControlHandle as _, RequestStream as _};
10use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
11use fidl_fuchsia_net_reachability as freachability;
12use log::{error, warn};
13
14use async_utils::stream::{WithEpitaph as _, WithTag as _};
15
16use super::{ConnectionStream, NetworkProperties};
17
18pub(crate) const MAX_REACHABILITY_WATCHERS: usize = 128;
19
20mod id {
21 use crate::network::connection_id;
22
23 connection_id!(ReachabilityWatcherConnectionId => ReachabilityWatcherConnectionIdAllocator);
25}
26
27pub use id::ReachabilityWatcherConnectionId;
28use id::ReachabilityWatcherConnectionIdAllocator;
29
30#[derive(Debug)]
31struct ReachabilityWatcherClient {
32 control_handle: freachability::MonitorControlHandle,
34 last_observed: Option<freachability::Snapshot>,
36 responder: Option<freachability::MonitorWatchResponder>,
38 can_set_options: bool,
43}
44
45impl ReachabilityWatcherClient {
46 fn close(&mut self, epitaph: Option<zx::Status>) {
51 match epitaph {
54 Some(status) => self.control_handle.shutdown_with_epitaph(status),
55 None => self.control_handle.shutdown(),
56 }
57 self.responder = None;
59 }
60}
61
62pub(crate) type ReachabilityStream =
65 ConnectionStream<ReachabilityWatcherConnectionId, freachability::MonitorRequestStream>;
66
67#[derive(Default)]
68pub(crate) struct ReachabilityHandler {
69 watchers: HashMap<ReachabilityWatcherConnectionId, ReachabilityWatcherClient>,
70 next_id: ReachabilityWatcherConnectionIdAllocator,
71}
72
73impl ReachabilityHandler {
74 pub(crate) fn add_stream(
75 &mut self,
76 stream: freachability::MonitorRequestStream,
77 ) -> Option<ReachabilityStream> {
78 if self.watchers.len() >= MAX_REACHABILITY_WATCHERS {
79 warn!(
80 "Max reachability watchers ({MAX_REACHABILITY_WATCHERS}) reached; rejecting stream."
81 );
82 stream.control_handle().shutdown_with_epitaph(zx::Status::NO_RESOURCES);
83 return None;
84 }
85 let id = self.next_id.allocate();
86 let previous = self.watchers.insert(
87 id,
88 ReachabilityWatcherClient {
89 control_handle: stream.control_handle(),
90 last_observed: None,
91 responder: None,
92 can_set_options: true,
93 },
94 );
95 assert!(previous.is_none(), "reachability watcher {id:?} is already registered");
96 Some(stream.tagged(id).with_epitaph(id))
97 }
98
99 pub(crate) fn synthesize_snapshot(
105 default_network: Option<&NetworkProperties>,
106 ) -> freachability::Snapshot {
107 let (gateway_reachable, internet_available, dns_active, http_active) = match default_network
110 .and_then(|properties| properties.connectivity_state)
111 {
112 Some(fnp_socketproxy::ConnectivityState::FullConnectivity) => (true, true, true, true),
115 Some(fnp_socketproxy::ConnectivityState::PartialConnectivity) => {
120 (true, true, true, false)
121 }
122 Some(
126 fnp_socketproxy::ConnectivityState::LocalConnectivity
127 | fnp_socketproxy::ConnectivityState::NoConnectivity,
128 )
129 | None => (false, false, false, false),
130 Some(fnp_socketproxy::ConnectivityState::__SourceBreaking { unknown_ordinal }) => {
131 unreachable!(
132 "New variants of ConnectivityState must be updated: {unknown_ordinal:?}"
133 )
134 }
135 };
136 freachability::Snapshot {
137 gateway_reachable: Some(gateway_reachable),
138 dns_active: Some(dns_active),
139 internet_available: Some(internet_available),
140 http_active: Some(http_active),
141 ..Default::default()
142 }
143 }
144
145 pub(crate) fn handle_request(
153 &mut self,
154 current_snapshot: &freachability::Snapshot,
155 id: ReachabilityWatcherConnectionId,
156 request: Option<Result<freachability::MonitorRequest, fidl::Error>>,
157 ) {
158 let mut entry = assert_matches!(
159 self.watchers.entry(id),
160 Entry::Occupied(entry) => entry,
161 "request for unknown reachability watcher {id:?}"
162 );
163
164 let request = match request {
165 Some(Ok(request)) => request,
166 Some(Err(e)) => {
167 error!("Reachability monitor client {id:?} stream error: {e}");
171 entry.get_mut().close(None);
172 return;
173 }
174 None => {
177 let _: ReachabilityWatcherClient = entry.remove();
178 return;
179 }
180 };
181
182 let client = entry.get_mut();
183 match request {
184 freachability::MonitorRequest::SetOptions {
185 payload: freachability::MonitorOptions { __source_breaking },
186 control_handle: _,
187 } => {
188 if !client.can_set_options {
189 warn!(
190 "Client {id:?} called SetOptions after SetOptions or Watch; \
191 closing channel."
192 );
193 client.close(Some(zx::Status::CONNECTION_ABORTED));
194 } else {
195 client.can_set_options = false;
196 }
197 }
198 freachability::MonitorRequest::Watch { responder } => {
199 if client.responder.is_some() {
200 warn!(
201 "Client {id:?} called Watch while a previous Watch was pending; \
202 closing channel."
203 );
204 client.close(Some(zx::Status::ALREADY_EXISTS));
205 return;
206 }
207 client.can_set_options = false;
208 if client.last_observed.as_ref() != Some(current_snapshot) {
209 client.last_observed = Some(current_snapshot.clone());
210 if let Err(e) = responder.send(current_snapshot) {
211 warn!("failed to send reachability snapshot to client {id:?}: {e}");
212 }
213 } else {
214 client.responder = Some(responder);
215 }
216 }
217 }
218 }
219
220 pub(crate) fn maybe_notify_watchers(&mut self, current_snapshot: &freachability::Snapshot) {
221 for (id, client) in self.watchers.iter_mut() {
222 if client.last_observed.as_ref() != Some(current_snapshot) {
223 if let Some(responder) = client.responder.take() {
224 client.last_observed = Some(current_snapshot.clone());
225 if let Err(e) = responder.send(current_snapshot) {
226 warn!("failed to send updated reachability snapshot to client {id:?}: {e}");
227 }
228 }
229 }
230 }
231 }
232
233 #[cfg(test)]
234 pub(crate) fn watcher_count(&self) -> usize {
235 self.watchers.len()
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::network::split_connection_item;
243 use futures::StreamExt as _;
244
245 fn disconnected_snapshot() -> freachability::Snapshot {
248 freachability::Snapshot {
249 gateway_reachable: Some(false),
250 dns_active: Some(false),
251 internet_available: Some(false),
252 http_active: Some(false),
253 ..Default::default()
254 }
255 }
256
257 fn limited_snapshot() -> freachability::Snapshot {
259 freachability::Snapshot {
260 gateway_reachable: Some(true),
261 dns_active: Some(true),
262 internet_available: Some(true),
263 http_active: Some(false),
264 ..Default::default()
265 }
266 }
267
268 fn validated_snapshot() -> freachability::Snapshot {
270 freachability::Snapshot {
271 gateway_reachable: Some(true),
272 dns_active: Some(true),
273 internet_available: Some(true),
274 http_active: Some(true),
275 ..Default::default()
276 }
277 }
278
279 async fn drain_stream(
284 handler: &mut ReachabilityHandler,
285 current_snapshot: &freachability::Snapshot,
286 stream: &mut ReachabilityStream,
287 ) {
288 while let Some(item) = stream.next().await {
289 let (id, request) = split_connection_item(item);
290 handler.handle_request(current_snapshot, id, request);
291 }
292 }
293
294 #[test]
295 fn test_synthesize_snapshot() {
296 assert_eq!(ReachabilityHandler::synthesize_snapshot(None), disconnected_snapshot());
298
299 let net_none = NetworkProperties {
301 connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
302 ..Default::default()
303 };
304 assert_eq!(
305 ReachabilityHandler::synthesize_snapshot(Some(&net_none)),
306 disconnected_snapshot()
307 );
308
309 let net_local = NetworkProperties {
311 connectivity_state: Some(fnp_socketproxy::ConnectivityState::LocalConnectivity),
312 ..Default::default()
313 };
314 assert_eq!(
315 ReachabilityHandler::synthesize_snapshot(Some(&net_local)),
316 disconnected_snapshot()
317 );
318
319 let net_limited = NetworkProperties {
322 connectivity_state: Some(fnp_socketproxy::ConnectivityState::PartialConnectivity),
323 ..Default::default()
324 };
325 assert_eq!(
326 ReachabilityHandler::synthesize_snapshot(Some(&net_limited)),
327 limited_snapshot()
328 );
329
330 let net_full = NetworkProperties {
332 connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
333 ..Default::default()
334 };
335 assert_eq!(ReachabilityHandler::synthesize_snapshot(Some(&net_full)), validated_snapshot());
336 }
337
338 #[fuchsia::test]
339 async fn test_reachability_handler_hanging_get() {
340 let mut handler = ReachabilityHandler::default();
341 let disconnected = disconnected_snapshot();
342 let validated = validated_snapshot();
343
344 let (proxy1, stream1) =
345 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
346 let mut s1 = handler.add_stream(stream1).expect("add stream");
347 assert_eq!(handler.watcher_count(), 1);
348
349 let watch_fut1 = proxy1.watch();
351 let (id, req) = split_connection_item(s1.next().await.expect("stream item"));
352 handler.handle_request(&disconnected, id, req);
353 let snapshot = watch_fut1.await.expect("watch error");
354 assert_eq!(snapshot, disconnected);
355
356 let (proxy2, stream2) =
358 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
359 let mut s2 = handler.add_stream(stream2).expect("add stream");
360 assert_eq!(handler.watcher_count(), 2);
361
362 let watch_fut2 = proxy2.watch();
364 let (id, req) = split_connection_item(s2.next().await.expect("stream item"));
365 handler.handle_request(&disconnected, id, req);
366 let snapshot2 = watch_fut2.await.expect("watch error");
367 assert_eq!(snapshot2, disconnected);
368
369 let mut second_watch1 = proxy1.watch();
371 let (id, req) = split_connection_item(s1.next().await.expect("stream item"));
372 handler.handle_request(&disconnected, id, req);
373 assert_matches!(futures::poll!(&mut second_watch1), std::task::Poll::Pending);
374
375 let mut second_watch2 = proxy2.watch();
376 let (id, req) = split_connection_item(s2.next().await.expect("stream item"));
377 handler.handle_request(&disconnected, id, req);
378 assert_matches!(futures::poll!(&mut second_watch2), std::task::Poll::Pending);
379
380 handler.maybe_notify_watchers(&validated);
382 let snap1 = second_watch1.await.expect("watch1 should succeed");
383 let snap2 = second_watch2.await.expect("watch2 should succeed");
384 assert_eq!(snap1, validated);
385 assert_eq!(snap2, validated);
386
387 drop(proxy2);
389 drain_stream(&mut handler, &validated, &mut s2).await;
390 assert_matches!(futures::poll!(s2.next()), std::task::Poll::Ready(None));
391 assert_eq!(handler.watcher_count(), 1);
392 }
393
394 #[fuchsia::test]
395 async fn test_set_options_validation() {
396 let mut handler = ReachabilityHandler::default();
397 let disconnected = disconnected_snapshot();
398 let (proxy, stream) =
399 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
400 let mut s = handler.add_stream(stream).expect("add stream");
401
402 proxy.set_options(&freachability::MonitorOptions::default()).expect("set_options FIDL");
404 let (id, req) = split_connection_item(s.next().await.expect("stream item"));
405 handler.handle_request(&disconnected, id, req);
406 assert_eq!(handler.watcher_count(), 1);
407
408 proxy.set_options(&freachability::MonitorOptions::default()).expect("set_options FIDL");
410 let (id, req) = split_connection_item(s.next().await.expect("stream item"));
411 handler.handle_request(&disconnected, id, req);
412
413 assert_matches!(
414 proxy.watch().await,
415 Err(fidl::Error::ClientChannelClosed { epitaph, .. })
416 if epitaph == zx::Status::CONNECTION_ABORTED
417 );
418
419 drain_stream(&mut handler, &disconnected, &mut s).await;
421 assert_matches!(futures::poll!(s.next()), std::task::Poll::Ready(None));
422 assert_eq!(handler.watcher_count(), 0);
423
424 let (proxy2, stream2) =
426 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
427 let mut s2 = handler.add_stream(stream2).expect("add stream");
428
429 let watch_fut = proxy2.watch();
430 let (id, req) = split_connection_item(s2.next().await.expect("stream item"));
431 handler.handle_request(&disconnected, id, req);
432 let _ = watch_fut.await.expect("initial watch");
433 assert_eq!(handler.watcher_count(), 1);
434
435 proxy2.set_options(&freachability::MonitorOptions::default()).expect("set_options FIDL");
436 let (id, req) = split_connection_item(s2.next().await.expect("stream item"));
437 handler.handle_request(&disconnected, id, req);
438
439 assert_matches!(
440 proxy2.watch().await,
441 Err(fidl::Error::ClientChannelClosed { epitaph, .. })
442 if epitaph == zx::Status::CONNECTION_ABORTED
443 );
444
445 drain_stream(&mut handler, &disconnected, &mut s2).await;
446 assert_matches!(futures::poll!(s2.next()), std::task::Poll::Ready(None));
447 assert_eq!(handler.watcher_count(), 0);
448 }
449
450 #[fuchsia::test]
451 async fn test_concurrent_watch_closes_channel() {
452 let mut handler = ReachabilityHandler::default();
453 let disconnected = disconnected_snapshot();
454 let (proxy, stream) =
455 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
456 let mut s = handler.add_stream(stream).expect("add stream");
457
458 let watch_fut1 = proxy.watch();
460 let (id, req) = split_connection_item(s.next().await.expect("stream item"));
461 handler.handle_request(&disconnected, id, req);
462 let _ = watch_fut1.await.expect("initial watch");
463
464 let mut second_watch1 = proxy.watch();
466 let (id, req) = split_connection_item(s.next().await.expect("stream item"));
467 handler.handle_request(&disconnected, id, req);
468 assert_matches!(futures::poll!(&mut second_watch1), std::task::Poll::Pending);
469
470 let second_watch2 = proxy.watch();
472 let (id, req) = split_connection_item(s.next().await.expect("stream item"));
473 handler.handle_request(&disconnected, id, req);
474
475 assert_matches!(
476 second_watch2.await,
477 Err(fidl::Error::ClientChannelClosed { epitaph, .. })
478 if epitaph == zx::Status::ALREADY_EXISTS
479 );
480
481 assert_matches!(second_watch1.await, Err(fidl::Error::ClientChannelClosed { .. }));
484 drain_stream(&mut handler, &disconnected, &mut s).await;
485 assert_matches!(futures::poll!(s.next()), std::task::Poll::Ready(None));
486 assert_eq!(handler.watcher_count(), 0);
487 }
488
489 #[fuchsia::test]
490 async fn test_max_watchers_limit() {
491 let mut handler = ReachabilityHandler::default();
492 let mut proxies = Vec::new();
493 for _ in 0..MAX_REACHABILITY_WATCHERS {
494 let (proxy, stream) =
495 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
496 assert!(handler.add_stream(stream).is_some());
497 proxies.push(proxy);
498 }
499 assert_eq!(handler.watcher_count(), MAX_REACHABILITY_WATCHERS);
500
501 let (overflow_proxy, overflow_stream) =
503 fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
504 assert!(handler.add_stream(overflow_stream).is_none());
505 assert_matches!(
506 overflow_proxy.watch().await,
507 Err(fidl::Error::ClientChannelClosed { epitaph, .. })
508 if epitaph == zx::Status::NO_RESOURCES
509 );
510 }
511}