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.default_network_id = None;
303        self.networks.clear();
304    }
305
306    fn update_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryUpdateResult {
307        let network = network.validate()?;
308        let network_id = network.network_id;
309        *self
310            .networks
311            .get_mut(&network_id)
312            .ok_or(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)
313            .inspect(|_| self.updates.successes += 1)
314            .inspect_err(|_| self.updates.errors += 1)? = network;
315        Ok(())
316    }
317
318    fn remove_network(&mut self, network_id: u32) -> fnp_socketproxy::NetworkRegistryRemoveResult {
319        if self.default_network_id == Some(network_id) {
320            self.removes.errors += 1;
321            return Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork);
322        }
323        match self.networks.remove(&network_id) {
324            Some(_) => {
325                self.removes.successes += 1;
326                Ok(())
327            }
328            None => {
329                self.removes.errors += 1;
330                Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)
331            }
332        }
333    }
334
335    /// Update the currently set default network id.
336    ///
337    /// If `network_id` is None, the default network id will be unset.
338    fn set_default_network(
339        &mut self,
340        network_id: Option<u32>,
341    ) -> fnp_socketproxy::NetworkRegistrySetDefaultResult {
342        if let Some(network_id) = network_id {
343            if !self.networks.contains_key(&network_id) {
344                self.set_defaults.errors += 1;
345                return Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound);
346            }
347        }
348        self.set_defaults.successes += 1;
349        self.default_network_id = network_id;
350
351        Ok(())
352    }
353
354    /// Returns a collated list of DnsServerList objects.
355    pub(crate) fn dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
356        self.networks
357            .iter()
358            .map(|(id, network)| fnp_socketproxy::DnsServerList {
359                source_network_id: Some(*id),
360                addresses: Some(network.dns_servers()),
361                ..Default::default()
362            })
363            .collect()
364    }
365
366    fn current_mark(&self) -> Option<u32> {
367        match (self.default_network_id, self.networks.is_empty()) {
368            (None, false) => Some(DEFAULT_SOCKET_MARK),
369            (id, _) => id.and_then(|id| self.networks[&id].info.mark()),
370        }
371    }
372
373    fn default_network_update(&self) -> fnp_properties::DefaultNetworkUpdate {
374        fnp_properties::DefaultNetworkUpdate {
375            interface_id: self.default_network_id.map(u64::from),
376            socket_marks: Some(fnet::Marks {
377                mark_1: self.current_mark(),
378                mark_2: None,
379                ..Default::default()
380            }),
381            ..Default::default()
382        }
383    }
384
385    fn len(&self) -> usize {
386        self.networks.len()
387    }
388}
389
390#[derive(Inspect, Clone, Debug, Default)]
391pub struct NetworkRegistries {
392    starnix: Arc<Mutex<NetworkRegistry>>,
393    fuchsia: Arc<Mutex<NetworkRegistry>>,
394}
395
396impl NetworkRegistries {
397    // When Fuchsia has a default network, then prefer its mark
398    // over any existing mark. When it is unset, then fallback
399    // to the mark provided by Starnix.
400    async fn current_mark(&self) -> Option<u32> {
401        {
402            let fuchsia = self.fuchsia.lock().await;
403            if fuchsia.has_default_network() {
404                return fuchsia.current_mark();
405            }
406        }
407
408        return self.starnix.lock().await.networks.current_mark();
409    }
410
411    // When Fuchsia has a default network, then prefer its DNS
412    // over any existing DNS servers. When it is unset, then
413    // fallback to the DNS servers provided by Starnix.
414    async fn current_dns_servers(&self) -> Vec<fnp_socketproxy::DnsServerList> {
415        {
416            let fuchsia = self.fuchsia.lock().await;
417            if fuchsia.has_default_network() {
418                return fuchsia.dns_servers();
419            }
420        }
421
422        return self.starnix.lock().await.dns_servers();
423    }
424
425    async fn default_network_update(&self) -> fnp_properties::DefaultNetworkUpdate {
426        {
427            let fuchsia = self.fuchsia.lock().await;
428            if fuchsia.has_default_network() {
429                info!("FuchsiaNetworks has a default network, preferring Fuchsia network.");
430                return fuchsia.default_network_update();
431            }
432        }
433
434        {
435            let starnix = self.starnix.lock().await;
436            if starnix.has_default_network() {
437                return starnix.default_network_update();
438            }
439        }
440
441        // An empty update to signify that there is no default.
442        Default::default()
443    }
444}
445
446#[derive(Debug)]
447enum RegistryType {
448    Starnix,
449    Fuchsia,
450}
451
452#[derive(Inspect, Clone, Debug)]
453pub struct Registry {
454    #[inspect(forward)]
455    networks: NetworkRegistries,
456    // Reflects the marks that are set on the sockets vended
457    // by this component.
458    marks: Arc<Mutex<crate::SocketMarks>>,
459    dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
460    default_network_tx: mpsc::Sender<fnp_properties::DefaultNetworkUpdate>,
461
462    starnix_occupant: Arc<Mutex<()>>,
463    fuchsia_occupant: Arc<Mutex<()>>,
464}
465
466macro_rules! handle_registry_request {
467    ($request_type:ident, $request:expr, $network_registry:expr, $registry_type:expr) => {{
468        let mut networks = $network_registry.networks.as_mut();
469        let (op, send): (_, Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>) =
470            match $request {
471                $request_type::SetDefault { network_id, responder } => {
472                    let result = networks.set_default_network(match network_id {
473                        fposix_socket::OptionalUint32::Value(value) => Some(value),
474                        fposix_socket::OptionalUint32::Unset(_) => None,
475                    });
476                    ("set default", Box::new(move || responder.send(result)))
477                }
478                $request_type::Add { network, responder } => {
479                    let result = networks.add_network(network);
480                    ("add", Box::new(move || responder.send(result)))
481                }
482                $request_type::Update { network, responder } => {
483                    let result = networks.update_network(network);
484                    ("update", Box::new(move || responder.send(result)))
485                }
486                $request_type::Remove { network_id, responder } => {
487                    let result = networks.remove_network(network_id);
488                    ("remove", Box::new(move || responder.send(result)))
489                }
490            };
491        let new_mark = networks.current_mark();
492        info!(
493            "{:?} registry {op}. mark: {new_mark:?}, networks count: {}",
494            $registry_type,
495            networks.len()
496        );
497        std::mem::drop(networks);
498        send
499    }};
500}
501
502impl Registry {
503    pub(crate) fn new(
504        marks: Arc<Mutex<crate::SocketMarks>>,
505        dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
506        default_network_tx: mpsc::Sender<fnp_properties::DefaultNetworkUpdate>,
507    ) -> Result<Self, anyhow::Error> {
508        Ok(Self {
509            networks: Default::default(),
510            marks,
511            dns_tx,
512            default_network_tx,
513            starnix_occupant: Default::default(),
514            fuchsia_occupant: Default::default(),
515        })
516    }
517}
518
519impl Registry {
520    pub(crate) async fn run_starnix(
521        &self,
522        stream: fnp_socketproxy::StarnixNetworksRequestStream,
523    ) -> Result<(), Error> {
524        let _occupant = match self.starnix_occupant.try_lock() {
525            Some(o) => o,
526            None => {
527                warn!("Only one connection to StarnixNetworks is allowed at a time");
528                stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
529                return Ok(());
530            }
531        };
532
533        info!("Starting fuchsia.net.policy.socketproxy.StarnixNetworks server");
534        self.networks.starnix.lock().await.networks.as_mut().clear();
535        stream
536            .map(|result| result.context("failed request"))
537            .try_for_each(|request| {
538                async {
539                    let mut network_registry = self.networks.starnix.lock().await;
540                    let send: Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static> = handle_registry_request!(
541                        StarnixNetworksRequest,
542                        request,
543                        network_registry,
544                        RegistryType::Starnix
545                    );
546                    std::mem::drop(network_registry);
547
548                    self.handle_state_changed().await?;
549                    self.default_network_tx
550                        .clone()
551                        .feed(self.networks.default_network_update().await)
552                        .await?;
553                    send().context("error sending response")?;
554                    Ok(())
555                }
556            })
557            .await
558    }
559
560    pub(crate) async fn run_fuchsia(
561        &self,
562        stream: fnp_socketproxy::FuchsiaNetworksRequestStream,
563    ) -> Result<(), Error> {
564        let _occupant = match self.fuchsia_occupant.try_lock() {
565            Some(o) => o,
566            None => {
567                warn!("Only one connection to FuchsiaNetworks is allowed at a time");
568                stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
569                return Ok(());
570            }
571        };
572
573        info!("Starting fuchsia.net.policy.socketproxy.FuchsiaNetworks server");
574        self.networks.fuchsia.lock().await.networks.as_mut().clear();
575        stream
576            .map(|result| result.context("failed request"))
577            .try_for_each(|request| {
578                async {
579                    let mut network_registry = self.networks.fuchsia.lock().await;
580                    let send: Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static> = handle_registry_request!(
581                        FuchsiaNetworksRequest,
582                        request,
583                        network_registry,
584                        RegistryType::Fuchsia
585                    );
586                    std::mem::drop(network_registry);
587
588                    self.handle_state_changed().await?;
589                    self.default_network_tx
590                        .clone()
591                        .feed(self.networks.default_network_update().await)
592                        .await?;
593                    send().context("error sending response")?;
594                    Ok(())
595                }
596            })
597            .await
598    }
599
600    async fn handle_state_changed(&self) -> Result<(), Error> {
601        // We do feed here instead of send so that we don't wait for a flush
602        // in the event that the DnsServerWatcher is not running.
603        self.dns_tx.clone().feed(self.networks.current_dns_servers().await).await.unwrap_or_else(
604            |e| {
605                if !e.is_disconnected() {
606                    // Log if the feed fails for reasons other than disconnection.
607                    error!("Unable to feed DNS update: {e:?}")
608                }
609            },
610        );
611
612        // Ensure the mark is updated prior to sending out the response
613        // and dropping the registry.
614        // TODO(https://fxbug.dev/431822969): Replace this with a common definition of
615        // which mark domain is used for which purpose.
616        self.marks.lock().await.mark_1 = self.networks.current_mark().await.into_optional_uint32();
617        Ok(())
618    }
619}
620
621#[cfg(test)]
622mod test {
623    use super::*;
624    use fuchsia_component::server::ServiceFs;
625    use fuchsia_component_test::{
626        Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
627    };
628    use futures::channel::mpsc::Receiver;
629    use futures::future;
630    use net_declare::{fidl_ip, fidl_socket_addr};
631    use pretty_assertions::assert_eq;
632    use socket_proxy_testing::{RegistryType, ToDnsServerList as _, ToNetwork};
633    use test_case::test_case;
634
635    #[derive(Clone, Debug)]
636    enum Op<N: ToNetwork> {
637        SetDefault {
638            network_id: Option<u32>,
639            result: Result<(), fnp_socketproxy::NetworkRegistrySetDefaultError>,
640        },
641        Add {
642            network: N,
643            result: Result<(), fnp_socketproxy::NetworkRegistryAddError>,
644        },
645        Update {
646            network: N,
647            result: Result<(), fnp_socketproxy::NetworkRegistryUpdateError>,
648        },
649        Remove {
650            network_id: u32,
651            result: Result<(), fnp_socketproxy::NetworkRegistryRemoveError>,
652        },
653    }
654
655    macro_rules! execute {
656        ($self:ident, $proxy:ident, $registry:expr) => {{
657            match $self {
658                Op::SetDefault { network_id, result } => {
659                    assert_eq!(
660                        $proxy
661                            .set_default(&match network_id {
662                                Some(value) => fposix_socket::OptionalUint32::Value(*value),
663                                None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
664                            })
665                            .await?,
666                        *result
667                    )
668                }
669                Op::Add { network, result } => {
670                    assert_eq!($proxy.add(&network.to_network($registry)).await?, *result)
671                }
672                Op::Update { network, result } => {
673                    assert_eq!($proxy.update(&network.to_network($registry)).await?, *result)
674                }
675                Op::Remove { network_id, result } => {
676                    assert_eq!($proxy.remove(*network_id).await?, *result)
677                }
678            }
679            Ok(())
680        }};
681    }
682
683    impl<N: ToNetwork + Clone> Op<N> {
684        async fn execute_starnix(
685            &self,
686            starnix: &fnp_socketproxy::StarnixNetworksProxy,
687        ) -> Result<(), Error> {
688            execute!(self, starnix, RegistryType::Starnix)
689        }
690
691        async fn execute_fuchsia(
692            &self,
693            fuchsia: &fnp_socketproxy::FuchsiaNetworksProxy,
694        ) -> Result<(), Error> {
695            execute!(self, fuchsia, RegistryType::Fuchsia)
696        }
697
698        fn is_err(&self) -> bool {
699            match &self {
700                Op::SetDefault { network_id: _, result } => result.is_err(),
701                Op::Add { network: _, result } => result.is_err(),
702                Op::Update { network: _, result } => result.is_err(),
703                Op::Remove { network_id: _, result } => result.is_err(),
704            }
705        }
706    }
707
708    enum IncomingService {
709        StarnixNetworks(fnp_socketproxy::StarnixNetworksRequestStream),
710        FuchsiaNetworks(fnp_socketproxy::FuchsiaNetworksRequestStream),
711    }
712
713    async fn run_registry(
714        handles: LocalComponentHandles,
715        starnix_networks: Arc<Mutex<NetworkRegistry>>,
716        fuchsia_networks: Arc<Mutex<NetworkRegistry>>,
717        marks: Arc<Mutex<crate::SocketMarks>>,
718        dns_tx: mpsc::Sender<Vec<fnp_socketproxy::DnsServerList>>,
719        default_network_tx: mpsc::Sender<fnp_properties::DefaultNetworkUpdate>,
720    ) -> Result<(), Error> {
721        let mut fs = ServiceFs::new();
722        let _ = fs
723            .dir("svc")
724            .add_fidl_service(IncomingService::StarnixNetworks)
725            .add_fidl_service(IncomingService::FuchsiaNetworks);
726        let _ = fs.serve_connection(handles.outgoing_dir)?;
727
728        let registry = Registry {
729            networks: NetworkRegistries { starnix: starnix_networks, fuchsia: fuchsia_networks },
730            marks,
731            dns_tx,
732            default_network_tx,
733            starnix_occupant: Default::default(),
734            fuchsia_occupant: Default::default(),
735        };
736
737        fs.for_each_concurrent(0, |service| async {
738            match service {
739                IncomingService::StarnixNetworks(stream) => registry.run_starnix(stream).await,
740                IncomingService::FuchsiaNetworks(stream) => registry.run_fuchsia(stream).await,
741            }
742            .unwrap_or_else(|e| error!("{e:?}"))
743        })
744        .await;
745
746        Ok(())
747    }
748
749    async fn setup_test() -> Result<
750        (
751            RealmInstance,
752            Receiver<Vec<fnp_socketproxy::DnsServerList>>,
753            Receiver<fnp_properties::DefaultNetworkUpdate>,
754        ),
755        Error,
756    > {
757        let builder = RealmBuilder::new().await?;
758        let starnix_networks = Arc::new(Mutex::new(Default::default()));
759        let fuchsia_networks = Arc::new(Mutex::new(Default::default()));
760        let (dns_tx, dns_rx) = mpsc::channel(1);
761        let (default_network_tx, default_network_rx) = mpsc::channel(1);
762        let marks = Arc::new(Mutex::new(crate::SocketMarks::default()));
763        let registry = builder
764            .add_local_child(
765                "registry",
766                {
767                    let starnix_networks = starnix_networks.clone();
768                    let fuchsia_networks = fuchsia_networks.clone();
769                    let marks = marks.clone();
770                    let dns_tx = dns_tx.clone();
771                    let default_network_tx = default_network_tx.clone();
772                    move |handles: LocalComponentHandles| {
773                        Box::pin(run_registry(
774                            handles,
775                            starnix_networks.clone(),
776                            fuchsia_networks.clone(),
777                            marks.clone(),
778                            dns_tx.clone(),
779                            default_network_tx.clone(),
780                        ))
781                    }
782                },
783                ChildOptions::new(),
784            )
785            .await?;
786
787        builder
788            .add_route(
789                Route::new()
790                    .capability(Capability::protocol::<fnp_socketproxy::StarnixNetworksMarker>())
791                    .from(&registry)
792                    .to(Ref::parent()),
793            )
794            .await?;
795
796        builder
797            .add_route(
798                Route::new()
799                    .capability(Capability::protocol::<fnp_socketproxy::FuchsiaNetworksMarker>())
800                    .from(&registry)
801                    .to(Ref::parent()),
802            )
803            .await?;
804
805        let realm = builder.build().await?;
806
807        Ok((realm, dns_rx, default_network_rx))
808    }
809
810    #[test_case(&[
811        Op::Add { network: 1, result: Ok(()) },
812        Op::Update { network: 1, result: Ok(()) },
813        Op::Remove { network_id: 1, result: Ok(()) },
814    ]; "normal operation")]
815    #[test_case(&[
816        Op::Add { network: 1, result: Ok(()) },
817        Op::Add { network: 1, result: Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId) },
818    ]; "duplicate add")]
819    #[test_case(&[
820        Op::Update { network: 1, result: Err(fnp_socketproxy::NetworkRegistryUpdateError::NotFound) },
821    ]; "update missing")]
822    #[test_case(&[
823        Op::<u32>::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) },
824    ]; "remove missing")]
825    #[test_case(&[
826        Op::<u32>::SetDefault { network_id: Some(1), result: Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound) },
827    ]; "set default missing")]
828    #[test_case(&[
829        Op::Add { network: 1, result: Ok(()) },
830        Op::SetDefault { network_id: Some(1), result: Ok(()) },
831        Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
832    ]; "remove default network")]
833    #[test_case(&[
834        Op::Add { network: 1, result: Ok(()) },
835        Op::SetDefault { network_id: Some(1), result: Ok(()) },
836        Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
837        Op::Add { network: 2, result: Ok(()) },
838        Op::SetDefault { network_id: Some(2), result: Ok(()) },
839        Op::Remove { network_id: 1, result: Ok(()) },
840    ]; "remove formerly default network")]
841    #[test_case(&[
842        Op::Add { network: 1, result: Ok(()) },
843        Op::SetDefault { network_id: Some(1), result: Ok(()) },
844        Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
845        Op::SetDefault { network_id: None, result: Ok(()) },
846        Op::Remove { network_id: 1, result: Ok(()) },
847    ]; "remove last network")]
848    #[test_case(&[
849        Op::Add { network: 1, result: Ok(()) },
850        Op::Update { network: 1, result: Ok(()) },
851        Op::Add { network: 2, result: Ok(()) },
852        Op::Add { network: 3, result: Ok(()) },
853        Op::Add { network: 4, result: Ok(()) },
854        Op::Update { network: 4, result: Ok(()) },
855        Op::Update { network: 2, result: Ok(()) },
856        Op::Update { network: 3, result: Ok(()) },
857        Op::Add { network: 5, result: Ok(()) },
858        Op::Update { network: 5, result: Ok(()) },
859        Op::Add { network: 6, result: Ok(()) },
860        Op::Add { network: 7, result: Ok(()) },
861        Op::Add { network: 8, result: Ok(()) },
862        Op::Update { network: 8, result: Ok(()) },
863        Op::Update { network: 6, result: Ok(()) },
864        Op::Add { network: 9, result: Ok(()) },
865        Op::Update { network: 9, result: Ok(()) },
866        Op::Update { network: 7, result: Ok(()) },
867        Op::Add { network: 10, result: Ok(()) },
868        Op::Update { network: 10, result: Ok(()) },
869    ]; "many updates")]
870    #[fuchsia::test]
871    async fn test_operations<N: ToNetwork + Clone>(operations: &[Op<N>]) -> Result<(), Error> {
872        let (realm, _, _default_network_rx) = setup_test().await?;
873        let starnix_networks = realm
874            .root
875            .connect_to_protocol_at_exposed_dir()
876            .context("While connecting to StarnixNetworks")?;
877        let fuchsia_networks = realm
878            .root
879            .connect_to_protocol_at_exposed_dir()
880            .context("While connecting to FuchsiaNetworks")?;
881
882        for op in operations {
883            // Demonstrate that the same operations can be applied
884            // independently in both registries.
885            op.execute_starnix(&starnix_networks).await?;
886            op.execute_fuchsia(&fuchsia_networks).await?;
887        }
888
889        Ok(())
890    }
891
892    #[test_case(&[
893        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
894    ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
895    ; "normal operation (v4)")]
896    #[test_case(&[
897        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
898        Op::Update { network: (1, vec![fidl_ip!("192.0.2.1")]), result: Ok(()) },
899    ], vec![(1, vec![fidl_socket_addr!("192.0.2.1:53")]).to_dns_server_list()]
900    ; "update server list (v4)")]
901    #[test_case(&[
902        Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) },
903    ], vec![(1, vec![fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
904    ; "normal operation (v6)")]
905    #[test_case(&[
906        Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) },
907        Op::Update { network: (1, vec![fidl_ip!("2001:db8::2")]), result: Ok(()) },
908    ], vec![(1, vec![fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list()]
909    ; "update server list (v6)")]
910    #[test_case(&[
911        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
912    ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
913    ; "normal operation (mixed)")]
914    #[test_case(&[
915        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
916        Op::Update { network: (1, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) },
917    ], vec![(1, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list()]
918    ; "update server list (mixed)")]
919    #[test_case(&[
920        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) },
921        Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) },
922        Op::Add { network: (3, vec![fidl_ip!("192.0.2.2"), fidl_ip!("2001:db8::3")]), result: Ok(()) },
923    ], vec![
924        (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
925        (2, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list(),
926        (3, vec![fidl_socket_addr!("192.0.2.2:53"), fidl_socket_addr!("[2001:db8::3]:53")]).to_dns_server_list(),
927    ]; "multiple networks")]
928    #[fuchsia::test]
929    async fn test_dns_tracking<N: ToNetwork + Clone>(
930        operations: &[Op<N>],
931        dns_servers: Vec<fnp_socketproxy::DnsServerList>,
932    ) -> Result<(), Error> {
933        let (realm, mut dns_rx, _default_network_rx) = setup_test().await?;
934        let starnix_networks = realm
935            .root
936            .connect_to_protocol_at_exposed_dir()
937            .context("While connecting to StarnixNetworks")?;
938
939        let mut last_dns = None;
940        for op in operations {
941            // Starnix and Fuchsia registries have the same handling logic, so
942            // use the Starnix registry to confirm this behavior.
943            op.execute_starnix(&starnix_networks).await?;
944            last_dns = Some(dns_rx.next().await.expect("dns update expected after each operation"));
945        }
946
947        let mut last_dns = last_dns.expect("there should be at least one dns update");
948        last_dns.sort_by_key(|a| a.source_network_id);
949        assert_eq!(last_dns, dns_servers);
950
951        Ok(())
952    }
953
954    #[test_case(&[
955        (RegistryType::Fuchsia, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) }),
956        (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(1), result: Ok(()) }),
957    ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
958    ; "normal operation Fuchsia (v4)")]
959    #[test_case(&[
960        (RegistryType::Fuchsia, Op::Add { network: (1, vec![fidl_ip!("2001:db8::1")]), result: Ok(()) }),
961        (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(1), result: Ok(()) }),
962    ], vec![(1, vec![fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list()]
963    ; "normal operation Fuchsia (v6)")]
964    #[test_case(&[
965        (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) }),
966        (RegistryType::Fuchsia, Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) }),
967    ], vec![(1, vec![fidl_socket_addr!("192.0.2.0:53")]).to_dns_server_list()]
968    ; "attempt remove in wrong registry")]
969    #[test_case(&[
970        (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
971        (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
972    ], vec![
973        (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
974    ]; "Fuchsia default absent, use Starnix")]
975    #[test_case(&[
976        (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
977        (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
978        (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
979        ], vec![
980        (2, vec![fidl_socket_addr!("192.0.2.1:53"), fidl_socket_addr!("[2001:db8::2]:53")]).to_dns_server_list(),
981    ]; "Fuchsia default present, use Fuchsia")]
982    #[test_case(&[
983        (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
984        (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
985        (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
986        (RegistryType::Fuchsia, Op::SetDefault { network_id: None, result: Ok(()) }),
987        ], vec![
988        (1, vec![fidl_socket_addr!("192.0.2.0:53"), fidl_socket_addr!("[2001:db8::1]:53")]).to_dns_server_list(),
989    ]; "Fallback to Starnix network")]
990    #[test_case(&[
991        (RegistryType::Starnix, Op::Add { network: (1, vec![fidl_ip!("192.0.2.0"), fidl_ip!("2001:db8::1")]), result: Ok(()) }),
992        (RegistryType::Fuchsia, Op::Add { network: (2, vec![fidl_ip!("192.0.2.1"), fidl_ip!("2001:db8::2")]), result: Ok(()) }),
993        (RegistryType::Fuchsia, Op::SetDefault { network_id: Some(2), result: Ok(()) }),
994        (RegistryType::Fuchsia, Op::Update { network: (2, vec![fidl_ip!("192.0.2.2"), fidl_ip!("2001:db8::3")]), result: Ok(()) }),
995        ], vec![
996        (2, vec![fidl_socket_addr!("192.0.2.2:53"), fidl_socket_addr!("[2001:db8::3]:53")]).to_dns_server_list(),
997    ]; "Fuchsia default present then updated")]
998    #[fuchsia::test]
999    async fn test_dns_tracking_across_registries<N: ToNetwork + Clone>(
1000        operations: &[(RegistryType, Op<N>)],
1001        dns_servers: Vec<fnp_socketproxy::DnsServerList>,
1002    ) -> Result<(), Error> {
1003        let (realm, mut dns_rx, _default_network_rx) = setup_test().await?;
1004        let starnix_networks = realm
1005            .root
1006            .connect_to_protocol_at_exposed_dir()
1007            .context("While connecting to StarnixNetworks")?;
1008        let fuchsia_networks = realm
1009            .root
1010            .connect_to_protocol_at_exposed_dir()
1011            .context("While connecting to FuchsiaNetworks")?;
1012
1013        let mut last_dns = None;
1014        for (registry, op) in operations {
1015            match registry {
1016                RegistryType::Starnix => {
1017                    op.execute_starnix(&starnix_networks).await?;
1018                }
1019                RegistryType::Fuchsia => {
1020                    op.execute_fuchsia(&fuchsia_networks).await?;
1021                }
1022            }
1023            // When the operation results in an error, we don't expect that to
1024            // result in an additional DNS update.
1025            if !op.is_err() {
1026                last_dns =
1027                    Some(dns_rx.next().await.expect("dns update expected after each operation"));
1028            }
1029        }
1030
1031        let mut last_dns = last_dns.expect("there should be at least one dns update");
1032        last_dns.sort_by_key(|a| a.source_network_id);
1033        assert_eq!(last_dns, dns_servers);
1034
1035        Ok(())
1036    }
1037
1038    trait ToDefaultNetworkUpdate {
1039        fn to_default_network_update_request(self) -> fnp_properties::DefaultNetworkUpdate;
1040    }
1041
1042    impl ToDefaultNetworkUpdate for (u64, (u32, Option<u32>)) {
1043        fn to_default_network_update_request(self) -> fnp_properties::DefaultNetworkUpdate {
1044            fnp_properties::DefaultNetworkUpdate {
1045                interface_id: Some(self.0),
1046                socket_marks: Some(fnet::Marks {
1047                    mark_1: Some(self.1.0),
1048                    mark_2: self.1.1,
1049                    __source_breaking: fidl::marker::SourceBreaking,
1050                }),
1051                __source_breaking: fidl::marker::SourceBreaking,
1052            }
1053        }
1054    }
1055
1056    #[test_case(&[
1057        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1058    ], vec![
1059        Default::default(),
1060    ]
1061    ; "Add but no default")]
1062    #[test_case(&[
1063        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1064        Op::SetDefault { network_id: Some(1), result: Ok(()) },
1065    ], vec![
1066        Default::default(),
1067        (1, (1, None)).to_default_network_update_request()
1068    ]
1069    ; "Add and set default")]
1070    #[test_case(&[
1071        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1072        Op::Add { network: (2, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1073        Op::SetDefault { network_id: Some(1), result: Ok(()) },
1074        Op::SetDefault { network_id: Some(2), result: Ok(()) },
1075    ], vec![
1076        Default::default(),
1077        Default::default(),
1078        (1, (1, None)).to_default_network_update_request(),
1079        (2, (2, None)).to_default_network_update_request(),
1080    ]
1081    ; "Add two and set default")]
1082    #[test_case(&[
1083        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
1084        Op::SetDefault { network_id: Some(1), result: Ok(()) },
1085        Op::SetDefault { network_id: None, result: Ok(()) },
1086        Op::Remove { network_id: 1, result: Ok(()) },
1087    ], vec![
1088        Default::default(),
1089        (1, (1, None)).to_default_network_update_request(),
1090        // An empty update means the default network has been removed.
1091        Default::default(),
1092        Default::default(),
1093    ]
1094    ; "Add default and delete")]
1095    #[fuchsia::test]
1096    async fn test_default_network_update<N: ToNetwork + Clone>(
1097        operations: &[Op<N>],
1098        default_server_updates: Vec<fnp_properties::DefaultNetworkUpdate>,
1099    ) -> Result<(), Error> {
1100        let (realm, _, mut default_networks_rx) = setup_test().await?;
1101        let starnix_networks = realm
1102            .root
1103            .connect_to_protocol_at_exposed_dir()
1104            .context("While connecting to StarnixNetworks")?;
1105
1106        let (_, default_networks) = future::join(
1107            async move {
1108                for op in operations {
1109                    op.execute_starnix(&starnix_networks).await?;
1110                }
1111                std::mem::drop(realm);
1112                Ok::<(), anyhow::Error>(())
1113            },
1114            async move {
1115                let mut default_networks = Vec::new();
1116                while let Some(upd) = default_networks_rx.next().await {
1117                    default_networks.push(upd);
1118                }
1119                default_networks
1120            },
1121        )
1122        .await;
1123
1124        assert_eq!(*default_networks, default_server_updates);
1125
1126        Ok(())
1127    }
1128}