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