Skip to main content

netcfg/network/
mod.rs

1// Copyright 2025 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
5use crate::InterfaceId;
6use crate::dns::DNS_PORT;
7use crate::telemetry::{NetworkEventMetadata, TelemetryEvent, TelemetrySender};
8use anyhow::Context as _;
9use assert_matches::assert_matches;
10use async_utils::stream::{StreamItem, StreamWithEpitaph, Tagged, WithEpitaph as _, WithTag as _};
11use dns_server_watcher::DnsServers;
12use fidl::endpoints::{ControlHandle as _, RequestStream as _};
13use futures::StreamExt as _;
14use log::{debug, error, info, warn};
15use policy_properties::NetworkTokenExt as _;
16use std::collections::HashMap;
17use std::collections::hash_map::Entry;
18
19mod reachability;
20mod token_registry;
21
22use fidl_fuchsia_net as fnet;
23use fidl_fuchsia_net_name as fnet_name;
24use fidl_fuchsia_net_policy_properties as fnp_properties;
25use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
26use fidl_fuchsia_net_reachability as freachability;
27use fidl_fuchsia_posix_socket as fposix_socket;
28use fuchsia_inspect_derive::{IValue, Inspect, Unit, WithInspect as _};
29pub use reachability::ReachabilityWatcherConnectionId;
30use reachability::{ReachabilityHandler, ReachabilityStream};
31
32// The id for each network, separated by network source.
33//
34// NB: These are separated in the case that the same underlying
35// interface id is used by Fuchsia and a delegated actor.
36#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub enum NetworkId {
38    Fuchsia(InterfaceId),
39    Delegated(InterfaceId),
40}
41
42impl std::fmt::Display for NetworkId {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            NetworkId::Fuchsia(interface_id) => write!(f, "fuchsia:{interface_id}"),
46            NetworkId::Delegated(interface_id) => write!(f, "delegated:{interface_id}"),
47        }
48    }
49}
50
51impl NetworkId {
52    pub fn get(&self) -> InterfaceId {
53        match self {
54            NetworkId::Fuchsia(interface_id) => *interface_id,
55            NetworkId::Delegated(interface_id) => *interface_id,
56        }
57    }
58
59    pub fn fuchsia<I: Into<InterfaceId>>(id: I) -> Self {
60        NetworkId::Fuchsia(id.into())
61    }
62
63    pub fn delegated<I: Into<InterfaceId>>(id: I) -> Self {
64        NetworkId::Delegated(id.into())
65    }
66
67    pub fn is_fuchsia(&self) -> bool {
68        matches!(self, NetworkId::Fuchsia(_))
69    }
70}
71
72// Using the interface's current v4/v6 default route properties and v4/v6 default route
73// properties provided through `fnet_interfaces_ext::Event::Changed`, determine
74// whether the interface gained or lost candidacy because of the event, or maintained
75// the same state.
76//
77// Returns None when the candidacy has not changed, Some(true) when the interface
78// gains candidacy, and Some(false) when the interface loses candidacy.
79pub(crate) fn determine_interface_state_changed(
80    prev: &fidl_fuchsia_net_interfaces::Properties,
81    curr: &fidl_fuchsia_net_interfaces_ext::Properties<
82        fidl_fuchsia_net_interfaces_ext::DefaultInterest,
83    >,
84) -> Option<bool> {
85    let was_candidate = (prev.has_default_ipv4_route.unwrap_or(curr.has_default_ipv4_route)
86        || prev.has_default_ipv6_route.unwrap_or(curr.has_default_ipv6_route))
87        && prev.online.unwrap_or(curr.online);
88    let is_candidate = (curr.has_default_ipv4_route || curr.has_default_ipv6_route) && curr.online;
89    (is_candidate != was_candidate).then_some(is_candidate)
90}
91
92#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93pub(crate) struct NetworkTokenContents {
94    network_id: NetworkId,
95    is_default: bool,
96}
97
98/// Declares a connection id and the allocator that mints it.
99///
100/// The id wraps a private counter, so the allocator is the only way to construct one outside the
101/// declaring module. Request handlers rely on that: an id that exists came from an allocator, so
102/// it is always present in the map it indexes.
103macro_rules! connection_id {
104    ($id:ident => $allocator:ident) => {
105        /// Identifies a single client connection.
106        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
107        pub struct $id(usize);
108
109        #[doc = concat!("Mints unique [`", stringify!($id), "`]s.")]
110        #[derive(Default)]
111        pub struct $allocator(usize);
112
113        impl $allocator {
114            pub fn allocate(&mut self) -> $id {
115                let id = $id(self.0);
116                self.0 += 1;
117                id
118            }
119        }
120    };
121}
122pub(crate) use connection_id;
123
124mod id {
125    // A `fuchsia.net.policy.properties.Networks` client connection.
126    connection_id!(NetworksConnectionId => NetworksConnectionIdAllocator);
127
128    // A `fuchsia.net.policy.properties.PropertyWatcher` client connection.
129    connection_id!(PropertyWatcherConnectionId => PropertyWatcherConnectionIdAllocator);
130}
131
132pub use id::{NetworksConnectionId, PropertyWatcherConnectionId};
133use id::{NetworksConnectionIdAllocator, PropertyWatcherConnectionIdAllocator};
134
135#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
136pub struct UpdateGeneration {
137    /// The current generation for `fuchsia.net.policy.properties.WatchDefault`.
138    /// Incremented each time the default network changes.
139    default_network: usize,
140
141    /// The current generation for `fuchsia.net.policy.properties.WatchProperties`.
142    /// Incremented each time a network property changes.
143    properties: usize,
144}
145
146#[derive(Clone, Debug, Default)]
147pub struct UpdateGenerations {
148    default_network: HashMap<NetworksConnectionId, usize>,
149    properties: HashMap<PropertyWatcherConnectionId, usize>,
150}
151
152impl UpdateGenerations {
153    fn default_network(&self, id: &NetworksConnectionId) -> Option<usize> {
154        self.default_network.get(id).copied()
155    }
156
157    fn set_default_network(&mut self, id: NetworksConnectionId, generation: UpdateGeneration) {
158        *self.default_network.entry(id).or_default() = generation.default_network;
159    }
160
161    fn remove_default(&mut self, id: &NetworksConnectionId) -> Option<usize> {
162        self.default_network.remove(id)
163    }
164
165    fn properties(&self, id: &PropertyWatcherConnectionId) -> Option<usize> {
166        self.properties.get(id).copied()
167    }
168
169    fn set_properties(&mut self, id: PropertyWatcherConnectionId, generation: UpdateGeneration) {
170        *self.properties.entry(id).or_default() = generation.properties;
171    }
172
173    fn remove_properties(&mut self, id: &PropertyWatcherConnectionId) -> Option<usize> {
174        self.properties.remove(id)
175    }
176}
177
178trait SetMark {
179    fn set_mark(&mut self, domain: fnet::MarkDomain, value: Option<u32>);
180}
181
182impl SetMark for fnet::Marks {
183    fn set_mark(&mut self, domain: fnet::MarkDomain, value: Option<u32>) {
184        match domain {
185            fnet::MarkDomain::Mark1 => self.mark_1 = value,
186            fnet::MarkDomain::Mark2 => self.mark_2 = value,
187        }
188    }
189}
190
191/// State for a registered `fuchsia.net.policy.properties.PropertyWatcher` client,
192/// including the network token, properties being watched, and any pending `Watch` responder.
193#[derive(Debug)]
194struct Registration {
195    token: fnp_properties::NetworkToken,
196    properties: fnp_properties::PropertyInterest,
197    responder: Option<fnp_properties::PropertyWatcherWatchResponder>,
198    /// Used to close the connection. Closing guarantees the watcher's [`ConnectionStream`]
199    /// yields the terminal item that this registration is removed on.
200    control_handle: fnp_properties::PropertyWatcherControlHandle,
201}
202
203impl Registration {
204    /// Closes the watcher's connection, with `epitaph` if one is provided.
205    ///
206    /// The registration is left in place: shutting the connection down guarantees its
207    /// [`ConnectionStream`] yields the terminal item that the registration is removed on.
208    fn close(&mut self, epitaph: Option<zx::Status>) {
209        // Shut down before releasing the responder below to communicate an epitaph
210        // when one is provided.
211        match epitaph {
212            Some(status) => self.control_handle.shutdown_with_epitaph(status),
213            None => self.control_handle.shutdown(),
214        }
215        // Drop the hanging responder, if any, so a closed watcher is no longer served.
216        self.responder = None;
217    }
218}
219
220/// State for a connected `fuchsia.net.policy.properties.Networks` client.
221#[derive(Debug)]
222struct NetworksClient {
223    /// The responder for a hanging `WatchDefault` call, if one is outstanding.
224    responder: Option<fnp_properties::NetworksWatchDefaultResponder>,
225    /// Used to close the connection. Closing guarantees the client's [`ConnectionStream`]
226    /// yields the terminal item that this entry is removed on.
227    control_handle: fnp_properties::NetworksControlHandle,
228}
229
230impl NetworksClient {
231    /// Closes the client's connection, with `epitaph` if one is provided.
232    ///
233    /// The client is left in place: shutting the connection down guarantees its
234    /// [`ConnectionStream`] yields the terminal item that the client is removed on.
235    fn close(&mut self, epitaph: Option<zx::Status>) {
236        // Shut down before releasing the responder below to communicate an epitaph
237        // when one is provided.
238        match epitaph {
239            Some(status) => self.control_handle.shutdown_with_epitaph(status),
240            None => self.control_handle.shutdown(),
241        }
242        // Drop the hanging responder, if any, so a closed client is no longer served.
243        self.responder = None;
244    }
245}
246
247#[derive(Debug, PartialEq, Default, Clone)]
248struct NetworkProperties {
249    socket_marks: Option<fnet::Marks>,
250    dns_servers: Vec<fnet_name::DnsServer_>,
251    // TODO(https://fxbug.dev/486892417): Use this field for snapshot metrics.
252    #[allow(dead_code)]
253    connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
254    name: Option<String>,
255    network_type: Option<fnp_socketproxy::NetworkType>,
256}
257
258impl NetworkProperties {
259    fn get_marks(&self) -> Option<&fnet::Marks> {
260        self.socket_marks.as_ref()
261    }
262}
263
264/// The current state of all networks sent to the NetworkRegistry.
265#[derive(Default, Clone)]
266struct RegisteredNetworks {
267    /// The current default network, determined by the priority rules in
268    /// `calculate_active_default`.
269    default_network: Option<NetworkId>,
270    /// The Starnix default network, as determined by Starnix.
271    starnix_default: Option<NetworkId>,
272    networks: HashMap<NetworkId, NetworkProperties>,
273    dns_servers: Vec<fnet_name::DnsServer_>,
274}
275
276impl RegisteredNetworks {
277    // Determine the active default network based on the starnix_default and network registry.
278    // When one or more Fuchsia networks are present, they should be prioritized over Starnix
279    // networks. The 'most prioritized' Fuchsia network is the one with the lowest ID.
280    fn calculate_active_default(&self) -> Option<NetworkId> {
281        // Note: Fuchsia networks are only added to the NetworkRegistry if they meet
282        // certain criteria (ex: have a default route and are online).
283        let first_fuchsia = self.networks.keys().filter(|id| id.is_fuchsia()).cloned().min();
284        if let Some(fd) = first_fuchsia {
285            return Some(fd);
286        }
287
288        // Fallback to starnix_default. If it is unset, no default network is available.
289        if let Some(starnix_default) = self.starnix_default {
290            // Ensure that the network is present in the network registry.
291            assert!(self.networks.contains_key(&starnix_default));
292        }
293        self.starnix_default
294    }
295
296    // Handle updates to the active default network.
297    //
298    // Returns `Some(DefaultChangedEvent)` if the new default network
299    // is different from the old one, otherwise `None`.
300    fn handle_default_network_update(&mut self) -> Option<DefaultChangedEvent> {
301        let next_default = self.calculate_active_default();
302        if next_default != self.default_network {
303            let old_default = self.default_network;
304            self.default_network = next_default;
305            Some(DefaultChangedEvent { previous_default: old_default })
306        } else {
307            None
308        }
309    }
310
311    fn apply(&mut self, update: NetworkRegistryUpdate) -> RegistryUpdateResult {
312        match update {
313            NetworkRegistryUpdate::UnsetDefaultNetwork => {
314                // Handle Starnix unsetting its default network.
315                self.starnix_default = None;
316                RegistryUpdateResult {
317                    event: UpdateApplied::None,
318                    default_changed: self.handle_default_network_update(),
319                }
320            }
321            NetworkRegistryUpdate::ChangeNetwork(network_id, network_change) => {
322                match network_change {
323                    NetworkUpdate::Properties(event) => RegistryUpdateResult {
324                        event: self.handle_changed_network(network_id, event),
325                        default_changed: self.handle_default_network_update(),
326                    },
327                    NetworkUpdate::Remove => {
328                        if self.starnix_default == Some(network_id) {
329                            error!("Cannot remove the default delegated network. Update ignored.");
330                            RegistryUpdateResult {
331                                event: UpdateApplied::None,
332                                default_changed: None,
333                            }
334                        } else if self.networks.remove(&network_id).is_some() {
335                            // Elect fallback default network internally.
336                            RegistryUpdateResult {
337                                event: UpdateApplied::NetworkRemoved(network_id),
338                                default_changed: self.handle_default_network_update(),
339                            }
340                        } else {
341                            error!(
342                                "Cannot remove a non-existent network ({network_id:?}). \
343                                Update ignored."
344                            );
345                            RegistryUpdateResult {
346                                event: UpdateApplied::None,
347                                default_changed: None,
348                            }
349                        }
350                    }
351                    NetworkUpdate::MakeDefault => {
352                        match network_id {
353                            // Fuchsia networks are always the default network when present. Netcfg
354                            // does not use this API to set a Fuchsia network as the default.
355                            NetworkId::Fuchsia(_) => {}
356                            NetworkId::Delegated(_) => self.starnix_default = Some(network_id),
357                        }
358                        let default_changed = self.handle_default_network_update();
359                        RegistryUpdateResult { event: UpdateApplied::None, default_changed }
360                    }
361                }
362            }
363            NetworkRegistryUpdate::UpdateDns(dns_servers) => {
364                let event = if self.dns_servers != dns_servers {
365                    self.dns_servers = dns_servers;
366                    UpdateApplied::DnsChanged
367                } else {
368                    UpdateApplied::None
369                };
370                RegistryUpdateResult { event, default_changed: None }
371            }
372        }
373    }
374
375    // Handle the `NetworkPropertiesChange` in a `NetworkRegistryUpdate`, determining
376    // whether network properties changed as a result of the update.
377    //
378    // Returns an `UpdateApplied::NetworkChanged` event if this is a valid change.
379    fn handle_changed_network(
380        &mut self,
381        network_id: NetworkId,
382        event: NetworkPropertiesChange,
383    ) -> UpdateApplied {
384        let NetworkPropertiesChange {
385            added,
386            marks: socket_marks,
387            dns_servers: changed_dns_servers,
388            connectivity_state,
389            name,
390            network_type,
391        } = event;
392        let entry = self.networks.entry(network_id);
393        let result = match (added, &entry, network_id, socket_marks) {
394            (true, Entry::Occupied(_), _, _) => Err("add already added network"),
395            (false, Entry::Vacant(_), _, _) => Err("update a non-added network"),
396            (_, _, NetworkId::Fuchsia(_), Some(_)) => Err("have a fuchsia network with marks"),
397            (_, _, NetworkId::Delegated(_), None) => Err("have a delegated network without marks"),
398            (_, entry, NetworkId::Fuchsia(_), None) => {
399                let new_dns = changed_dns_servers.unwrap_or_default();
400                let changed_dns = match entry {
401                    Entry::Occupied(e) => e.get().dns_servers != new_dns,
402                    // When adding a new network, set `changed_dns` to true so responders receive
403                    // an explicit initial state.
404                    Entry::Vacant(_) => true,
405                };
406                Ok((
407                    NetworkProperties { dns_servers: new_dns, ..Default::default() },
408                    added,
409                    changed_dns,
410                ))
411            }
412            (_, entry, NetworkId::Delegated(_), Some(socket_marks)) => {
413                let new_dns = changed_dns_servers.unwrap_or_default();
414                let (changed_marks, changed_dns) = match entry {
415                    Entry::Occupied(e) => {
416                        (e.get().get_marks() != Some(&socket_marks), e.get().dns_servers != new_dns)
417                    }
418                    // When adding a new network, set both `changed_marks` and `changed_dns` to
419                    // true so responders receive an explicit initial state.
420                    Entry::Vacant(_) => (true, true),
421                };
422                Ok((
423                    NetworkProperties {
424                        socket_marks: Some(socket_marks),
425                        dns_servers: new_dns,
426                        ..Default::default()
427                    },
428                    changed_marks,
429                    changed_dns,
430                ))
431            }
432        };
433
434        match result {
435            Ok((mut properties, changed_marks, changed_dns)) => {
436                properties.connectivity_state = connectivity_state;
437                properties.network_type = network_type;
438                properties.name = name.clone();
439                let _ = entry.insert_entry(properties);
440                UpdateApplied::NetworkChanged {
441                    network_id,
442                    added,
443                    changed_marks,
444                    changed_dns,
445                    name,
446                    network_type,
447                }
448            }
449            Err(e) => {
450                error!("Cannot {e}. Update ignored.");
451                UpdateApplied::None
452            }
453        }
454    }
455
456    /// Returns the DNS servers for the default network if it is a Fuchsia network,
457    /// otherwise returns a concatenation of DNS servers from all delegated networks.
458    /// TODO(https://fxbug.dev/428712735): Remove once dns-resolver learns about DNS
459    /// via NetworkProperties.
460    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
461        if let Some(NetworkId::Fuchsia(if_id)) = self.default_network {
462            self.networks
463                .get(&NetworkId::Fuchsia(if_id))
464                .map(|p| p.dns_servers.clone())
465                .unwrap_or_default()
466        } else {
467            self.networks
468                .iter()
469                .filter(|(id, _)| matches!(id, NetworkId::Delegated(_)))
470                .flat_map(|(_, p)| &p.dns_servers)
471                .cloned()
472                .collect()
473        }
474    }
475
476    fn update_inspect(&self, node: &fuchsia_inspect::Node) {
477        node.clear_recorded();
478
479        if let Some(default_network) = &self.default_network {
480            node.record_string("default_network", default_network.to_string());
481        }
482
483        if let Some(starnix_default) = &self.starnix_default {
484            node.record_string("starnix_default", starnix_default.to_string());
485        }
486
487        let mut sorted_networks: Vec<_> = self.networks.iter().collect();
488        sorted_networks.sort_by_key(|(id, _)| *id);
489
490        for (network_id, properties) in sorted_networks {
491            let network_node_name = match network_id {
492                NetworkId::Fuchsia(InterfaceId(id)) => format!("fuchsia_{id}"),
493                NetworkId::Delegated(InterfaceId(id)) => {
494                    format!("delegated_{id}")
495                }
496            };
497
498            node.record_child(network_node_name, |net_node| {
499                if let Some(name) = &properties.name {
500                    net_node.record_string("name", name);
501                }
502                if let Some(network_type) = &properties.network_type {
503                    net_node.record_string("network_type", network_type.as_str());
504                }
505                if let Some(connectivity_state) = &properties.connectivity_state {
506                    net_node.record_string("connectivity_state", connectivity_state.as_str());
507                }
508            });
509        }
510    }
511}
512
513trait NetworkTypeInspectExt {
514    fn as_str(&self) -> &'static str;
515}
516
517impl NetworkTypeInspectExt for fnp_socketproxy::NetworkType {
518    fn as_str(&self) -> &'static str {
519        match self {
520            fnp_socketproxy::NetworkType::Unknown => "Unknown",
521            fnp_socketproxy::NetworkType::Ethernet => "Ethernet",
522            fnp_socketproxy::NetworkType::Wifi => "Wifi",
523            fnp_socketproxy::NetworkType::Bluetooth => "Bluetooth",
524            fnp_socketproxy::NetworkType::Cellular => "Cellular",
525            fnp_socketproxy::NetworkType::__SourceBreaking { .. } => "Unknown",
526        }
527    }
528}
529
530trait ConnectivityStateInspectExt {
531    fn as_str(&self) -> &'static str;
532}
533
534impl ConnectivityStateInspectExt for fnp_socketproxy::ConnectivityState {
535    fn as_str(&self) -> &'static str {
536        match self {
537            fnp_socketproxy::ConnectivityState::NoConnectivity => "NoConnectivity",
538            fnp_socketproxy::ConnectivityState::LocalConnectivity => "LocalConnectivity",
539            fnp_socketproxy::ConnectivityState::PartialConnectivity => "PartialConnectivity",
540            fnp_socketproxy::ConnectivityState::FullConnectivity => "FullConnectivity",
541            fnp_socketproxy::ConnectivityState::__SourceBreaking { .. } => "Unknown",
542        }
543    }
544}
545
546/// Helper trait for building property update lists based on a client's registration.
547trait PropertyUpdates {
548    fn add_socket_marks(
549        &mut self,
550        network_registry: &RegisteredNetworks,
551        network: &NetworkTokenContents,
552        watched_properties: fnp_properties::PropertyInterest,
553    );
554    fn add_dns(
555        &mut self,
556        network_registry: &RegisteredNetworks,
557        network: &NetworkTokenContents,
558        watched_properties: fnp_properties::PropertyInterest,
559    );
560}
561
562impl PropertyUpdates for fnp_properties::PropertyUpdate {
563    fn add_socket_marks(
564        &mut self,
565        network_registry: &RegisteredNetworks,
566        network: &NetworkTokenContents,
567        watched_properties: fnp_properties::PropertyInterest,
568    ) {
569        if !watched_properties.contains(fnp_properties::PropertyInterest::SOCKET_MARKS) {
570            return;
571        }
572
573        match network_registry.networks.get(&network.network_id) {
574            Some(network) => {
575                if let Some(socket_marks) = network.get_marks() {
576                    self.socket_marks = Some(socket_marks.clone());
577                }
578                return;
579            }
580            None => {
581                error!(
582                    "State is inconsistent. We attempted to add marks for a \
583            network that is not known: {:?}",
584                    network.network_id
585                );
586            }
587        }
588    }
589
590    fn add_dns(
591        &mut self,
592        network_registry: &RegisteredNetworks,
593        network: &NetworkTokenContents,
594        watched_properties: fnp_properties::PropertyInterest,
595    ) {
596        if !watched_properties.contains(fnp_properties::PropertyInterest::DNS_CONFIGURATION) {
597            return;
598        }
599
600        let interface_id = network.network_id;
601        self.dns_configuration = Some(fnp_properties::DnsConfiguration {
602            servers: Some(
603                network_registry
604                    .dns_servers
605                    .iter()
606                    .filter(|d| {
607                        match &d.source {
608                            Some(source) => match source {
609                                fnet_name::DnsServerSource::StaticSource(_) => true,
610                                // `extract_dns_servers` prefers IPv4 DNS
611                                // over IPv6 DNS when DNS servers are
612                                // provided by the SocketProxy.
613                                fnet_name::DnsServerSource::SocketProxy(
614                                    fnet_name::SocketProxyDnsServerSource {
615                                        source_interface, ..
616                                    },
617                                ) => match (interface_id, source_interface) {
618                                    (_, None) => true,
619                                    (id1, Some(id2)) => {
620                                        Ok(id1)
621                                            == InterfaceId::try_from(*id2)
622                                                .map(|id| NetworkId::delegated(id))
623                                    }
624                                },
625                                fnet_name::DnsServerSource::Dhcp(
626                                    fnet_name::DhcpDnsServerSource { source_interface, .. },
627                                )
628                                | fnet_name::DnsServerSource::Ndp(
629                                    fnet_name::NdpDnsServerSource { source_interface, .. },
630                                )
631                                | fnet_name::DnsServerSource::Dhcpv6(
632                                    fnet_name::Dhcpv6DnsServerSource { source_interface, .. },
633                                ) => match (interface_id, source_interface) {
634                                    (_, None) => true,
635                                    (id1, Some(id2)) => {
636                                        Ok(id1)
637                                            == InterfaceId::try_from(*id2)
638                                                .map(|id| NetworkId::fuchsia(id))
639                                    }
640                                },
641
642                                _ => {
643                                    error!("unhandled DnsServerSource: {source:?}");
644                                    false
645                                }
646                            },
647
648                            // No source, assume static source, so include it.
649                            None => true,
650                        }
651                    })
652                    .cloned()
653                    .collect::<Vec<_>>(),
654            ),
655            ..Default::default()
656        });
657    }
658}
659
660/// An event representing the properties that changed for a network.
661#[derive(Clone, Debug, Default)]
662pub struct NetworkPropertiesChange {
663    /// When true, this is a new network being added. Otherwise, this is an
664    /// update to an existing network.
665    pub added: bool,
666    /// The new marks for the network.
667    pub marks: Option<fnet::Marks>,
668    /// If present, contains the new DNS servers for this network.
669    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
670    /// The new connectivity state of the network.
671    pub connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
672    /// The name of the network.
673    pub name: Option<String>,
674    /// The transport type of the network.
675    pub network_type: Option<fnp_socketproxy::NetworkType>,
676}
677
678#[derive(Debug, Clone)]
679pub enum NetworkUpdate {
680    /// Change a network's properties.
681    Properties(NetworkPropertiesChange),
682    Remove,
683    MakeDefault,
684}
685
686#[derive(Debug, PartialEq, Eq, Clone)]
687struct DefaultChangedEvent {
688    previous_default: Option<NetworkId>,
689}
690
691#[derive(Debug, PartialEq, Eq)]
692struct RegistryUpdateResult {
693    event: UpdateApplied,
694    /// Stores whether the default network has changed, and the previous default
695    /// network, if any.
696    default_changed: Option<DefaultChangedEvent>,
697}
698
699#[derive(Debug, PartialEq, Eq, Clone)]
700enum UpdateApplied {
701    /// No update was performed.
702    None,
703
704    /// Whether the DNS servers changed.
705    DnsChanged,
706
707    /// Network was added or updated, contains the NetworkId of the added network.
708    NetworkChanged {
709        network_id: NetworkId,
710        added: bool,
711        changed_marks: bool,
712        changed_dns: bool,
713        name: Option<String>,
714        network_type: Option<fnp_socketproxy::NetworkType>,
715    },
716
717    /// Network was removed, contains the NetworkId of the removed network.
718    NetworkRemoved(NetworkId),
719}
720
721#[derive(Debug, Clone)]
722pub enum NetworkRegistryUpdate {
723    UnsetDefaultNetwork,
724    ChangeNetwork(NetworkId, NetworkUpdate),
725    UpdateDns(Vec<fnet_name::DnsServer_>),
726}
727
728impl NetworkRegistryUpdate {
729    pub fn unset_default() -> Self {
730        NetworkRegistryUpdate::UnsetDefaultNetwork
731    }
732
733    pub fn dns(dns_servers: &DnsServers) -> Self {
734        // TODO(https://fxbug.dev/477980011): Switch to deriving dns servers from
735        // NetworkRegistry updates.
736        NetworkRegistryUpdate::UpdateDns(dns_servers.consolidated_dns_servers())
737    }
738}
739
740/// A validated copy of [`fnp_socketproxy::Network`] ensuring required fields
741/// are present for delegated (Starnix) networks.
742///
743/// Netcfg discovers native Fuchsia networks internally from Netstack, so
744/// external `NetworkRegistry` requests handled by this type are strictly
745/// delegated networks registered by runtimes like Starnix.
746struct ValidatedNetwork {
747    network_id: NetworkId,
748    marks: fnet::Marks,
749    dns_servers: Vec<fnet_name::DnsServer_>,
750    connectivity: Option<fnp_socketproxy::ConnectivityState>,
751    name: Option<String>,
752    network_type: Option<fnp_socketproxy::NetworkType>,
753}
754
755#[derive(Debug, PartialEq, Eq)]
756enum NetworkValidationError {
757    MissingNetworkId,
758    MissingNetworkInfo,
759}
760
761impl From<NetworkValidationError> for fnp_socketproxy::NetworkRegistryAddError {
762    fn from(error: NetworkValidationError) -> Self {
763        match error {
764            NetworkValidationError::MissingNetworkId => Self::MissingNetworkId,
765            NetworkValidationError::MissingNetworkInfo => Self::MissingNetworkInfo,
766        }
767    }
768}
769
770impl From<NetworkValidationError> for fnp_socketproxy::NetworkRegistryUpdateError {
771    fn from(error: NetworkValidationError) -> Self {
772        match error {
773            NetworkValidationError::MissingNetworkId => Self::MissingNetworkId,
774            NetworkValidationError::MissingNetworkInfo => Self::MissingNetworkInfo,
775        }
776    }
777}
778
779impl TryFrom<fnp_socketproxy::Network> for ValidatedNetwork {
780    type Error = NetworkValidationError;
781
782    fn try_from(network: fnp_socketproxy::Network) -> Result<Self, Self::Error> {
783        let raw_network_id = network.network_id.ok_or(NetworkValidationError::MissingNetworkId)?;
784        let network_id = InterfaceId::try_from(raw_network_id)
785            .map(NetworkId::delegated)
786            .map_err(|_| NetworkValidationError::MissingNetworkId)?;
787        let Some(fnp_socketproxy::NetworkInfo::Starnix(info)) = network.info else {
788            return Err(NetworkValidationError::MissingNetworkInfo);
789        };
790
791        let mut marks = fnet::Marks::default();
792        marks.set_mark(fnet::MARK_DOMAIN_SO_MARK, info.mark);
793
794        let dns_servers =
795            NetpolNetworksService::extract_dns_servers(&network.dns_servers, raw_network_id.into());
796
797        Ok(Self {
798            network_id,
799            marks,
800            dns_servers,
801            connectivity: network.connectivity,
802            name: network.name,
803            network_type: network.network_type,
804        })
805    }
806}
807
808/// The result of a delegated network update.
809///
810/// Returned to the main event loop to propagate system-wide configuration
811/// changes (such as DNS server updates) and notify active watchers.
812#[derive(Debug, Default, PartialEq)]
813pub struct DelegatedNetworkUpdateResult {
814    /// If present, contains the new consolidated DNS servers known by the
815    /// network registry.
816    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
817}
818
819/// A public wrapper enum for FIDL request streams accepted by
820/// [`NetpolNetworksService::add_stream`].
821///
822/// This type represents incoming streams before they are attached to the service's event loop.
823pub enum NetworkRequestStream {
824    Networks(fnp_properties::NetworksRequestStream),
825    NetworkTokenResolver(fnp_properties::NetworkTokenResolverRequestStream),
826    PropertyWatcher {
827        connection_id: PropertyWatcherConnectionId,
828        stream: fnp_properties::PropertyWatcherRequestStream,
829    },
830    DelegatedNetworks(fnp_socketproxy::NetworkRegistryRequestStream),
831    Reachability(freachability::MonitorRequestStream),
832}
833
834impl From<fnp_properties::NetworksRequestStream> for NetworkRequestStream {
835    fn from(s: fnp_properties::NetworksRequestStream) -> Self {
836        Self::Networks(s)
837    }
838}
839impl From<fnp_properties::NetworkTokenResolverRequestStream> for NetworkRequestStream {
840    fn from(s: fnp_properties::NetworkTokenResolverRequestStream) -> Self {
841        Self::NetworkTokenResolver(s)
842    }
843}
844
845impl From<fnp_socketproxy::NetworkRegistryRequestStream> for NetworkRequestStream {
846    fn from(s: fnp_socketproxy::NetworkRegistryRequestStream) -> Self {
847        Self::DelegatedNetworks(s)
848    }
849}
850
851impl From<freachability::MonitorRequestStream> for NetworkRequestStream {
852    fn from(s: freachability::MonitorRequestStream) -> Self {
853        Self::Reachability(s)
854    }
855}
856
857impl From<(PropertyWatcherConnectionId, fnp_properties::PropertyWatcherRequestStream)>
858    for NetworkRequestStream
859{
860    fn from(
861        s: (PropertyWatcherConnectionId, fnp_properties::PropertyWatcherRequestStream),
862    ) -> Self {
863        let (connection_id, stream) = s;
864        Self::PropertyWatcher { connection_id, stream }
865    }
866}
867
868/// A client request stream tagged with its connection id, ending in an epitaph carrying that id.
869///
870/// The epitaph is what the event loop cleans up on; without it [`futures::stream::SelectAll`]
871/// would drop the finished stream silently and the connection's state would leak.
872pub(crate) type ConnectionStream<Id, S> = StreamWithEpitaph<Tagged<Id, S>, Id>;
873
874/// Flattens an item from a [`ConnectionStream`] into its connection id and the request, where
875/// `None` marks the end of the connection.
876fn split_connection_item<Id, T>(item: StreamItem<(Id, T), Id>) -> (Id, Option<T>) {
877    match item {
878        StreamItem::Item((id, request)) => (id, Some(request)),
879        StreamItem::Epitaph(id) => (id, None),
880    }
881}
882
883/// The single-client `NetworkRegistry` request stream, ending in an epitaph so the
884/// event loop observes when the client disconnects.
885type DelegatedNetworksStream = StreamWithEpitaph<fnp_socketproxy::NetworkRegistryRequestStream, ()>;
886
887/// An internal wrapper enum for active FIDL request streams stored in
888/// [`NetpolNetworksService::streams`].
889enum NetworkRequestStreamInner {
890    Networks(ConnectionStream<NetworksConnectionId, fnp_properties::NetworksRequestStream>),
891    NetworkTokenResolver(fnp_properties::NetworkTokenResolverRequestStream),
892    PropertyWatcher(
893        ConnectionStream<PropertyWatcherConnectionId, fnp_properties::PropertyWatcherRequestStream>,
894    ),
895    DelegatedNetworks(DelegatedNetworksStream),
896    Reachability(ReachabilityStream),
897}
898
899impl futures::Stream for NetworkRequestStreamInner {
900    type Item = NetworkRequest;
901
902    fn poll_next(
903        mut self: std::pin::Pin<&mut Self>,
904        cx: &mut std::task::Context<'_>,
905    ) -> std::task::Poll<Option<Self::Item>> {
906        match *self {
907            NetworkRequestStreamInner::Networks(ref mut stream) => {
908                stream.poll_next_unpin(cx).map(|o| {
909                    o.map(split_connection_item)
910                        .map(|(id, request)| NetworkAttributesRequest { id, request })
911                        .map(NetworkRequest::NetworkAttributes)
912                })
913            }
914            NetworkRequestStreamInner::NetworkTokenResolver(ref mut stream) => {
915                stream.poll_next_unpin(cx).map(|o| {
916                    o.map(|request| NetworkTokenResolverRequest { request })
917                        .map(NetworkRequest::NetworkTokenResolver)
918                })
919            }
920            NetworkRequestStreamInner::PropertyWatcher(ref mut stream) => {
921                stream.poll_next_unpin(cx).map(|o| {
922                    o.map(split_connection_item)
923                        .map(|(id, request)| PropertyWatcherRequest { id, request })
924                        .map(NetworkRequest::PropertyWatcher)
925                })
926            }
927            NetworkRequestStreamInner::DelegatedNetworks(ref mut stream) => {
928                stream.poll_next_unpin(cx).map(|o| {
929                    o.map(|item| match item {
930                        StreamItem::Item(request) => {
931                            DelegatedNetworksRequest { request: Some(request) }
932                        }
933                        StreamItem::Epitaph(()) => DelegatedNetworksRequest { request: None },
934                    })
935                    .map(NetworkRequest::DelegatedNetworks)
936                })
937            }
938            NetworkRequestStreamInner::Reachability(ref mut stream) => {
939                stream.poll_next_unpin(cx).map(|o| {
940                    o.map(split_connection_item)
941                        .map(|(id, request)| ReachabilityRequest { id, request })
942                        .map(NetworkRequest::Reachability)
943                })
944            }
945        }
946    }
947}
948
949/// A wrapper for [`fnp_properties::NetworksRequest`] that includes the
950/// [`NetworksConnectionId`] of the connection that sent the request, or `None` if the client
951/// connection closed.
952pub struct NetworkAttributesRequest {
953    pub id: NetworksConnectionId,
954    pub request: Option<Result<fnp_properties::NetworksRequest, fidl::Error>>,
955}
956
957/// A wrapper for [`fnp_properties::NetworkTokenResolverRequest`].
958pub struct NetworkTokenResolverRequest {
959    pub request: Result<fnp_properties::NetworkTokenResolverRequest, fidl::Error>,
960}
961
962/// A wrapper for [`fnp_properties::PropertyWatcherRequest`] that includes the
963/// [`PropertyWatcherConnectionId`] of the connection that sent the request, or `None` if the
964/// client connection closed.
965pub struct PropertyWatcherRequest {
966    pub id: PropertyWatcherConnectionId,
967    pub request: Option<Result<fnp_properties::PropertyWatcherRequest, fidl::Error>>,
968}
969
970/// A wrapper for [`fnp_socketproxy::NetworkRegistryRequest`], or `None` if the client
971/// connection closed.
972pub struct DelegatedNetworksRequest {
973    pub request: Option<Result<fnp_socketproxy::NetworkRegistryRequest, fidl::Error>>,
974}
975
976/// A wrapper for [`freachability::MonitorRequest`] that includes the
977/// [`ReachabilityWatcherConnectionId`] and indicates whether the watcher stream is still open.
978pub struct ReachabilityRequest {
979    pub id: ReachabilityWatcherConnectionId,
980    /// Note: Storing the Request inside the Option allows us to distinguish between the channel
981    /// being closed and a Watch request being sent to support cleaner channel-closing logic.
982    pub request: Option<Result<freachability::MonitorRequest, fidl::Error>>,
983}
984
985/// An enum representing all possible events that can be received by the NetpolNetworksService
986/// event loop.
987pub enum NetworkRequest {
988    NetworkAttributes(NetworkAttributesRequest),
989    NetworkTokenResolver(NetworkTokenResolverRequest),
990    PropertyWatcher(PropertyWatcherRequest),
991    DelegatedNetworks(DelegatedNetworksRequest),
992    Reachability(ReachabilityRequest),
993}
994
995impl futures::Stream for NetpolNetworksService {
996    type Item = NetworkRequest;
997
998    fn poll_next(
999        mut self: std::pin::Pin<&mut Self>,
1000        cx: &mut std::task::Context<'_>,
1001    ) -> std::task::Poll<Option<Self::Item>> {
1002        self.streams.poll_next_unpin(cx)
1003    }
1004}
1005
1006impl futures::stream::FusedStream for NetpolNetworksService {
1007    fn is_terminated(&self) -> bool {
1008        self.streams.is_terminated()
1009    }
1010}
1011
1012#[derive(Unit, Debug, Default, Clone)]
1013struct MethodInspect {
1014    successes: u32,
1015    errors: u32,
1016}
1017
1018#[derive(Unit, Debug, Default, Clone)]
1019struct RegistryMetrics {
1020    default_network_id: Option<u32>,
1021    adds: MethodInspect,
1022    removes: MethodInspect,
1023    /// Counts invocations of the `SetDefault` FIDL method. Both setting a
1024    /// default network ID and unsetting the default network are
1025    /// valid operations that count as successes.
1026    set_defaults: MethodInspect,
1027    updates: MethodInspect,
1028}
1029
1030#[derive(Inspect, Default)]
1031struct OperationsMetrics {
1032    delegated: IValue<RegistryMetrics>,
1033    inspect_node: fuchsia_inspect::Node,
1034}
1035
1036impl OperationsMetrics {
1037    fn record_set_default(
1038        &mut self,
1039        result: &Result<(), fnp_socketproxy::NetworkRegistrySetDefaultError>,
1040    ) {
1041        let mut delegated = self.delegated.as_mut();
1042        match result {
1043            Ok(()) => delegated.set_defaults.successes += 1,
1044            Err(_) => delegated.set_defaults.errors += 1,
1045        }
1046    }
1047
1048    fn record_add(&mut self, result: &Result<(), fnp_socketproxy::NetworkRegistryAddError>) {
1049        let mut delegated = self.delegated.as_mut();
1050        match result {
1051            Ok(()) => delegated.adds.successes += 1,
1052            Err(_) => delegated.adds.errors += 1,
1053        }
1054    }
1055
1056    fn record_update(&mut self, result: &Result<(), fnp_socketproxy::NetworkRegistryUpdateError>) {
1057        let mut delegated = self.delegated.as_mut();
1058        match result {
1059            Ok(()) => delegated.updates.successes += 1,
1060            Err(_) => delegated.updates.errors += 1,
1061        }
1062    }
1063
1064    fn record_remove(&mut self, result: &Result<(), fnp_socketproxy::NetworkRegistryRemoveError>) {
1065        let mut delegated = self.delegated.as_mut();
1066        match result {
1067            Ok(()) => delegated.removes.successes += 1,
1068            Err(_) => delegated.removes.errors += 1,
1069        }
1070    }
1071}
1072
1073#[derive(Default)]
1074pub struct NetpolNetworksService {
1075    // The current generation
1076    current_generation: UpdateGeneration,
1077    // The last generation sent per connection
1078    generations_by_connection: UpdateGenerations,
1079    // Networks connections
1080    networks_clients: HashMap<NetworksConnectionId, NetworksClient>,
1081    // The active `fuchsia.net.policy.socketproxy.NetworkRegistry` client connection, if any.
1082    delegated_networks_client: Option<fnp_socketproxy::NetworkRegistryControlHandle>,
1083    tokens: token_registry::TokenRegistry<NetworkTokenContents>,
1084    // NetworkProperty Watchers
1085    property_watchers: HashMap<PropertyWatcherConnectionId, Registration>,
1086    // The networks known to the system
1087    network_registry: RegisteredNetworks,
1088    telemetry: Option<TelemetrySender>,
1089    // Mints ids for networks connections
1090    next_networks_id: NetworksConnectionIdAllocator,
1091    // Mints ids for property watcher connections
1092    next_watcher_id: PropertyWatcherConnectionIdAllocator,
1093    // Reachability Monitor Watcher Handler
1094    reachability_handler: ReachabilityHandler,
1095    // The multiplexed stream of events handled by the eventloop
1096    streams: futures::stream::SelectAll<NetworkRequestStreamInner>,
1097
1098    // Inspect metrics for operations
1099    metrics: OperationsMetrics,
1100    // Inspect node for network topology & properties
1101    networks_inspect_node: Option<fuchsia_inspect::Node>,
1102}
1103
1104impl NetpolNetworksService {
1105    pub fn with_operations_inspect(
1106        mut self,
1107        parent: &fuchsia_inspect::Node,
1108        name: impl AsRef<str>,
1109    ) -> Result<Self, fuchsia_inspect_derive::AttachError> {
1110        self.metrics = OperationsMetrics::default().with_inspect(parent, name)?;
1111        Ok(self)
1112    }
1113
1114    pub fn with_network_registry_inspect(
1115        mut self,
1116        parent: &fuchsia_inspect::Node,
1117        name: impl AsRef<str>,
1118    ) -> Self {
1119        let network_registry_node = parent.create_child(name.as_ref());
1120        self.network_registry.update_inspect(&network_registry_node);
1121        self.networks_inspect_node = Some(network_registry_node);
1122        self
1123    }
1124
1125    pub fn with_inspect(
1126        self,
1127        telemetry_parent: &fuchsia_inspect::Node,
1128        operations_name: impl AsRef<str>,
1129        networks_parent: &fuchsia_inspect::Node,
1130        networks_name: impl AsRef<str>,
1131    ) -> Result<Self, fuchsia_inspect_derive::AttachError> {
1132        let service = self.with_operations_inspect(telemetry_parent, operations_name)?;
1133        Ok(service.with_network_registry_inspect(networks_parent, networks_name))
1134    }
1135
1136    pub fn synthesize_reachability_snapshot(&self) -> freachability::Snapshot {
1137        ReachabilityHandler::synthesize_snapshot(
1138            self.network_registry
1139                .default_network
1140                .and_then(|id| self.network_registry.networks.get(&id)),
1141        )
1142    }
1143
1144    fn maybe_notify_watchers(&mut self) {
1145        let current_snapshot = self.synthesize_reachability_snapshot();
1146        self.reachability_handler.maybe_notify_watchers(&current_snapshot);
1147    }
1148
1149    fn handle_reachability_request(
1150        &mut self,
1151        id: ReachabilityWatcherConnectionId,
1152        request: Option<Result<freachability::MonitorRequest, fidl::Error>>,
1153    ) {
1154        let current_snapshot = self.synthesize_reachability_snapshot();
1155        self.reachability_handler.handle_request(&current_snapshot, id, request);
1156    }
1157
1158    #[cfg(test)]
1159    pub fn reachability_watcher_count(&self) -> usize {
1160        self.reachability_handler.watcher_count()
1161    }
1162
1163    pub fn set_telemetry(&mut self, telemetry: TelemetrySender) {
1164        self.telemetry = Some(telemetry);
1165    }
1166
1167    pub fn add_stream<S: Into<NetworkRequestStream>>(&mut self, s: S) {
1168        match s.into() {
1169            NetworkRequestStream::Networks(stream) => {
1170                let id = self.next_networks_id.allocate();
1171                let previous = self.networks_clients.insert(
1172                    id,
1173                    NetworksClient { responder: None, control_handle: stream.control_handle() },
1174                );
1175                assert!(previous.is_none(), "networks client {id:?} is already registered");
1176                self.streams
1177                    .push(NetworkRequestStreamInner::Networks(stream.tagged(id).with_epitaph(id)));
1178            }
1179            NetworkRequestStream::NetworkTokenResolver(stream) => {
1180                self.streams.push(NetworkRequestStreamInner::NetworkTokenResolver(stream));
1181            }
1182            NetworkRequestStream::DelegatedNetworks(stream) => {
1183                if self.delegated_networks_client.is_some() {
1184                    warn!(
1185                        "Only one connection to \
1186                        fuchsia.net.policy.socketproxy/NetworkRegistry is allowed at a time"
1187                    );
1188                    stream.control_handle().shutdown_with_epitaph(zx::Status::ALREADY_EXISTS);
1189                } else {
1190                    self.delegated_networks_client = Some(stream.control_handle());
1191                    self.streams.push(NetworkRequestStreamInner::DelegatedNetworks(
1192                        stream.with_epitaph(()),
1193                    ));
1194                }
1195            }
1196            NetworkRequestStream::PropertyWatcher { connection_id, stream } => {
1197                self.streams.push(NetworkRequestStreamInner::PropertyWatcher(
1198                    stream.tagged(connection_id).with_epitaph(connection_id),
1199                ));
1200            }
1201            NetworkRequestStream::Reachability(stream) => {
1202                if let Some(reachability_stream) = self.reachability_handler.add_stream(stream) {
1203                    self.streams.push(NetworkRequestStreamInner::Reachability(reachability_stream));
1204                }
1205            }
1206        }
1207    }
1208
1209    pub async fn handle_event(
1210        &mut self,
1211        event: NetworkRequest,
1212    ) -> Result<DelegatedNetworkUpdateResult, anyhow::Error> {
1213        match event {
1214            NetworkRequest::NetworkAttributes(NetworkAttributesRequest { id, request }) => {
1215                self.handle_network_attributes_request(id, request).await?;
1216                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1217            }
1218            NetworkRequest::NetworkTokenResolver(NetworkTokenResolverRequest { request }) => {
1219                self.handle_network_token_resolver_request(request).await?;
1220                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1221            }
1222            NetworkRequest::DelegatedNetworks(DelegatedNetworksRequest { request }) => {
1223                Ok(self.handle_delegated_networks_update(request).await)
1224            }
1225            NetworkRequest::PropertyWatcher(PropertyWatcherRequest { id, request }) => {
1226                self.handle_property_watcher_request(id, request).await?;
1227                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1228            }
1229            NetworkRequest::Reachability(ReachabilityRequest { id, request }) => {
1230                self.handle_reachability_request(id, request);
1231                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1232            }
1233        }
1234    }
1235
1236    /// Returns the consolidated DNS servers from the Network Registry.
1237    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
1238        self.network_registry.consolidated_dns_servers()
1239    }
1240
1241    /// Handles a single item produced by a client's [`NetworksRequestStream`].
1242    ///
1243    /// # Panics
1244    ///
1245    /// Panics if `id` does not correspond to a currently registered client. A client is removed
1246    /// only when its stream yields the terminal `None` item, and the stream is fused, so every
1247    /// item from the stream belongs to a live client.
1248    pub async fn handle_network_attributes_request(
1249        &mut self,
1250        id: NetworksConnectionId,
1251        req: Option<Result<fnp_properties::NetworksRequest, fidl::Error>>,
1252    ) -> Result<(), anyhow::Error> {
1253        let mut entry = assert_matches!(
1254            self.networks_clients.entry(id),
1255            Entry::Occupied(entry) => entry,
1256            "request for unknown networks client {id:?}"
1257        );
1258
1259        let req = match req {
1260            Some(Ok(req)) => req,
1261            Some(Err(e)) => {
1262                // A clean disconnect yields the terminal item below rather than an error, so
1263                // this is always abnormal: a malformed request or a channel-level read failure.
1264                // No epitaph is sent because the channel may no longer be writable.
1265                error!("Networks client {id:?} stream error: {e}");
1266                entry.get_mut().close(None);
1267                return Ok(());
1268            }
1269            // The client's stream has terminated, either because the client went away or
1270            // because of a `close` above. This is the only place clients are removed.
1271            None => {
1272                let _: NetworksClient = entry.remove();
1273                let _: Option<_> = self.generations_by_connection.remove_default(&id);
1274                return Ok(());
1275            }
1276        };
1277        match req {
1278            fnp_properties::NetworksRequest::WatchDefault { responder } => {
1279                let client = entry.get_mut();
1280                if client.responder.is_some() {
1281                    warn!(
1282                        "Only one call to fuchsia.net.policy.properties/Networks.WatchDefault \
1283                          may be active per connection"
1284                    );
1285                    // Close the connection; the terminal item performs the cleanup.
1286                    client.close(Some(zx::Status::ALREADY_EXISTS));
1287                } else {
1288                    let network_id =
1289                        if self.generations_by_connection.default_network(&id).unwrap_or_default()
1290                            < self.current_generation.default_network
1291                        {
1292                            self.network_registry.default_network
1293                        } else {
1294                            None
1295                        };
1296                    if let Some(network_id) = network_id {
1297                        self.generations_by_connection
1298                            .set_default_network(id, self.current_generation);
1299                        let token = self
1300                            .tokens
1301                            .ensure_token(NetworkTokenContents { network_id, is_default: true })
1302                            .get()
1303                            .duplicate()
1304                            .context("could not duplicate token")?;
1305                        responder
1306                            .send(fnp_properties::NetworksWatchDefaultResponse::Network(token))?;
1307                    } else {
1308                        client.responder = Some(responder);
1309                    }
1310                }
1311            }
1312            fnp_properties::NetworksRequest::WatchProperties {
1313                payload:
1314                    fnp_properties::NetworksWatchPropertiesRequest {
1315                        network, properties, watcher, ..
1316                    },
1317                responder,
1318            } => match (network, properties, watcher) {
1319                (None, _, _) | (_, None, _) | (_, _, None) => {
1320                    responder.send(Err(fnp_properties::WatchError::MissingRequiredArgument))?
1321                }
1322                (Some(network), Some(properties), Some(watcher)) => {
1323                    if properties == fnp_properties::PropertyInterest::default() {
1324                        responder.send(Err(fnp_properties::WatchError::NoProperties))?
1325                    } else {
1326                        match self.tokens.get_contents(&network) {
1327                            Err(e) => {
1328                                warn!("Unknown network token. ({network:?}: {e})");
1329                                responder
1330                                    .send(Err(fnp_properties::WatchError::InvalidNetworkToken))?
1331                            }
1332                            Ok(_network_contents) => {
1333                                // Bind the stateful PropertyWatcher session: cache the token and
1334                                // requested properties, register the watcher's event stream, and
1335                                // reply immediately.
1336                                let watcher_stream = watcher.into_stream();
1337                                let watcher_id = self.next_watcher_id.allocate();
1338                                let registration = Registration {
1339                                    token: network,
1340                                    properties,
1341                                    responder: None,
1342                                    control_handle: watcher_stream.control_handle(),
1343                                };
1344                                let previous =
1345                                    self.property_watchers.insert(watcher_id, registration);
1346                                assert!(
1347                                    previous.is_none(),
1348                                    "property watcher {watcher_id:?} is already registered"
1349                                );
1350                                self.add_stream((watcher_id, watcher_stream));
1351                                responder.send(Ok(()))?;
1352                            }
1353                        }
1354                    }
1355                }
1356            },
1357            fnp_properties::NetworksRequest::_UnknownMethod { ordinal, .. } => {
1358                warn!("Received unexpected request {ordinal}")
1359            }
1360        }
1361
1362        Ok(())
1363    }
1364
1365    /// Handles a single item produced by a client's [`PropertyWatcherRequestStream`].
1366    ///
1367    /// # Panics
1368    ///
1369    /// Panics if `id` does not correspond to a currently registered client. A client is removed
1370    /// only when its stream yields the terminal `None` item, and the stream is fused, so every
1371    /// item from the stream belongs to a live client.
1372    pub async fn handle_property_watcher_request(
1373        &mut self,
1374        id: PropertyWatcherConnectionId,
1375        req: Option<Result<fnp_properties::PropertyWatcherRequest, fidl::Error>>,
1376    ) -> Result<(), anyhow::Error> {
1377        let mut entry = assert_matches!(
1378            self.property_watchers.entry(id),
1379            Entry::Occupied(entry) => entry,
1380            "request for unknown property watcher {id:?}"
1381        );
1382
1383        let req = match req {
1384            Some(Ok(req)) => req,
1385            Some(Err(e)) => {
1386                // A clean disconnect yields the terminal item below rather than an error, so
1387                // this is always abnormal: a malformed request or a channel-level read failure.
1388                // No epitaph is sent because the channel may no longer be writable.
1389                error!("Property watcher {id:?} stream error: {e}");
1390                entry.get_mut().close(None);
1391                return Ok(());
1392            }
1393            // The client's stream has terminated, either because the client went away or
1394            // because of a `close` above. This is the only place registrations are removed.
1395            None => {
1396                let _: Registration = entry.remove();
1397                let _: Option<_> = self.generations_by_connection.remove_properties(&id);
1398                return Ok(());
1399            }
1400        };
1401        match req {
1402            fnp_properties::PropertyWatcherRequest::Watch { responder } => {
1403                let registration = entry.get_mut();
1404                if registration.responder.is_some() {
1405                    warn!(
1406                        "Only one call to \
1407                        fuchsia.net.policy.properties/PropertyWatcher.Watch may be \
1408                        active per connection"
1409                    );
1410                    // Close the connection; the terminal item performs the cleanup.
1411                    registration.close(Some(zx::Status::ALREADY_EXISTS));
1412                } else {
1413                    registration.responder = Some(responder);
1414                    match self.tokens.get_contents(&registration.token) {
1415                        Ok(network_contents) => {
1416                            // Determine whether a new update is available
1417                            // (last_sent_generation < current_generation)
1418                            let is_initial =
1419                                self.generations_by_connection.properties(&id).is_none();
1420                            if is_initial
1421                                || self
1422                                    .generations_by_connection
1423                                    .properties(&id)
1424                                    .unwrap_or_default()
1425                                    < self.current_generation.properties
1426                            {
1427                                let mut updates = fnp_properties::PropertyUpdate::default();
1428                                updates.add_socket_marks(
1429                                    &self.network_registry,
1430                                    &network_contents,
1431                                    registration.properties,
1432                                );
1433                                updates.add_dns(
1434                                    &self.network_registry,
1435                                    &network_contents,
1436                                    registration.properties,
1437                                );
1438                                let network_is_known = self
1439                                    .network_registry
1440                                    .networks
1441                                    .contains_key(&network_contents.network_id);
1442                                if (is_initial && network_is_known)
1443                                    || updates != fnp_properties::PropertyUpdate::default()
1444                                {
1445                                    self.generations_by_connection
1446                                        .set_properties(id, self.current_generation);
1447                                    if let Some(responder) = registration.responder.take() {
1448                                        responder.send(Ok(&updates))?;
1449                                    }
1450                                }
1451                            }
1452                        }
1453                        // `NOT_FOUND` means the network was removed, and its token dropped,
1454                        // before this `Watch` call. Any other error is unexpected.
1455                        Err(e) => {
1456                            if e != zx::Status::NOT_FOUND {
1457                                warn!("Unexpected error fetching token contents: {e}");
1458                            }
1459                            let _: Option<_> =
1460                                self.generations_by_connection.remove_properties(&id);
1461                            if let Some(responder) = registration.responder.take() {
1462                                if let Err(e) = responder
1463                                    .send(Err(fnp_properties::PropertyWatcherError::NetworkGone))
1464                                {
1465                                    warn!("Could not send to responder: {e}");
1466                                }
1467                                registration.close(None);
1468                            }
1469                        }
1470                    }
1471                }
1472            }
1473        }
1474
1475        Ok(())
1476    }
1477
1478    /// Handles delegated network updates coming from Starnix.
1479    ///
1480    /// Resolves network events requested through the `NetworkRegistry` interface, applies the
1481    /// corresponding properties changes, and yields the computed DNS configuration targets for
1482    /// output updates.
1483    ///
1484    /// TODO(https://fxbug.dev/428712735): Stop returning DnsServer list once
1485    /// dns-resolver learns about DNS via NetworkProperties.
1486    ///
1487    /// # Panics
1488    ///
1489    /// Panics if there is no currently active delegated networks client. A client is removed
1490    /// only when its stream yields the terminal `None` item, and the stream is fused, so every
1491    /// item from the stream belongs to a live client.
1492    pub async fn handle_delegated_networks_update(
1493        &mut self,
1494        update: Option<Result<fnp_socketproxy::NetworkRegistryRequest, fidl::Error>>,
1495    ) -> DelegatedNetworkUpdateResult {
1496        use fnp_socketproxy::{
1497            NetworkRegistryAddError, NetworkRegistryRemoveError, NetworkRegistryRequest,
1498            NetworkRegistrySetDefaultError, NetworkRegistryUpdateError,
1499        };
1500
1501        let control_handle = assert_matches!(
1502            self.delegated_networks_client.as_ref(),
1503            Some(control_handle) => control_handle,
1504            "request with no active delegated networks client"
1505        );
1506
1507        let req = match update {
1508            Some(Ok(req)) => req,
1509            Some(Err(e)) => {
1510                // A clean disconnect yields the terminal item below rather than an error, so
1511                // this is always abnormal: a malformed request or a channel-level read failure.
1512                // No epitaph is sent because the channel may no longer be writable.
1513                error!("Delegated networks client stream error: {e}");
1514                control_handle.shutdown();
1515                return DelegatedNetworkUpdateResult { dns_servers: None };
1516            }
1517            // The client's stream has terminated, either because the client went away or
1518            // because of a `shutdown` above. This is the only place the client is removed.
1519            None => {
1520                let _: Option<_> = self.delegated_networks_client.take();
1521                return self.clear_delegated_networks().await;
1522            }
1523        };
1524
1525        let action_result = match req {
1526            NetworkRegistryRequest::SetDefault { network_id, responder } => {
1527                let set_default_result = match network_id {
1528                    fposix_socket::OptionalUint32::Value(interface_id) => {
1529                        match InterfaceId::try_from(interface_id) {
1530                            Ok(id) => {
1531                                let delegated_id = NetworkId::delegated(id);
1532                                if self.network_registry.networks.contains_key(&delegated_id) {
1533                                    self.update(NetworkRegistryUpdate::ChangeNetwork(
1534                                        delegated_id,
1535                                        NetworkUpdate::MakeDefault,
1536                                    ))
1537                                    .await;
1538                                    Ok(())
1539                                } else {
1540                                    Err(NetworkRegistrySetDefaultError::NotFound)
1541                                }
1542                            }
1543                            Err(_) => Err(NetworkRegistrySetDefaultError::NotFound),
1544                        }
1545                    }
1546                    fposix_socket::OptionalUint32::Unset(_) => {
1547                        self.update(NetworkRegistryUpdate::unset_default()).await;
1548                        Ok(())
1549                    }
1550                };
1551
1552                self.metrics.record_set_default(&set_default_result);
1553
1554                self.respond_to_delegated_network_update(
1555                    set_default_result,
1556                    |reply| responder.send(reply),
1557                    "failed to send SetDefault result",
1558                )
1559            }
1560            NetworkRegistryRequest::Add { network, responder } => {
1561                let add_result = match ValidatedNetwork::try_from(network).map_err(Into::into) {
1562                    Ok(valid) => {
1563                        if self.network_registry.networks.contains_key(&valid.network_id) {
1564                            Err(NetworkRegistryAddError::DuplicateNetworkId)
1565                        } else {
1566                            self.update(NetworkRegistryUpdate::ChangeNetwork(
1567                                valid.network_id,
1568                                NetworkUpdate::Properties(NetworkPropertiesChange {
1569                                    added: true,
1570                                    marks: Some(valid.marks),
1571                                    dns_servers: Some(valid.dns_servers),
1572                                    connectivity_state: valid.connectivity,
1573                                    name: valid.name,
1574                                    network_type: valid.network_type,
1575                                }),
1576                            ))
1577                            .await;
1578                            Ok(())
1579                        }
1580                    }
1581                    Err(e) => Err(e),
1582                };
1583
1584                self.metrics.record_add(&add_result);
1585
1586                self.respond_to_delegated_network_update(
1587                    add_result,
1588                    |reply| responder.send(reply),
1589                    "failed to send Add result",
1590                )
1591            }
1592            NetworkRegistryRequest::Update { network, responder } => {
1593                let update_result = match ValidatedNetwork::try_from(network).map_err(Into::into) {
1594                    Ok(valid) => {
1595                        if !self.network_registry.networks.contains_key(&valid.network_id) {
1596                            Err(NetworkRegistryUpdateError::NotFound)
1597                        } else {
1598                            self.update(NetworkRegistryUpdate::ChangeNetwork(
1599                                valid.network_id,
1600                                NetworkUpdate::Properties(NetworkPropertiesChange {
1601                                    added: false,
1602                                    marks: Some(valid.marks),
1603                                    dns_servers: Some(valid.dns_servers),
1604                                    connectivity_state: valid.connectivity,
1605                                    name: valid.name,
1606                                    network_type: valid.network_type,
1607                                }),
1608                            ))
1609                            .await;
1610                            Ok(())
1611                        }
1612                    }
1613                    Err(e) => Err(e),
1614                };
1615
1616                self.metrics.record_update(&update_result);
1617
1618                self.respond_to_delegated_network_update(
1619                    update_result,
1620                    |reply| responder.send(reply),
1621                    "failed to send Update result",
1622                )
1623            }
1624            NetworkRegistryRequest::Remove { network_id, responder } => {
1625                let remove_result = match InterfaceId::try_from(network_id) {
1626                    Ok(id) => {
1627                        let delegated_id = NetworkId::delegated(id);
1628                        if self.network_registry.starnix_default == Some(delegated_id) {
1629                            Err(NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)
1630                        } else if !self.network_registry.networks.contains_key(&delegated_id) {
1631                            Err(NetworkRegistryRemoveError::NotFound)
1632                        } else {
1633                            self.update(NetworkRegistryUpdate::ChangeNetwork(
1634                                delegated_id,
1635                                NetworkUpdate::Remove,
1636                            ))
1637                            .await;
1638                            Ok(())
1639                        }
1640                    }
1641                    Err(_) => Err(NetworkRegistryRemoveError::NotFound),
1642                };
1643
1644                self.metrics.record_remove(&remove_result);
1645
1646                self.respond_to_delegated_network_update(
1647                    remove_result,
1648                    |reply| responder.send(reply),
1649                    "failed to send Remove result",
1650                )
1651            }
1652        };
1653
1654        action_result
1655    }
1656
1657    /// Unsets `starnix_default` and removes all `NetworkId::Delegated` networks when the
1658    /// `NetworkRegistry` client disconnects, returning the updated DNS server list if any
1659    /// delegated networks were removed.
1660    async fn clear_delegated_networks(&mut self) -> DelegatedNetworkUpdateResult {
1661        // Unset `starnix_default` first because `RegisteredNetworks::apply` refuses to remove a
1662        // network while it is still marked as `starnix_default`.
1663        if self.network_registry.starnix_default.is_some() {
1664            self.update(NetworkRegistryUpdate::unset_default()).await;
1665        }
1666
1667        let delegated_ids = self
1668            .network_registry
1669            .networks
1670            .keys()
1671            .filter(|id| match id {
1672                NetworkId::Delegated(_) => true,
1673                NetworkId::Fuchsia(_) => false,
1674            })
1675            .copied()
1676            .collect::<Vec<_>>();
1677
1678        for id in &delegated_ids {
1679            self.update(NetworkRegistryUpdate::ChangeNetwork(*id, NetworkUpdate::Remove)).await;
1680        }
1681
1682        DelegatedNetworkUpdateResult {
1683            dns_servers: (!delegated_ids.is_empty())
1684                .then(|| self.network_registry.consolidated_dns_servers()),
1685        }
1686    }
1687
1688    // Resolves the operation result, sends the success or failure status to
1689    // the FIDL responder, and returns the updated network registry settings.
1690    fn respond_to_delegated_network_update<E, F>(
1691        &self,
1692        operation_result: Result<(), E>,
1693        send_response: F,
1694        context_message: &'static str,
1695    ) -> DelegatedNetworkUpdateResult
1696    where
1697        F: FnOnce(Result<(), E>) -> Result<(), fidl::Error>,
1698    {
1699        // Return consolidated DNS servers if the operation was successful.
1700        let dns_servers = operation_result
1701            .as_ref()
1702            .ok()
1703            .map(|()| self.network_registry.consolidated_dns_servers());
1704
1705        // Send success or failure status to the FIDL responder.
1706        if let Err(e) = send_response(operation_result) {
1707            if !e.is_closed() {
1708                error!(
1709                    "Failed to send delegated network update result \
1710                for {context_message}: {e}"
1711                );
1712            }
1713        }
1714
1715        DelegatedNetworkUpdateResult { dns_servers }
1716    }
1717
1718    // Converts `NetworkDnsServers` to `Vec<DnsServer_>` for a given network.
1719    //
1720    // Note: We prioritize IPv4 servers over IPv6 servers. This is impactful
1721    // when sending DNS servers through NetworkProperties or to dns-resolver.
1722    fn extract_dns_servers(
1723        dns_servers: &Option<fnp_socketproxy::NetworkDnsServers>,
1724        network_id: u64,
1725    ) -> Vec<fnet_name::DnsServer_> {
1726        let make_server = |address| fnet_name::DnsServer_ {
1727            address: Some(address),
1728            source: Some(fnet_name::DnsServerSource::SocketProxy(
1729                fnet_name::SocketProxyDnsServerSource {
1730                    source_interface: Some(network_id),
1731                    ..Default::default()
1732                },
1733            )),
1734            ..Default::default()
1735        };
1736
1737        dns_servers
1738            .as_ref()
1739            .map(|dns| {
1740                dns.v4
1741                    .as_ref()
1742                    .into_iter()
1743                    .flatten()
1744                    .map(|&address| {
1745                        make_server(fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
1746                            address,
1747                            port: DNS_PORT,
1748                        }))
1749                    })
1750                    .chain(dns.v6.as_ref().into_iter().flatten().map(|&address| {
1751                        make_server(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
1752                            address,
1753                            port: DNS_PORT,
1754                            zone_index: 0,
1755                        }))
1756                    }))
1757                    .collect()
1758            })
1759            .unwrap_or_default()
1760    }
1761
1762    pub(crate) async fn handle_network_token_resolver_request(
1763        &mut self,
1764        request: Result<fnp_properties::NetworkTokenResolverRequest, fidl::Error>,
1765    ) -> Result<(), anyhow::Error> {
1766        use fnp_properties::NetworkTokenResolverResolveTokenError as ResolveTokenError;
1767
1768        let request = request.context("while handling NetworkTokenResolver request")?;
1769        match request {
1770            fnp_properties::NetworkTokenResolverRequest::ResolveToken { token, responder } => {
1771                let maybe_contents = self.tokens.get_contents(&token).copied();
1772                match maybe_contents {
1773                    Err(e) => {
1774                        warn!("Unknown network token. ({token:?}: {e})");
1775                        responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
1776                    }
1777                    Ok(contents) => {
1778                        if contents.is_default {
1779                            // This is a default network token, we need to grab
1780                            // the non-default variant.
1781                            let query = NetworkTokenContents { is_default: false, ..contents };
1782                            if let Some(tok) = self.tokens.get_token(&query) {
1783                                responder.send(tok.duplicate().map_err(|e| {
1784                                    warn!("Encountered issue duplicating generated token. {e}");
1785                                    ResolveTokenError::InvalidNetworkToken
1786                                }))?;
1787                            } else {
1788                                warn!("Requested canonical version of unregistered network.");
1789                                responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
1790                            }
1791                        } else {
1792                            responder.send(Ok(token))?;
1793                        }
1794                    }
1795                }
1796            }
1797            fidl_fuchsia_net_policy_properties::NetworkTokenResolverRequest::_UnknownMethod {
1798                ordinal,
1799                control_handle,
1800                method_type,
1801                ..
1802            } => warn!(
1803                "Encountered unknown method call on NetworkTokenResolver: {ordinal} \
1804                {control_handle:?} {method_type:?}"
1805            ),
1806        }
1807
1808        Ok(())
1809    }
1810
1811    async fn changed_default_network(
1812        &mut self,
1813        previous_default_network: Option<NetworkId>,
1814        property_watchers: &mut HashMap<PropertyWatcherConnectionId, Registration>,
1815    ) {
1816        for (id, registration) in property_watchers.iter_mut() {
1817            if let Ok(contents) = self.tokens.get_contents(&registration.token) {
1818                if contents.is_default {
1819                    let _: Option<_> = self.generations_by_connection.remove_properties(id);
1820                    if let Some(responder) = registration.responder.take() {
1821                        if let Err(e) =
1822                            responder.send(Err(fnp_properties::PropertyWatcherError::NetworkGone))
1823                        {
1824                            warn!("Could not send to responder: {e}");
1825                        }
1826                        registration.close(None);
1827                    }
1828                }
1829            }
1830        }
1831        self.tokens.drop_if(|&c| {
1832            c.is_default && previous_default_network.is_some_and(|i| i == c.network_id)
1833        });
1834    }
1835
1836    pub async fn update(&mut self, update: NetworkRegistryUpdate) {
1837        let RegistryUpdateResult { event, default_changed } = self.network_registry.apply(update);
1838
1839        if default_changed.is_some() {
1840            self.metrics.delegated.as_mut().default_network_id =
1841                self.network_registry.starnix_default.map(|id| id.get().get() as u32);
1842        }
1843        if let Some(networks_node) = &self.networks_inspect_node {
1844            self.network_registry.update_inspect(networks_node);
1845        }
1846
1847        if let UpdateApplied::None = event {
1848            if default_changed.is_none() {
1849                // Return early if there were absolutely no changes and the default stayed the same.
1850                return;
1851            }
1852        }
1853
1854        if event != UpdateApplied::None {
1855            self.current_generation.properties += 1;
1856        }
1857
1858        let mut property_watchers = HashMap::new();
1859        std::mem::swap(&mut self.property_watchers, &mut property_watchers);
1860
1861        // Clean up or register tokens based on whether the network was added or removed.
1862        match event {
1863            UpdateApplied::NetworkChanged { network_id, added: true, .. } => {
1864                let _ = self
1865                    .tokens
1866                    .ensure_token(NetworkTokenContents { network_id, is_default: false });
1867            }
1868            UpdateApplied::NetworkRemoved(network_id) => {
1869                info!("Removing network {network_id}. Reporting NETWORK_GONE to watchers.");
1870                // Notify all property watchers bound to the removed network. The watcher is
1871                // kept in the `property_watchers` map so that idle clients
1872                // can receive NETWORK_GONE.
1873                for (id, registration) in property_watchers.iter_mut() {
1874                    if let Ok(network) = self.tokens.get_contents(&registration.token) {
1875                        if network.network_id == network_id {
1876                            let _: Option<_> = self.generations_by_connection.remove_properties(id);
1877                            if let Some(responder) = registration.responder.take() {
1878                                if let Err(e) = responder
1879                                    .send(Err(fnp_properties::PropertyWatcherError::NetworkGone))
1880                                {
1881                                    warn!("Could not send to responder: {e}");
1882                                }
1883                                registration.close(None);
1884                            }
1885                        }
1886                    }
1887                }
1888                self.tokens.drop_if(|c| !c.is_default && c.network_id == network_id);
1889            }
1890            UpdateApplied::NetworkChanged { added: false, .. }
1891            | UpdateApplied::DnsChanged
1892            | UpdateApplied::None => {}
1893        }
1894
1895        // Notify watchers of default network changes if one occurred.
1896        if let Some(DefaultChangedEvent { previous_default }) = default_changed {
1897            self.notify_default_network_changed(previous_default, &mut property_watchers).await;
1898            std::mem::swap(&mut self.property_watchers, &mut property_watchers);
1899            self.maybe_notify_watchers();
1900            return;
1901        }
1902
1903        if let UpdateApplied::NetworkChanged { network_id, .. } = event {
1904            if let Some(telemetry) = &self.telemetry {
1905                if let Some(props) = self.network_registry.networks.get(&network_id) {
1906                    telemetry.send(TelemetryEvent::NetworkChanged(NetworkEventMetadata {
1907                        id: network_id.get().get(),
1908                        name: props.name.clone(),
1909                        transport: props
1910                            .network_type
1911                            .unwrap_or(fnp_socketproxy::NetworkType::Unknown),
1912                        is_fuchsia_provisioned: network_id.is_fuchsia(),
1913                        connectivity_state: props.connectivity_state,
1914                    }));
1915                }
1916            }
1917        }
1918
1919        for (id, mut registration) in property_watchers {
1920            let mut updates = fnp_properties::PropertyUpdate::default();
1921            match self.tokens.get_contents(&registration.token) {
1922                Ok(network) => match event {
1923                    UpdateApplied::NetworkChanged {
1924                        network_id,
1925                        changed_marks,
1926                        changed_dns,
1927                        ..
1928                    } => {
1929                        if network.network_id == network_id {
1930                            if changed_marks {
1931                                updates.add_socket_marks(
1932                                    &self.network_registry,
1933                                    &network,
1934                                    registration.properties,
1935                                );
1936                            }
1937                            if changed_dns {
1938                                updates.add_dns(
1939                                    &self.network_registry,
1940                                    &network,
1941                                    registration.properties,
1942                                );
1943                            }
1944                        }
1945                    }
1946                    UpdateApplied::DnsChanged => {
1947                        updates.add_dns(&self.network_registry, &network, registration.properties);
1948                    }
1949                    UpdateApplied::NetworkRemoved(_id) => {}
1950                    UpdateApplied::None => {}
1951                },
1952                Err(e) => {
1953                    debug!(
1954                        "Token {:?} not found for watcher {:?};
1955                    network was likely removed while idle ({})",
1956                        registration.token, id, e
1957                    );
1958                }
1959            }
1960
1961            // Update the client's generation state to keep them in sync with the global
1962            // properties generation.
1963            let has_updates = updates != fnp_properties::PropertyUpdate::default();
1964            if self.generations_by_connection.properties(&id).is_some() {
1965                match (has_updates, registration.responder.take()) {
1966                    (true, Some(responder)) => {
1967                        // Sync generation and send update.
1968                        self.generations_by_connection.set_properties(id, self.current_generation);
1969                        if let Err(e) = responder.send(Ok(&updates)) {
1970                            warn!("Failed to send watch updates: {}", e);
1971                        }
1972                    }
1973                    (false, maybe_responder) => {
1974                        // Sync generation to catch up and restore responder.
1975                        self.generations_by_connection.set_properties(id, self.current_generation);
1976                        registration.responder = maybe_responder;
1977                    }
1978                    (true, None) => {
1979                        // If a relevant change occurs while the client is idle, we leave the client
1980                        // on the old generation so they receive the update immediately upon the next
1981                        // `Watch()` call.
1982                    }
1983                }
1984            }
1985
1986            assert_matches!(
1987                self.property_watchers.insert(id, registration),
1988                None,
1989                "Re-inserted in an existing registration slot."
1990            );
1991        }
1992        self.maybe_notify_watchers();
1993    }
1994
1995    async fn notify_default_network_changed(
1996        &mut self,
1997        old_default: Option<NetworkId>,
1998        property_watchers: &mut HashMap<PropertyWatcherConnectionId, Registration>,
1999    ) {
2000        self.changed_default_network(old_default, property_watchers).await;
2001        let default_network = self.network_registry.default_network;
2002        if let Some(telemetry) = &self.telemetry {
2003            match default_network {
2004                Some(default_network) => {
2005                    if let Some(props) = self.network_registry.networks.get(&default_network) {
2006                        telemetry.send(TelemetryEvent::DefaultNetworkChanged(
2007                            NetworkEventMetadata {
2008                                id: default_network.get().get(),
2009                                name: props.name.clone(),
2010                                transport: props
2011                                    .network_type
2012                                    .unwrap_or(fnp_socketproxy::NetworkType::Unknown),
2013                                is_fuchsia_provisioned: default_network.is_fuchsia(),
2014                                connectivity_state: props.connectivity_state,
2015                            },
2016                        ));
2017                    } else {
2018                        warn!("Could not fetch network data for default network.");
2019                    }
2020                }
2021                None => telemetry.send(TelemetryEvent::DefaultNetworkLost),
2022            }
2023        }
2024        self.current_generation.default_network += 1;
2025        // Answer every outstanding `WatchDefault`. Only the responders are taken; the clients
2026        // stay registered until their streams end.
2027        let responders = self
2028            .networks_clients
2029            .iter_mut()
2030            .filter_map(|(id, client)| client.responder.take().map(|responder| (*id, responder)));
2031        for (id, responder) in responders {
2032            self.generations_by_connection.set_default_network(id, self.current_generation);
2033            let response = match default_network {
2034                Some(network_id) => {
2035                    match self
2036                        .tokens
2037                        .ensure_token(NetworkTokenContents { network_id, is_default: true })
2038                        .get()
2039                        .duplicate()
2040                    {
2041                        Ok(token) => fnp_properties::NetworksWatchDefaultResponse::Network(token),
2042                        Err(e) => {
2043                            warn!("Could not duplicate token: {e}");
2044                            continue;
2045                        }
2046                    }
2047                }
2048                None => fnp_properties::NetworksWatchDefaultResponse::NoDefaultNetwork(
2049                    fnp_properties::Empty,
2050                ),
2051            };
2052            if let Err(e) = responder.send(response) {
2053                warn!("Could not send to responder: {e}");
2054            }
2055        }
2056    }
2057}
2058
2059#[cfg(test)]
2060mod tests {
2061    use super::*;
2062    use assert_matches::assert_matches;
2063    use diagnostics_assertions::assert_data_tree;
2064    use fnp_socketproxy::{
2065        NetworkInfo, NetworkRegistryAddError, NetworkRegistryMarker, NetworkRegistryRemoveError,
2066        NetworkRegistrySetDefaultError, NetworkRegistryUpdateError, StarnixNetworkInfo,
2067    };
2068    use futures::FutureExt as _;
2069    use std::num::NonZeroU64;
2070    use test_case::test_case;
2071
2072    const ID_1: InterfaceId = InterfaceId(NonZeroU64::new(1).unwrap());
2073    const ID_2: InterfaceId = InterfaceId(NonZeroU64::new(2).unwrap());
2074    const NAME_1: &str = "testif1";
2075    const NAME_2: &str = "testif2";
2076
2077    const FUCHSIA_ID_1: NetworkId = NetworkId::Fuchsia(ID_1);
2078    const FUCHSIA_ID_2: NetworkId = NetworkId::Fuchsia(ID_2);
2079    const DELEGATED_ID_1: NetworkId = NetworkId::Delegated(ID_1);
2080    const DELEGATED_ID_2: NetworkId = NetworkId::Delegated(ID_2);
2081
2082    #[derive(Clone, Copy)]
2083    struct TestNetwork {
2084        id: NetworkId,
2085        name: &'static str,
2086        network_type: fnp_socketproxy::NetworkType,
2087    }
2088
2089    #[derive(Default)]
2090    struct AppliedChanges {
2091        added: bool,
2092        changed_marks: bool,
2093        changed_dns: bool,
2094    }
2095
2096    impl TestNetwork {
2097        fn added(&self, added: bool) -> NetworkPropertiesChange {
2098            NetworkPropertiesChange {
2099                added,
2100                name: Some(self.name.to_string()),
2101                network_type: Some(self.network_type),
2102                ..Default::default()
2103            }
2104        }
2105
2106        fn change_with(
2107            &self,
2108            added: bool,
2109            marks: Option<fnet::Marks>,
2110            dns_servers: Option<Vec<fnet_name::DnsServer_>>,
2111            connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
2112        ) -> NetworkPropertiesChange {
2113            NetworkPropertiesChange {
2114                added,
2115                marks,
2116                dns_servers,
2117                connectivity_state,
2118                name: Some(self.name.to_string()),
2119                network_type: Some(self.network_type),
2120            }
2121        }
2122
2123        fn applied(&self, changes: AppliedChanges) -> UpdateApplied {
2124            let AppliedChanges { added, changed_marks, changed_dns } = changes;
2125            UpdateApplied::NetworkChanged {
2126                network_id: self.id,
2127                added,
2128                changed_marks,
2129                changed_dns,
2130                name: Some(self.name.to_string()),
2131                network_type: Some(self.network_type),
2132            }
2133        }
2134    }
2135
2136    const DELEGATED_NET_1: TestNetwork = TestNetwork {
2137        id: DELEGATED_ID_1,
2138        name: NAME_1,
2139        network_type: fnp_socketproxy::NetworkType::Ethernet,
2140    };
2141    const FUCHSIA_NET_1: TestNetwork = TestNetwork {
2142        id: FUCHSIA_ID_1,
2143        name: NAME_1,
2144        network_type: fnp_socketproxy::NetworkType::Ethernet,
2145    };
2146    const FUCHSIA_NET_2: TestNetwork = TestNetwork {
2147        id: FUCHSIA_ID_2,
2148        name: NAME_2,
2149        network_type: fnp_socketproxy::NetworkType::Wifi,
2150    };
2151
2152    fn test_marks() -> fnet::Marks {
2153        fnet::Marks { mark_1: Some(123), ..Default::default() }
2154    }
2155
2156    fn test_marks_updated() -> fnet::Marks {
2157        fnet::Marks { mark_1: Some(456), ..Default::default() }
2158    }
2159
2160    fn test_dns_1() -> Vec<fnet_name::DnsServer_> {
2161        vec![fnet_name::DnsServer_ {
2162            address: Some(net_declare::fidl_socket_addr!("192.0.2.1:53")),
2163            ..Default::default()
2164        }]
2165    }
2166
2167    fn test_dns_2() -> Vec<fnet_name::DnsServer_> {
2168        vec![fnet_name::DnsServer_ {
2169            address: Some(net_declare::fidl_socket_addr!("192.0.2.2:53")),
2170            ..Default::default()
2171        }]
2172    }
2173
2174    fn delegated_properties() -> NetworkProperties {
2175        NetworkProperties { socket_marks: Some(test_marks()), ..Default::default() }
2176    }
2177
2178    fn fuchsia_properties() -> NetworkProperties {
2179        NetworkProperties::default()
2180    }
2181
2182    fn added_properties(name: &str) -> NetworkPropertiesChange {
2183        NetworkPropertiesChange {
2184            added: true,
2185            marks: None,
2186            dns_servers: None,
2187            connectivity_state: None,
2188            name: Some(name.to_string()),
2189            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
2190        }
2191    }
2192
2193    impl NetpolNetworksService {
2194        pub(crate) fn default_network(&self) -> Option<NetworkId> {
2195            self.network_registry.default_network
2196        }
2197
2198        pub(crate) fn has_network(&self, id: NetworkId) -> bool {
2199            self.network_registry.networks.contains_key(&id)
2200        }
2201
2202        pub(crate) fn has_token(&self, network_id: NetworkId, is_default: bool) -> bool {
2203            self.tokens.get_token(&NetworkTokenContents { network_id, is_default }).is_some()
2204        }
2205
2206        pub(crate) fn ensure_token_for_test(&mut self, network_id: NetworkId, is_default: bool) {
2207            let _token: crate::network::token_registry::TokenEntry<'_, _> =
2208                self.tokens.ensure_token(NetworkTokenContents { network_id, is_default });
2209        }
2210    }
2211
2212    #[test_case(
2213        DELEGATED_NET_1,
2214        Some(test_marks()),
2215        Some(test_marks_updated()); "delegated network"
2216    )]
2217    #[test_case(FUCHSIA_NET_2, None, None; "fuchsia network")]
2218    fn test_handle_changed_network(
2219        net: TestNetwork,
2220        initial_marks: Option<fnet::Marks>,
2221        updated_marks: Option<fnet::Marks>,
2222    ) {
2223        let mut networks = RegisteredNetworks::default();
2224        let dns1 = test_dns_1();
2225        let dns2 = test_dns_2();
2226
2227        // Adding network with DNS servers should have changed_marks=true and changed_dns=true.
2228        let event = net.change_with(
2229            true,
2230            initial_marks.clone(),
2231            Some(dns1.clone()),
2232            Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
2233        );
2234        assert_eq!(
2235            networks.handle_changed_network(net.id, event),
2236            net.applied(AppliedChanges { added: true, changed_marks: true, changed_dns: true }),
2237        );
2238        assert_eq!(
2239            networks.networks.get(&net.id).expect("network should be present"),
2240            &NetworkProperties {
2241                socket_marks: initial_marks.clone(),
2242                dns_servers: dns1,
2243                connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
2244                name: Some(net.name.to_string()),
2245                network_type: Some(net.network_type),
2246            }
2247        );
2248
2249        // Updating with same marks and different DNS should have changed_marks=false and
2250        // changed_dns=true.
2251        let event = net.change_with(
2252            false,
2253            initial_marks.clone(),
2254            Some(dns2.clone()),
2255            Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
2256        );
2257        assert_eq!(
2258            networks.handle_changed_network(net.id, event),
2259            net.applied(AppliedChanges { added: false, changed_marks: false, changed_dns: true }),
2260        );
2261        assert_eq!(
2262            networks.networks.get(&net.id).expect("network should be present"),
2263            &NetworkProperties {
2264                socket_marks: initial_marks.clone(),
2265                dns_servers: dns2.clone(),
2266                connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
2267                name: Some(net.name.to_string()),
2268                network_type: Some(net.network_type),
2269            }
2270        );
2271
2272        // Updating marks (if delegated) with same DNS should have changed_dns=false.
2273        let marks_changed = initial_marks != updated_marks;
2274        let event = net.change_with(
2275            false,
2276            updated_marks.clone(),
2277            Some(dns2.clone()),
2278            Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
2279        );
2280        assert_eq!(
2281            networks.handle_changed_network(net.id, event),
2282            net.applied(AppliedChanges {
2283                added: false,
2284                changed_marks: marks_changed,
2285                changed_dns: false,
2286            }),
2287        );
2288        assert_eq!(
2289            networks.networks.get(&net.id).expect("network should be present"),
2290            &NetworkProperties {
2291                socket_marks: updated_marks,
2292                dns_servers: dns2,
2293                connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
2294                name: Some(net.name.to_string()),
2295                network_type: Some(net.network_type),
2296            }
2297        );
2298    }
2299
2300    #[test]
2301    fn test_handle_changed_network_validation() {
2302        let mut networks = RegisteredNetworks::default();
2303        let marks = test_marks();
2304        let net = DELEGATED_NET_1;
2305
2306        // Update a non-added network
2307        let event = NetworkPropertiesChange { marks: Some(marks.clone()), ..net.added(false) };
2308        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
2309
2310        // Add the network
2311        let event = NetworkPropertiesChange { marks: Some(marks.clone()), ..net.added(true) };
2312
2313        assert_eq!(
2314            networks.handle_changed_network(net.id, event),
2315            net.applied(AppliedChanges { added: true, changed_marks: true, changed_dns: true }),
2316        );
2317
2318        // Add already added network
2319        let event = NetworkPropertiesChange { marks: Some(marks.clone()), ..net.added(true) };
2320        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
2321
2322        // Fuchsia network with marks
2323        let fuchsia_net = FUCHSIA_NET_1;
2324        let event =
2325            NetworkPropertiesChange { marks: Some(marks.clone()), ..fuchsia_net.added(true) };
2326        assert_eq!(networks.handle_changed_network(fuchsia_net.id, event), UpdateApplied::None);
2327
2328        // Delegated network without marks
2329        let event = net.added(true);
2330        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
2331    }
2332
2333    // Unit tests the election algorithm directly by manipulating internal state.
2334    // Verifies prioritization and intermediate fallback election logic.
2335    #[test]
2336    fn fallback_election_and_prioritization() {
2337        let mut networks = RegisteredNetworks::default();
2338
2339        // Initial State: Empty, no default network.
2340        assert_eq!(networks.calculate_active_default(), None);
2341
2342        // Add a delegated network and set as the Starnix default.
2343        let _ = networks.networks.insert(DELEGATED_ID_1, delegated_properties());
2344        networks.starnix_default = Some(DELEGATED_ID_1);
2345        assert_eq!(
2346            networks.handle_default_network_update(),
2347            Some(DefaultChangedEvent { previous_default: None })
2348        );
2349        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
2350
2351        // Replace the delegated network with another delegated network.
2352        // The new network should take over.
2353        let _ = networks.networks.insert(DELEGATED_ID_2, delegated_properties());
2354        networks.starnix_default = Some(DELEGATED_ID_2);
2355        assert_eq!(
2356            networks.handle_default_network_update(),
2357            Some(DefaultChangedEvent { previous_default: Some(DELEGATED_ID_1) })
2358        );
2359        assert_eq!(networks.default_network, Some(DELEGATED_ID_2));
2360
2361        // Add a Fuchsia network. This Fuchsia network should take over because of
2362        // Fuchsia network priority.
2363        let _ = networks.networks.insert(FUCHSIA_ID_2, fuchsia_properties());
2364        assert_eq!(
2365            networks.handle_default_network_update(),
2366            Some(DefaultChangedEvent { previous_default: Some(DELEGATED_ID_2) })
2367        );
2368        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
2369
2370        // Add a Fuchsia network with a smaller ID. The smaller ID Fuchsia network
2371        // should take over.
2372        let _ = networks.networks.insert(FUCHSIA_ID_1, fuchsia_properties());
2373        assert_eq!(
2374            networks.handle_default_network_update(),
2375            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
2376        );
2377        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2378
2379        // Remove FUCHSIA_ID_1. The next Fuchsia network should take over.
2380        let _ = networks.networks.remove(&FUCHSIA_ID_1);
2381        assert_eq!(
2382            networks.handle_default_network_update(),
2383            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_1) })
2384        );
2385        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
2386
2387        // Remove FUCHSIA_ID_2. There are no more Fuchsia networks, so the default
2388        // should fallback to the delegated network.
2389        let _ = networks.networks.remove(&FUCHSIA_ID_2);
2390        assert_eq!(
2391            networks.handle_default_network_update(),
2392            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
2393        );
2394        assert_eq!(networks.default_network, Some(DELEGATED_ID_2));
2395
2396        // Unset the default delegated network prior to removal.
2397        assert_eq!(
2398            networks.apply(NetworkRegistryUpdate::UnsetDefaultNetwork),
2399            RegistryUpdateResult {
2400                event: UpdateApplied::None,
2401                default_changed: Some(DefaultChangedEvent {
2402                    previous_default: Some(DELEGATED_ID_2)
2403                })
2404            }
2405        );
2406        assert_eq!(networks.default_network, None);
2407
2408        // Remove the delegated network.
2409        assert_eq!(
2410            networks
2411                .apply(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove)),
2412            RegistryUpdateResult {
2413                event: UpdateApplied::NetworkRemoved(DELEGATED_ID_1),
2414                default_changed: None
2415            }
2416        );
2417    }
2418
2419    // Tests the integration of `RegisteredNetworks::apply` updates, verifying
2420    // fallback priority from Fuchsia to Delegated networks and ensuring that
2421    // active default delegated networks cannot be removed.
2422    #[test]
2423    fn test_remove_fuchsia_network_fallback() {
2424        let mut networks = RegisteredNetworks::default();
2425        let marks = test_marks();
2426        let fuchsia_added = NetworkPropertiesChange { added: true, ..Default::default() };
2427
2428        // Add a Fuchsia network. This should become the default network.
2429        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2430            FUCHSIA_ID_1,
2431            NetworkUpdate::Properties(fuchsia_added.clone()),
2432        ));
2433        assert_matches!(
2434            result.event,
2435            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
2436            if id == FUCHSIA_ID_1
2437        );
2438        assert_eq!(result.default_changed, Some(DefaultChangedEvent { previous_default: None }));
2439
2440        // Add a second Fuchsia network. This should not change the default network.
2441        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2442            FUCHSIA_ID_2,
2443            NetworkUpdate::Properties(fuchsia_added),
2444        ));
2445        assert_matches!(
2446            result.event,
2447            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
2448            if id == FUCHSIA_ID_2
2449        );
2450        assert_eq!(result.default_changed, None);
2451
2452        // Add a delegated network. This should not change the default network.
2453        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2454            DELEGATED_ID_1,
2455            NetworkUpdate::Properties(NetworkPropertiesChange {
2456                added: true,
2457                marks: Some(marks),
2458                ..Default::default()
2459            }),
2460        ));
2461        assert_matches!(
2462            result.event,
2463            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
2464            if id == DELEGATED_ID_1
2465        );
2466        assert_eq!(result.default_changed, None);
2467
2468        // Make the delegated network default (ignored because a Fuchsia
2469        // network is present).
2470        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2471            DELEGATED_ID_1,
2472            NetworkUpdate::MakeDefault,
2473        ));
2474        assert_eq!(
2475            result,
2476            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
2477        );
2478        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2479
2480        // Remove the first Fuchsia network (fallback to the second Fuchsia
2481        // network).
2482        let result = networks
2483            .apply(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_1, NetworkUpdate::Remove));
2484        assert_eq!(result.event, UpdateApplied::NetworkRemoved(FUCHSIA_ID_1));
2485        assert_eq!(
2486            result.default_changed,
2487            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_1) })
2488        );
2489        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
2490
2491        // Remove the second Fuchsia network (fallback to the
2492        // delegated network since it is default).
2493        let result = networks
2494            .apply(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove));
2495        assert_eq!(result.event, UpdateApplied::NetworkRemoved(FUCHSIA_ID_2));
2496        assert_eq!(
2497            result.default_changed,
2498            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
2499        );
2500        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
2501
2502        // Remove the delegated network (rejected because it is default).
2503        let result = networks
2504            .apply(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove));
2505        assert_eq!(
2506            result,
2507            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
2508        );
2509        assert!(networks.networks.contains_key(&DELEGATED_ID_1));
2510        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
2511    }
2512
2513    #[test]
2514    fn remove_non_default_fuchsia_preserves_default() {
2515        let mut networks = RegisteredNetworks::default();
2516
2517        // Add both networks.
2518        let _ = networks.networks.insert(FUCHSIA_ID_1, NetworkProperties::default());
2519        let _ = networks.networks.insert(FUCHSIA_ID_2, NetworkProperties::default());
2520
2521        // On election, FUCHSIA_ID_1 (the smaller ID) is elected as default.
2522        assert_eq!(
2523            networks.handle_default_network_update(),
2524            Some(DefaultChangedEvent { previous_default: None })
2525        );
2526        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2527
2528        // Remove the non-default network.
2529        assert_eq!(
2530            networks
2531                .apply(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove)),
2532            RegistryUpdateResult {
2533                event: UpdateApplied::NetworkRemoved(FUCHSIA_ID_2),
2534                default_changed: None
2535            }
2536        );
2537
2538        // Verify that FUCHSIA_ID_1 is still the active default.
2539        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2540    }
2541
2542    #[fuchsia::test]
2543    async fn remove_default_network_cleans_up_tokens() {
2544        let mut service = NetpolNetworksService::default();
2545
2546        // Add two Fuchsia networks via ChangeNetwork updates.
2547        service
2548            .update(NetworkRegistryUpdate::ChangeNetwork(
2549                FUCHSIA_ID_1,
2550                NetworkUpdate::Properties(added_properties(NAME_1)),
2551            ))
2552            .await;
2553
2554        service
2555            .update(NetworkRegistryUpdate::ChangeNetwork(
2556                FUCHSIA_ID_2,
2557                NetworkUpdate::Properties(added_properties(NAME_2)),
2558            ))
2559            .await;
2560
2561        // On election, FUCHSIA_ID_1 (the smaller ID) is elected as default.
2562        assert_eq!(service.default_network(), Some(FUCHSIA_ID_1));
2563
2564        // Ensure non-default tokens exist for both networks.
2565        assert!(service.has_token(FUCHSIA_ID_1, false /* is_default */));
2566        assert!(service.has_token(FUCHSIA_ID_2, false /* is_default */));
2567
2568        // Manually create default token for FUCHSIA_ID_1 to simulate a client WatchDefault call.
2569        service.ensure_token_for_test(FUCHSIA_ID_1, true /* is_default */);
2570        assert!(service.has_token(FUCHSIA_ID_1, true /* is_default */));
2571
2572        // Remove FUCHSIA_ID_1 (the default network). This should trigger fallback to FUCHSIA_ID_2
2573        // and clean up FUCHSIA_ID_1's tokens.
2574        service
2575            .update(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_1, NetworkUpdate::Remove))
2576            .await;
2577
2578        // Verify fallback happened.
2579        assert_eq!(service.default_network(), Some(FUCHSIA_ID_2));
2580
2581        // Verify FUCHSIA_ID_1 tokens are gone.
2582        assert!(!service.has_token(FUCHSIA_ID_1, false /* is_default */));
2583        assert!(!service.has_token(FUCHSIA_ID_1, true /* is_default */));
2584
2585        // Verify FUCHSIA_ID_2 tokens still exist.
2586        assert!(service.has_token(FUCHSIA_ID_2, false /* is_default */));
2587    }
2588
2589    #[fuchsia::test]
2590    async fn test_property_watcher_generation_increment_on_unpolled_update() {
2591        let mut service = NetpolNetworksService::default();
2592
2593        // Set up initial network state and register a PropertyWatcher connection.
2594        service
2595            .update(NetworkRegistryUpdate::ChangeNetwork(
2596                DELEGATED_ID_1,
2597                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
2598                    true,
2599                    Some(test_marks()),
2600                    None,
2601                    None,
2602                )),
2603            ))
2604            .await;
2605
2606        let token = service
2607            .tokens
2608            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
2609            .get()
2610            .duplicate()
2611            .unwrap();
2612
2613        let (watcher, server_end) =
2614            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2615        let (networks_proxy, networks_stream) =
2616            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2617
2618        service.add_stream(networks_stream);
2619
2620        // Watch for socket marks changes on `DELEGATED_ID_1` and expect the initial state.
2621        let request = fnp_properties::NetworksWatchPropertiesRequest {
2622            network: Some(token),
2623            properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
2624            watcher: Some(server_end),
2625            ..Default::default()
2626        };
2627
2628        let watch_req = networks_proxy.watch_properties(request);
2629        let req_event = service.select_next_some().await;
2630        assert_eq!(
2631            service.handle_event(req_event).await.expect("Failed to handle event"),
2632            DelegatedNetworkUpdateResult::default()
2633        );
2634        assert_matches!(watch_req.await, Ok(Ok(())));
2635
2636        // First watch call should return the initial state.
2637        let watch_fut1 = watcher.watch();
2638        let pw_event1 = service.select_next_some().await;
2639        assert_eq!(
2640            service.handle_event(pw_event1).await.expect("Failed to handle event"),
2641            DelegatedNetworkUpdateResult::default()
2642        );
2643        let initial_updates = watch_fut1.await.unwrap().unwrap();
2644        assert_eq!(
2645            initial_updates,
2646            fnp_properties::PropertyUpdate {
2647                socket_marks: Some(test_marks()),
2648                dns_configuration: None,
2649                ..Default::default()
2650            }
2651        );
2652
2653        // Update the network properties while the client is idle, ensuring the server increments
2654        // its generation without bumping the unpolled client's generation.
2655        service
2656            .update(NetworkRegistryUpdate::ChangeNetwork(
2657                DELEGATED_ID_1,
2658                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
2659                    false,
2660                    Some(test_marks_updated()),
2661                    None,
2662                    None,
2663                )),
2664            ))
2665            .await;
2666
2667        // Verify the pending change is immediately available to the client.
2668        let watch_fut2 = watcher.watch();
2669        let pw_event2 = service.select_next_some().await;
2670        assert_eq!(
2671            service.handle_event(pw_event2).await.expect("Failed to handle event"),
2672            DelegatedNetworkUpdateResult::default()
2673        );
2674        let updates = watch_fut2.await.unwrap().unwrap();
2675        assert_eq!(
2676            updates,
2677            fnp_properties::PropertyUpdate {
2678                socket_marks: Some(test_marks_updated()),
2679                dns_configuration: None,
2680                ..Default::default()
2681            }
2682        );
2683    }
2684
2685    #[fuchsia::test]
2686    async fn test_watch_should_return_error_on_concurrent_call() {
2687        let mut service = NetpolNetworksService::default();
2688
2689        let token = service
2690            .tokens
2691            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
2692            .get()
2693            .duplicate()
2694            .unwrap();
2695
2696        let (watcher, server_end) =
2697            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2698        let (networks_proxy, networks_stream) =
2699            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2700
2701        service.add_stream(networks_stream);
2702
2703        let watch_req =
2704            networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
2705                network: Some(token),
2706                properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
2707                watcher: Some(server_end),
2708                ..Default::default()
2709            });
2710        let req_event = service.select_next_some().await;
2711        assert_eq!(
2712            service.handle_event(req_event).await.expect("Failed to handle event"),
2713            DelegatedNetworkUpdateResult::default()
2714        );
2715        assert_matches!(watch_req.await, Ok(Ok(())));
2716
2717        // Call `watch()` twice concurrently on the same connection.
2718        let watch_fut1 = watcher.watch();
2719        let pw_event1 = service.select_next_some().await;
2720        assert_eq!(
2721            service.handle_event(pw_event1).await.expect("Failed to handle event"),
2722            DelegatedNetworkUpdateResult::default()
2723        );
2724
2725        let watch_fut2 = watcher.watch();
2726        let pw_event2 = service.select_next_some().await;
2727        assert_eq!(
2728            service.handle_event(pw_event2).await.expect("Failed to handle event"),
2729            DelegatedNetworkUpdateResult::default()
2730        );
2731
2732        for res in [watch_fut1.await, watch_fut2.await] {
2733            assert_matches!(
2734                res,
2735                Err(fidl::Error::ClientChannelClosed { epitaph, .. })
2736                    if epitaph == zx::Status::ALREADY_EXISTS
2737            );
2738        }
2739        // The registration is removed when the closed stream yields its terminal item.
2740        let disconnect_event = service.select_next_some().await;
2741        assert_eq!(
2742            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
2743            DelegatedNetworkUpdateResult::default()
2744        );
2745        assert!(service.property_watchers.is_empty());
2746    }
2747
2748    #[fuchsia::test]
2749    async fn test_watch_default_should_return_error_on_concurrent_call() {
2750        let mut service = NetpolNetworksService::default();
2751
2752        let (networks_proxy, networks_stream) =
2753            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2754
2755        service.add_stream(networks_stream);
2756
2757        // Call `watch_default` twice concurrently on the same connection.
2758        let watch_fut1 = networks_proxy.watch_default();
2759        let req_event1 = service.select_next_some().await;
2760        assert_eq!(
2761            service.handle_event(req_event1).await.expect("Failed to handle event"),
2762            DelegatedNetworkUpdateResult::default()
2763        );
2764
2765        let watch_fut2 = networks_proxy.watch_default();
2766        let req_event2 = service.select_next_some().await;
2767        assert_eq!(
2768            service.handle_event(req_event2).await.expect("Failed to handle event"),
2769            DelegatedNetworkUpdateResult::default()
2770        );
2771
2772        for res in [watch_fut1.await, watch_fut2.await] {
2773            assert_matches!(
2774                res,
2775                Err(fidl::Error::ClientChannelClosed { epitaph, .. })
2776                    if epitaph == zx::Status::ALREADY_EXISTS
2777            );
2778        }
2779        // The client is removed when the closed stream yields its terminal item.
2780        let disconnect_event = service.select_next_some().await;
2781        assert_eq!(
2782            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
2783            DelegatedNetworkUpdateResult::default()
2784        );
2785        assert!(service.networks_clients.is_empty());
2786    }
2787
2788    #[fuchsia::test]
2789    async fn test_network_removal_reports_network_gone() {
2790        let mut service = NetpolNetworksService::default();
2791
2792        service
2793            .update(NetworkRegistryUpdate::ChangeNetwork(
2794                DELEGATED_ID_1,
2795                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
2796                    true,
2797                    Some(test_marks()),
2798                    None,
2799                    None,
2800                )),
2801            ))
2802            .await;
2803
2804        let token1 = service
2805            .tokens
2806            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
2807            .get()
2808            .duplicate()
2809            .unwrap();
2810        let token2 = service
2811            .tokens
2812            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
2813            .get()
2814            .duplicate()
2815            .unwrap();
2816
2817        let (active_watcher, active_server_end) =
2818            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2819        let (idle_watcher, idle_server_end) =
2820            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2821        let (networks_proxy, networks_stream) =
2822            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2823
2824        service.add_stream(networks_stream);
2825
2826        for (token, server_end) in [(token1, active_server_end), (token2, idle_server_end)] {
2827            let watch_req =
2828                networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
2829                    network: Some(token),
2830                    properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
2831                    watcher: Some(server_end),
2832                    ..Default::default()
2833                });
2834            let req_event = service.select_next_some().await;
2835            assert_eq!(
2836                service.handle_event(req_event).await.expect("Failed to handle event"),
2837                DelegatedNetworkUpdateResult::default()
2838            );
2839            assert_matches!(watch_req.await, Ok(Ok(())));
2840        }
2841
2842        // Active watcher's first watch call returns the initial snapshot.
2843        let active_watch_fut1 = active_watcher.watch();
2844        let active_pw_event1 = service.select_next_some().await;
2845        assert_eq!(
2846            service.handle_event(active_pw_event1).await.expect("Failed to handle event"),
2847            DelegatedNetworkUpdateResult::default()
2848        );
2849        let initial_updates = active_watch_fut1.await.unwrap().unwrap();
2850        assert_eq!(
2851            initial_updates,
2852            fnp_properties::PropertyUpdate {
2853                socket_marks: Some(test_marks()),
2854                dns_configuration: None,
2855                ..Default::default()
2856            }
2857        );
2858
2859        // Active watcher starts an in-flight watch call before network removal.
2860        let mut active_watch_fut2 = active_watcher.watch();
2861        let active_pw_event2 = service.select_next_some().await;
2862        assert_eq!(
2863            service.handle_event(active_pw_event2).await.expect("Failed to handle event"),
2864            DelegatedNetworkUpdateResult::default()
2865        );
2866        assert_matches!((&mut active_watch_fut2).now_or_never(), None);
2867
2868        // Remove the network.
2869        service
2870            .update(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove))
2871            .await;
2872
2873        // Active watcher immediately observes NetworkGone on its in-flight call, and its
2874        // stream yields the terminal item because `update` closed the channel.
2875        assert_matches!(
2876            active_watch_fut2.await,
2877            Ok(Err(fnp_properties::PropertyWatcherError::NetworkGone))
2878        );
2879        let active_closed_event = service.select_next_some().await;
2880        assert_eq!(
2881            service.handle_event(active_closed_event).await.expect("Failed to handle event"),
2882            DelegatedNetworkUpdateResult::default()
2883        );
2884
2885        // Idle watcher calls watch() after network removal and also observes NetworkGone.
2886        let idle_watch_fut = idle_watcher.watch();
2887        let idle_pw_event = service.select_next_some().await;
2888        assert_eq!(
2889            service.handle_event(idle_pw_event).await.expect("Failed to handle event"),
2890            DelegatedNetworkUpdateResult::default()
2891        );
2892        assert_matches!(
2893            idle_watch_fut.await,
2894            Ok(Err(fnp_properties::PropertyWatcherError::NetworkGone))
2895        );
2896    }
2897
2898    #[fuchsia::test]
2899    async fn test_idle_default_watcher_reports_network_gone_on_default_change() {
2900        let mut service = NetpolNetworksService::default();
2901
2902        // Add network and set it as the default network.
2903        service
2904            .update(NetworkRegistryUpdate::ChangeNetwork(
2905                DELEGATED_ID_1,
2906                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
2907                    true,
2908                    Some(test_marks()),
2909                    None,
2910                    None,
2911                )),
2912            ))
2913            .await;
2914        service
2915            .update(NetworkRegistryUpdate::ChangeNetwork(
2916                DELEGATED_ID_1,
2917                NetworkUpdate::MakeDefault,
2918            ))
2919            .await;
2920
2921        let (networks_proxy, networks_stream) =
2922            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2923        service.add_stream(networks_stream);
2924
2925        // Client calls watch_default() to get default token.
2926        let default_fut = networks_proxy.watch_default();
2927        let req_event = service.select_next_some().await;
2928        assert_eq!(
2929            service.handle_event(req_event).await.expect("Failed to handle event"),
2930            DelegatedNetworkUpdateResult::default()
2931        );
2932        let default_token = match default_fut.await.unwrap() {
2933            fnp_properties::NetworksWatchDefaultResponse::Network(token) => token,
2934            res => panic!("Expected Network token, got {res:?}"),
2935        };
2936
2937        // Client registers PropertyWatcher for the default token.
2938        let (watcher, server_end) =
2939            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2940        let watch_req =
2941            networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
2942                network: Some(default_token),
2943                properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
2944                watcher: Some(server_end),
2945                ..Default::default()
2946            });
2947        let req_event = service.select_next_some().await;
2948        assert_eq!(
2949            service.handle_event(req_event).await.expect("Failed to handle event"),
2950            DelegatedNetworkUpdateResult::default()
2951        );
2952        assert_matches!(watch_req.await, Ok(Ok(())));
2953
2954        // Client fetches initial property snapshot.
2955        let initial_fut = watcher.watch();
2956        let pw_event = service.select_next_some().await;
2957        assert_eq!(
2958            service.handle_event(pw_event).await.expect("Failed to handle event"),
2959            DelegatedNetworkUpdateResult::default()
2960        );
2961        assert_matches!(initial_fut.await, Ok(Ok(_)));
2962
2963        // Client has no pending watch calls. Unset the default network.
2964        service.update(NetworkRegistryUpdate::unset_default()).await;
2965
2966        // Idle watcher calls watch() after default network change and receives NetworkGone.
2967        let idle_watch_fut = watcher.watch();
2968        let idle_pw_event = service.select_next_some().await;
2969        assert_eq!(
2970            service.handle_event(idle_pw_event).await.expect("Failed to handle event"),
2971            DelegatedNetworkUpdateResult::default()
2972        );
2973        assert_matches!(
2974            idle_watch_fut.await,
2975            Ok(Err(fnp_properties::PropertyWatcherError::NetworkGone))
2976        );
2977    }
2978
2979    #[test_case(
2980        None,
2981        Some(fnp_socketproxy::NetworkInfo::Starnix(
2982            fnp_socketproxy::StarnixNetworkInfo {
2983                mark: Some(123),
2984                ..Default::default()
2985            }
2986        )),
2987        Err(NetworkValidationError::MissingNetworkId);
2988        "missing network_id"
2989    )]
2990    #[test_case(
2991        Some(0),
2992        Some(fnp_socketproxy::NetworkInfo::Starnix(
2993            fnp_socketproxy::StarnixNetworkInfo {
2994                mark: Some(123),
2995                ..Default::default()
2996            }
2997        )),
2998        Err(NetworkValidationError::MissingNetworkId);
2999        "zero network_id"
3000    )]
3001    #[test_case(
3002        Some(1),
3003        None,
3004        Err(NetworkValidationError::MissingNetworkInfo);
3005        "missing network_info"
3006    )]
3007    #[test_case(
3008        Some(1),
3009        Some(fnp_socketproxy::NetworkInfo::Fuchsia(Default::default())),
3010        Err(NetworkValidationError::MissingNetworkInfo);
3011        "fuchsia network_info"
3012    )]
3013    #[test_case(
3014        Some(1),
3015        Some(fnp_socketproxy::NetworkInfo::Starnix(
3016            fnp_socketproxy::StarnixNetworkInfo {
3017                mark: Some(123),
3018                ..Default::default()
3019            }
3020        )),
3021        Ok(NetworkId::delegated(InterfaceId::new(1).unwrap()));
3022        "valid starnix network"
3023    )]
3024    fn test_validated_network_validation(
3025        network_id: Option<u32>,
3026        info: Option<fnp_socketproxy::NetworkInfo>,
3027        expected: Result<NetworkId, NetworkValidationError>,
3028    ) {
3029        let net = fnp_socketproxy::Network { network_id, info, ..Default::default() };
3030        let result = ValidatedNetwork::try_from(net).map(|v| v.network_id);
3031        assert_eq!(result, expected);
3032    }
3033
3034    fn starnix_network_payload(id: u32, mark: u32) -> fnp_socketproxy::Network {
3035        fnp_socketproxy::Network {
3036            network_id: Some(id),
3037            info: Some(NetworkInfo::Starnix(StarnixNetworkInfo {
3038                mark: Some(mark),
3039                ..Default::default()
3040            })),
3041            ..Default::default()
3042        }
3043    }
3044
3045    async fn process_fidl_request<E>(
3046        service: &mut NetpolNetworksService,
3047        request_future: impl Future<Output = Result<Result<(), E>, fidl::Error>>,
3048    ) -> Result<(), E> {
3049        let event = service.select_next_some().await;
3050        let update_result = service.handle_event(event).await.expect("failed to handle event");
3051        let client_result = request_future.await.expect("FIDL call failed");
3052        assert_eq!(update_result.dns_servers.is_some(), client_result.is_ok());
3053        client_result
3054    }
3055
3056    #[fuchsia::test]
3057    async fn test_delegated_networks_fidl_errors() {
3058        let mut service = NetpolNetworksService::default();
3059        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3060        service.add_stream(stream);
3061
3062        const NETWORK_ID_1: u32 = 1;
3063        const NETWORK_ID_2: u32 = 2;
3064        const SOCKET_MARK: u32 = 100;
3065
3066        // Updating a network before it has been added returns NotFound.
3067        assert_eq!(
3068            process_fidl_request(
3069                &mut service,
3070                proxy.update(&starnix_network_payload(NETWORK_ID_1, SOCKET_MARK,)),
3071            )
3072            .await,
3073            Err(NetworkRegistryUpdateError::NotFound)
3074        );
3075
3076        // Adding a valid network succeeds.
3077        assert_eq!(
3078            process_fidl_request(
3079                &mut service,
3080                proxy.add(&starnix_network_payload(NETWORK_ID_1, SOCKET_MARK)),
3081            )
3082            .await,
3083            Ok(())
3084        );
3085
3086        // Adding a duplicate network ID returns DuplicateNetworkId.
3087        assert_eq!(
3088            process_fidl_request(
3089                &mut service,
3090                proxy.add(&starnix_network_payload(NETWORK_ID_1, SOCKET_MARK)),
3091            )
3092            .await,
3093            Err(NetworkRegistryAddError::DuplicateNetworkId)
3094        );
3095
3096        // Removing a non-existent network returns NotFound.
3097        assert_eq!(
3098            process_fidl_request(&mut service, proxy.remove(NETWORK_ID_2)).await,
3099            Err(NetworkRegistryRemoveError::NotFound)
3100        );
3101
3102        // Setting a non-existent network as default returns NotFound.
3103        assert_eq!(
3104            process_fidl_request(
3105                &mut service,
3106                proxy.set_default(&fposix_socket::OptionalUint32::Value(NETWORK_ID_2,)),
3107            )
3108            .await,
3109            Err(NetworkRegistrySetDefaultError::NotFound)
3110        );
3111
3112        // Setting an existing network as default succeeds.
3113        assert_eq!(
3114            process_fidl_request(
3115                &mut service,
3116                proxy.set_default(&fposix_socket::OptionalUint32::Value(NETWORK_ID_1,)),
3117            )
3118            .await,
3119            Ok(())
3120        );
3121
3122        // Attempting to remove the active default network returns
3123        // CannotRemoveDefaultNetwork.
3124        assert_eq!(
3125            process_fidl_request(&mut service, proxy.remove(NETWORK_ID_1)).await,
3126            Err(NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)
3127        );
3128
3129        // Unsetting the default network succeeds.
3130        assert_eq!(
3131            process_fidl_request(
3132                &mut service,
3133                proxy.set_default(&fposix_socket::OptionalUint32::Unset(fposix_socket::Empty)),
3134            )
3135            .await,
3136            Ok(())
3137        );
3138
3139        // Once unset from default, removing the network succeeds.
3140        assert_eq!(process_fidl_request(&mut service, proxy.remove(NETWORK_ID_1)).await, Ok(()));
3141    }
3142
3143    #[fuchsia::test]
3144    async fn test_inspect_metrics() {
3145        let inspector = fuchsia_inspect::Inspector::default();
3146        let telemetry_node = inspector.root().create_child("telemetry");
3147        let mut service = NetpolNetworksService::default()
3148            .with_inspect(&telemetry_node, "operations", &telemetry_node, "network_registry")
3149            .expect("failed to initialize inspect");
3150        inspector.root().record(telemetry_node);
3151
3152        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3153        service.add_stream(stream);
3154
3155        let make_network = |id: u32, mark: u32| fnp_socketproxy::Network {
3156            network_id: Some(id),
3157            info: Some(NetworkInfo::Starnix(StarnixNetworkInfo {
3158                mark: Some(mark),
3159                ..Default::default()
3160            })),
3161            name: Some("wlan0".to_string()),
3162            network_type: Some(fnp_socketproxy::NetworkType::Wifi),
3163            connectivity: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
3164            ..Default::default()
3165        };
3166
3167        // Initial check - all values should be default.
3168        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3169        assert_data_tree!(
3170            hierarchy,
3171            root: contains {
3172                telemetry: contains {
3173                    operations: contains {
3174                        delegated: contains {
3175                            adds: contains { successes: 0u64, errors: 0u64 },
3176                            removes: contains { successes: 0u64, errors: 0u64 },
3177                            set_defaults: contains {
3178                                successes: 0u64,
3179                                errors: 0u64,
3180                            },
3181                            updates: contains {
3182                                successes: 0u64,
3183                                errors: 0u64,
3184                            },
3185                        }
3186                    }
3187                }
3188            }
3189        );
3190
3191        // Add valid network 1.
3192        assert_eq!(
3193            process_fidl_request(&mut service, proxy.add(&make_network(1, 100))).await,
3194            Ok(())
3195        );
3196
3197        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3198        assert_data_tree!(
3199            hierarchy,
3200            root: contains {
3201                telemetry: contains {
3202                    operations: contains {
3203                        delegated: contains {
3204                            adds: contains { successes: 1u64, errors: 0u64 },
3205                        }
3206                    }
3207                }
3208            }
3209        );
3210
3211        // Add duplicate network 1 -> error.
3212        assert_eq!(
3213            process_fidl_request(&mut service, proxy.add(&make_network(1, 100))).await,
3214            Err(NetworkRegistryAddError::DuplicateNetworkId)
3215        );
3216
3217        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3218        assert_data_tree!(
3219            hierarchy,
3220            root: contains {
3221                telemetry: contains {
3222                    operations: contains {
3223                        delegated: contains {
3224                            adds: contains { successes: 1u64, errors: 1u64 },
3225                        }
3226                    }
3227                }
3228            }
3229        );
3230
3231        // Update network 1 properties.
3232        assert_eq!(
3233            process_fidl_request(&mut service, proxy.update(&make_network(1, 150))).await,
3234            Ok(())
3235        );
3236
3237        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3238        assert_data_tree!(
3239            hierarchy,
3240            root: contains {
3241                telemetry: contains {
3242                    operations: contains {
3243                        delegated: contains {
3244                            updates: contains {
3245                                successes: 1u64,
3246                                errors: 0u64,
3247                            },
3248                        }
3249                    }
3250                }
3251            }
3252        );
3253
3254        // Make network 1 default.
3255        assert_eq!(
3256            process_fidl_request(
3257                &mut service,
3258                proxy.set_default(&fposix_socket::OptionalUint32::Value(1)),
3259            )
3260            .await,
3261            Ok(())
3262        );
3263
3264        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3265        assert_data_tree!(
3266            hierarchy,
3267            root: contains {
3268                telemetry: contains {
3269                    operations: contains {
3270                        delegated: contains {
3271                            set_defaults: contains {
3272                                successes: 1u64,
3273                                errors: 0u64,
3274                            },
3275                            default_network_id: 1u64,
3276                        }
3277                    }
3278                }
3279            }
3280        );
3281
3282        // Make non-existent network 2 default -> error.
3283        assert_eq!(
3284            process_fidl_request(
3285                &mut service,
3286                proxy.set_default(&fposix_socket::OptionalUint32::Value(2)),
3287            )
3288            .await,
3289            Err(NetworkRegistrySetDefaultError::NotFound)
3290        );
3291
3292        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3293        assert_data_tree!(
3294            hierarchy,
3295            root: contains {
3296                telemetry: contains {
3297                    operations: contains {
3298                        delegated: contains {
3299                            set_defaults: contains {
3300                                successes: 1u64,
3301                                errors: 1u64,
3302                            },
3303                        }
3304                    }
3305                }
3306            }
3307        );
3308
3309        // Try removing default network 1 -> error.
3310        assert_eq!(
3311            process_fidl_request(&mut service, proxy.remove(1)).await,
3312            Err(NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)
3313        );
3314
3315        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3316        assert_data_tree!(
3317            hierarchy,
3318            root: contains {
3319                telemetry: contains {
3320                    operations: contains {
3321                        delegated: contains {
3322                            removes: contains { successes: 0u64, errors: 1u64 },
3323                        }
3324                    }
3325                }
3326            }
3327        );
3328
3329        // Unset default.
3330        assert_eq!(
3331            process_fidl_request(
3332                &mut service,
3333                proxy.set_default(&fposix_socket::OptionalUint32::Unset(fposix_socket::Empty)),
3334            )
3335            .await,
3336            Ok(())
3337        );
3338
3339        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3340        assert_data_tree!(
3341            hierarchy,
3342            root: contains {
3343                telemetry: contains {
3344                    operations: contains {
3345                        delegated: contains {
3346                            set_defaults: contains {
3347                                successes: 2u64,
3348                                errors: 1u64,
3349                            },
3350                        }
3351                    }
3352                }
3353            }
3354        );
3355
3356        // Remove network 1 -> success.
3357        assert_eq!(process_fidl_request(&mut service, proxy.remove(1)).await, Ok(()));
3358
3359        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3360        assert_data_tree!(
3361            hierarchy,
3362            root: contains {
3363                telemetry: contains {
3364                    operations: contains {
3365                        delegated: contains {
3366                            removes: contains { successes: 1u64, errors: 1u64 },
3367                        }
3368                    }
3369                }
3370            }
3371        );
3372    }
3373
3374    #[fuchsia::test]
3375    async fn test_network_registry_inspect() {
3376        const FUCHSIA_ID_2: NetworkId = NetworkId::Fuchsia(ID_2);
3377        const DELEGATED_ID_100: NetworkId =
3378            NetworkId::Delegated(InterfaceId(NonZeroU64::new(100).unwrap()));
3379
3380        let inspector = fuchsia_inspect::Inspector::default();
3381        let telemetry_node = inspector.root().create_child("telemetry");
3382        let mut service = NetpolNetworksService::default()
3383            .with_inspect(&telemetry_node, "operations", &telemetry_node, "network_registry")
3384            .expect("failed to initialize inspect");
3385        inspector.root().record(telemetry_node);
3386
3387        // Initial check: empty network_registry node
3388        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3389        assert_data_tree!(
3390            hierarchy,
3391            root: contains {
3392                telemetry: contains {
3393                    network_registry: {}
3394                }
3395            }
3396        );
3397
3398        // Add a Fuchsia network (ID_2)
3399        service
3400            .update(NetworkRegistryUpdate::ChangeNetwork(
3401                FUCHSIA_ID_2,
3402                NetworkUpdate::Properties(NetworkPropertiesChange {
3403                    added: true,
3404                    name: Some("wlan0".to_string()),
3405                    network_type: Some(fnp_socketproxy::NetworkType::Wifi),
3406                    connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
3407                    ..Default::default()
3408                }),
3409            ))
3410            .await;
3411
3412        // Add a Delegated network (ID 100)
3413        let mut marks = fnet::Marks::default();
3414        marks.mark_1 = Some(100);
3415        service
3416            .update(NetworkRegistryUpdate::ChangeNetwork(
3417                DELEGATED_ID_100,
3418                NetworkUpdate::Properties(NetworkPropertiesChange {
3419                    added: true,
3420                    marks: Some(marks.clone()),
3421                    name: Some("eth0".to_string()),
3422                    network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
3423                    connectivity_state: Some(fnp_socketproxy::ConnectivityState::LocalConnectivity),
3424                    ..Default::default()
3425                }),
3426            ))
3427            .await;
3428
3429        // Make Delegated network Starnix default
3430        service
3431            .update(NetworkRegistryUpdate::ChangeNetwork(
3432                DELEGATED_ID_100,
3433                NetworkUpdate::MakeDefault,
3434            ))
3435            .await;
3436
3437        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3438        assert_data_tree!(
3439            hierarchy,
3440            root: contains {
3441                telemetry: contains {
3442                    network_registry: {
3443                        default_network: "fuchsia:2",
3444                        starnix_default: "delegated:100",
3445                        fuchsia_2: {
3446                            name: "wlan0",
3447                            network_type: "Wifi",
3448                            connectivity_state: "FullConnectivity",
3449                        },
3450                        delegated_100: {
3451                            name: "eth0",
3452                            network_type: "Ethernet",
3453                            connectivity_state: "LocalConnectivity",
3454                        },
3455                    }
3456                }
3457            }
3458        );
3459
3460        // Update Delegated network properties (change connectivity_state and name)
3461        service
3462            .update(NetworkRegistryUpdate::ChangeNetwork(
3463                DELEGATED_ID_100,
3464                NetworkUpdate::Properties(NetworkPropertiesChange {
3465                    added: false,
3466                    marks: Some(marks),
3467                    name: Some("eth0_updated".to_string()),
3468                    network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
3469                    connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
3470                    ..Default::default()
3471                }),
3472            ))
3473            .await;
3474
3475        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3476        assert_data_tree!(
3477            hierarchy,
3478            root: contains {
3479                telemetry: contains {
3480                    network_registry: {
3481                        default_network: "fuchsia:2",
3482                        starnix_default: "delegated:100",
3483                        fuchsia_2: {
3484                            name: "wlan0",
3485                            network_type: "Wifi",
3486                            connectivity_state: "FullConnectivity",
3487                        },
3488                        delegated_100: {
3489                            name: "eth0_updated",
3490                            network_type: "Ethernet",
3491                            connectivity_state: "FullConnectivity",
3492                        },
3493                    }
3494                }
3495            }
3496        );
3497
3498        // Remove the Fuchsia network (ID_2).
3499        // Default network should now fall back to the delegated network.
3500        service
3501            .update(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove))
3502            .await;
3503
3504        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3505        assert_data_tree!(
3506            hierarchy,
3507            root: contains {
3508                telemetry: contains {
3509                    network_registry: {
3510                        default_network: "delegated:100",
3511                        starnix_default: "delegated:100",
3512                        delegated_100: {
3513                            name: "eth0_updated",
3514                            network_type: "Ethernet",
3515                            connectivity_state: "FullConnectivity",
3516                        },
3517                    }
3518                }
3519            }
3520        );
3521
3522        // Unset Starnix default and remove Delegated network.
3523        // network_registry node should now be completely empty.
3524        service.update(NetworkRegistryUpdate::UnsetDefaultNetwork).await;
3525        service
3526            .update(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_100, NetworkUpdate::Remove))
3527            .await;
3528
3529        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3530        assert_data_tree!(
3531            hierarchy,
3532            root: contains {
3533                telemetry: contains {
3534                    network_registry: {}
3535                }
3536            }
3537        );
3538    }
3539
3540    #[fuchsia::test]
3541    async fn test_reachability_service_integration() {
3542        let mut service = NetpolNetworksService::default();
3543
3544        let mut expected_disconnected = freachability::Snapshot::default();
3545        expected_disconnected.gateway_reachable = Some(false);
3546        expected_disconnected.internet_available = Some(false);
3547        expected_disconnected.dns_active = Some(false);
3548        expected_disconnected.http_active = Some(false);
3549
3550        let mut expected_validated = freachability::Snapshot::default();
3551        expected_validated.gateway_reachable = Some(true);
3552        expected_validated.internet_available = Some(true);
3553        expected_validated.dns_active = Some(true);
3554        expected_validated.http_active = Some(true);
3555
3556        let (proxy, stream) =
3557            fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
3558        service.add_stream(stream);
3559        assert_eq!(service.reachability_watcher_count(), 1);
3560
3561        let watch_fut = proxy.watch();
3562        let event = service.select_next_some().await;
3563        assert_eq!(
3564            service.handle_event(event).await.expect("handle event"),
3565            DelegatedNetworkUpdateResult::default()
3566        );
3567        let snapshot = watch_fut.await.expect("watch error");
3568        assert_eq!(snapshot, expected_disconnected);
3569
3570        let mut second_watch = proxy.watch();
3571        let event = service.select_next_some().await;
3572        assert_eq!(
3573            service.handle_event(event).await.expect("handle event"),
3574            DelegatedNetworkUpdateResult::default()
3575        );
3576        assert_matches!(futures::poll!(&mut second_watch), std::task::Poll::Pending);
3577
3578        service
3579            .update(NetworkRegistryUpdate::ChangeNetwork(
3580                DELEGATED_ID_1,
3581                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
3582                    true,
3583                    Some(test_marks()),
3584                    None,
3585                    Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
3586                )),
3587            ))
3588            .await;
3589        service
3590            .update(NetworkRegistryUpdate::ChangeNetwork(
3591                DELEGATED_ID_1,
3592                NetworkUpdate::MakeDefault,
3593            ))
3594            .await;
3595
3596        let snapshot = second_watch.await.expect("watch should succeed");
3597        assert_eq!(snapshot, expected_validated);
3598    }
3599
3600    #[fuchsia::test]
3601    async fn test_networks_client_disconnect_cleans_up_state() {
3602        let mut service = NetpolNetworksService::default();
3603        let (proxy, stream) =
3604            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3605        service.add_stream(stream);
3606
3607        // Client calls WatchDefault when there is no default network (so it hangs).
3608        let mut watch_default_fut = proxy.watch_default();
3609        let event = service.select_next_some().await;
3610        assert_eq!(
3611            service.handle_event(event).await.expect("Failed to handle event"),
3612            DelegatedNetworkUpdateResult::default()
3613        );
3614        assert_matches!((&mut watch_default_fut).now_or_never(), None);
3615        assert_matches!(
3616            &service.networks_clients.values().collect::<Vec<_>>()[..],
3617            [NetworksClient { responder: Some(_), .. }]
3618        );
3619
3620        // Client closes the connection.
3621        drop(watch_default_fut);
3622        drop(proxy);
3623
3624        let event = service.select_next_some().await;
3625        assert_eq!(
3626            service.handle_event(event).await.expect("Failed to handle event"),
3627            DelegatedNetworkUpdateResult::default()
3628        );
3629        // Client state and connection generations must be cleaned up.
3630        assert!(service.networks_clients.is_empty());
3631        assert!(service.generations_by_connection.default_network.is_empty());
3632    }
3633
3634    #[fuchsia::test]
3635    async fn test_property_watcher_client_disconnect_cleans_up_state() {
3636        let mut service = NetpolNetworksService::default();
3637
3638        service
3639            .update(NetworkRegistryUpdate::ChangeNetwork(
3640                DELEGATED_ID_1,
3641                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
3642                    true,
3643                    Some(test_marks()),
3644                    None,
3645                    None,
3646                )),
3647            ))
3648            .await;
3649
3650        let token = service
3651            .tokens
3652            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
3653            .get()
3654            .duplicate()
3655            .unwrap();
3656
3657        let (watcher, server_end) =
3658            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
3659        let (networks_proxy, networks_stream) =
3660            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3661        service.add_stream(networks_stream);
3662
3663        let watch_req =
3664            networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
3665                network: Some(token),
3666                properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
3667                watcher: Some(server_end),
3668                ..Default::default()
3669            });
3670
3671        let req_event = service.select_next_some().await;
3672        assert_eq!(
3673            service.handle_event(req_event).await.expect("Failed to handle event"),
3674            DelegatedNetworkUpdateResult::default()
3675        );
3676        assert_matches!(watch_req.await, Ok(Ok(())));
3677
3678        // Consume initial snapshot.
3679        let watch_fut1 = watcher.watch();
3680        let pw_event1 = service.select_next_some().await;
3681        assert_eq!(
3682            service.handle_event(pw_event1).await.expect("Failed to handle event"),
3683            DelegatedNetworkUpdateResult::default()
3684        );
3685        let _ = watch_fut1.await.unwrap().unwrap();
3686
3687        // Second watch hangs waiting for updates.
3688        let mut watch_fut2 = watcher.watch();
3689        let pw_event2 = service.select_next_some().await;
3690        assert_eq!(
3691            service.handle_event(pw_event2).await.expect("Failed to handle event"),
3692            DelegatedNetworkUpdateResult::default()
3693        );
3694        assert_matches!((&mut watch_fut2).now_or_never(), None);
3695        assert_eq!(service.property_watchers.len(), 1);
3696
3697        // Client closes the PropertyWatcher connection.
3698        drop(watch_fut2);
3699        drop(watcher);
3700
3701        let disconnect_event = service.select_next_some().await;
3702        assert_eq!(
3703            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
3704            DelegatedNetworkUpdateResult::default()
3705        );
3706
3707        // Watcher registration and connection generation must be cleanly purged.
3708        assert!(service.property_watchers.is_empty());
3709        assert!(service.generations_by_connection.properties.is_empty());
3710    }
3711
3712    #[fuchsia::test]
3713    async fn test_delegated_networks_single_connection() {
3714        let mut service = NetpolNetworksService::default();
3715        let (proxy1, stream1) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3716        service.add_stream(stream1);
3717        assert!(service.delegated_networks_client.is_some());
3718
3719        // First connection succeeds.
3720        assert_eq!(
3721            process_fidl_request(&mut service, proxy1.remove(1)).await,
3722            Err(NetworkRegistryRemoveError::NotFound)
3723        );
3724
3725        // Concurrent second connection is rejected with ALREADY_EXISTS.
3726        let (proxy2, stream2) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3727        service.add_stream(stream2);
3728        assert_matches!(
3729            proxy2.remove(1).await,
3730            Err(fidl::Error::ClientChannelClosed { epitaph, .. })
3731                if epitaph == zx::Status::ALREADY_EXISTS
3732        );
3733
3734        // First connection continues to work after the second connection is rejected.
3735        assert_eq!(
3736            process_fidl_request(&mut service, proxy1.remove(1)).await,
3737            Err(NetworkRegistryRemoveError::NotFound)
3738        );
3739
3740        // Once the first connection closes and its terminal item is processed,
3741        // a new connection is allowed.
3742        drop(proxy1);
3743        let disconnect_event = service.select_next_some().await;
3744        assert_eq!(
3745            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
3746            DelegatedNetworkUpdateResult::default()
3747        );
3748        assert!(service.delegated_networks_client.is_none());
3749
3750        let (proxy3, stream3) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3751        service.add_stream(stream3);
3752        assert_eq!(
3753            process_fidl_request(&mut service, proxy3.remove(1)).await,
3754            Err(NetworkRegistryRemoveError::NotFound)
3755        );
3756    }
3757
3758    #[fuchsia::test]
3759    async fn test_delegated_networks_client_disconnect_cleans_up_state() {
3760        let mut service = NetpolNetworksService::default();
3761        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3762        service.add_stream(stream);
3763
3764        // The raw id used by the `fnp_socketproxy::Network` as a `network_id`.
3765        const STARNIX_ID_1: u32 = 1;
3766        const STARNIX_ID_2: u32 = 2;
3767        const SOCKET_MARK_1: u32 = 100;
3768        const SOCKET_MARK_2: u32 = 200;
3769
3770        // Register a native Fuchsia network alongside two delegated networks.
3771        service
3772            .update(NetworkRegistryUpdate::ChangeNetwork(
3773                FUCHSIA_ID_1,
3774                NetworkUpdate::Properties(NetworkPropertiesChange {
3775                    added: true,
3776                    ..Default::default()
3777                }),
3778            ))
3779            .await;
3780
3781        assert_eq!(
3782            process_fidl_request(
3783                &mut service,
3784                proxy.add(&starnix_network_payload(STARNIX_ID_1, SOCKET_MARK_1))
3785            )
3786            .await,
3787            Ok(())
3788        );
3789        assert_eq!(
3790            process_fidl_request(
3791                &mut service,
3792                proxy.add(&starnix_network_payload(STARNIX_ID_2, SOCKET_MARK_2))
3793            )
3794            .await,
3795            Ok(())
3796        );
3797        assert_eq!(
3798            process_fidl_request(
3799                &mut service,
3800                proxy.set_default(&fposix_socket::OptionalUint32::Value(STARNIX_ID_1)),
3801            )
3802            .await,
3803            Ok(())
3804        );
3805        assert_eq!(service.network_registry.starnix_default, Some(DELEGATED_ID_1));
3806        assert_eq!(service.network_registry.networks.len(), 3);
3807
3808        // Disconnect the client and verify all delegated state is cleared while Fuchsia state
3809        // remains intact.
3810        drop(proxy);
3811        let disconnect_event = service.select_next_some().await;
3812        assert_eq!(
3813            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
3814            DelegatedNetworkUpdateResult { dns_servers: Some(vec![]) }
3815        );
3816        assert_eq!(service.network_registry.starnix_default, None);
3817        assert_eq!(
3818            service.network_registry.networks.keys().copied().collect::<Vec<_>>(),
3819            vec![FUCHSIA_ID_1]
3820        );
3821
3822        // A reconnecting client can re-add NETWORK_ID_1 without hitting DuplicateNetworkId.
3823        let (proxy2, stream2) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3824        service.add_stream(stream2);
3825        assert_eq!(
3826            process_fidl_request(
3827                &mut service,
3828                proxy2.add(&starnix_network_payload(STARNIX_ID_1, SOCKET_MARK_2)),
3829            )
3830            .await,
3831            Ok(())
3832        );
3833    }
3834}