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