1use anyhow::{Context, Error};
8use fidl::endpoints::{ControlHandle, RequestStream};
9use fidl_fuchsia_netpol_socketproxy::{
10 self as fnp_socketproxy, FuchsiaNetworksRequest, Network, NetworkDnsServers, NetworkInfo,
11 StarnixNetworksRequest,
12};
13use fuchsia_inspect_derive::{IValue, Inspect, Unit};
14use futures::channel::mpsc;
15use futures::lock::Mutex;
16use futures::{SinkExt as _, StreamExt as _, TryStreamExt as _};
17use log::{error, info, warn};
18use std::collections::HashMap;
19use std::sync::Arc;
20use {fidl_fuchsia_net as fnet, fidl_fuchsia_posix_socket as fposix_socket};
21
22const DEFAULT_DNS_PORT: u16 = 53;
24
25pub(crate) const DEFAULT_SOCKET_MARK: u32 = 0;
28
29enum CommonErrors {
30 MissingNetworkId,
31 MissingNetworkInfo,
32 MissingNetworkDnsServers,
33}
34
35trait IpAddressExt {
36 fn to_dns_socket_address(self) -> fnet::SocketAddress;
37}
38
39impl<T: IpAddressExt + Copy> IpAddressExt for &T {
40 fn to_dns_socket_address(self) -> fnet::SocketAddress {
41 (*self).to_dns_socket_address()
42 }
43}
44
45impl IpAddressExt for fnet::Ipv4Address {
46 fn to_dns_socket_address(self) -> fnet::SocketAddress {
47 fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress { address: self, port: DEFAULT_DNS_PORT })
48 }
49}
50
51impl IpAddressExt for fnet::Ipv6Address {
52 fn to_dns_socket_address(self) -> fnet::SocketAddress {
53 fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
54 address: self,
55 port: DEFAULT_DNS_PORT,
56 zone_index: 0,
57 })
58 }
59}
60
61trait NetworkInfoExt {
62 fn mark(&self) -> Option<u32>;
63}
64
65impl NetworkInfoExt for NetworkInfo {
66 fn mark(&self) -> Option<u32> {
67 match self {
68 NetworkInfo::Starnix(s) => s.mark,
69 NetworkInfo::Fuchsia(_) | _ => None,
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub(crate) struct ValidatedNetwork {
79 network_id: u32,
80 info: NetworkInfo,
81 dns_servers: NetworkDnsServers,
82}
83
84impl ValidatedNetwork {
85 fn dns_servers(&self) -> Vec<fnet::SocketAddress> {
86 self.dns_servers
87 .v4
88 .iter()
89 .flat_map(|a| a.iter().map(IpAddressExt::to_dns_socket_address))
90 .chain(
91 self.dns_servers
92 .v6
93 .iter()
94 .flat_map(|a| a.iter().map(IpAddressExt::to_dns_socket_address)),
95 )
96 .collect()
97 }
98}
99
100trait ValidateNetworkExt {
101 fn validate(self) -> Result<ValidatedNetwork, CommonErrors>;
102}
103
104impl ValidateNetworkExt for Network {
105 fn validate(self) -> Result<ValidatedNetwork, CommonErrors> {
106 match self {
107 Network { network_id: None, .. } => Err(CommonErrors::MissingNetworkId),
108 Network { info: None, .. } => Err(CommonErrors::MissingNetworkInfo),
109 Network { dns_servers: None, .. } => Err(CommonErrors::MissingNetworkDnsServers),
110 Network {
111 network_id: Some(network_id),
112 info: Some(info),
113 dns_servers: Some(dns_servers),
114 ..
115 } => Ok(ValidatedNetwork { network_id, info, dns_servers }),
116 }
117 }
118}
119
120macro_rules! common_errors_impl {
121 ($($p:ty),+) => {
122 $(
123 impl From<CommonErrors> for $p {
124 fn from(value: CommonErrors) -> Self {
125 use CommonErrors::*;
126 match value {
127 MissingNetworkId => <$p>::MissingNetworkId,
128 MissingNetworkInfo => <$p>::MissingNetworkInfo,
129 MissingNetworkDnsServers => <$p>::MissingNetworkDnsServers,
130 }
131 }
132 }
133 )+
134 }
135}
136
137common_errors_impl!(
138 fnp_socketproxy::NetworkRegistryAddError,
139 fnp_socketproxy::NetworkRegistryUpdateError
140);
141
142#[derive(Inspect, Debug, Default)]
144struct NetworkRegistry {
145 networks: IValue<RegisteredNetworks>,
146
147 inspect_node: fuchsia_inspect::Node,
148}
149
150impl NetworkRegistry {
151 pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
153 self.networks.dns_servers()
154 }
155
156 pub(crate) fn has_default_network(&self) -> bool {
158 self.networks.default_network_id.is_some()
159 }
160}
161
162#[derive(Unit, Debug, Default)]
163struct MethodInspect {
164 successes: u32,
165 errors: u32,
166}
167
168#[derive(Unit, Default, Debug)]
169struct RegisteredNetworks {
170 default_network_id: Option<u32>,
171
172 #[inspect(skip)]
173 networks: HashMap<u32, ValidatedNetwork>,
175
176 adds: MethodInspect,
177 removes: MethodInspect,
178 set_defaults: MethodInspect,
179 updates: MethodInspect,
180}
181
182impl RegisteredNetworks {
183 fn add_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryAddResult {
184 let network = network.validate()?;
185 #[allow(clippy::map_entry, reason = "mass allow for https://fxbug.dev/381896734")]
186 if self.networks.contains_key(&network.network_id) {
187 self.adds.errors += 1;
188 Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId)
189 } else {
190 let _: Option<_> = self.networks.insert(network.network_id, network);
191 self.adds.successes += 1;
192 Ok(())
193 }
194 }
195
196 pub(crate) fn clear(&mut self) {
198 self.networks.clear();
199 }
200
201 fn update_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryUpdateResult {
202 let network = network.validate()?;
203 let network_id = network.network_id;
204 *self
205 .networks
206 .get_mut(&network_id)
207 .ok_or(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)
208 .inspect(|_| self.updates.successes += 1)
209 .inspect_err(|_| self.updates.errors += 1)? = network;
210 Ok(())
211 }
212
213 fn remove_network(&mut self, network_id: u32) -> fnp_socketproxy::NetworkRegistryRemoveResult {
214 if self.default_network_id == Some(network_id) {
215 self.removes.errors += 1;
216 return Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork);
217 }
218 match self.networks.remove(&network_id) {
219 Some(_) => {
220 self.removes.successes += 1;
221 Ok(())
222 }
223 None => {
224 self.removes.errors += 1;
225 Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)
226 }
227 }
228 }
229
230 fn set_default_network(
234 &mut self,
235 network_id: Option<u32>,
236 ) -> fnp_socketproxy::NetworkRegistrySetDefaultResult {
237 if let Some(network_id) = network_id {
238 if !self.networks.contains_key(&network_id) {
239 self.set_defaults.errors += 1;
240 return Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound);
241 }
242 }
243 self.set_defaults.successes += 1;
244 self.default_network_id = network_id;
245
246 Ok(())
247 }
248
249 pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
251 self.networks
252 .iter()
253 .map(|(id, network)| fnp_socketproxy::DnsServerList {
254 source_network_id: Some(*id),
255 addresses: Some(network.dns_servers()),
256 ..Default::default()
257 })
258 .collect()
259 }
260
261 fn current_mark(&self) -> fposix_socket::OptionalUint32 {
262 use fposix_socket::OptionalUint32::*;
263 match (self.default_network_id, self.networks.is_empty()) {
264 (None, false) => Value(DEFAULT_SOCKET_MARK),
265 (id, _) => match id.and_then(|id| self.networks[&id].info.mark()) {
266 Some(value) => Value(value),
267 None => Unset(fposix_socket::Empty),
268 },
269 }
270 }
271
272 fn len(&self) -> usize {
273 self.networks.len()
274 }
275}
276
277#[derive(Inspect, Clone, Debug, Default)]
278pub struct NetworkRegistries {
279 starnix: Arc<Mutex<NetworkRegistry>>,
280 fuchsia: Arc<Mutex<NetworkRegistry>>,
281}
282
283impl NetworkRegistries {
284 async fn current_mark(&self) -> fposix_socket::OptionalUint32 {
288 if self.fuchsia.lock().await.has_default_network() {
289 info!("FuchsiaNetworks has a default network, preferring Fuchsia mark.");
290 return self.fuchsia.lock().await.networks.current_mark();
291 }
292
293 return self.starnix.lock().await.networks.current_mark();
294 }
295
296 async fn current_dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
300 if self.fuchsia.lock().await.has_default_network() {
301 info!("FuchsiaNetworks has a default network, preferring Fuchsia DNS.");
302 return self.fuchsia.lock().await.dns_servers();
303 }
304
305 return self.starnix.lock().await.dns_servers();
306 }
307}
308
309#[derive(Inspect, Clone, Debug)]
310pub struct Registry {
311 #[inspect(forward)]
312 networks: NetworkRegistries,
313 marks: Arc<Mutex<crate::SocketMarks>>,
316 dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
317
318 starnix_occupant: Arc<Mutex<()>>,
319 fuchsia_occupant: Arc<Mutex<()>>,
320}
321
322macro_rules! handle_registry_request {
323 ($request_type:ident, $request:expr, $network_registry:expr, $name:expr) => {{
324 let mut networks = $network_registry.networks.as_mut();
325 let (op, send, did_state_change): (
326 _,
327 Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>,
328 bool,
329 ) = match $request {
330 $request_type::SetDefault { network_id, responder } => {
331 let result = networks.set_default_network(match network_id {
332 fposix_socket::OptionalUint32::Value(value) => Some(value),
333 fposix_socket::OptionalUint32::Unset(_) => None,
334 });
335 ("set default", Box::new(move || responder.send(result)), true)
336 }
337 $request_type::Add { network, responder } => {
338 let result = networks.add_network(network);
339 ("add", Box::new(move || responder.send(result)), true)
340 }
341 $request_type::Update { network, responder } => {
342 let result = networks.update_network(network);
343 ("update", Box::new(move || responder.send(result)), true)
344 }
345 $request_type::Remove { network_id, responder } => {
346 let result = networks.remove_network(network_id);
347 ("remove", Box::new(move || responder.send(result)), true)
348 }
349 $request_type::CheckPresence { responder } => {
350 ("check_presence", Box::new(move || responder.send()), false)
351 }
352 };
353 if did_state_change {
354 let new_mark = networks.current_mark();
355 info!(
356 "{} registry {op}. mark: {new_mark:?}, networks count: {}",
357 $name,
358 networks.len()
359 );
360 }
361 std::mem::drop(networks);
362 (send, did_state_change)
363 }};
364}
365
366impl Registry {
367 pub(crate) fn new(
368 marks: Arc<Mutex<crate::SocketMarks>>,
369 dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
370 ) -> Self {
371 Self {
372 networks: Default::default(),
373 marks,
374 dns_tx,
375 starnix_occupant: Default::default(),
376 fuchsia_occupant: Default::default(),
377 }
378 }
379
380 pub(crate) async fn run_starnix(
381 &self,
382 stream: fnp_socketproxy::StarnixNetworksRequestStream,
383 ) -> Result<(), Error> {
384 let _occupant = match self.starnix_occupant.try_lock() {
385 Some(o) => o,
386 None => {
387 warn!("Only one connection to StarnixNetworks is allowed at a time");
388 stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
389 return Ok(());
390 }
391 };
392
393 info!("Starting fuchsia.netpol.socketproxy.StarnixNetworks server");
394 self.networks.starnix.lock().await.networks.as_mut().clear();
395 stream
396 .map(|result| result.context("failed request"))
397 .try_for_each(|request| async {
398 let mut network_registry = self.networks.starnix.lock().await;
399 let (send, did_state_change): (
400 Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>,
401 bool,
402 ) = handle_registry_request!(
403 StarnixNetworksRequest,
404 request,
405 network_registry,
406 "starnix"
407 );
408 std::mem::drop(network_registry);
409
410 if did_state_change {
411 self.dns_tx
414 .clone()
415 .feed(self.networks.current_dns_servers().await)
416 .await
417 .unwrap_or_else(|e| {
418 if !e.is_disconnected() {
419 error!("Unable to feed DNS update: {e:?}")
421 }
422 });
423
424 self.marks.lock().await.mark_1 = self.networks.current_mark().await;
427 send().context("error sending response")?;
428 } else {
429 let _: Result<(), fidl::Error> = send();
432 }
433 Ok(())
434 })
435 .await
436 }
437
438 pub(crate) async fn run_fuchsia(
439 &self,
440 stream: fnp_socketproxy::FuchsiaNetworksRequestStream,
441 ) -> Result<(), Error> {
442 let _occupant = match self.fuchsia_occupant.try_lock() {
443 Some(o) => o,
444 None => {
445 warn!("Only one connection to FuchsiaNetworks is allowed at a time");
446 stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
447 return Ok(());
448 }
449 };
450
451 info!("Starting fuchsia.netpol.socketproxy.FuchsiaNetworks server");
452 self.networks.fuchsia.lock().await.networks.as_mut().clear();
453 stream
454 .map(|result| result.context("failed request"))
455 .try_for_each(|request| async {
456 let mut network_registry = self.networks.fuchsia.lock().await;
457 let (send, did_state_change): (
458 Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>,
459 bool,
460 ) = handle_registry_request!(
461 FuchsiaNetworksRequest,
462 request,
463 network_registry,
464 "fuchsia"
465 );
466 std::mem::drop(network_registry);
467
468 if did_state_change {
469 self.dns_tx
472 .clone()
473 .feed(self.networks.current_dns_servers().await)
474 .await
475 .unwrap_or_else(|e| {
476 if !e.is_disconnected() {
477 error!("Unable to feed DNS update: {e:?}")
479 }
480 });
481
482 self.marks.lock().await.mark_1 = self.networks.current_mark().await;
485 send().context("error sending response")?;
486 } else {
487 let _: Result<(), fidl::Error> = send();
490 }
491 Ok(())
492 })
493 .await
494 }
495}
496
497#[cfg(test)]
498mod test {
499 use super::*;
500 use fuchsia_component::server::ServiceFs;
501 use fuchsia_component_test::{
502 Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
503 };
504 use futures::channel::mpsc::Receiver;
505 use net_declare::{fidl_ip, fidl_socket_addr};
506 use pretty_assertions::assert_eq;
507 use socket_proxy_testing::{RegistryType, ToDnsServerList as _, ToNetwork};
508 use test_case::test_case;
509
510 #[derive(Clone, Debug)]
511 enum Op<N: ToNetwork> {
512 SetDefault {
513 network_id: Option<u32>,
514 result: Result<(), fnp_socketproxy::NetworkRegistrySetDefaultError>,
515 },
516 Add {
517 network: N,
518 result: Result<(), fnp_socketproxy::NetworkRegistryAddError>,
519 },
520 Update {
521 network: N,
522 result: Result<(), fnp_socketproxy::NetworkRegistryUpdateError>,
523 },
524 Remove {
525 network_id: u32,
526 result: Result<(), fnp_socketproxy::NetworkRegistryRemoveError>,
527 },
528 }
529
530 macro_rules! execute {
531 ($self:ident, $proxy:ident, $registry:expr) => {{
532 match $self {
533 Op::SetDefault { network_id, result } => {
534 assert_eq!(
535 $proxy
536 .set_default(&match network_id {
537 Some(value) => fposix_socket::OptionalUint32::Value(*value),
538 None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
539 })
540 .await?,
541 *result
542 )
543 }
544 Op::Add { network, result } => {
545 assert_eq!($proxy.add(&network.to_network($registry)).await?, *result)
546 }
547 Op::Update { network, result } => {
548 assert_eq!($proxy.update(&network.to_network($registry)).await?, *result)
549 }
550 Op::Remove { network_id, result } => {
551 assert_eq!($proxy.remove(*network_id).await?, *result)
552 }
553 }
554 Ok(())
555 }};
556 }
557
558 impl<N: ToNetwork + Clone> Op<N> {
559 async fn execute_starnix(
560 &self,
561 starnix: &fnp_socketproxy::StarnixNetworksProxy,
562 ) -> Result<(), Error> {
563 execute!(self, starnix, RegistryType::Starnix)
564 }
565
566 async fn execute_fuchsia(
567 &self,
568 fuchsia: &fnp_socketproxy::FuchsiaNetworksProxy,
569 ) -> Result<(), Error> {
570 execute!(self, fuchsia, RegistryType::Fuchsia)
571 }
572
573 fn is_err(&self) -> bool {
574 match &self {
575 Op::SetDefault { network_id: _, result } => result.is_err(),
576 Op::Add { network: _, result } => result.is_err(),
577 Op::Update { network: _, result } => result.is_err(),
578 Op::Remove { network_id: _, result } => result.is_err(),
579 }
580 }
581 }
582
583 enum IncomingService {
584 StarnixNetworks(fnp_socketproxy::StarnixNetworksRequestStream),
585 FuchsiaNetworks(fnp_socketproxy::FuchsiaNetworksRequestStream),
586 }
587
588 async fn run_registry(
589 handles: LocalComponentHandles,
590 starnix_networks: Arc<Mutex<NetworkRegistry>>,
591 fuchsia_networks: Arc<Mutex<NetworkRegistry>>,
592 marks: Arc<Mutex<crate::SocketMarks>>,
593 dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
594 ) -> Result<(), Error> {
595 let mut fs = ServiceFs::new();
596 let _ = fs
597 .dir("svc")
598 .add_fidl_service(IncomingService::StarnixNetworks)
599 .add_fidl_service(IncomingService::FuchsiaNetworks);
600 let _ = fs.serve_connection(handles.outgoing_dir)?;
601
602 let registry = Registry {
603 networks: NetworkRegistries { starnix: starnix_networks, fuchsia: fuchsia_networks },
604 marks,
605 dns_tx,
606 starnix_occupant: Default::default(),
607 fuchsia_occupant: Default::default(),
608 };
609
610 fs.for_each_concurrent(0, |service| async {
611 match service {
612 IncomingService::StarnixNetworks(stream) => registry.run_starnix(stream).await,
613 IncomingService::FuchsiaNetworks(stream) => registry.run_fuchsia(stream).await,
614 }
615 .unwrap_or_else(|e| error!("{e:?}"))
616 })
617 .await;
618
619 Ok(())
620 }
621
622 async fn setup_test(
623 ) -> Result<(RealmInstance, Receiver<Vec<fnp_socketproxy::DnsServerList>>), Error> {
624 let builder = RealmBuilder::new().await?;
625 let starnix_networks = Arc::new(Mutex::new(Default::default()));
626 let fuchsia_networks = Arc::new(Mutex::new(Default::default()));
627 let (dns_tx, dns_rx) = mpsc::channel(1);
628 let marks = Arc::new(Mutex::new(crate::SocketMarks::default()));
629 let registry = builder
630 .add_local_child(
631 "registry",
632 {
633 let starnix_networks = starnix_networks.clone();
634 let fuchsia_networks = fuchsia_networks.clone();
635 let marks = marks.clone();
636 let dns_tx = dns_tx.clone();
637 move |handles: LocalComponentHandles| {
638 Box::pin(run_registry(
639 handles,
640 starnix_networks.clone(),
641 fuchsia_networks.clone(),
642 marks.clone(),
643 dns_tx.clone(),
644 ))
645 }
646 },
647 ChildOptions::new(),
648 )
649 .await?;
650
651 builder
652 .add_route(
653 Route::new()
654 .capability(Capability::protocol::<fnp_socketproxy::StarnixNetworksMarker>())
655 .from(®istry)
656 .to(Ref::parent()),
657 )
658 .await?;
659
660 builder
661 .add_route(
662 Route::new()
663 .capability(Capability::protocol::<fnp_socketproxy::FuchsiaNetworksMarker>())
664 .from(®istry)
665 .to(Ref::parent()),
666 )
667 .await?;
668
669 let realm = builder.build().await?;
670
671 Ok((realm, dns_rx))
672 }
673
674 #[test_case(&[
675 Op::Add { network: 1, result: Ok(()) },
676 Op::Update { network: 1, result: Ok(()) },
677 Op::Remove { network_id: 1, result: Ok(()) },
678 ]; "normal operation")]
679 #[test_case(&[
680 Op::Add { network: 1, result: Ok(()) },
681 Op::Add { network: 1, result: Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId) },
682 ]; "duplicate add")]
683 #[test_case(&[
684 Op::Update { network: 1, result: Err(fnp_socketproxy::NetworkRegistryUpdateError::NotFound) },
685 ]; "update missing")]
686 #[test_case(&[
687 Op::<u32>::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) },
688 ]; "remove missing")]
689 #[test_case(&[
690 Op::<u32>::SetDefault { network_id: Some(1), result: Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound) },
691 ]; "set default missing")]
692 #[test_case(&[
693 Op::Add { network: 1, result: Ok(()) },
694 Op::SetDefault { network_id: Some(1), result: Ok(()) },
695 Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
696 ]; "remove default network")]
697 #[test_case(&[
698 Op::Add { network: 1, result: Ok(()) },
699 Op::SetDefault { network_id: Some(1), result: Ok(()) },
700 Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
701 Op::Add { network: 2, result: Ok(()) },
702 Op::SetDefault { network_id: Some(2), result: Ok(()) },
703 Op::Remove { network_id: 1, result: Ok(()) },
704 ]; "remove formerly default network")]
705 #[test_case(&[
706 Op::Add { network: 1, result: Ok(()) },
707 Op::SetDefault { network_id: Some(1), result: Ok(()) },
708 Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
709 Op::SetDefault { network_id: None, result: Ok(()) },
710 Op::Remove { network_id: 1, result: Ok(()) },
711 ]; "remove last network")]
712 #[test_case(&[
713 Op::Add { network: 1, result: Ok(()) },
714 Op::Update { network: 1, result: Ok(()) },
715 Op::Add { network: 2, result: Ok(()) },
716 Op::Add { network: 3, result: Ok(()) },
717 Op::Add { network: 4, result: Ok(()) },
718 Op::Update { network: 4, result: Ok(()) },
719 Op::Update { network: 2, result: Ok(()) },
720 Op::Update { network: 3, result: Ok(()) },
721 Op::Add { network: 5, result: Ok(()) },
722 Op::Update { network: 5, result: Ok(()) },
723 Op::Add { network: 6, result: Ok(()) },
724 Op::Add { network: 7, result: Ok(()) },
725 Op::Add { network: 8, result: Ok(()) },
726 Op::Update { network: 8, result: Ok(()) },
727 Op::Update { network: 6, result: Ok(()) },
728 Op::Add { network: 9, result: Ok(()) },
729 Op::Update { network: 9, result: Ok(()) },
730 Op::Update { network: 7, result: Ok(()) },
731 Op::Add { network: 10, result: Ok(()) },
732 Op::Update { network: 10, result: Ok(()) },
733 ]; "many updates")]
734 #[fuchsia::test]
735 async fn test_operations<N: ToNetwork + Clone>(operations: &[Op<N>]) -> Result<(), Error> {
736 let (realm, _) = setup_test().await?;
737 let starnix_networks = realm
738 .root
739 .connect_to_protocol_at_exposed_dir::<fnp_socketproxy::StarnixNetworksMarker>()
740 .context("While connecting to StarnixNetworks")?;
741 let fuchsia_networks = realm
742 .root
743 .connect_to_protocol_at_exposed_dir::<fnp_socketproxy::FuchsiaNetworksMarker>()
744 .context("While connecting to FuchsiaNetworks")?;
745
746 for op in operations {
747 op.execute_starnix(&starnix_networks).await?;
750 op.execute_fuchsia(&fuchsia_networks).await?;
751 }
752
753 Ok(())
754 }
755
756 #[test_case(&[
757 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
758 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
759 ; "normal operation (v4)")]
760 #[test_case(&[
761 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
762 Op::Update { network: (1, vec![fidl_ip!("192.0.2.1")]), result: Ok(()) },
763 ], vec![(1, vec![fidl_socket_addr!("192.0.2.1:53")]).to_dns_server_list()]
764 ; "update server list (v4)")]
765 #[test_case(&[
766 Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) },
767 ], vec![(1, vec![fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
768 ; "normal operation (v6)")]
769 #[test_case(&[
770 Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) },
771 Op::Update { network: (1, vec![fidl_ip!("2001:db8::2")]), result: Ok(()) },
772 ], vec![(1, vec![fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list()]
773 ; "update server list (v6)")]
774 #[test_case(&[
775 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
776 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
777 ; "normal operation (mixed)")]
778 #[test_case(&[
779 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
780 Op::Update { network: (1, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) },
781 ], vec![(1, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list()]
782 ; "update server list (mixed)")]
783 #[test_case(&[
784 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
785 Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) },
786 Op::Add { network: (3, vec![fidl_ip!("192.0.2.2"), fidl_ip!("2001:db8::3")]), result: Ok(()) },
787 ], vec![
788 (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
789 (2, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list(),
790 (3, vec![fidl_socket_addr!("192.0.2.2:53"), fidl_socket_addr!("[2001:db8::3]:53")]).to_dns_server_list(),
791 ]; "multiple networks")]
792 #[fuchsia::test]
793 async fn test_dns_tracking<N: ToNetwork + Clone>(
794 operations: &[Op<N>],
795 dns_servers: Vec<fnp_socketproxy::DnsServerList>,
796 ) -> Result<(), Error> {
797 let (realm, mut dns_rx) = setup_test().await?;
798 let starnix_networks = realm
799 .root
800 .connect_to_protocol_at_exposed_dir::<fnp_socketproxy::StarnixNetworksMarker>()
801 .context("While connecting to StarnixNetworks")?;
802
803 let mut last_dns = None;
804 for op in operations {
805 op.execute_starnix(&starnix_networks).await?;
808 last_dns = Some(dns_rx.next().await.expect("dns update expected after each operation"));
809 }
810
811 let mut last_dns = last_dns.expect("there should be at least one dns update");
812 last_dns.sort_by_key(|a| a.source_network_id);
813 assert_eq!(last_dns, dns_servers);
814
815 Ok(())
816 }
817
818 #[test_case(&[
819 (RegistryType::Fuchsia, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) }),
820 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(1), result: Ok(()) }),
821 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
822 ; "normal operation Fuchsia (v4)")]
823 #[test_case(&[
824 (RegistryType::Fuchsia, Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) }),
825 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(1), result: Ok(()) }),
826 ], vec![(1, vec![fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
827 ; "normal operation Fuchsia (v6)")]
828 #[test_case(&[
829 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) }),
830 (RegistryType::Fuchsia, Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) }),
831 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
832 ; "attempt remove in wrong registry")]
833 #[test_case(&[
834 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
835 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
836 ], vec![
837 (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
838 ]; "Fuchsia default absent, use Starnix")]
839 #[test_case(&[
840 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
841 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
842 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
843 ], vec![
844 (2, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list(),
845 ]; "Fuchsia default present, use Fuchsia")]
846 #[test_case(&[
847 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
848 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
849 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
850 (RegistryType::Fuchsia, Op::SetDefault { network_id: None, result: Ok(()) }),
851 ], vec![
852 (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
853 ]; "Fallback to Starnix network")]
854 #[test_case(&[
855 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
856 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
857 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
858 (RegistryType::Fuchsia, Op::Update { network: (2, vec![fidl_ip!("192.0.2.2"), fidl_ip!("2001:db8::3")]), result: Ok(()) }),
859 ], vec![
860 (2, vec![fidl_socket_addr!("192.0.2.2:53"), fidl_socket_addr!("[2001:db8::3]:53")]).to_dns_server_list(),
861 ]; "Fuchsia default present then updated")]
862 #[fuchsia::test]
863 async fn test_dns_tracking_across_registries<N: ToNetwork + Clone>(
864 operations: &[(RegistryType, Op<N>)],
865 dns_servers: Vec<fnp_socketproxy::DnsServerList>,
866 ) -> Result<(), Error> {
867 let (realm, mut dns_rx) = setup_test().await?;
868 let starnix_networks = realm
869 .root
870 .connect_to_protocol_at_exposed_dir::<fnp_socketproxy::StarnixNetworksMarker>()
871 .context("While connecting to StarnixNetworks")?;
872 let fuchsia_networks = realm
873 .root
874 .connect_to_protocol_at_exposed_dir::<fnp_socketproxy::FuchsiaNetworksMarker>()
875 .context("While connecting to FuchsiaNetworks")?;
876
877 let mut last_dns = None;
878 for (registry, op) in operations {
879 match registry {
880 RegistryType::Starnix => {
881 op.execute_starnix(&starnix_networks).await?;
882 }
883 RegistryType::Fuchsia => {
884 op.execute_fuchsia(&fuchsia_networks).await?;
885 }
886 }
887 if !op.is_err() {
890 last_dns =
891 Some(dns_rx.next().await.expect("dns update expected after each operation"));
892 }
893 }
894
895 let mut last_dns = last_dns.expect("there should be at least one dns update");
896 last_dns.sort_by_key(|a| a.source_network_id);
897 assert_eq!(last_dns, dns_servers);
898
899 Ok(())
900 }
901}