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;
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
29/// RFC-1035ยง4.2 specifies port 53 (decimal) as the default port for DNS requests.
30const DEFAULT_DNS_PORT: u16 = 53;
31
32/// If there are networks registered, but no default has been set, this value
33/// will be used, otherwise the mark will be OptionalUint32::Unset(Empty).
34pub(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            // Sockets express using Fuchsia's default network by setting
90            // the mark to None.
91            NetworkInfo::Fuchsia(_) | _ => None,
92        }
93    }
94}
95
96// Errors produced when communicating updates to
97// the socket proxy.
98#[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        // We expect interface ids to safely fit in the range of u32 values.
145        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                // No Fuchsia-specific information to provide.
153                ..Default::default()
154            })),
155            // DNS servers of Fuchsia networks are observable in netcfg already, so don't provide
156            // them to the Socketproxy. Socketproxy requires these fields to be provided so
157            // instantiate the v4 and v6 fields as empty vectors.
158            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/// A copy of fnp_socketproxy::Network that ensures that all fields are present.
170#[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/// NetworkRegistry tracks the networks that have been registered.
236#[derive(Inspect, Debug, Default)]
237struct NetworkRegistry {
238    networks: IValue<RegisteredNetworks>,
239
240    inspect_node: fuchsia_inspect::Node,
241}
242
243impl NetworkRegistry {
244    /// Returns a collated list of DnsServerList objects.
245    pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
246        self.networks.dns_servers()
247    }
248
249    /// Returns whether the network registry has a default network set.
250    pub(crate) fn has_default_network(&self) -> bool {
251        self.networks.default_network_id.is_some()
252    }
253
254    /// Returns current socket mark for the default network.
255    pub(crate) fn current_mark(&self) -> Option<u32> {
256        self.networks.current_mark()
257    }
258
259    /// Constructs a [`DefaultNetworkUpdate`] for sending to
260    /// `fuchsia.net.policy.properties/DefaultNetworkWatcher`.
261    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    /// A mapping from network id to ValidatedNetwork for each registered network.
278    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    /// Empties the registered networks.
301    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    /// Update the currently set default network id.
335    ///
336    /// If `network_id` is None, the default network id will be unset.
337    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    /// Returns a collated list of DnsServerList objects.
354    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    // When Fuchsia has a default network, then prefer its mark
397    // over any existing mark. When it is unset, then fallback
398    // to the mark provided by Starnix.
399    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    // When Fuchsia has a default network, then prefer its DNS
411    // over any existing DNS servers. When it is unset, then
412    // fallback to the DNS servers provided by Starnix.
413    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        // An empty update to signify that there is no default.
441        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    // Reflects the marks that are set on the sockets vended
456    // by this component.
457    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                        // Not a request involving a state change,
567                        // so ignore any errors.
568                        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                        // Not a request involving a state change,
616                        // so ignore any errors.
617                        let _: Result<(), fidl::Error> = send();
618                    }
619                    Ok(())
620                }
621            })
622            .await
623    }
624
625    async fn handle_state_changed(&self) -> Result<(), Error> {
626        // We do feed here instead of send so that we don't wait for a flush
627        // in the event that the DnsServerWatcher is not running.
628        self.dns_tx.clone().feed(self.networks.current_dns_servers().await).await.unwrap_or_else(
629            |e| {
630                if !e.is_disconnected() {
631                    // Log if the feed fails for reasons other than disconnection.
632                    error!("Unable to feed DNS update: {e:?}")
633                }
634            },
635        );
636
637        // Ensure the mark is updated prior to sending out the response
638        // and dropping the registry.
639        // TODO(https://fxbug.dev/431822969): Replace this with a common definition of
640        // which mark domain is used for which purpose.
641        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(&registry)
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(&registry)
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            // Demonstrate that the same operations can be applied
909            // independently in both registries.
910            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            // Starnix and Fuchsia registries have the same handling logic, so
967            // use the Starnix registry to confirm this behavior.
968            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            // When the operation results in an error, we don't expect that to
1049            // result in an additional DNS update.
1050            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        // An empty update means the default network has been removed.
1116        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}