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