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;
22
23use {
24 fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
25 fidl_fuchsia_net_policy_properties as fnp_properties,
26 fidl_fuchsia_posix_socket as fposix_socket,
27};
28
29const DEFAULT_DNS_PORT: u16 = 53;
31
32pub(crate) const DEFAULT_SOCKET_MARK: u32 = 0;
35
36enum CommonErrors {
37 MissingNetworkId,
38 MissingNetworkInfo,
39 MissingNetworkDnsServers,
40}
41
42trait IpAddressExt {
43 fn to_dns_socket_address(self) -> fnet::SocketAddress;
44}
45
46impl<T: IpAddressExt + Copy> IpAddressExt for &T {
47 fn to_dns_socket_address(self) -> fnet::SocketAddress {
48 (*self).to_dns_socket_address()
49 }
50}
51
52impl IpAddressExt for fnet::Ipv4Address {
53 fn to_dns_socket_address(self) -> fnet::SocketAddress {
54 fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress { address: self, port: DEFAULT_DNS_PORT })
55 }
56}
57
58impl IpAddressExt for fnet::Ipv6Address {
59 fn to_dns_socket_address(self) -> fnet::SocketAddress {
60 fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
61 address: self,
62 port: DEFAULT_DNS_PORT,
63 zone_index: 0,
64 })
65 }
66}
67
68trait IntoOptionalUint32 {
69 fn into_optional_uint32(self) -> fposix_socket::OptionalUint32;
70}
71
72impl IntoOptionalUint32 for Option<u32> {
73 fn into_optional_uint32(self) -> fposix_socket::OptionalUint32 {
74 match self {
75 Some(value) => fposix_socket::OptionalUint32::Value(value),
76 None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
77 }
78 }
79}
80
81trait NetworkInfoExt {
82 fn mark(&self) -> Option<u32>;
83}
84
85impl NetworkInfoExt for NetworkInfo {
86 fn mark(&self) -> Option<u32> {
87 match self {
88 NetworkInfo::Starnix(s) => s.mark,
89 NetworkInfo::Fuchsia(_) | _ => None,
92 }
93 }
94}
95
96#[derive(Clone, Debug, Error)]
99pub enum NetworkRegistryError {
100 #[error("Error during socketproxy Add: {0:?}")]
101 Add(NetworkRegistryAddError),
102 #[error("Error during socketproxy Remove: {0:?}")]
103 Remove(NetworkRegistryRemoveError),
104 #[error("Error during socketproxy SetDefault: {0:?}")]
105 SetDefault(NetworkRegistrySetDefaultError),
106}
107
108impl From<NetworkRegistryAddError> for NetworkRegistryError {
109 fn from(error: NetworkRegistryAddError) -> Self {
110 NetworkRegistryError::Add(error)
111 }
112}
113
114impl From<NetworkRegistryRemoveError> for NetworkRegistryError {
115 fn from(error: NetworkRegistryRemoveError) -> Self {
116 NetworkRegistryError::Remove(error)
117 }
118}
119
120impl From<NetworkRegistrySetDefaultError> for NetworkRegistryError {
121 fn from(error: NetworkRegistrySetDefaultError) -> Self {
122 NetworkRegistryError::SetDefault(error)
123 }
124}
125
126#[derive(Clone, Debug, Error)]
127pub enum NetworkConversionError {
128 #[error("Could not convert id ({0}) to u32")]
129 InvalidInterfaceId(u64),
130}
131
132pub trait NetworkExt<I: fnet_interfaces_ext::FieldInterests> {
133 fn from_watcher_properties(
134 properties: &fnet_interfaces_ext::Properties<I>,
135 ) -> Result<Self, NetworkConversionError>
136 where
137 Self: Sized;
138}
139
140impl<I: fnet_interfaces_ext::FieldInterests> NetworkExt<I> for Network {
141 fn from_watcher_properties(
142 properties: &fnet_interfaces_ext::Properties<I>,
143 ) -> Result<Self, NetworkConversionError> {
144 let network_id: u32 =
146 properties.id.get().try_into().or_else(|_| {
147 Err(NetworkConversionError::InvalidInterfaceId(properties.id.into()))
148 })?;
149 let network = Self {
150 network_id: Some(network_id),
151 info: Some(NetworkInfo::Fuchsia(FuchsiaNetworkInfo {
152 ..Default::default()
154 })),
155 dns_servers: Some(fnp_socketproxy::NetworkDnsServers {
159 v4: Some(vec![]),
160 v6: Some(vec![]),
161 ..Default::default()
162 }),
163 ..Default::default()
164 };
165 Ok(network)
166 }
167}
168
169#[derive(Debug, Clone)]
171pub(crate) struct ValidatedNetwork {
172 network_id: u32,
173 info: NetworkInfo,
174 dns_servers: NetworkDnsServers,
175}
176
177impl ValidatedNetwork {
178 fn dns_servers(&self) -> Vec<fnet::SocketAddress> {
179 self.dns_servers
180 .v4
181 .iter()
182 .flat_map(|a| a.iter().map(IpAddressExt::to_dns_socket_address))
183 .chain(
184 self.dns_servers
185 .v6
186 .iter()
187 .flat_map(|a| a.iter().map(IpAddressExt::to_dns_socket_address)),
188 )
189 .collect()
190 }
191}
192
193trait ValidateNetworkExt {
194 fn validate(self) -> Result<ValidatedNetwork, CommonErrors>;
195}
196
197impl ValidateNetworkExt for Network {
198 fn validate(self) -> Result<ValidatedNetwork, CommonErrors> {
199 match self {
200 Network { network_id: None, .. } => Err(CommonErrors::MissingNetworkId),
201 Network { info: None, .. } => Err(CommonErrors::MissingNetworkInfo),
202 Network { dns_servers: None, .. } => Err(CommonErrors::MissingNetworkDnsServers),
203 Network {
204 network_id: Some(network_id),
205 info: Some(info),
206 dns_servers: Some(dns_servers),
207 ..
208 } => Ok(ValidatedNetwork { network_id, info, dns_servers }),
209 }
210 }
211}
212
213macro_rules! common_errors_impl {
214 ($($p:ty),+) => {
215 $(
216 impl From<CommonErrors> for $p {
217 fn from(value: CommonErrors) -> Self {
218 use CommonErrors::*;
219 match value {
220 MissingNetworkId => <$p>::MissingNetworkId,
221 MissingNetworkInfo => <$p>::MissingNetworkInfo,
222 MissingNetworkDnsServers => <$p>::MissingNetworkDnsServers,
223 }
224 }
225 }
226 )+
227 }
228}
229
230common_errors_impl!(
231 fnp_socketproxy::NetworkRegistryAddError,
232 fnp_socketproxy::NetworkRegistryUpdateError
233);
234
235#[derive(Inspect, Debug, Default)]
237struct NetworkRegistry {
238 networks: IValue<RegisteredNetworks>,
239
240 inspect_node: fuchsia_inspect::Node,
241}
242
243impl NetworkRegistry {
244 pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
246 self.networks.dns_servers()
247 }
248
249 pub(crate) fn has_default_network(&self) -> bool {
251 self.networks.default_network_id.is_some()
252 }
253
254 pub(crate) fn current_mark(&self) -> Option<u32> {
256 self.networks.current_mark()
257 }
258
259 pub(crate) fn default_network_update(&self) -> fnp_properties::DefaultNetworkUpdate {
262 self.networks.default_network_update()
263 }
264}
265
266#[derive(Unit, Debug, Default)]
267struct MethodInspect {
268 successes: u32,
269 errors: u32,
270}
271
272#[derive(Unit, Default, Debug)]
273struct RegisteredNetworks {
274 default_network_id: Option<u32>,
275
276 #[inspect(skip)]
277 networks: HashMap<u32, ValidatedNetwork>,
279
280 adds: MethodInspect,
281 removes: MethodInspect,
282 set_defaults: MethodInspect,
283 updates: MethodInspect,
284}
285
286impl RegisteredNetworks {
287 fn add_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryAddResult {
288 let network = network.validate()?;
289 #[allow(clippy::map_entry, reason = "mass allow for https://fxbug.dev/381896734")]
290 if self.networks.contains_key(&network.network_id) {
291 self.adds.errors += 1;
292 Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId)
293 } else {
294 let _: Option<_> = self.networks.insert(network.network_id, network);
295 self.adds.successes += 1;
296 Ok(())
297 }
298 }
299
300 pub(crate) fn clear(&mut self) {
302 self.networks.clear();
303 }
304
305 fn update_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryUpdateResult {
306 let network = network.validate()?;
307 let network_id = network.network_id;
308 *self
309 .networks
310 .get_mut(&network_id)
311 .ok_or(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)
312 .inspect(|_| self.updates.successes += 1)
313 .inspect_err(|_| self.updates.errors += 1)? = network;
314 Ok(())
315 }
316
317 fn remove_network(&mut self, network_id: u32) -> fnp_socketproxy::NetworkRegistryRemoveResult {
318 if self.default_network_id == Some(network_id) {
319 self.removes.errors += 1;
320 return Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork);
321 }
322 match self.networks.remove(&network_id) {
323 Some(_) => {
324 self.removes.successes += 1;
325 Ok(())
326 }
327 None => {
328 self.removes.errors += 1;
329 Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)
330 }
331 }
332 }
333
334 fn set_default_network(
338 &mut self,
339 network_id: Option<u32>,
340 ) -> fnp_socketproxy::NetworkRegistrySetDefaultResult {
341 if let Some(network_id) = network_id {
342 if !self.networks.contains_key(&network_id) {
343 self.set_defaults.errors += 1;
344 return Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound);
345 }
346 }
347 self.set_defaults.successes += 1;
348 self.default_network_id = network_id;
349
350 Ok(())
351 }
352
353 pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
355 self.networks
356 .iter()
357 .map(|(id, network)| fnp_socketproxy::DnsServerList {
358 source_network_id: Some(*id),
359 addresses: Some(network.dns_servers()),
360 ..Default::default()
361 })
362 .collect()
363 }
364
365 fn current_mark(&self) -> Option<u32> {
366 match (self.default_network_id, self.networks.is_empty()) {
367 (None, false) => Some(DEFAULT_SOCKET_MARK),
368 (id, _) => id.and_then(|id| self.networks[&id].info.mark()),
369 }
370 }
371
372 fn default_network_update(&self) -> fnp_properties::DefaultNetworkUpdate {
373 fnp_properties::DefaultNetworkUpdate {
374 interface_id: self.default_network_id.map(u64::from),
375 socket_marks: Some(fnet::Marks {
376 mark_1: self.current_mark(),
377 mark_2: None,
378 ..Default::default()
379 }),
380 ..Default::default()
381 }
382 }
383
384 fn len(&self) -> usize {
385 self.networks.len()
386 }
387}
388
389#[derive(Inspect, Clone, Debug, Default)]
390pub struct NetworkRegistries {
391 starnix: Arc<Mutex<NetworkRegistry>>,
392 fuchsia: Arc<Mutex<NetworkRegistry>>,
393}
394
395impl NetworkRegistries {
396 async fn current_mark(&self) -> Option<u32> {
400 {
401 let fuchsia = self.fuchsia.lock().await;
402 if fuchsia.has_default_network() {
403 return fuchsia.current_mark();
404 }
405 }
406
407 return self.starnix.lock().await.networks.current_mark();
408 }
409
410 async fn current_dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
414 {
415 let fuchsia = self.fuchsia.lock().await;
416 if fuchsia.has_default_network() {
417 return fuchsia.dns_servers();
418 }
419 }
420
421 return self.starnix.lock().await.dns_servers();
422 }
423
424 async fn default_network_update(&self) -> fnp_properties::DefaultNetworkUpdate {
425 {
426 let fuchsia = self.fuchsia.lock().await;
427 if fuchsia.has_default_network() {
428 info!("FuchsiaNetworks has a default network, preferring Fuchsia network.");
429 return fuchsia.default_network_update();
430 }
431 }
432
433 {
434 let starnix = self.starnix.lock().await;
435 if starnix.has_default_network() {
436 return starnix.default_network_update();
437 }
438 }
439
440 Default::default()
442 }
443}
444
445#[derive(Debug)]
446enum RegistryType {
447 Starnix,
448 Fuchsia,
449}
450
451#[derive(Inspect, Clone, Debug)]
452pub struct Registry {
453 #[inspect(forward)]
454 networks: NetworkRegistries,
455 marks: Arc<Mutex<crate::SocketMarks>>,
458 dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
459 default_network_tx: mpsc::Sender<fnp_properties::DefaultNetworkUpdate>,
460
461 starnix_occupant: Arc<Mutex<()>>,
462 fuchsia_occupant: Arc<Mutex<()>>,
463}
464
465macro_rules! handle_registry_request {
466 ($request_type:ident, $request:expr, $network_registry:expr, $registry_type:expr) => {{
467 let mut networks = $network_registry.networks.as_mut();
468 let (op, send, did_state_change): (
469 _,
470 Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>,
471 bool,
472 ) = match $request {
473 $request_type::SetDefault { network_id, responder } => {
474 let result = networks.set_default_network(match network_id {
475 fposix_socket::OptionalUint32::Value(value) => Some(value),
476 fposix_socket::OptionalUint32::Unset(_) => None,
477 });
478 ("set default", Box::new(move || responder.send(result)), true)
479 }
480 $request_type::Add { network, responder } => {
481 let result = networks.add_network(network);
482 ("add", Box::new(move || responder.send(result)), true)
483 }
484 $request_type::Update { network, responder } => {
485 let result = networks.update_network(network);
486 ("update", Box::new(move || responder.send(result)), true)
487 }
488 $request_type::Remove { network_id, responder } => {
489 let result = networks.remove_network(network_id);
490 ("remove", Box::new(move || responder.send(result)), true)
491 }
492 $request_type::CheckPresence { responder } => {
493 ("check_presence", Box::new(move || responder.send()), false)
494 }
495 };
496 if did_state_change {
497 let new_mark = networks.current_mark();
498 info!(
499 "{:?} registry {op}. mark: {new_mark:?}, networks count: {}",
500 $registry_type,
501 networks.len()
502 );
503 }
504 std::mem::drop(networks);
505 (send, did_state_change)
506 }};
507}
508
509impl Registry {
510 pub(crate) fn new(
511 marks: Arc<Mutex<crate::SocketMarks>>,
512 dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
513 default_network_tx: mpsc::Sender<fnp_properties::DefaultNetworkUpdate>,
514 ) -> Result<Self, anyhow::Error> {
515 Ok(Self {
516 networks: Default::default(),
517 marks,
518 dns_tx,
519 default_network_tx,
520 starnix_occupant: Default::default(),
521 fuchsia_occupant: Default::default(),
522 })
523 }
524}
525
526impl Registry {
527 pub(crate) async fn run_starnix(
528 &self,
529 stream: fnp_socketproxy::StarnixNetworksRequestStream,
530 ) -> Result<(), Error> {
531 let _occupant = match self.starnix_occupant.try_lock() {
532 Some(o) => o,
533 None => {
534 warn!("Only one connection to StarnixNetworks is allowed at a time");
535 stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
536 return Ok(());
537 }
538 };
539
540 info!("Starting fuchsia.net.policy.socketproxy.StarnixNetworks server");
541 self.networks.starnix.lock().await.networks.as_mut().clear();
542 stream
543 .map(|result| result.context("failed request"))
544 .try_for_each(|request| {
545 async {
546 let mut network_registry = self.networks.starnix.lock().await;
547 let (send, did_state_change): (
548 Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>,
549 bool,
550 ) = handle_registry_request!(
551 StarnixNetworksRequest,
552 request,
553 network_registry,
554 RegistryType::Starnix
555 );
556 std::mem::drop(network_registry);
557
558 if did_state_change {
559 self.handle_state_changed().await?;
560 self.default_network_tx
561 .clone()
562 .feed(self.networks.default_network_update().await)
563 .await?;
564 send().context("error sending response")?;
565 } else {
566 let _: Result<(), fidl::Error> = send();
569 }
570 Ok(())
571 }
572 })
573 .await
574 }
575
576 pub(crate) async fn run_fuchsia(
577 &self,
578 stream: fnp_socketproxy::FuchsiaNetworksRequestStream,
579 ) -> Result<(), Error> {
580 let _occupant = match self.fuchsia_occupant.try_lock() {
581 Some(o) => o,
582 None => {
583 warn!("Only one connection to FuchsiaNetworks is allowed at a time");
584 stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
585 return Ok(());
586 }
587 };
588
589 info!("Starting fuchsia.net.policy.socketproxy.FuchsiaNetworks server");
590 self.networks.fuchsia.lock().await.networks.as_mut().clear();
591 stream
592 .map(|result| result.context("failed request"))
593 .try_for_each(|request| {
594 async {
595 let mut network_registry = self.networks.fuchsia.lock().await;
596 let (send, did_state_change): (
597 Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>,
598 bool,
599 ) = handle_registry_request!(
600 FuchsiaNetworksRequest,
601 request,
602 network_registry,
603 RegistryType::Fuchsia
604 );
605 std::mem::drop(network_registry);
606
607 if did_state_change {
608 self.handle_state_changed().await?;
609 self.default_network_tx
610 .clone()
611 .feed(self.networks.default_network_update().await)
612 .await?;
613 send().context("error sending response")?;
614 } else {
615 let _: Result<(), fidl::Error> = send();
618 }
619 Ok(())
620 }
621 })
622 .await
623 }
624
625 async fn handle_state_changed(&self) -> Result<(), Error> {
626 self.dns_tx.clone().feed(self.networks.current_dns_servers().await).await.unwrap_or_else(
629 |e| {
630 if !e.is_disconnected() {
631 error!("Unable to feed DNS update: {e:?}")
633 }
634 },
635 );
636
637 self.marks.lock().await.mark_1 = self.networks.current_mark().await.into_optional_uint32();
642 Ok(())
643 }
644}
645
646#[cfg(test)]
647mod test {
648 use super::*;
649 use fuchsia_component::server::ServiceFs;
650 use fuchsia_component_test::{
651 Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
652 };
653 use futures::channel::mpsc::Receiver;
654 use futures::future;
655 use net_declare::{fidl_ip, fidl_socket_addr};
656 use pretty_assertions::assert_eq;
657 use socket_proxy_testing::{RegistryType, ToDnsServerList as _, ToNetwork};
658 use test_case::test_case;
659
660 #[derive(Clone, Debug)]
661 enum Op<N: ToNetwork> {
662 SetDefault {
663 network_id: Option<u32>,
664 result: Result<(), fnp_socketproxy::NetworkRegistrySetDefaultError>,
665 },
666 Add {
667 network: N,
668 result: Result<(), fnp_socketproxy::NetworkRegistryAddError>,
669 },
670 Update {
671 network: N,
672 result: Result<(), fnp_socketproxy::NetworkRegistryUpdateError>,
673 },
674 Remove {
675 network_id: u32,
676 result: Result<(), fnp_socketproxy::NetworkRegistryRemoveError>,
677 },
678 }
679
680 macro_rules! execute {
681 ($self:ident, $proxy:ident, $registry:expr) => {{
682 match $self {
683 Op::SetDefault { network_id, result } => {
684 assert_eq!(
685 $proxy
686 .set_default(&match network_id {
687 Some(value) => fposix_socket::OptionalUint32::Value(*value),
688 None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
689 })
690 .await?,
691 *result
692 )
693 }
694 Op::Add { network, result } => {
695 assert_eq!($proxy.add(&network.to_network($registry)).await?, *result)
696 }
697 Op::Update { network, result } => {
698 assert_eq!($proxy.update(&network.to_network($registry)).await?, *result)
699 }
700 Op::Remove { network_id, result } => {
701 assert_eq!($proxy.remove(*network_id).await?, *result)
702 }
703 }
704 Ok(())
705 }};
706 }
707
708 impl<N: ToNetwork + Clone> Op<N> {
709 async fn execute_starnix(
710 &self,
711 starnix: &fnp_socketproxy::StarnixNetworksProxy,
712 ) -> Result<(), Error> {
713 execute!(self, starnix, RegistryType::Starnix)
714 }
715
716 async fn execute_fuchsia(
717 &self,
718 fuchsia: &fnp_socketproxy::FuchsiaNetworksProxy,
719 ) -> Result<(), Error> {
720 execute!(self, fuchsia, RegistryType::Fuchsia)
721 }
722
723 fn is_err(&self) -> bool {
724 match &self {
725 Op::SetDefault { network_id: _, result } => result.is_err(),
726 Op::Add { network: _, result } => result.is_err(),
727 Op::Update { network: _, result } => result.is_err(),
728 Op::Remove { network_id: _, result } => result.is_err(),
729 }
730 }
731 }
732
733 enum IncomingService {
734 StarnixNetworks(fnp_socketproxy::StarnixNetworksRequestStream),
735 FuchsiaNetworks(fnp_socketproxy::FuchsiaNetworksRequestStream),
736 }
737
738 async fn run_registry(
739 handles: LocalComponentHandles,
740 starnix_networks: Arc<Mutex<NetworkRegistry>>,
741 fuchsia_networks: Arc<Mutex<NetworkRegistry>>,
742 marks: Arc<Mutex<crate::SocketMarks>>,
743 dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
744 default_network_tx: mpsc::Sender<fnp_properties::DefaultNetworkUpdate>,
745 ) -> Result<(), Error> {
746 let mut fs = ServiceFs::new();
747 let _ = fs
748 .dir("svc")
749 .add_fidl_service(IncomingService::StarnixNetworks)
750 .add_fidl_service(IncomingService::FuchsiaNetworks);
751 let _ = fs.serve_connection(handles.outgoing_dir)?;
752
753 let registry = Registry {
754 networks: NetworkRegistries { starnix: starnix_networks, fuchsia: fuchsia_networks },
755 marks,
756 dns_tx,
757 default_network_tx,
758 starnix_occupant: Default::default(),
759 fuchsia_occupant: Default::default(),
760 };
761
762 fs.for_each_concurrent(0, |service| async {
763 match service {
764 IncomingService::StarnixNetworks(stream) => registry.run_starnix(stream).await,
765 IncomingService::FuchsiaNetworks(stream) => registry.run_fuchsia(stream).await,
766 }
767 .unwrap_or_else(|e| error!("{e:?}"))
768 })
769 .await;
770
771 Ok(())
772 }
773
774 async fn setup_test() -> Result<
775 (
776 RealmInstance,
777 Receiver<Vec<fnp_socketproxy::DnsServerList>>,
778 Receiver<fnp_properties::DefaultNetworkUpdate>,
779 ),
780 Error,
781 > {
782 let builder = RealmBuilder::new().await?;
783 let starnix_networks = Arc::new(Mutex::new(Default::default()));
784 let fuchsia_networks = Arc::new(Mutex::new(Default::default()));
785 let (dns_tx, dns_rx) = mpsc::channel(1);
786 let (default_network_tx, default_network_rx) = mpsc::channel(1);
787 let marks = Arc::new(Mutex::new(crate::SocketMarks::default()));
788 let registry = builder
789 .add_local_child(
790 "registry",
791 {
792 let starnix_networks = starnix_networks.clone();
793 let fuchsia_networks = fuchsia_networks.clone();
794 let marks = marks.clone();
795 let dns_tx = dns_tx.clone();
796 let default_network_tx = default_network_tx.clone();
797 move |handles: LocalComponentHandles| {
798 Box::pin(run_registry(
799 handles,
800 starnix_networks.clone(),
801 fuchsia_networks.clone(),
802 marks.clone(),
803 dns_tx.clone(),
804 default_network_tx.clone(),
805 ))
806 }
807 },
808 ChildOptions::new(),
809 )
810 .await?;
811
812 builder
813 .add_route(
814 Route::new()
815 .capability(Capability::protocol::<fnp_socketproxy::StarnixNetworksMarker>())
816 .from(®istry)
817 .to(Ref::parent()),
818 )
819 .await?;
820
821 builder
822 .add_route(
823 Route::new()
824 .capability(Capability::protocol::<fnp_socketproxy::FuchsiaNetworksMarker>())
825 .from(®istry)
826 .to(Ref::parent()),
827 )
828 .await?;
829
830 let realm = builder.build().await?;
831
832 Ok((realm, dns_rx, default_network_rx))
833 }
834
835 #[test_case(&[
836 Op::Add { network: 1, result: Ok(()) },
837 Op::Update { network: 1, result: Ok(()) },
838 Op::Remove { network_id: 1, result: Ok(()) },
839 ]; "normal operation")]
840 #[test_case(&[
841 Op::Add { network: 1, result: Ok(()) },
842 Op::Add { network: 1, result: Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId) },
843 ]; "duplicate add")]
844 #[test_case(&[
845 Op::Update { network: 1, result: Err(fnp_socketproxy::NetworkRegistryUpdateError::NotFound) },
846 ]; "update missing")]
847 #[test_case(&[
848 Op::<u32>::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) },
849 ]; "remove missing")]
850 #[test_case(&[
851 Op::<u32>::SetDefault { network_id: Some(1), result: Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound) },
852 ]; "set default missing")]
853 #[test_case(&[
854 Op::Add { network: 1, result: Ok(()) },
855 Op::SetDefault { network_id: Some(1), result: Ok(()) },
856 Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
857 ]; "remove default network")]
858 #[test_case(&[
859 Op::Add { network: 1, result: Ok(()) },
860 Op::SetDefault { network_id: Some(1), result: Ok(()) },
861 Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
862 Op::Add { network: 2, result: Ok(()) },
863 Op::SetDefault { network_id: Some(2), result: Ok(()) },
864 Op::Remove { network_id: 1, result: Ok(()) },
865 ]; "remove formerly default network")]
866 #[test_case(&[
867 Op::Add { network: 1, result: Ok(()) },
868 Op::SetDefault { network_id: Some(1), result: Ok(()) },
869 Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
870 Op::SetDefault { network_id: None, result: Ok(()) },
871 Op::Remove { network_id: 1, result: Ok(()) },
872 ]; "remove last network")]
873 #[test_case(&[
874 Op::Add { network: 1, result: Ok(()) },
875 Op::Update { network: 1, result: Ok(()) },
876 Op::Add { network: 2, result: Ok(()) },
877 Op::Add { network: 3, result: Ok(()) },
878 Op::Add { network: 4, result: Ok(()) },
879 Op::Update { network: 4, result: Ok(()) },
880 Op::Update { network: 2, result: Ok(()) },
881 Op::Update { network: 3, result: Ok(()) },
882 Op::Add { network: 5, result: Ok(()) },
883 Op::Update { network: 5, result: Ok(()) },
884 Op::Add { network: 6, result: Ok(()) },
885 Op::Add { network: 7, result: Ok(()) },
886 Op::Add { network: 8, result: Ok(()) },
887 Op::Update { network: 8, result: Ok(()) },
888 Op::Update { network: 6, result: Ok(()) },
889 Op::Add { network: 9, result: Ok(()) },
890 Op::Update { network: 9, result: Ok(()) },
891 Op::Update { network: 7, result: Ok(()) },
892 Op::Add { network: 10, result: Ok(()) },
893 Op::Update { network: 10, result: Ok(()) },
894 ]; "many updates")]
895 #[fuchsia::test]
896 async fn test_operations<N: ToNetwork + Clone>(operations: &[Op<N>]) -> Result<(), Error> {
897 let (realm, _, _default_network_rx) = setup_test().await?;
898 let starnix_networks = realm
899 .root
900 .connect_to_protocol_at_exposed_dir()
901 .context("While connecting to StarnixNetworks")?;
902 let fuchsia_networks = realm
903 .root
904 .connect_to_protocol_at_exposed_dir()
905 .context("While connecting to FuchsiaNetworks")?;
906
907 for op in operations {
908 op.execute_starnix(&starnix_networks).await?;
911 op.execute_fuchsia(&fuchsia_networks).await?;
912 }
913
914 Ok(())
915 }
916
917 #[test_case(&[
918 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
919 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
920 ; "normal operation (v4)")]
921 #[test_case(&[
922 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
923 Op::Update { network: (1, vec![fidl_ip!("192.0.2.1")]), result: Ok(()) },
924 ], vec![(1, vec![fidl_socket_addr!("192.0.2.1:53")]).to_dns_server_list()]
925 ; "update server list (v4)")]
926 #[test_case(&[
927 Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) },
928 ], vec![(1, vec![fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
929 ; "normal operation (v6)")]
930 #[test_case(&[
931 Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) },
932 Op::Update { network: (1, vec![fidl_ip!("2001:db8::2")]), result: Ok(()) },
933 ], vec![(1, vec![fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list()]
934 ; "update server list (v6)")]
935 #[test_case(&[
936 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
937 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
938 ; "normal operation (mixed)")]
939 #[test_case(&[
940 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
941 Op::Update { network: (1, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) },
942 ], vec![(1, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list()]
943 ; "update server list (mixed)")]
944 #[test_case(&[
945 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
946 Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) },
947 Op::Add { network: (3, vec![fidl_ip!("192.0.2.2"), fidl_ip!("2001:db8::3")]), result: Ok(()) },
948 ], vec![
949 (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
950 (2, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list(),
951 (3, vec![fidl_socket_addr!("192.0.2.2:53"), fidl_socket_addr!("[2001:db8::3]:53")]).to_dns_server_list(),
952 ]; "multiple networks")]
953 #[fuchsia::test]
954 async fn test_dns_tracking<N: ToNetwork + Clone>(
955 operations: &[Op<N>],
956 dns_servers: Vec<fnp_socketproxy::DnsServerList>,
957 ) -> Result<(), Error> {
958 let (realm, mut dns_rx, _default_network_rx) = setup_test().await?;
959 let starnix_networks = realm
960 .root
961 .connect_to_protocol_at_exposed_dir()
962 .context("While connecting to StarnixNetworks")?;
963
964 let mut last_dns = None;
965 for op in operations {
966 op.execute_starnix(&starnix_networks).await?;
969 last_dns = Some(dns_rx.next().await.expect("dns update expected after each operation"));
970 }
971
972 let mut last_dns = last_dns.expect("there should be at least one dns update");
973 last_dns.sort_by_key(|a| a.source_network_id);
974 assert_eq!(last_dns, dns_servers);
975
976 Ok(())
977 }
978
979 #[test_case(&[
980 (RegistryType::Fuchsia, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) }),
981 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(1), result: Ok(()) }),
982 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
983 ; "normal operation Fuchsia (v4)")]
984 #[test_case(&[
985 (RegistryType::Fuchsia, Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) }),
986 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(1), result: Ok(()) }),
987 ], vec![(1, vec![fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
988 ; "normal operation Fuchsia (v6)")]
989 #[test_case(&[
990 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) }),
991 (RegistryType::Fuchsia, Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) }),
992 ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
993 ; "attempt remove in wrong registry")]
994 #[test_case(&[
995 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
996 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
997 ], vec![
998 (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
999 ]; "Fuchsia default absent, use Starnix")]
1000 #[test_case(&[
1001 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
1002 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
1003 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
1004 ], vec![
1005 (2, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list(),
1006 ]; "Fuchsia default present, use Fuchsia")]
1007 #[test_case(&[
1008 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
1009 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
1010 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
1011 (RegistryType::Fuchsia, Op::SetDefault { network_id: None, result: Ok(()) }),
1012 ], vec![
1013 (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
1014 ]; "Fallback to Starnix network")]
1015 #[test_case(&[
1016 (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
1017 (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
1018 (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
1019 (RegistryType::Fuchsia, Op::Update { network: (2, vec![fidl_ip!("192.0.2.2"), fidl_ip!("2001:db8::3")]), result: Ok(()) }),
1020 ], vec![
1021 (2, vec![fidl_socket_addr!("192.0.2.2:53"), fidl_socket_addr!("[2001:db8::3]:53")]).to_dns_server_list(),
1022 ]; "Fuchsia default present then updated")]
1023 #[fuchsia::test]
1024 async fn test_dns_tracking_across_registries<N: ToNetwork + Clone>(
1025 operations: &[(RegistryType, Op<N>)],
1026 dns_servers: Vec<fnp_socketproxy::DnsServerList>,
1027 ) -> Result<(), Error> {
1028 let (realm, mut dns_rx, _default_network_rx) = setup_test().await?;
1029 let starnix_networks = realm
1030 .root
1031 .connect_to_protocol_at_exposed_dir()
1032 .context("While connecting to StarnixNetworks")?;
1033 let fuchsia_networks = realm
1034 .root
1035 .connect_to_protocol_at_exposed_dir()
1036 .context("While connecting to FuchsiaNetworks")?;
1037
1038 let mut last_dns = None;
1039 for (registry, op) in operations {
1040 match registry {
1041 RegistryType::Starnix => {
1042 op.execute_starnix(&starnix_networks).await?;
1043 }
1044 RegistryType::Fuchsia => {
1045 op.execute_fuchsia(&fuchsia_networks).await?;
1046 }
1047 }
1048 if !op.is_err() {
1051 last_dns =
1052 Some(dns_rx.next().await.expect("dns update expected after each operation"));
1053 }
1054 }
1055
1056 let mut last_dns = last_dns.expect("there should be at least one dns update");
1057 last_dns.sort_by_key(|a| a.source_network_id);
1058 assert_eq!(last_dns, dns_servers);
1059
1060 Ok(())
1061 }
1062
1063 trait ToDefaultNetworkUpdate {
1064 fn to_default_network_update_request(self) -> fnp_properties::DefaultNetworkUpdate;
1065 }
1066
1067 impl ToDefaultNetworkUpdate for (u64, (u32, Option<u32>)) {
1068 fn to_default_network_update_request(self) -> fnp_properties::DefaultNetworkUpdate {
1069 fnp_properties::DefaultNetworkUpdate {
1070 interface_id: Some(self.0),
1071 socket_marks: Some(fnet::Marks {
1072 mark_1: Some(self.1.0),
1073 mark_2: self.1.1,
1074 __source_breaking: fidl::marker::SourceBreaking,
1075 }),
1076 __source_breaking: fidl::marker::SourceBreaking,
1077 }
1078 }
1079 }
1080
1081 #[test_case(&[
1082 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1083 ], vec![
1084 Default::default(),
1085 ]
1086 ; "Add but no default")]
1087 #[test_case(&[
1088 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1089 Op::SetDefault { network_id: Some(1), result: Ok(()) },
1090 ], vec![
1091 Default::default(),
1092 (1, (1, None)).to_default_network_update_request()
1093 ]
1094 ; "Add and set default")]
1095 #[test_case(&[
1096 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1097 Op::Add { network: (2, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1098 Op::SetDefault { network_id: Some(1), result: Ok(()) },
1099 Op::SetDefault { network_id: Some(2), result: Ok(()) },
1100 ], vec![
1101 Default::default(),
1102 Default::default(),
1103 (1, (1, None)).to_default_network_update_request(),
1104 (2, (2, None)).to_default_network_update_request(),
1105 ]
1106 ; "Add two and set default")]
1107 #[test_case(&[
1108 Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1109 Op::SetDefault { network_id: Some(1), result: Ok(()) },
1110 Op::SetDefault { network_id: None, result: Ok(()) },
1111 Op::Remove { network_id: 1, result: Ok(()) },
1112 ], vec![
1113 Default::default(),
1114 (1, (1, None)).to_default_network_update_request(),
1115 Default::default(),
1117 Default::default(),
1118 ]
1119 ; "Add default and delete")]
1120 #[fuchsia::test]
1121 async fn test_default_network_update<N: ToNetwork + Clone>(
1122 operations: &[Op<N>],
1123 default_server_updates: Vec<fnp_properties::DefaultNetworkUpdate>,
1124 ) -> Result<(), Error> {
1125 let (realm, _, mut default_networks_rx) = setup_test().await?;
1126 let starnix_networks = realm
1127 .root
1128 .connect_to_protocol_at_exposed_dir()
1129 .context("While connecting to StarnixNetworks")?;
1130
1131 let (_, default_networks) = future::join(
1132 async move {
1133 for op in operations {
1134 op.execute_starnix(&starnix_networks).await?;
1135 }
1136 std::mem::drop(realm);
1137 Ok::<(), anyhow::Error>(())
1138 },
1139 async move {
1140 let mut default_networks = Vec::new();
1141 while let Some(upd) = default_networks_rx.next().await {
1142 default_networks.push(upd);
1143 }
1144 default_networks
1145 },
1146 )
1147 .await;
1148
1149 assert_eq!(*default_networks, default_server_updates);
1150
1151 Ok(())
1152 }
1153}