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