Skip to main content

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, anyhow};
8use fidl::endpoints::RequestStream;
9use fidl_fuchsia_net_policy_socketproxy::{
10    self as fnp_socketproxy, FuchsiaNetworkInfo, FuchsiaNetworksRequest, Network, NetworkInfo,
11    NetworkRegistryAddError, NetworkRegistryRemoveError, NetworkRegistrySetDefaultError,
12    NetworkRegistryUpdateError, StarnixNetworksRequest,
13};
14use fuchsia_component::client::connect_to_protocol;
15use fuchsia_inspect_derive::{IValue, Inspect, Unit};
16use futures::channel::mpsc;
17use futures::lock::Mutex;
18use futures::{SinkExt as _, StreamExt as _, TryStreamExt as _};
19use log::{error, info, warn};
20use std::collections::HashMap;
21use std::sync::Arc;
22use thiserror::Error;
23
24use fidl_fuchsia_net as fnet;
25use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
26use fidl_fuchsia_posix_socket as fposix_socket;
27
28/// If there are networks registered, but no default has been set, this value
29/// will be used, otherwise the mark will be OptionalUint32::Unset(Empty).
30pub(crate) const DEFAULT_SOCKET_MARK: u32 = 0;
31
32pub(crate) struct RequestForwarder {
33    forwarder_rx: mpsc::Receiver<NetworkRegistryRequest>,
34    registry: fnp_socketproxy::NetworkRegistryProxy,
35}
36
37impl RequestForwarder {
38    pub(crate) fn new(
39        forwarder_rx: mpsc::Receiver<NetworkRegistryRequest>,
40    ) -> Result<Self, anyhow::Error> {
41        Ok(Self {
42            forwarder_rx,
43            registry: connect_to_protocol::<fnp_socketproxy::NetworkRegistryMarker>()
44                .context("error connecting to network registry")?,
45        })
46    }
47
48    pub(crate) async fn run(&mut self) -> Result<(), anyhow::Error> {
49        while let Some(request) = self.forwarder_rx.next().await {
50            match self.forward_request(request).await {
51                Err(e) => {
52                    info!(
53                        "FIDL error while forwarding request to delegated networks. \
54                            Protocol likely not available: {e:?}"
55                    );
56                    return Err(e);
57                }
58                Ok(Err(e)) => {
59                    error!(
60                        "Failed to forward request to delegated networks. Future updates \
61                                 will be ignored, and netcfg will not have complete state: {e:?}"
62                    );
63                    return Err(anyhow!("Delegated registry has inconsistent state: {e:?}"));
64                }
65                Ok(Ok(_)) => continue,
66            }
67        }
68        error!(
69            "RequestForwarder stream ended. No more requests will be \
70            forwarded to netcfg"
71        );
72        Ok(())
73    }
74
75    async fn forward_request(
76        &self,
77        request: NetworkRegistryRequest,
78    ) -> Result<Result<(), NetworkRegistryError>, anyhow::Error> {
79        info!("forwarding Starnix NetworkRegistry change to netcfg: {request:?}");
80        let res = match request {
81            NetworkRegistryRequest::SetDefault { network_id } => self
82                .registry
83                .set_default(&match network_id {
84                    Some(id) => fposix_socket::OptionalUint32::Value(id),
85                    None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
86                })
87                .await
88                .context("fidl error forwarding set_default")?
89                .map_err(|e| e.into()),
90            NetworkRegistryRequest::Add { network } => self
91                .registry
92                .add(&network)
93                .await
94                .context("fidl error forwarding add")?
95                .map_err(|e| e.into()),
96            NetworkRegistryRequest::Update { network } => self
97                .registry
98                .update(&network)
99                .await
100                .context("fidl error forwarding update")?
101                .map_err(|e| e.into()),
102            NetworkRegistryRequest::Remove { network_id } => self
103                .registry
104                .remove(network_id)
105                .await
106                .context("fidl error forwarding remove")?
107                .map_err(|e| e.into()),
108        };
109        Ok(res)
110    }
111}
112
113enum CommonErrors {
114    MissingNetworkId,
115    MissingNetworkInfo,
116}
117
118trait NetworkInfoExt {
119    fn mark(&self) -> Option<u32>;
120}
121
122impl NetworkInfoExt for NetworkInfo {
123    fn mark(&self) -> Option<u32> {
124        match self {
125            NetworkInfo::Starnix(s) => s.mark,
126            // Sockets express using Fuchsia's default network by setting
127            // the mark to None.
128            NetworkInfo::Fuchsia(_) | _ => None,
129        }
130    }
131}
132
133// Errors produced when communicating updates to
134// the socket proxy.
135#[derive(Clone, Debug, Error)]
136pub enum NetworkRegistryError {
137    #[error("Error during socketproxy Add: {0:?}")]
138    Add(NetworkRegistryAddError),
139    #[error("Error during socketproxy Remove: {0:?}")]
140    Remove(NetworkRegistryRemoveError),
141    #[error("Error during socketproxy SetDefault: {0:?}")]
142    SetDefault(NetworkRegistrySetDefaultError),
143    #[error("Error during socketproxy Update: {0:?}")]
144    Update(NetworkRegistryUpdateError),
145}
146
147impl From<NetworkRegistryAddError> for NetworkRegistryError {
148    fn from(error: NetworkRegistryAddError) -> Self {
149        NetworkRegistryError::Add(error)
150    }
151}
152
153impl From<NetworkRegistryRemoveError> for NetworkRegistryError {
154    fn from(error: NetworkRegistryRemoveError) -> Self {
155        NetworkRegistryError::Remove(error)
156    }
157}
158
159impl From<NetworkRegistrySetDefaultError> for NetworkRegistryError {
160    fn from(error: NetworkRegistrySetDefaultError) -> Self {
161        NetworkRegistryError::SetDefault(error)
162    }
163}
164
165impl From<NetworkRegistryUpdateError> for NetworkRegistryError {
166    fn from(error: NetworkRegistryUpdateError) -> Self {
167        NetworkRegistryError::Update(error)
168    }
169}
170
171#[derive(Clone, Debug, Error)]
172pub enum NetworkConversionError {
173    #[error("Could not convert id ({0}) to u32")]
174    InvalidInterfaceId(u64),
175}
176
177pub trait NetworkExt<I: fnet_interfaces_ext::FieldInterests> {
178    fn from_watcher_properties(
179        properties: &fnet_interfaces_ext::Properties<I>,
180    ) -> Result<Self, NetworkConversionError>
181    where
182        Self: Sized;
183}
184
185impl<I: fnet_interfaces_ext::FieldInterests> NetworkExt<I> for Network {
186    fn from_watcher_properties(
187        properties: &fnet_interfaces_ext::Properties<I>,
188    ) -> Result<Self, NetworkConversionError> {
189        // We expect interface ids to safely fit in the range of u32 values.
190        let network_id: u32 =
191            properties.id.get().try_into().or_else(|_| {
192                Err(NetworkConversionError::InvalidInterfaceId(properties.id.into()))
193            })?;
194        let network = Self {
195            network_id: Some(network_id),
196            info: Some(NetworkInfo::Fuchsia(FuchsiaNetworkInfo {
197                // No Fuchsia-specific information to provide.
198                ..Default::default()
199            })),
200            // DNS servers of Fuchsia networks are observable in netcfg already, so don't provide
201            // them to the Socketproxy. Socketproxy requires these fields to be provided so
202            // instantiate the v4 and v6 fields as empty vectors.
203            dns_servers: Some(fnp_socketproxy::NetworkDnsServers {
204                v4: Some(vec![]),
205                v6: Some(vec![]),
206                ..Default::default()
207            }),
208            ..Default::default()
209        };
210        Ok(network)
211    }
212}
213
214#[derive(Debug, Clone, PartialEq)]
215/// A generic version of a NetworkRegistry request with the responder removed.
216pub enum NetworkRegistryRequest {
217    /// Sets the default network.
218    ///
219    /// The network must have previously been registered by a call to `Add`.
220    SetDefault { network_id: Option<u32> },
221    /// Add a new network.
222    ///
223    /// This call will not return until the DNS servers have been successfully
224    /// updated in netcfg.
225    Add { network: Network },
226    /// Update a previously Added network.
227    ///
228    /// This call will not return until the DNS servers have been
229    /// successfully updated in netcfg.
230    Update { network: Network },
231    /// Remove a previously Added network.
232    ///
233    /// This call will not return until the DNS servers have been
234    /// successfully updated in netcfg.
235    Remove { network_id: u32 },
236}
237
238impl From<&fnp_socketproxy::NetworkRegistryRequest> for NetworkRegistryRequest {
239    fn from(value: &fnp_socketproxy::NetworkRegistryRequest) -> Self {
240        match *value {
241            fnp_socketproxy::NetworkRegistryRequest::SetDefault { network_id, responder: _ } => {
242                NetworkRegistryRequest::SetDefault {
243                    network_id: match network_id {
244                        fposix_socket::OptionalUint32::Value(v) => Some(v),
245                        fposix_socket::OptionalUint32::Unset(_) => None,
246                    },
247                }
248            }
249            fnp_socketproxy::NetworkRegistryRequest::Add { ref network, responder: _ } => {
250                NetworkRegistryRequest::Add { network: network.clone() }
251            }
252            fnp_socketproxy::NetworkRegistryRequest::Update { ref network, responder: _ } => {
253                NetworkRegistryRequest::Update { network: network.clone() }
254            }
255            fnp_socketproxy::NetworkRegistryRequest::Remove { network_id, responder: _ } => {
256                NetworkRegistryRequest::Remove { network_id }
257            }
258        }
259    }
260}
261impl From<&StarnixNetworksRequest> for NetworkRegistryRequest {
262    fn from(value: &StarnixNetworksRequest) -> Self {
263        match *value {
264            StarnixNetworksRequest::SetDefault { network_id, responder: _ } => {
265                NetworkRegistryRequest::SetDefault {
266                    network_id: match network_id {
267                        fposix_socket::OptionalUint32::Value(v) => Some(v),
268                        fposix_socket::OptionalUint32::Unset(_) => None,
269                    },
270                }
271            }
272            StarnixNetworksRequest::Add { ref network, responder: _ } => {
273                NetworkRegistryRequest::Add { network: network.clone() }
274            }
275            StarnixNetworksRequest::Update { ref network, responder: _ } => {
276                NetworkRegistryRequest::Update { network: network.clone() }
277            }
278            StarnixNetworksRequest::Remove { network_id, responder: _ } => {
279                NetworkRegistryRequest::Remove { network_id }
280            }
281        }
282    }
283}
284impl From<&FuchsiaNetworksRequest> for NetworkRegistryRequest {
285    fn from(value: &FuchsiaNetworksRequest) -> Self {
286        match *value {
287            FuchsiaNetworksRequest::SetDefault { network_id, responder: _ } => {
288                NetworkRegistryRequest::SetDefault {
289                    network_id: match network_id {
290                        fposix_socket::OptionalUint32::Value(v) => Some(v),
291                        fposix_socket::OptionalUint32::Unset(_) => None,
292                    },
293                }
294            }
295            FuchsiaNetworksRequest::Add { ref network, responder: _ } => {
296                NetworkRegistryRequest::Add { network: network.clone() }
297            }
298            FuchsiaNetworksRequest::Update { ref network, responder: _ } => {
299                NetworkRegistryRequest::Update { network: network.clone() }
300            }
301            FuchsiaNetworksRequest::Remove { network_id, responder: _ } => {
302                NetworkRegistryRequest::Remove { network_id }
303            }
304        }
305    }
306}
307
308/// A copy of fnp_socketproxy::Network that ensures that all fields are present.
309#[derive(Debug, Clone)]
310pub(crate) struct ValidatedNetwork {
311    network_id: u32,
312    info: NetworkInfo,
313}
314
315trait ValidateNetworkExt {
316    fn validate(self) -> Result<ValidatedNetwork, CommonErrors>;
317}
318
319impl ValidateNetworkExt for Network {
320    fn validate(self) -> Result<ValidatedNetwork, CommonErrors> {
321        match self {
322            Network { network_id: None, .. } => Err(CommonErrors::MissingNetworkId),
323            Network { info: None, .. } => Err(CommonErrors::MissingNetworkInfo),
324            Network { network_id: Some(network_id), info: Some(info), .. } => {
325                Ok(ValidatedNetwork { network_id, info })
326            }
327        }
328    }
329}
330
331macro_rules! common_errors_impl {
332    ($($p:ty),+) => {
333        $(
334            impl From<CommonErrors> for $p {
335                fn from(value: CommonErrors) -> Self {
336                    use CommonErrors::*;
337                    match value {
338                        MissingNetworkId => <$p>::MissingNetworkId,
339                        MissingNetworkInfo => <$p>::MissingNetworkInfo,
340                    }
341                }
342            }
343        )+
344    }
345}
346
347common_errors_impl!(
348    fnp_socketproxy::NetworkRegistryAddError,
349    fnp_socketproxy::NetworkRegistryUpdateError
350);
351
352/// NetworkRegistry tracks the networks that have been registered.
353#[derive(Inspect, Debug, Default)]
354struct NetworkRegistry {
355    networks: IValue<RegisteredNetworks>,
356
357    inspect_node: fuchsia_inspect::Node,
358}
359
360impl NetworkRegistry {
361    /// Returns whether the network registry has a default network set.
362    pub(crate) fn has_default_network(&self) -> bool {
363        self.networks.default_network_id.is_some()
364    }
365
366    /// Returns current socket mark for the default network.
367    pub(crate) fn current_mark(&self) -> Option<u32> {
368        self.networks.current_mark()
369    }
370}
371
372#[derive(Unit, Debug, Default)]
373struct MethodInspect {
374    successes: u32,
375    errors: u32,
376}
377
378#[derive(Unit, Default, Debug)]
379struct RegisteredNetworks {
380    default_network_id: Option<u32>,
381
382    #[inspect(skip)]
383    /// A mapping from network id to ValidatedNetwork for each registered network.
384    networks: HashMap<u32, ValidatedNetwork>,
385
386    adds: MethodInspect,
387    removes: MethodInspect,
388    set_defaults: MethodInspect,
389    updates: MethodInspect,
390}
391
392impl RegisteredNetworks {
393    fn add_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryAddResult {
394        let network = network.validate()?;
395        #[allow(clippy::map_entry, reason = "mass allow for https://fxbug.dev/381896734")]
396        if self.networks.contains_key(&network.network_id) {
397            self.adds.errors += 1;
398            Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId)
399        } else {
400            let _: Option<_> = self.networks.insert(network.network_id, network);
401            self.adds.successes += 1;
402            Ok(())
403        }
404    }
405
406    /// Empties the registered networks.
407    pub(crate) fn clear(&mut self) {
408        self.default_network_id = None;
409        self.networks.clear();
410    }
411
412    fn update_network(&mut self, network: Network) -> fnp_socketproxy::NetworkRegistryUpdateResult {
413        let network = network.validate()?;
414        let network_id = network.network_id;
415        *self
416            .networks
417            .get_mut(&network_id)
418            .ok_or(fnp_socketproxy::NetworkRegistryUpdateError::NotFound)
419            .inspect(|_| self.updates.successes += 1)
420            .inspect_err(|_| self.updates.errors += 1)? = network;
421        Ok(())
422    }
423
424    fn remove_network(&mut self, network_id: u32) -> fnp_socketproxy::NetworkRegistryRemoveResult {
425        if self.default_network_id == Some(network_id) {
426            self.removes.errors += 1;
427            return Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork);
428        }
429        match self.networks.remove(&network_id) {
430            Some(_) => {
431                self.removes.successes += 1;
432                Ok(())
433            }
434            None => {
435                self.removes.errors += 1;
436                Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound)
437            }
438        }
439    }
440
441    /// Update the currently set default network id.
442    ///
443    /// If `network_id` is None, the default network id will be unset.
444    fn set_default_network(
445        &mut self,
446        network_id: Option<u32>,
447    ) -> fnp_socketproxy::NetworkRegistrySetDefaultResult {
448        if let Some(network_id) = network_id {
449            if !self.networks.contains_key(&network_id) {
450                self.set_defaults.errors += 1;
451                return Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound);
452            }
453        }
454        self.set_defaults.successes += 1;
455        self.default_network_id = network_id;
456
457        Ok(())
458    }
459
460    fn current_mark(&self) -> Option<u32> {
461        match (self.default_network_id, self.networks.is_empty()) {
462            (None, false) => Some(DEFAULT_SOCKET_MARK),
463            (id, _) => id.and_then(|id| self.networks[&id].info.mark()),
464        }
465    }
466
467    fn len(&self) -> usize {
468        self.networks.len()
469    }
470}
471
472#[derive(Inspect, Clone, Debug, Default)]
473pub struct NetworkRegistries {
474    starnix: Arc<Mutex<NetworkRegistry>>,
475    fuchsia: Arc<Mutex<NetworkRegistry>>,
476}
477
478impl NetworkRegistries {
479    // When Fuchsia has a default network, then prefer its mark
480    // over any existing mark. When it is unset, then fallback
481    // to the mark provided by Starnix.
482    async fn current_mark(&self) -> Option<u32> {
483        {
484            let fuchsia = self.fuchsia.lock().await;
485            if fuchsia.has_default_network() {
486                return fuchsia.current_mark();
487            }
488        }
489
490        return self.starnix.lock().await.networks.current_mark();
491    }
492}
493
494#[derive(Debug)]
495enum RegistryType {
496    Starnix,
497    Fuchsia,
498}
499
500#[derive(Inspect, Debug)]
501pub struct Registry {
502    #[inspect(forward)]
503    networks: NetworkRegistries,
504    // Reflects the marks that are set on the sockets vended
505    // by this component.
506    marks: Arc<Mutex<crate::SocketMarks>>,
507    forwarder_tx: mpsc::Sender<NetworkRegistryRequest>,
508    starnix_occupant: Mutex<()>,
509    fuchsia_occupant: Mutex<()>,
510}
511
512macro_rules! handle_registry_request {
513    ($request_type:ident, $request:expr, $network_registry:expr, $registry_type:expr) => {{
514        let mut networks = $network_registry.networks.as_mut();
515        let (op, send): (_, Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static>) =
516            match $request {
517                $request_type::SetDefault { network_id, responder } => {
518                    let result = networks.set_default_network(match network_id {
519                        fposix_socket::OptionalUint32::Value(value) => Some(value),
520                        fposix_socket::OptionalUint32::Unset(_) => None,
521                    });
522                    ("set default", Box::new(move || responder.send(result)))
523                }
524                $request_type::Add { network, responder } => {
525                    let result = networks.add_network(network);
526                    ("add", Box::new(move || responder.send(result)))
527                }
528                $request_type::Update { network, responder } => {
529                    let result = networks.update_network(network);
530                    ("update", Box::new(move || responder.send(result)))
531                }
532                $request_type::Remove { network_id, responder } => {
533                    let result = networks.remove_network(network_id);
534                    ("remove", Box::new(move || responder.send(result)))
535                }
536            };
537        let new_mark = networks.current_mark();
538        info!(
539            "{:?} registry {op}. mark: {new_mark:?}, networks count: {}",
540            $registry_type,
541            networks.len()
542        );
543        std::mem::drop(networks);
544        send
545    }};
546}
547
548impl Registry {
549    pub(crate) fn new(
550        marks: Arc<Mutex<crate::SocketMarks>>,
551        forwarder_tx: mpsc::Sender<NetworkRegistryRequest>,
552    ) -> Result<Self, anyhow::Error> {
553        Ok(Self {
554            networks: Default::default(),
555            marks,
556            forwarder_tx,
557            starnix_occupant: Default::default(),
558            fuchsia_occupant: Default::default(),
559        })
560    }
561}
562
563impl Registry {
564    pub(crate) async fn run_starnix(
565        &self,
566        stream: fnp_socketproxy::StarnixNetworksRequestStream,
567    ) -> Result<(), Error> {
568        let _occupant = match self.starnix_occupant.try_lock() {
569            Some(o) => o,
570            None => {
571                warn!("Only one connection to StarnixNetworks is allowed at a time");
572                stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
573                return Ok(());
574            }
575        };
576
577        info!("Starting fuchsia.net.policy.socketproxy.StarnixNetworks server");
578        self.networks.starnix.lock().await.networks.as_mut().clear();
579        stream
580            .map(|result| result.context("failed request"))
581            .try_for_each(|request| {
582                async {
583                    self.forwarder_tx.clone().feed((&request).into()).await.unwrap_or_else(
584                        |e| {
585                            if !e.is_disconnected() {
586                                // Log if the feed fails for reasons other than disconnection.
587                                error!("Unable to feed request forward: {e:?}")
588                            }
589                        },
590                    );
591                    let mut network_registry = self.networks.starnix.lock().await;
592                    let send: Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static> =
593                        handle_registry_request!(
594                            StarnixNetworksRequest,
595                            request,
596                            network_registry,
597                            RegistryType::Starnix
598                        );
599                    std::mem::drop(network_registry);
600
601                    self.handle_state_changed().await?;
602                    send().context("error sending response")?;
603                    Ok(())
604                }
605            })
606            .await
607    }
608
609    pub(crate) async fn run_fuchsia(
610        &self,
611        stream: fnp_socketproxy::FuchsiaNetworksRequestStream,
612    ) -> Result<(), Error> {
613        let _occupant = match self.fuchsia_occupant.try_lock() {
614            Some(o) => o,
615            None => {
616                warn!("Only one connection to FuchsiaNetworks is allowed at a time");
617                stream.control_handle().shutdown_with_epitaph(fidl::Status::ACCESS_DENIED);
618                return Ok(());
619            }
620        };
621
622        info!("Starting fuchsia.net.policy.socketproxy.FuchsiaNetworks server");
623        self.networks.fuchsia.lock().await.networks.as_mut().clear();
624        stream
625            .map(|result| result.context("failed request"))
626            .try_for_each(|request| {
627                async {
628                    let mut network_registry = self.networks.fuchsia.lock().await;
629                    let send: Box<dyn FnOnce() -> Result<(), _> + Send + Sync + 'static> =
630                        handle_registry_request!(
631                            FuchsiaNetworksRequest,
632                            request,
633                            network_registry,
634                            RegistryType::Fuchsia
635                        );
636                    std::mem::drop(network_registry);
637
638                    self.handle_state_changed().await?;
639                    send().context("error sending response")?;
640                    Ok(())
641                }
642            })
643            .await
644    }
645
646    async fn handle_state_changed(&self) -> Result<(), Error> {
647        // Ensure the mark is updated prior to sending out the response
648        // and dropping the registry.
649        let mark = self.networks.current_mark().await;
650        self.marks.lock().await.set_mark(fnet::MARK_DOMAIN_SO_MARK, mark);
651        Ok(())
652    }
653}
654
655#[cfg(test)]
656mod test {
657    use super::*;
658    use fuchsia_component::server::ServiceFs;
659    use fuchsia_component_test::{
660        Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
661    };
662    use futures::channel::mpsc::Receiver;
663    use futures::future;
664    use net_declare::fidl_ip;
665    use pretty_assertions::assert_eq;
666    use socket_proxy_testing::{RegistryType, ToNetwork};
667    use test_case::test_case;
668
669    #[derive(Clone, Debug)]
670    enum Op<N: ToNetwork> {
671        SetDefault {
672            network_id: Option<u32>,
673            result: Result<(), fnp_socketproxy::NetworkRegistrySetDefaultError>,
674        },
675        Add {
676            network: N,
677            result: Result<(), fnp_socketproxy::NetworkRegistryAddError>,
678        },
679        Update {
680            network: N,
681            result: Result<(), fnp_socketproxy::NetworkRegistryUpdateError>,
682        },
683        Remove {
684            network_id: u32,
685            result: Result<(), fnp_socketproxy::NetworkRegistryRemoveError>,
686        },
687    }
688
689    impl<N: ToNetwork + Clone> From<&Op<N>> for NetworkRegistryRequest {
690        fn from(value: &Op<N>) -> Self {
691            match value {
692                Op::SetDefault { network_id, result: _ } => {
693                    NetworkRegistryRequest::SetDefault { network_id: *network_id }
694                }
695                Op::Add { network, result: _ } => NetworkRegistryRequest::Add {
696                    network: network.clone().to_network(RegistryType::Starnix),
697                },
698                Op::Update { network, result: _ } => NetworkRegistryRequest::Update {
699                    network: network.clone().to_network(RegistryType::Starnix),
700                },
701                Op::Remove { network_id, result: _ } => {
702                    NetworkRegistryRequest::Remove { network_id: *network_id }
703                }
704            }
705        }
706    }
707
708    macro_rules! execute {
709        ($self:ident, $proxy:ident, $registry:expr) => {{
710            match $self {
711                Op::SetDefault { network_id, result } => {
712                    assert_eq!(
713                        $proxy
714                            .set_default(&match network_id {
715                                Some(value) => fposix_socket::OptionalUint32::Value(*value),
716                                None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
717                            })
718                            .await?,
719                        *result
720                    )
721                }
722                Op::Add { network, result } => {
723                    assert_eq!($proxy.add(&network.to_network($registry)).await?, *result)
724                }
725                Op::Update { network, result } => {
726                    assert_eq!($proxy.update(&network.to_network($registry)).await?, *result)
727                }
728                Op::Remove { network_id, result } => {
729                    assert_eq!($proxy.remove(*network_id).await?, *result)
730                }
731            }
732            Ok(())
733        }};
734    }
735
736    impl<N: ToNetwork + Clone> Op<N> {
737        async fn execute_starnix(
738            &self,
739            starnix: &fnp_socketproxy::StarnixNetworksProxy,
740        ) -> Result<(), Error> {
741            execute!(self, starnix, RegistryType::Starnix)
742        }
743
744        async fn execute_fuchsia(
745            &self,
746            fuchsia: &fnp_socketproxy::FuchsiaNetworksProxy,
747        ) -> Result<(), Error> {
748            execute!(self, fuchsia, RegistryType::Fuchsia)
749        }
750    }
751
752    enum IncomingService {
753        StarnixNetworks(fnp_socketproxy::StarnixNetworksRequestStream),
754        FuchsiaNetworks(fnp_socketproxy::FuchsiaNetworksRequestStream),
755    }
756
757    async fn run_registry(
758        handles: LocalComponentHandles,
759        starnix_networks: Arc<Mutex<NetworkRegistry>>,
760        fuchsia_networks: Arc<Mutex<NetworkRegistry>>,
761        marks: Arc<Mutex<crate::SocketMarks>>,
762        forwarder_tx: mpsc::Sender<NetworkRegistryRequest>,
763    ) -> Result<(), Error> {
764        let mut fs = ServiceFs::new();
765        let _ = fs
766            .dir("svc")
767            .add_fidl_service(IncomingService::StarnixNetworks)
768            .add_fidl_service(IncomingService::FuchsiaNetworks);
769        let _ = fs.serve_connection(handles.outgoing_dir)?;
770
771        let registry = Registry {
772            networks: NetworkRegistries { starnix: starnix_networks, fuchsia: fuchsia_networks },
773            marks,
774            forwarder_tx,
775            starnix_occupant: Default::default(),
776            fuchsia_occupant: Default::default(),
777        };
778
779        fs.for_each_concurrent(0, |service| async {
780            match service {
781                IncomingService::StarnixNetworks(stream) => registry.run_starnix(stream).await,
782                IncomingService::FuchsiaNetworks(stream) => registry.run_fuchsia(stream).await,
783            }
784            .unwrap_or_else(|e| error!("{e:?}"))
785        })
786        .await;
787
788        Ok(())
789    }
790
791    async fn setup_test() -> Result<(RealmInstance, Receiver<NetworkRegistryRequest>), Error> {
792        let builder = RealmBuilder::new().await?;
793        let starnix_networks = Arc::new(Mutex::new(Default::default()));
794        let fuchsia_networks = Arc::new(Mutex::new(Default::default()));
795        let (forwarder_tx, forwarder_rx) = mpsc::channel(1);
796        let marks = Arc::new(Mutex::new(crate::SocketMarks::default()));
797        let registry = builder
798            .add_local_child(
799                "registry",
800                {
801                    let starnix_networks = starnix_networks.clone();
802                    let fuchsia_networks = fuchsia_networks.clone();
803                    let marks = marks.clone();
804                    move |handles: LocalComponentHandles| {
805                        Box::pin(run_registry(
806                            handles,
807                            starnix_networks.clone(),
808                            fuchsia_networks.clone(),
809                            marks.clone(),
810                            forwarder_tx.clone(),
811                        ))
812                    }
813                },
814                ChildOptions::new(),
815            )
816            .await?;
817
818        builder
819            .add_route(
820                Route::new()
821                    .capability(Capability::protocol::<fnp_socketproxy::StarnixNetworksMarker>())
822                    .from(&registry)
823                    .to(Ref::parent()),
824            )
825            .await?;
826
827        builder
828            .add_route(
829                Route::new()
830                    .capability(Capability::protocol::<fnp_socketproxy::FuchsiaNetworksMarker>())
831                    .from(&registry)
832                    .to(Ref::parent()),
833            )
834            .await?;
835
836        let realm = builder.build().await?;
837
838        Ok((realm, forwarder_rx))
839    }
840
841    #[test_case(&[
842        Op::Add { network: 1, result: Ok(()) },
843        Op::Update { network: 1, result: Ok(()) },
844        Op::Remove { network_id: 1, result: Ok(()) },
845    ]; "normal operation")]
846    #[test_case(&[
847        Op::Add { network: 1, result: Ok(()) },
848        Op::Add { network: 1, result: Err(fnp_socketproxy::NetworkRegistryAddError::DuplicateNetworkId) },
849    ]; "duplicate add")]
850    #[test_case(&[
851        Op::Update { network: 1, result: Err(fnp_socketproxy::NetworkRegistryUpdateError::NotFound) },
852    ]; "update missing")]
853    #[test_case(&[
854        Op::<u32>::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::NotFound) },
855    ]; "remove missing")]
856    #[test_case(&[
857        Op::<u32>::SetDefault { network_id: Some(1), result: Err(fnp_socketproxy::NetworkRegistrySetDefaultError::NotFound) },
858    ]; "set default missing")]
859    #[test_case(&[
860        Op::Add { network: 1, result: Ok(()) },
861        Op::SetDefault { network_id: Some(1), result: Ok(()) },
862        Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
863    ]; "remove default network")]
864    #[test_case(&[
865        Op::Add { network: 1, result: Ok(()) },
866        Op::SetDefault { network_id: Some(1), result: Ok(()) },
867        Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
868        Op::Add { network: 2, result: Ok(()) },
869        Op::SetDefault { network_id: Some(2), result: Ok(()) },
870        Op::Remove { network_id: 1, result: Ok(()) },
871    ]; "remove formerly default network")]
872    #[test_case(&[
873        Op::Add { network: 1, result: Ok(()) },
874        Op::SetDefault { network_id: Some(1), result: Ok(()) },
875        Op::Remove { network_id: 1, result: Err(fnp_socketproxy::NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)},
876        Op::SetDefault { network_id: None, result: Ok(()) },
877        Op::Remove { network_id: 1, result: Ok(()) },
878    ]; "remove last network")]
879    #[test_case(&[
880        Op::Add { network: 1, result: Ok(()) },
881        Op::Update { network: 1, result: Ok(()) },
882        Op::Add { network: 2, result: Ok(()) },
883        Op::Add { network: 3, result: Ok(()) },
884        Op::Add { network: 4, result: Ok(()) },
885        Op::Update { network: 4, result: Ok(()) },
886        Op::Update { network: 2, result: Ok(()) },
887        Op::Update { network: 3, result: Ok(()) },
888        Op::Add { network: 5, result: Ok(()) },
889        Op::Update { network: 5, result: Ok(()) },
890        Op::Add { network: 6, result: Ok(()) },
891        Op::Add { network: 7, result: Ok(()) },
892        Op::Add { network: 8, result: Ok(()) },
893        Op::Update { network: 8, result: Ok(()) },
894        Op::Update { network: 6, result: Ok(()) },
895        Op::Add { network: 9, result: Ok(()) },
896        Op::Update { network: 9, result: Ok(()) },
897        Op::Update { network: 7, result: Ok(()) },
898        Op::Add { network: 10, result: Ok(()) },
899        Op::Update { network: 10, result: Ok(()) },
900    ]; "many updates")]
901    #[fuchsia::test]
902    async fn test_operations<N: ToNetwork + Clone>(operations: &[Op<N>]) -> Result<(), Error> {
903        let (realm, _) = setup_test().await?;
904        let starnix_networks = realm
905            .root
906            .connect_to_protocol_at_exposed_dir()
907            .context("While connecting to StarnixNetworks")?;
908        let fuchsia_networks = realm
909            .root
910            .connect_to_protocol_at_exposed_dir()
911            .context("While connecting to FuchsiaNetworks")?;
912
913        for op in operations {
914            // Demonstrate that the same operations can be applied
915            // independently in both registries.
916            op.execute_starnix(&starnix_networks).await?;
917            op.execute_fuchsia(&fuchsia_networks).await?;
918        }
919
920        Ok(())
921    }
922
923    #[test_case(&[
924        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
925    ]
926    ; "Add but no default")]
927    #[test_case(&[
928        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
929        Op::SetDefault { network_id: Some(1), result: Ok(()) },
930    ]
931    ; "Add and set default")]
932    #[test_case(&[
933        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
934        Op::Add { network: (2, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
935        Op::SetDefault { network_id: Some(1), result: Ok(()) },
936        Op::SetDefault { network_id: Some(2), result: Ok(()) },
937    ]
938    ; "Add two and set default")]
939    #[test_case(&[
940        Op::Add { network: (1, vec![fidl_ip!("192.0.2.0")]), result: Ok(()) },
941        Op::SetDefault { network_id: Some(1), result: Ok(()) },
942        Op::SetDefault { network_id: None, result: Ok(()) },
943        Op::Remove { network_id: 1, result: Ok(()) },
944    ]
945    ; "Add default and delete")]
946    #[fuchsia::test]
947    async fn test_forward_network_update<N: ToNetwork + Clone + std::fmt::Debug>(
948        operations: &[Op<N>],
949    ) -> Result<(), Error> {
950        let (realm, mut forwarder_rx) = setup_test().await?;
951        let starnix_networks = realm
952            .root
953            .connect_to_protocol_at_exposed_dir()
954            .context("While connecting to StarnixNetworks")?;
955
956        let (_, seen_updates) = future::join(
957            async move {
958                for op in operations {
959                    op.execute_starnix(&starnix_networks).await?;
960                }
961                std::mem::drop(realm);
962                Ok::<(), anyhow::Error>(())
963            },
964            async move {
965                let mut forwarded_requests = Vec::new();
966                while let Some(req) = forwarder_rx.next().await {
967                    forwarded_requests.push(req);
968                }
969                forwarded_requests
970            },
971        )
972        .await;
973
974        let expected_updates =
975            operations.iter().map(NetworkRegistryRequest::from).collect::<Vec<_>>();
976        assert_eq!(expected_updates, seen_updates);
977
978        Ok(())
979    }
980}