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