socket_proxy/
registry.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implements fuchsia.net.policy.socketproxy.NetworkRegistry.
6
7use 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
27/// RFC-1035ยง4.2 specifies port 53 (decimal) as the default port for DNS requests.
28const DEFAULT_DNS_PORT: u16 = 53;
29
30/// If there are networks registered, but no default has been set, this value
31/// will be used, otherwise the mark will be OptionalUint32::Unset(Empty).
32pub(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            // Sockets express using Fuchsia's default network by setting
75            // the mark to None.
76            NetworkInfo::Fuchsia(_) | _ => None,
77        }
78    }
79}
80
81// Errors produced when communicating updates to
82// the socket proxy.
83#[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        // We expect interface ids to safely fit in the range of u32 values.
130        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                // No Fuchsia-specific information to provide.
138                ..Default::default()
139            })),
140            // DNS servers of Fuchsia networks are observable in netcfg already, so don't provide
141            // them to the Socketproxy. Socketproxy requires these fields to be provided so
142            // instantiate the v4 and v6 fields as empty vectors.
143            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/// A copy of fnp_socketproxy::Network that ensures that all fields are present.
155#[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/// NetworkRegistry tracks the networks that have been registered.
221#[derive(Inspect, Debug, Default)]
222struct NetworkRegistry {
223    networks: IValue<RegisteredNetworks>,
224
225    inspect_node: fuchsia_inspect::Node,
226}
227
228impl NetworkRegistry {
229    /// Returns a collated list of DnsServerList objects.
230    pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
231        self.networks.dns_servers()
232    }
233
234    /// Returns whether the network registry has a default network set.
235    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    /// A mapping from network id to ValidatedNetwork for each registered network.
252    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    /// Empties the registered networks.
275    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    /// Update the currently set default network id.
309    ///
310    /// If `network_id` is None, the default network id will be unset.
311    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    /// Returns a collated list of DnsServerList objects.
328    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    // When Fuchsia has a default network, then prefer its mark
363    // over any existing mark. When it is unset, then fallback
364    // to the mark provided by Starnix.
365    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    // When Fuchsia has a default network, then prefer its DNS
375    // over any existing DNS servers. When it is unset, then
376    // fallback to the DNS servers provided by Starnix.
377    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    // Reflects the marks that are set on the sockets vended
392    // by this component.
393    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                    // We do feed here instead of send so that we don't wait for a flush
490                    // in the event that the DnsServerWatcher is not running.
491                    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                                // Log if the feed fails for reasons other than disconnection.
498                                error!("Unable to feed DNS update: {e:?}")
499                            }
500                        });
501
502                    // Ensure the mark is updated prior to sending out the response
503                    // and dropping the registry.
504                    self.marks.lock().await.mark_1 = self.networks.current_mark().await;
505                    send().context("error sending response")?;
506                } else {
507                    // Not a request involving a state change,
508                    // so ignore any errors.
509                    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                    // We do feed here instead of send so that we don't wait for a flush
548                    // in the event that the DnsServerWatcher is not running.
549                    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                                // Log if the feed fails for reasons other than disconnection.
556                                error!("Unable to feed DNS update: {e:?}")
557                            }
558                        });
559
560                    // Ensure the mark is updated prior to sending out the response
561                    // and dropping the registry.
562                    self.marks.lock().await.mark_1 = self.networks.current_mark().await;
563                    send().context("error sending response")?;
564                } else {
565                    // Not a request involving a state change,
566                    // so ignore any errors.
567                    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(&registry)
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(&registry)
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            // Demonstrate that the same operations can be applied
826            // independently in both registries.
827            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            // Starnix and Fuchsia registries have the same handling logic, so
884            // use the Starnix registry to confirm this behavior.
885            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            // When the operation results in an error, we don't expect that to
966            // result in an additional DNS update.
967            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}