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 async_utils::stream::{Tagged, WithTag as _};
10use dns_server_watcher::DnsServers;
11use fidl::endpoints::{ControlHandle as _, Responder as _};
12use log::{error, info, warn};
13use policy_properties::NetworkTokenExt as _;
14use std::collections::HashMap;
15use std::collections::hash_map::Entry;
16
17mod token_registry;
18
19use fidl_fuchsia_net as fnet;
20use fidl_fuchsia_net_name as fnet_name;
21use fidl_fuchsia_net_policy_properties as fnp_properties;
22use fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy;
23use fidl_fuchsia_posix_socket as fposix_socket;
24
25// The id for each network, separated by network source.
26//
27// NB: These are separated in the case that the same underlying
28// interface id is used by Fuchsia and a delegated actor.
29#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub enum NetworkId {
31    Fuchsia(InterfaceId),
32    Delegated(InterfaceId),
33}
34
35impl std::fmt::Display for NetworkId {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            NetworkId::Fuchsia(interface_id) => write!(f, "fuchsia:{interface_id}"),
39            NetworkId::Delegated(interface_id) => write!(f, "delegated:{interface_id}"),
40        }
41    }
42}
43
44impl NetworkId {
45    pub fn get(&self) -> InterfaceId {
46        match self {
47            NetworkId::Fuchsia(interface_id) => *interface_id,
48            NetworkId::Delegated(interface_id) => *interface_id,
49        }
50    }
51
52    pub fn fuchsia<I: Into<InterfaceId>>(id: I) -> Self {
53        NetworkId::Fuchsia(id.into())
54    }
55
56    pub fn delegated<I: Into<InterfaceId>>(id: I) -> Self {
57        NetworkId::Delegated(id.into())
58    }
59
60    pub fn is_fuchsia(&self) -> bool {
61        matches!(self, NetworkId::Fuchsia(_))
62    }
63
64    pub fn is_delegated(&self) -> bool {
65        matches!(self, NetworkId::Delegated(_))
66    }
67}
68
69#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub(crate) struct NetworkTokenContents {
71    network_id: NetworkId,
72    is_default: bool,
73}
74
75#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
76pub struct ConnectionId(usize);
77
78#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct UpdateGeneration {
80    /// The current generation for `fuchsia.net.policy.properties.WatchDefault`.
81    /// Incremented each time the default network changes.
82    default_network: usize,
83
84    /// The current generation for `fuchsia.net.policy.properties.WatchProperties`.
85    /// Incremented each time a network property changes.
86    properties: usize,
87}
88
89#[derive(Clone, Debug, Default)]
90pub struct UpdateGenerations(HashMap<ConnectionId, UpdateGeneration>);
91
92impl UpdateGenerations {
93    fn default_network(&self, id: &ConnectionId) -> Option<usize> {
94        self.0.get(id).map(|g| g.default_network)
95    }
96
97    fn set_default_network(&mut self, id: ConnectionId, generation: UpdateGeneration) {
98        self.0.entry(id).or_default().default_network = generation.default_network;
99    }
100
101    fn properties(&self, id: &ConnectionId) -> Option<usize> {
102        self.0.get(id).map(|g| g.properties)
103    }
104
105    fn set_properties(&mut self, id: ConnectionId, generation: UpdateGeneration) {
106        self.0.entry(id).or_default().properties = generation.properties;
107    }
108
109    fn remove(&mut self, id: &ConnectionId) -> Option<UpdateGeneration> {
110        self.0.remove(id)
111    }
112}
113
114trait SetMark {
115    fn set_mark(&mut self, domain: fnet::MarkDomain, value: Option<u32>);
116}
117
118impl SetMark for fnet::Marks {
119    fn set_mark(&mut self, domain: fnet::MarkDomain, value: Option<u32>) {
120        match domain {
121            fnet::MarkDomain::Mark1 => self.mark_1 = value,
122            fnet::MarkDomain::Mark2 => self.mark_2 = value,
123        }
124    }
125}
126
127#[derive(Debug)]
128pub(crate) struct NetworkPropertyResponder {
129    token: fnp_properties::NetworkToken,
130    watched_properties: Vec<fnp_properties::Property>,
131    responder: fnp_properties::NetworksWatchPropertiesResponder,
132}
133
134impl NetworkPropertyResponder {
135    fn respond(
136        self,
137        response: Result<&[fnp_properties::PropertyUpdate], fnp_properties::WatchError>,
138    ) -> Result<(), fidl::Error> {
139        self.responder.send(response)
140    }
141}
142
143#[derive(Debug, PartialEq, Default, Clone)]
144struct NetworkProperties {
145    socket_marks: Option<fnet::Marks>,
146    dns_servers: Vec<fnet_name::DnsServer_>,
147    // TODO(https://fxbug.dev/486892417): Use this field for snapshot metrics.
148    #[allow(dead_code)]
149    connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
150    name: Option<String>,
151    network_type: Option<fnp_socketproxy::NetworkType>,
152}
153
154impl NetworkProperties {
155    fn get_marks(&self) -> Option<&fnet::Marks> {
156        self.socket_marks.as_ref()
157    }
158}
159
160/// The current state of all networks sent to the NetworkRegistry.
161#[derive(Default, Clone)]
162struct RegisteredNetworks {
163    /// The current default network, determined by the priority rules in
164    /// `calculate_active_default`.
165    default_network: Option<NetworkId>,
166    /// The Starnix default network, as determined by Starnix.
167    starnix_default: Option<NetworkId>,
168    networks: HashMap<NetworkId, NetworkProperties>,
169    dns_servers: Vec<fnet_name::DnsServer_>,
170}
171
172impl RegisteredNetworks {
173    // Determine the active default network based on the starnix_default and network registry.
174    // When one or more Fuchsia networks are present, they should be prioritized over Starnix
175    // networks. The 'most prioritized' Fuchsia network is the one with the lowest ID.
176    fn calculate_active_default(&self) -> Option<NetworkId> {
177        // Note: Fuchsia networks are only added to the NetworkRegistry if they meet
178        // certain criteria (ex: have a default route and are online).
179        let first_fuchsia = self.networks.keys().filter(|id| id.is_fuchsia()).cloned().min();
180        if let Some(fd) = first_fuchsia {
181            return Some(fd);
182        }
183
184        // Fallback to starnix_default. If it is unset, no default network is available.
185        if let Some(starnix_default) = self.starnix_default {
186            // Ensure that the network is present in the network registry.
187            assert!(self.networks.contains_key(&starnix_default));
188        }
189        self.starnix_default
190    }
191
192    // Handle updates to the active default network.
193    //
194    // Returns `Some(DefaultChangedEvent)` if the new default network
195    // is different from the old one, otherwise `None`.
196    fn handle_default_network_update(&mut self) -> Option<DefaultChangedEvent> {
197        let next_default = self.calculate_active_default();
198        if next_default != self.default_network {
199            let old_default = self.default_network;
200            self.default_network = next_default;
201            Some(DefaultChangedEvent { previous_default: old_default })
202        } else {
203            None
204        }
205    }
206
207    fn apply(&mut self, update: PropertyUpdate) -> RegistryUpdateResult {
208        match update {
209            PropertyUpdate::LoseDefaultNetwork => {
210                // Handle Starnix unsetting its default network.
211                self.starnix_default = None;
212                RegistryUpdateResult {
213                    event: UpdateApplied::None,
214                    default_changed: self.handle_default_network_update(),
215                }
216            }
217            PropertyUpdate::ChangeNetwork(network_id, network_change) => match network_change {
218                NetworkUpdate::Properties(event) => RegistryUpdateResult {
219                    event: self.handle_changed_network(network_id, event),
220                    default_changed: self.handle_default_network_update(),
221                },
222                NetworkUpdate::Remove => {
223                    if self.starnix_default == Some(network_id) {
224                        error!("Cannot remove the default delegated network. Update ignored.");
225                        RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
226                    } else if self.networks.remove(&network_id).is_some() {
227                        // Elect fallback default network internally.
228                        RegistryUpdateResult {
229                            event: UpdateApplied::NetworkRemoved(network_id),
230                            default_changed: self.handle_default_network_update(),
231                        }
232                    } else {
233                        error!("Cannot remove a non-existent network. Update ignored.");
234                        RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
235                    }
236                }
237                NetworkUpdate::MakeDefault => {
238                    match network_id {
239                        // Fuchsia networks are always the default network when present. Netcfg
240                        // does not use this API to set a Fuchsia network as the default.
241                        NetworkId::Fuchsia(_) => {}
242                        NetworkId::Delegated(_) => self.starnix_default = Some(network_id),
243                    }
244                    let default_changed = self.handle_default_network_update();
245                    RegistryUpdateResult { event: UpdateApplied::None, default_changed }
246                }
247            },
248            PropertyUpdate::UpdateDns(dns_servers) => {
249                let event = if self.dns_servers != dns_servers {
250                    self.dns_servers = dns_servers;
251                    UpdateApplied::DnsChanged
252                } else {
253                    UpdateApplied::None
254                };
255                RegistryUpdateResult { event, default_changed: None }
256            }
257        }
258    }
259
260    // Handle the `NetworkPropertiesChange` in a `PropertyUpdate`, determining
261    // whether network properties changed as a result of the update.
262    //
263    // Returns an `UpdateApplied::NetworkChanged` event if this is a valid change.
264    fn handle_changed_network(
265        &mut self,
266        network_id: NetworkId,
267        event: NetworkPropertiesChange,
268    ) -> UpdateApplied {
269        let NetworkPropertiesChange {
270            added,
271            marks: socket_marks,
272            dns_servers: changed_dns_servers,
273            connectivity_state,
274            name,
275            network_type,
276        } = event;
277        let entry = self.networks.entry(network_id);
278        let result = match (added, &entry, network_id, socket_marks) {
279            (true, Entry::Occupied(_), _, _) => Err("add already added network"),
280            (false, Entry::Vacant(_), _, _) => Err("update a non-added network"),
281            (_, _, NetworkId::Fuchsia(_), Some(_)) => Err("have a fuchsia network with marks"),
282            (_, _, NetworkId::Delegated(_), None) => Err("have a delegated network without marks"),
283            (_, entry, NetworkId::Fuchsia(_), None) => {
284                let new_dns = changed_dns_servers.unwrap_or_default();
285                let changed_dns = match entry {
286                    Entry::Occupied(e) => e.get().dns_servers != new_dns,
287                    // When adding a new network, set `changed_dns` to true so responders receive
288                    // an explicit initial state.
289                    Entry::Vacant(_) => true,
290                };
291                Ok((
292                    NetworkProperties { dns_servers: new_dns, ..Default::default() },
293                    added,
294                    changed_dns,
295                ))
296            }
297            (_, entry, NetworkId::Delegated(_), Some(socket_marks)) => {
298                let new_dns = changed_dns_servers.unwrap_or_default();
299                let (changed_marks, changed_dns) = match entry {
300                    Entry::Occupied(e) => {
301                        (e.get().get_marks() != Some(&socket_marks), e.get().dns_servers != new_dns)
302                    }
303                    // When adding a new network, set both `changed_marks` and `changed_dns` to
304                    // true so responders receive an explicit initial state.
305                    Entry::Vacant(_) => (true, true),
306                };
307                Ok((
308                    NetworkProperties {
309                        socket_marks: Some(socket_marks),
310                        dns_servers: new_dns,
311                        ..Default::default()
312                    },
313                    changed_marks,
314                    changed_dns,
315                ))
316            }
317        };
318
319        match result {
320            Ok((mut properties, changed_marks, changed_dns)) => {
321                properties.connectivity_state = connectivity_state;
322                properties.network_type = network_type;
323                properties.name = name.clone();
324                let _ = entry.insert_entry(properties);
325                UpdateApplied::NetworkChanged {
326                    network_id,
327                    added,
328                    changed_marks,
329                    changed_dns,
330                    name,
331                    network_type,
332                }
333            }
334            Err(e) => {
335                error!("Cannot {e}. Update ignored.");
336                UpdateApplied::None
337            }
338        }
339    }
340
341    /// Returns the DNS servers for the default network if it is a Fuchsia network,
342    /// otherwise returns a concatenation of DNS servers from all delegated networks.
343    /// TODO(https://fxbug.dev/428712735): Remove once dns-resolver learns about DNS
344    /// via NetworkProperties.
345    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
346        if let Some(NetworkId::Fuchsia(if_id)) = self.default_network {
347            self.networks
348                .get(&NetworkId::Fuchsia(if_id))
349                .map(|p| p.dns_servers.clone())
350                .unwrap_or_default()
351        } else {
352            self.networks
353                .iter()
354                .filter(|(id, _)| matches!(id, NetworkId::Delegated(_)))
355                .flat_map(|(_, p)| &p.dns_servers)
356                .cloned()
357                .collect()
358        }
359    }
360
361    fn maybe_respond(
362        &self,
363        network: &NetworkTokenContents,
364        responder: NetworkPropertyResponder,
365    ) -> Option<NetworkPropertyResponder> {
366        let mut updates = Vec::new();
367        updates.add_socket_marks(self, network, &responder);
368        updates.add_dns(self, network, &responder);
369
370        if updates.is_empty() {
371            Some(responder)
372        } else {
373            if let Err(e) = responder.respond(Ok(&updates)) {
374                warn!("Could not send to responder: {e}");
375            }
376            None
377        }
378    }
379}
380
381trait PropertyUpdates {
382    fn add_socket_marks(
383        &mut self,
384        network_registry: &RegisteredNetworks,
385        network: &NetworkTokenContents,
386        responder: &NetworkPropertyResponder,
387    );
388    fn add_dns(
389        &mut self,
390        network_registry: &RegisteredNetworks,
391        network: &NetworkTokenContents,
392        responder: &NetworkPropertyResponder,
393    );
394}
395
396impl PropertyUpdates for Vec<fnp_properties::PropertyUpdate> {
397    fn add_socket_marks(
398        &mut self,
399        network_registry: &RegisteredNetworks,
400        network: &NetworkTokenContents,
401        responder: &NetworkPropertyResponder,
402    ) {
403        if !responder.watched_properties.contains(&fnp_properties::Property::SocketMarks) {
404            return;
405        }
406
407        match network_registry.networks.get(&network.network_id) {
408            Some(network) => {
409                if let Some(socket_marks) = network.get_marks() {
410                    self.push(fnp_properties::PropertyUpdate::SocketMarks(socket_marks.clone()));
411                }
412                return;
413            }
414            None => {
415                error!(
416                    "State is inconsistent. We attempted to add marks for a \
417            network that is not known: {:?}",
418                    network.network_id
419                );
420            }
421        }
422    }
423
424    fn add_dns(
425        &mut self,
426        network_registry: &RegisteredNetworks,
427        network: &NetworkTokenContents,
428        responder: &NetworkPropertyResponder,
429    ) {
430        if !responder.watched_properties.contains(&fnp_properties::Property::DnsConfiguration) {
431            return;
432        }
433
434        let interface_id = network.network_id;
435        self.push(fnp_properties::PropertyUpdate::DnsConfiguration(
436            fnp_properties::DnsConfiguration {
437                servers: Some(
438                    network_registry
439                        .dns_servers
440                        .iter()
441                        .filter(|d| {
442                            match &d.source {
443                                Some(source) => match source {
444                                    fnet_name::DnsServerSource::StaticSource(_) => true,
445                                    // `extract_dns_servers` prefers IPv4 DNS
446                                    // over IPv6 DNS when DNS servers are
447                                    // provided by the SocketProxy.
448                                    fnet_name::DnsServerSource::SocketProxy(
449                                        fnet_name::SocketProxyDnsServerSource {
450                                            source_interface,
451                                            ..
452                                        },
453                                    ) => match (interface_id, source_interface) {
454                                        (_, None) => true,
455                                        (id1, Some(id2)) => {
456                                            Ok(id1)
457                                                == InterfaceId::try_from(*id2)
458                                                    .map(|id| NetworkId::delegated(id))
459                                        }
460                                    },
461                                    fnet_name::DnsServerSource::Dhcp(
462                                        fnet_name::DhcpDnsServerSource { source_interface, .. },
463                                    )
464                                    | fnet_name::DnsServerSource::Ndp(
465                                        fnet_name::NdpDnsServerSource { source_interface, .. },
466                                    )
467                                    | fnet_name::DnsServerSource::Dhcpv6(
468                                        fnet_name::Dhcpv6DnsServerSource {
469                                            source_interface, ..
470                                        },
471                                    ) => match (interface_id, source_interface) {
472                                        (_, None) => true,
473                                        (id1, Some(id2)) => {
474                                            Ok(id1)
475                                                == InterfaceId::try_from(*id2)
476                                                    .map(|id| NetworkId::fuchsia(id))
477                                        }
478                                    },
479
480                                    _ => {
481                                        error!("unhandled DnsServerSource: {source:?}");
482                                        false
483                                    }
484                                },
485
486                                // No source, assume static source, so include it.
487                                None => true,
488                            }
489                        })
490                        .cloned()
491                        .collect::<Vec<_>>(),
492                ),
493                ..Default::default()
494            },
495        ));
496    }
497}
498
499/// An event representing the properties that changed for a network.
500#[derive(Clone, Debug, Default)]
501pub struct NetworkPropertiesChange {
502    /// When true, this is a new network being added. Otherwise, this is an
503    /// update to an existing network.
504    pub added: bool,
505    /// The new marks for the network.
506    pub marks: Option<fnet::Marks>,
507    /// If present, contains the new DNS servers for this network.
508    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
509    /// The new connectivity state of the network.
510    pub connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
511    /// The name of the network.
512    pub name: Option<String>,
513    /// The transport type of the network.
514    pub network_type: Option<fnp_socketproxy::NetworkType>,
515}
516
517#[derive(Debug, Clone)]
518pub enum NetworkUpdate {
519    /// Change a network's properties.
520    Properties(NetworkPropertiesChange),
521    Remove,
522    MakeDefault,
523}
524
525#[derive(Debug, PartialEq, Eq, Clone)]
526struct DefaultChangedEvent {
527    previous_default: Option<NetworkId>,
528}
529
530#[derive(Debug, PartialEq, Eq)]
531struct RegistryUpdateResult {
532    event: UpdateApplied,
533    /// Stores whether the default network has changed, and the previous default
534    /// network, if any.
535    default_changed: Option<DefaultChangedEvent>,
536}
537
538#[derive(Debug, PartialEq, Eq, Clone)]
539enum UpdateApplied {
540    /// No update was performed.
541    None,
542
543    /// Whether the DNS servers changed.
544    DnsChanged,
545
546    /// Network was added or updated, contains the NetworkId of the added network.
547    NetworkChanged {
548        network_id: NetworkId,
549        added: bool,
550        changed_marks: bool,
551        changed_dns: bool,
552        name: Option<String>,
553        network_type: Option<fnp_socketproxy::NetworkType>,
554    },
555
556    /// Network was removed, contains the NetworkId of the removed network.
557    NetworkRemoved(NetworkId),
558}
559
560#[derive(Debug, Clone)]
561pub enum PropertyUpdate {
562    LoseDefaultNetwork,
563    ChangeNetwork(NetworkId, NetworkUpdate),
564    UpdateDns(Vec<fnet_name::DnsServer_>),
565}
566
567impl PropertyUpdate {
568    pub fn default_network_lost() -> Self {
569        PropertyUpdate::LoseDefaultNetwork
570    }
571
572    pub fn dns(dns_servers: &DnsServers) -> Self {
573        // TODO(https://fxbug.dev/477980011): Switch to deriving dns servers from
574        // NetworkRegistry updates.
575        PropertyUpdate::UpdateDns(dns_servers.consolidated_dns_servers())
576    }
577}
578
579/// The result of a delegated network update.
580///
581/// Returned to the main event loop to propagate system-wide configuration
582/// changes (such as DNS server updates) and notify active watchers.
583#[derive(Debug, PartialEq)]
584pub struct DelegatedNetworkUpdateResult {
585    /// If present, contains the new consolidated DNS servers known by the
586    /// network registry.
587    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
588}
589
590#[derive(Default)]
591pub struct NetpolNetworksService {
592    // The current generation
593    current_generation: UpdateGeneration,
594    // The last generation sent per connection
595    generations_by_connection: UpdateGenerations,
596    // Default Network Watchers
597    default_network_responders:
598        HashMap<ConnectionId, fnp_properties::NetworksWatchDefaultResponder>,
599    tokens: token_registry::TokenRegistry<NetworkTokenContents>,
600    // NetworkProperty Watchers
601    property_responders: HashMap<ConnectionId, NetworkPropertyResponder>,
602    // The networks known to the system
603    network_registry: RegisteredNetworks,
604    telemetry: Option<TelemetrySender>,
605}
606
607impl NetpolNetworksService {
608    pub fn set_telemetry(&mut self, telemetry: TelemetrySender) {
609        self.telemetry = Some(telemetry);
610    }
611
612    /// Returns the consolidated DNS servers from the Network Registry.
613    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
614        self.network_registry.consolidated_dns_servers()
615    }
616
617    pub async fn handle_network_attributes_request(
618        &mut self,
619        id: ConnectionId,
620        req: Result<fnp_properties::NetworksRequest, fidl::Error>,
621    ) -> Result<(), anyhow::Error> {
622        let req = req.context("network attributes request")?;
623        match req {
624            fnp_properties::NetworksRequest::WatchDefault { responder } => {
625                match self.default_network_responders.entry(id) {
626                    std::collections::hash_map::Entry::Occupied(_) => {
627                        warn!(
628                            "Only one call to fuchsia.net.policy.properties/Networks.WatchDefault \
629                             may be active per connection"
630                        );
631                        responder
632                            .control_handle()
633                            .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
634                    }
635                    std::collections::hash_map::Entry::Vacant(vacant_entry) => {
636                        let network_id = if self
637                            .generations_by_connection
638                            .default_network(&id)
639                            .unwrap_or_default()
640                            < self.current_generation.default_network
641                        {
642                            self.network_registry.default_network
643                        } else {
644                            None
645                        };
646                        if let Some(network_id) = network_id {
647                            self.generations_by_connection
648                                .set_default_network(id, self.current_generation);
649                            let token = self
650                                .tokens
651                                .ensure_token(NetworkTokenContents { network_id, is_default: true })
652                                .get()
653                                .duplicate()
654                                .context("could not duplicate token")?;
655                            responder.send(
656                                fnp_properties::NetworksWatchDefaultResponse::Network(token),
657                            )?;
658
659                            if let Some(responder) = self.property_responders.remove(&id) {
660                                let _: Option<_> = self.generations_by_connection.remove(&id);
661                                let _: Result<(), fidl::Error> =
662                                    responder.respond(Err(fnp_properties::WatchError::NetworkGone));
663                            }
664                        } else {
665                            let _: &mut _ = vacant_entry.insert(responder);
666                        }
667                    }
668                }
669            }
670            fnp_properties::NetworksRequest::WatchProperties {
671                payload: fnp_properties::NetworksWatchPropertiesRequest { network, properties, .. },
672                responder,
673            } => match (network, properties) {
674                (None, _) | (_, None) => {
675                    responder.send(Err(fnp_properties::WatchError::MissingRequiredArgument))?
676                }
677                (Some(network), Some(properties)) => {
678                    if properties.is_empty() {
679                        responder.send(Err(fnp_properties::WatchError::NoProperties))?;
680                    } else {
681                        match self.property_responders.entry(id) {
682                            std::collections::hash_map::Entry::Occupied(_) => {
683                                warn!(
684                                    "Only one call to \
685                                    fuchsia.net.policy.properties/Networks.WatchProperties may be \
686                                    active per connection"
687                                );
688                                responder
689                                    .control_handle()
690                                    .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
691                            }
692                            std::collections::hash_map::Entry::Vacant(vacant_entry) => {
693                                match self.tokens.get_contents(&network) {
694                                    Err(e) => {
695                                        warn!("Unknown network token. ({network:?}: {e})");
696                                        responder.send(Err(
697                                            fnp_properties::WatchError::InvalidNetworkToken,
698                                        ))?;
699                                    }
700                                    Ok(network_contents) => {
701                                        let responder = NetworkPropertyResponder {
702                                            token: network,
703                                            watched_properties: properties,
704                                            responder,
705                                        };
706                                        if self
707                                            .generations_by_connection
708                                            .properties(&id)
709                                            .unwrap_or_default()
710                                            < self.current_generation.properties
711                                        {
712                                            self.generations_by_connection
713                                                .set_properties(id, self.current_generation);
714                                            if let Some(responder) = self
715                                                .network_registry
716                                                .maybe_respond(&network_contents, responder)
717                                            {
718                                                let _: &mut NetworkPropertyResponder =
719                                                    vacant_entry.insert(responder);
720                                            }
721                                        } else {
722                                            let _: &mut NetworkPropertyResponder =
723                                                vacant_entry.insert(responder);
724                                        }
725                                    }
726                                }
727                            }
728                        }
729                    }
730                }
731            },
732            _ => {
733                warn!("Received unexpected request {req:?}");
734            }
735        }
736
737        Ok(())
738    }
739
740    /// Handles delegated network updates coming from Starnix.
741    ///
742    /// Resolves network events requested through the `NetworkRegistry` interface, applies the
743    /// corresponding properties changes, and yields the computed DNS configuration targets for
744    /// output updates.
745    ///
746    /// TODO(https://fxbug.dev/428712735): Stop returning DnsServer list once
747    /// dns-resolver learns about DNS via NetworkProperties.
748    pub async fn handle_delegated_networks_update(
749        &mut self,
750        update: Result<fnp_socketproxy::NetworkRegistryRequest, fidl::Error>,
751    ) -> Result<DelegatedNetworkUpdateResult, anyhow::Error> {
752        use fnp_socketproxy::{
753            NetworkInfo, NetworkRegistryAddError, NetworkRegistryRemoveError,
754            NetworkRegistryRequest, NetworkRegistrySetDefaultError, NetworkRegistryUpdateError,
755        };
756
757        let action_result = match update {
758            Err(e) => {
759                error!(
760                    "Encountered error watching for delegated network \
761                                    updates: {e:?}"
762                );
763                return Err(anyhow::anyhow!(e));
764            }
765            Ok(NetworkRegistryRequest::SetDefault { network_id, responder }) => {
766                let update_result = match network_id {
767                    fposix_socket::OptionalUint32::Value(interface_id) => {
768                        match InterfaceId::try_from(interface_id) {
769                            Ok(id) => {
770                                let delegated_id = NetworkId::delegated(id);
771                                self.update(PropertyUpdate::ChangeNetwork(
772                                    delegated_id,
773                                    NetworkUpdate::MakeDefault,
774                                ))
775                                .await;
776                                Ok(())
777                            }
778                            Err(_) => Err(NetworkRegistrySetDefaultError::NotFound),
779                        }
780                    }
781                    fposix_socket::OptionalUint32::Unset(_) => {
782                        self.update(PropertyUpdate::default_network_lost()).await;
783                        Ok(())
784                    }
785                };
786
787                self.respond_to_delegated_network_update(
788                    update_result,
789                    |reply| responder.send(reply),
790                    "failed to send SetDefault result",
791                )
792            }
793            Ok(NetworkRegistryRequest::Add { network, responder }) => {
794                let extracted_properties = (|| {
795                    let raw_network_id =
796                        network.network_id.ok_or(NetworkRegistryAddError::MissingNetworkId)?;
797                    let network_id = InterfaceId::try_from(raw_network_id)
798                        .map(|id| NetworkId::delegated(id))
799                        .map_err(|_| NetworkRegistryAddError::MissingNetworkId)?;
800                    let NetworkInfo::Starnix(info) =
801                        network.info.ok_or(NetworkRegistryAddError::MissingNetworkInfo)?
802                    else {
803                        return Err(NetworkRegistryAddError::MissingNetworkInfo);
804                    };
805
806                    let mut marks = fnet::Marks::default();
807                    marks.set_mark(fnet::MARK_DOMAIN_SO_MARK, info.mark);
808
809                    let dns_servers =
810                        Self::extract_dns_servers(&network.dns_servers, raw_network_id.into());
811
812                    Ok((network_id, marks, dns_servers))
813                })();
814
815                let update_result = match extracted_properties {
816                    Ok((network_id, marks, dns_servers)) => {
817                        self.update(PropertyUpdate::ChangeNetwork(
818                            network_id,
819                            NetworkUpdate::Properties(NetworkPropertiesChange {
820                                added: true,
821                                marks: Some(marks),
822                                dns_servers: Some(dns_servers.clone()),
823                                connectivity_state: network.connectivity,
824                                name: network.name,
825                                network_type: network.network_type,
826                            }),
827                        ))
828                        .await;
829                        Ok(())
830                    }
831                    Err(e) => Err(e),
832                };
833
834                self.respond_to_delegated_network_update(
835                    update_result,
836                    |reply| responder.send(reply),
837                    "failed to send Add result",
838                )
839            }
840            Ok(NetworkRegistryRequest::Update { network, responder }) => {
841                let extracted_properties = (|| {
842                    let raw_network_id =
843                        network.network_id.ok_or(NetworkRegistryUpdateError::MissingNetworkId)?;
844                    let network_id = InterfaceId::try_from(raw_network_id)
845                        .map(|id| NetworkId::delegated(id))
846                        .map_err(|_| NetworkRegistryUpdateError::MissingNetworkId)?;
847                    let NetworkInfo::Starnix(info) =
848                        network.info.ok_or(NetworkRegistryUpdateError::MissingNetworkInfo)?
849                    else {
850                        return Err(NetworkRegistryUpdateError::MissingNetworkInfo);
851                    };
852
853                    let mut marks = fnet::Marks::default();
854                    marks.set_mark(fnet::MARK_DOMAIN_SO_MARK, info.mark);
855
856                    let dns_servers =
857                        Self::extract_dns_servers(&network.dns_servers, raw_network_id.into());
858
859                    Ok((network_id, marks, dns_servers))
860                })();
861
862                let update_result = match extracted_properties {
863                    Ok((network_id, marks, dns_servers)) => {
864                        self.update(PropertyUpdate::ChangeNetwork(
865                            network_id,
866                            NetworkUpdate::Properties(NetworkPropertiesChange {
867                                added: false,
868                                marks: Some(marks),
869                                dns_servers: Some(dns_servers.clone()),
870                                connectivity_state: network.connectivity,
871                                name: network.name,
872                                network_type: network.network_type,
873                            }),
874                        ))
875                        .await;
876                        Ok(())
877                    }
878                    Err(e) => Err(e),
879                };
880
881                self.respond_to_delegated_network_update(
882                    update_result,
883                    |reply| responder.send(reply),
884                    "failed to send Update result",
885                )
886            }
887            Ok(NetworkRegistryRequest::Remove { network_id, responder }) => {
888                let update_result = match InterfaceId::try_from(network_id) {
889                    Ok(id) => {
890                        let delegated_id = NetworkId::delegated(id);
891                        self.update(PropertyUpdate::ChangeNetwork(
892                            delegated_id,
893                            NetworkUpdate::Remove,
894                        ))
895                        .await;
896                        Ok(())
897                    }
898                    Err(_) => Err(NetworkRegistryRemoveError::NotFound),
899                };
900
901                self.respond_to_delegated_network_update(
902                    update_result,
903                    |reply| responder.send(reply),
904                    "failed to send Remove result",
905                )
906            }
907        };
908
909        Ok(action_result)
910    }
911
912    // Resolves the operation result, sends the success or failure status to
913    // the FIDL responder, and returns the updated network registry settings.
914    fn respond_to_delegated_network_update<E, F>(
915        &self,
916        operation_result: Result<(), E>,
917        send_response: F,
918        context_message: &'static str,
919    ) -> DelegatedNetworkUpdateResult
920    where
921        F: FnOnce(Result<(), E>) -> Result<(), fidl::Error>,
922    {
923        // Return consolidated DNS servers if the operation was successful.
924        let dns_servers = operation_result
925            .as_ref()
926            .ok()
927            .map(|()| self.network_registry.consolidated_dns_servers());
928
929        // Send success or failure status to the FIDL responder.
930        if let Err(e) = send_response(operation_result) {
931            if !e.is_closed() {
932                error!(
933                    "Failed to send delegated network update result \
934                for {context_message}: {e}"
935                );
936            }
937        }
938
939        DelegatedNetworkUpdateResult { dns_servers }
940    }
941
942    // Converts `NetworkDnsServers` to `Vec<DnsServer_>` for a given network.
943    //
944    // Note: We prioritize IPv4 servers over IPv6 servers. This is impactful
945    // when sending DNS servers through NetworkProperties or to dns-resolver.
946    fn extract_dns_servers(
947        dns_servers: &Option<fnp_socketproxy::NetworkDnsServers>,
948        network_id: u64,
949    ) -> Vec<fnet_name::DnsServer_> {
950        let make_server = |address| fnet_name::DnsServer_ {
951            address: Some(address),
952            source: Some(fnet_name::DnsServerSource::SocketProxy(
953                fnet_name::SocketProxyDnsServerSource {
954                    source_interface: Some(network_id),
955                    ..Default::default()
956                },
957            )),
958            ..Default::default()
959        };
960
961        dns_servers
962            .as_ref()
963            .map(|dns| {
964                dns.v4
965                    .as_ref()
966                    .into_iter()
967                    .flatten()
968                    .map(|&address| {
969                        make_server(fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
970                            address,
971                            port: DNS_PORT,
972                        }))
973                    })
974                    .chain(dns.v6.as_ref().into_iter().flatten().map(|&address| {
975                        make_server(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
976                            address,
977                            port: DNS_PORT,
978                            zone_index: 0,
979                        }))
980                    }))
981                    .collect()
982            })
983            .unwrap_or_default()
984    }
985
986    pub(crate) async fn handle_network_token_resolver_request(
987        &mut self,
988        request: Result<fnp_properties::NetworkTokenResolverRequest, fidl::Error>,
989    ) -> Result<(), anyhow::Error> {
990        use fnp_properties::NetworkTokenResolverResolveTokenError as ResolveTokenError;
991
992        let request = request.context("while handling NetworkTokenResolver request")?;
993        match request {
994            fnp_properties::NetworkTokenResolverRequest::ResolveToken { token, responder } => {
995                let maybe_contents = self.tokens.get_contents(&token).copied();
996                match maybe_contents {
997                    Err(e) => {
998                        warn!("Unknown network token. ({token:?}: {e})");
999                        responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
1000                    }
1001                    Ok(contents) => {
1002                        if contents.is_default {
1003                            // This is a default network token, we need to grab
1004                            // the non-default variant.
1005                            let query = NetworkTokenContents { is_default: false, ..contents };
1006                            if let Some(tok) = self.tokens.get_token(&query) {
1007                                responder.send(tok.duplicate().map_err(|e| {
1008                                    warn!("Encountered issue duplicating generated token. {e}");
1009                                    ResolveTokenError::InvalidNetworkToken
1010                                }))?;
1011                            } else {
1012                                warn!("Requested canonical version of unregistered network.");
1013                                responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
1014                            }
1015                        } else {
1016                            responder.send(Ok(token))?;
1017                        }
1018                    }
1019                }
1020            }
1021            fidl_fuchsia_net_policy_properties::NetworkTokenResolverRequest::_UnknownMethod {
1022                ordinal,
1023                control_handle,
1024                method_type,
1025                ..
1026            } => warn!(
1027                "Encountered unknown method call on NetworkTokenResolver: {ordinal} \
1028                {control_handle:?} {method_type:?}"
1029            ),
1030        }
1031
1032        Ok(())
1033    }
1034
1035    async fn changed_default_network(
1036        &mut self,
1037        previous_default_network: Option<NetworkId>,
1038        responders: &mut HashMap<ConnectionId, NetworkPropertyResponder>,
1039    ) {
1040        let mut r = HashMap::new();
1041        std::mem::swap(&mut r, responders);
1042        r = r
1043            .into_iter()
1044            .filter_map(|(id, responder)| {
1045                match self.tokens.get_contents(&responder.token) {
1046                    Ok(contents) => {
1047                        // We only want to remove when watching a default token.
1048                        if contents.is_default {
1049                            let _: Option<_> = self.generations_by_connection.remove(&id);
1050                            let _: Result<(), fidl::Error> =
1051                                responder.respond(Err(fnp_properties::WatchError::NetworkGone));
1052                            return None;
1053                        }
1054                    }
1055                    Err(zx::Status::NOT_FOUND) => {
1056                        warn!("Token provided to get_contents is not valid.");
1057                    }
1058                    Err(e) => {
1059                        warn!("Encountered unknown issue while getting contents: {e}");
1060                    }
1061                }
1062                Some((id, responder))
1063            })
1064            .collect::<HashMap<_, _>>();
1065        std::mem::swap(&mut r, responders);
1066        self.tokens.drop_if(|&c| {
1067            c.is_default && previous_default_network.is_some_and(|i| i == c.network_id)
1068        });
1069    }
1070
1071    pub(crate) async fn remove_network(&mut self, network_id: NetworkId) {
1072        info!("Removing interface {network_id}. Reporting NETWORK_GONE to all clients.");
1073        let mut responders = HashMap::new();
1074        std::mem::swap(&mut self.property_responders, &mut responders);
1075        for (id, responder) in responders {
1076            let network = match self.tokens.get_contents(&responder.token) {
1077                Ok(network) => network,
1078                Err(e) => {
1079                    warn!("Could not fetch network data for responder: {e}");
1080                    continue;
1081                }
1082            };
1083            if network.network_id == network_id {
1084                // Report that this interface was removed
1085                if let Err(e) = responder.respond(Err(fnp_properties::WatchError::NetworkGone)) {
1086                    warn!("Could not send to responder: {e}");
1087                }
1088            } else {
1089                if self.property_responders.insert(id, responder).is_some() {
1090                    error!("Re-inserted in an existing responder slot. This should be impossible.");
1091                }
1092            }
1093        }
1094    }
1095
1096    pub async fn update(&mut self, update: PropertyUpdate) {
1097        self.current_generation.properties += 1;
1098        let RegistryUpdateResult { event, default_changed } = self.network_registry.apply(update);
1099
1100        if let UpdateApplied::None = event {
1101            if default_changed.is_none() {
1102                // Return early if there were absolutely no changes and the default stayed the same.
1103                return;
1104            }
1105        }
1106
1107        let mut property_responders = HashMap::new();
1108        std::mem::swap(&mut self.property_responders, &mut property_responders);
1109
1110        // Clean up or register tokens based on whether the network was added or removed.
1111        match event {
1112            UpdateApplied::NetworkChanged { network_id, added: true, .. } => {
1113                let _ = self
1114                    .tokens
1115                    .ensure_token(NetworkTokenContents { network_id, is_default: false });
1116            }
1117            UpdateApplied::NetworkRemoved(network_id) => {
1118                self.tokens.drop_if(|c| !c.is_default && c.network_id == network_id);
1119            }
1120            UpdateApplied::NetworkChanged { added: false, .. }
1121            | UpdateApplied::DnsChanged
1122            | UpdateApplied::None => {}
1123        }
1124
1125        // Notify watchers of default network changes if one occurred.
1126        if let Some(DefaultChangedEvent { previous_default }) = default_changed {
1127            self.notify_default_network_changed(previous_default, &mut property_responders).await;
1128            return;
1129        }
1130
1131        if let UpdateApplied::NetworkChanged { network_id, .. } = event {
1132            if let Some(telemetry) = &self.telemetry {
1133                if let Some(props) = self.network_registry.networks.get(&network_id) {
1134                    telemetry.send(TelemetryEvent::NetworkChanged(NetworkEventMetadata {
1135                        id: network_id.get().get(),
1136                        name: props.name.clone(),
1137                        transport: props
1138                            .network_type
1139                            .unwrap_or(fnp_socketproxy::NetworkType::Unknown),
1140                        is_fuchsia_provisioned: network_id.is_fuchsia(),
1141                        connectivity_state: props.connectivity_state,
1142                    }));
1143                }
1144            }
1145        }
1146
1147        for (id, responder) in property_responders {
1148            let mut updates = Vec::new();
1149            let network = match self.tokens.get_contents(&responder.token) {
1150                Ok(network) => network,
1151                Err(e) => {
1152                    warn!("Could not fetch network data for responder: {e}");
1153                    continue;
1154                }
1155            };
1156
1157            if let UpdateApplied::NetworkChanged {
1158                network_id, changed_marks, changed_dns, ..
1159            } = event
1160            {
1161                if network.network_id == network_id {
1162                    if changed_marks {
1163                        updates.add_socket_marks(&self.network_registry, &network, &responder);
1164                    }
1165                    if changed_dns {
1166                        updates.add_dns(&self.network_registry, &network, &responder);
1167                    }
1168                }
1169            }
1170            if let UpdateApplied::DnsChanged = event {
1171                updates.add_dns(&self.network_registry, &network, &responder);
1172            }
1173
1174            self.generations_by_connection.set_properties(id, self.current_generation);
1175            if updates.is_empty() {
1176                if self.property_responders.insert(id, responder).is_some() {
1177                    warn!("Re-inserted in an existing responder slot. This should be impossible.");
1178                }
1179            } else {
1180                if let Err(e) = responder.respond(Ok(&updates)) {
1181                    warn!("Could not send to responder: {e}");
1182                }
1183            }
1184        }
1185    }
1186
1187    async fn notify_default_network_changed(
1188        &mut self,
1189        old_default: Option<NetworkId>,
1190        property_responders: &mut HashMap<ConnectionId, NetworkPropertyResponder>,
1191    ) {
1192        self.changed_default_network(old_default, property_responders).await;
1193        match self.network_registry.default_network {
1194            Some(default_network) => {
1195                if let Some(telemetry) = &self.telemetry {
1196                    if let Some(props) = self.network_registry.networks.get(&default_network) {
1197                        telemetry.send(TelemetryEvent::DefaultNetworkChanged(
1198                            NetworkEventMetadata {
1199                                id: default_network.get().get(),
1200                                name: props.name.clone(),
1201                                transport: props
1202                                    .network_type
1203                                    .unwrap_or(fnp_socketproxy::NetworkType::Unknown),
1204                                is_fuchsia_provisioned: default_network.is_fuchsia(),
1205                                connectivity_state: props.connectivity_state,
1206                            },
1207                        ));
1208                    } else {
1209                        warn!("Could not fetch network data for default network.");
1210                    }
1211                }
1212                self.current_generation.default_network += 1;
1213                let mut responders = HashMap::new();
1214                std::mem::swap(&mut self.default_network_responders, &mut responders);
1215                for (id, responder) in responders {
1216                    self.generations_by_connection.set_default_network(id, self.current_generation);
1217                    match self
1218                        .tokens
1219                        .ensure_token(NetworkTokenContents {
1220                            network_id: default_network,
1221                            is_default: true,
1222                        })
1223                        .get()
1224                        .duplicate()
1225                    {
1226                        Ok(token) => {
1227                            if let Err(e) = responder
1228                                .send(fnp_properties::NetworksWatchDefaultResponse::Network(token))
1229                            {
1230                                warn!("Could not send to responder: {e}");
1231                            }
1232                        }
1233                        Err(e) => warn!("Could not duplicate token: {e}"),
1234                    };
1235                }
1236            }
1237            None => {
1238                if let Some(telemetry) = &self.telemetry {
1239                    telemetry.send(TelemetryEvent::DefaultNetworkLost);
1240                }
1241                // The default network has been lost.
1242                self.current_generation.default_network += 1;
1243                let mut responders = HashMap::new();
1244                std::mem::swap(&mut self.default_network_responders, &mut responders);
1245                for (id, responder) in responders {
1246                    self.generations_by_connection.set_default_network(id, self.current_generation);
1247                    if let Err(e) = responder.send(
1248                        fnp_properties::NetworksWatchDefaultResponse::NoDefaultNetwork(
1249                            fnp_properties::Empty,
1250                        ),
1251                    ) {
1252                        warn!("Could not send to responder: {e}");
1253                    }
1254                }
1255            }
1256        }
1257    }
1258}
1259
1260pub struct ConnectionTagged<Stream: futures::Stream + Unpin> {
1261    next_id: ConnectionId,
1262    streams: futures::stream::SelectAll<Tagged<ConnectionId, Stream>>,
1263}
1264
1265impl<Stream: futures::Stream + Unpin> Default for ConnectionTagged<Stream> {
1266    fn default() -> Self {
1267        Self { next_id: Default::default(), streams: Default::default() }
1268    }
1269}
1270
1271impl<Stream: futures::Stream + Unpin> ConnectionTagged<Stream> {
1272    pub fn push(&mut self, stream: Stream) {
1273        self.streams.push(stream.tagged(self.next_id));
1274        self.next_id.0 += 1;
1275    }
1276}
1277
1278impl<Stream: futures::Stream + Unpin> futures::Stream for ConnectionTagged<Stream> {
1279    type Item = (ConnectionId, <Stream as futures::Stream>::Item);
1280
1281    fn poll_next(
1282        mut self: std::pin::Pin<&mut Self>,
1283        cx: &mut std::task::Context<'_>,
1284    ) -> std::task::Poll<Option<Self::Item>> {
1285        std::pin::Pin::new(&mut self.streams).poll_next(cx)
1286    }
1287}
1288
1289impl<Stream: futures::Stream + Unpin> futures::stream::FusedStream for ConnectionTagged<Stream> {
1290    fn is_terminated(&self) -> bool {
1291        self.streams.is_terminated()
1292    }
1293}
1294
1295#[cfg(test)]
1296mod tests {
1297    use super::*;
1298    use assert_matches::assert_matches;
1299    use std::num::NonZeroU64;
1300    use test_case::test_case;
1301
1302    const ID_1: InterfaceId = InterfaceId(NonZeroU64::new(1).unwrap());
1303    const ID_2: InterfaceId = InterfaceId(NonZeroU64::new(2).unwrap());
1304    const NAME_1: &str = "testif1";
1305    const NAME_2: &str = "testif2";
1306
1307    const FUCHSIA_ID_1: NetworkId = NetworkId::Fuchsia(ID_1);
1308    const FUCHSIA_ID_2: NetworkId = NetworkId::Fuchsia(ID_2);
1309    const DELEGATED_ID_1: NetworkId = NetworkId::Delegated(ID_1);
1310    const DELEGATED_ID_2: NetworkId = NetworkId::Delegated(ID_2);
1311
1312    #[derive(Clone, Copy)]
1313    struct TestNetwork {
1314        id: NetworkId,
1315        name: &'static str,
1316        network_type: fnp_socketproxy::NetworkType,
1317    }
1318
1319    #[derive(Default)]
1320    struct AppliedChanges {
1321        added: bool,
1322        changed_marks: bool,
1323        changed_dns: bool,
1324    }
1325
1326    impl TestNetwork {
1327        fn added(&self, added: bool) -> NetworkPropertiesChange {
1328            NetworkPropertiesChange {
1329                added,
1330                name: Some(self.name.to_string()),
1331                network_type: Some(self.network_type),
1332                ..Default::default()
1333            }
1334        }
1335
1336        fn change_with(
1337            &self,
1338            added: bool,
1339            marks: Option<fnet::Marks>,
1340            dns_servers: Option<Vec<fnet_name::DnsServer_>>,
1341            connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
1342        ) -> NetworkPropertiesChange {
1343            NetworkPropertiesChange {
1344                added,
1345                marks,
1346                dns_servers,
1347                connectivity_state,
1348                name: Some(self.name.to_string()),
1349                network_type: Some(self.network_type),
1350            }
1351        }
1352
1353        fn applied(&self, changes: AppliedChanges) -> UpdateApplied {
1354            let AppliedChanges { added, changed_marks, changed_dns } = changes;
1355            UpdateApplied::NetworkChanged {
1356                network_id: self.id,
1357                added,
1358                changed_marks,
1359                changed_dns,
1360                name: Some(self.name.to_string()),
1361                network_type: Some(self.network_type),
1362            }
1363        }
1364    }
1365
1366    const DELEGATED_NET_1: TestNetwork = TestNetwork {
1367        id: DELEGATED_ID_1,
1368        name: NAME_1,
1369        network_type: fnp_socketproxy::NetworkType::Ethernet,
1370    };
1371    const FUCHSIA_NET_1: TestNetwork = TestNetwork {
1372        id: FUCHSIA_ID_1,
1373        name: NAME_1,
1374        network_type: fnp_socketproxy::NetworkType::Ethernet,
1375    };
1376    const FUCHSIA_NET_2: TestNetwork = TestNetwork {
1377        id: FUCHSIA_ID_2,
1378        name: NAME_2,
1379        network_type: fnp_socketproxy::NetworkType::Wifi,
1380    };
1381
1382    fn test_marks() -> fnet::Marks {
1383        fnet::Marks { mark_1: Some(123), ..Default::default() }
1384    }
1385
1386    fn test_marks_updated() -> fnet::Marks {
1387        fnet::Marks { mark_1: Some(456), ..Default::default() }
1388    }
1389
1390    fn test_dns_1() -> Vec<fnet_name::DnsServer_> {
1391        vec![fnet_name::DnsServer_ {
1392            address: Some(net_declare::fidl_socket_addr!("192.0.2.1:53")),
1393            ..Default::default()
1394        }]
1395    }
1396
1397    fn test_dns_2() -> Vec<fnet_name::DnsServer_> {
1398        vec![fnet_name::DnsServer_ {
1399            address: Some(net_declare::fidl_socket_addr!("192.0.2.2:53")),
1400            ..Default::default()
1401        }]
1402    }
1403
1404    fn delegated_properties() -> NetworkProperties {
1405        NetworkProperties { socket_marks: Some(test_marks()), ..Default::default() }
1406    }
1407
1408    fn fuchsia_properties() -> NetworkProperties {
1409        NetworkProperties::default()
1410    }
1411
1412    fn added_properties(name: &str) -> NetworkPropertiesChange {
1413        NetworkPropertiesChange {
1414            added: true,
1415            marks: None,
1416            dns_servers: None,
1417            connectivity_state: None,
1418            name: Some(name.to_string()),
1419            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1420        }
1421    }
1422
1423    impl NetpolNetworksService {
1424        pub(crate) fn default_network(&self) -> Option<NetworkId> {
1425            self.network_registry.default_network
1426        }
1427
1428        pub(crate) fn has_network(&self, id: NetworkId) -> bool {
1429            self.network_registry.networks.contains_key(&id)
1430        }
1431
1432        pub(crate) fn has_token(&self, network_id: NetworkId, is_default: bool) -> bool {
1433            self.tokens.get_token(&NetworkTokenContents { network_id, is_default }).is_some()
1434        }
1435
1436        pub(crate) fn ensure_token_for_test(&mut self, network_id: NetworkId, is_default: bool) {
1437            let _token: crate::network::token_registry::TokenEntry<'_, _> =
1438                self.tokens.ensure_token(NetworkTokenContents { network_id, is_default });
1439        }
1440    }
1441
1442    #[test_case(
1443        DELEGATED_NET_1,
1444        Some(test_marks()),
1445        Some(test_marks_updated()); "delegated network"
1446    )]
1447    #[test_case(FUCHSIA_NET_2, None, None; "fuchsia network")]
1448    fn test_handle_changed_network(
1449        net: TestNetwork,
1450        initial_marks: Option<fnet::Marks>,
1451        updated_marks: Option<fnet::Marks>,
1452    ) {
1453        let mut networks = RegisteredNetworks::default();
1454        let dns1 = test_dns_1();
1455        let dns2 = test_dns_2();
1456
1457        // Adding network with DNS servers should have changed_marks=true and changed_dns=true.
1458        let event = net.change_with(
1459            true,
1460            initial_marks.clone(),
1461            Some(dns1.clone()),
1462            Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
1463        );
1464        assert_eq!(
1465            networks.handle_changed_network(net.id, event),
1466            net.applied(AppliedChanges { added: true, changed_marks: true, changed_dns: true }),
1467        );
1468        assert_eq!(
1469            networks.networks.get(&net.id).expect("network should be present"),
1470            &NetworkProperties {
1471                socket_marks: initial_marks.clone(),
1472                dns_servers: dns1,
1473                connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
1474                name: Some(net.name.to_string()),
1475                network_type: Some(net.network_type),
1476            }
1477        );
1478
1479        // Updating with same marks and different DNS should have changed_marks=false and
1480        // changed_dns=true.
1481        let event = net.change_with(
1482            false,
1483            initial_marks.clone(),
1484            Some(dns2.clone()),
1485            Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
1486        );
1487        assert_eq!(
1488            networks.handle_changed_network(net.id, event),
1489            net.applied(AppliedChanges { added: false, changed_marks: false, changed_dns: true }),
1490        );
1491        assert_eq!(
1492            networks.networks.get(&net.id).expect("network should be present"),
1493            &NetworkProperties {
1494                socket_marks: initial_marks.clone(),
1495                dns_servers: dns2.clone(),
1496                connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
1497                name: Some(net.name.to_string()),
1498                network_type: Some(net.network_type),
1499            }
1500        );
1501
1502        // Updating marks (if delegated) with same DNS should have changed_dns=false.
1503        let marks_changed = initial_marks != updated_marks;
1504        let event = net.change_with(
1505            false,
1506            updated_marks.clone(),
1507            Some(dns2.clone()),
1508            Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
1509        );
1510        assert_eq!(
1511            networks.handle_changed_network(net.id, event),
1512            net.applied(AppliedChanges {
1513                added: false,
1514                changed_marks: marks_changed,
1515                changed_dns: false,
1516            }),
1517        );
1518        assert_eq!(
1519            networks.networks.get(&net.id).expect("network should be present"),
1520            &NetworkProperties {
1521                socket_marks: updated_marks,
1522                dns_servers: dns2.clone(),
1523                connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
1524                name: Some(net.name.to_string()),
1525                network_type: Some(net.network_type),
1526            }
1527        );
1528    }
1529
1530    #[test]
1531    fn test_handle_changed_network_validation() {
1532        let mut networks = RegisteredNetworks::default();
1533        let marks = test_marks();
1534        let net = DELEGATED_NET_1;
1535
1536        // Update a non-added network
1537        let event = NetworkPropertiesChange { marks: Some(marks.clone()), ..net.added(false) };
1538        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
1539
1540        // Add the network
1541        let event = NetworkPropertiesChange { marks: Some(marks.clone()), ..net.added(true) };
1542        assert_eq!(
1543            networks.handle_changed_network(net.id, event),
1544            net.applied(AppliedChanges { added: true, changed_marks: true, changed_dns: true }),
1545        );
1546
1547        // Add already added network
1548        let event = NetworkPropertiesChange { marks: Some(marks.clone()), ..net.added(true) };
1549        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
1550
1551        // Fuchsia network with marks
1552        let fuchsia_net = FUCHSIA_NET_1;
1553        let event =
1554            NetworkPropertiesChange { marks: Some(marks.clone()), ..fuchsia_net.added(true) };
1555        assert_eq!(networks.handle_changed_network(fuchsia_net.id, event), UpdateApplied::None);
1556
1557        // Delegated network without marks
1558        let event = net.added(true);
1559        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
1560    }
1561
1562    // Unit tests the election algorithm directly by manipulating internal state.
1563    // Verifies prioritization and intermediate fallback election logic.
1564    #[test]
1565    fn fallback_election_and_prioritization() {
1566        let mut networks = RegisteredNetworks::default();
1567
1568        // Initial State: Empty, no default network.
1569        assert_eq!(networks.calculate_active_default(), None);
1570
1571        // Add a delegated network and set as the Starnix default.
1572        let _ = networks.networks.insert(DELEGATED_ID_1, delegated_properties());
1573        networks.starnix_default = Some(DELEGATED_ID_1);
1574        assert_eq!(
1575            networks.handle_default_network_update(),
1576            Some(DefaultChangedEvent { previous_default: None })
1577        );
1578        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
1579
1580        // Replace the delegated network with another delegated network.
1581        // The new network should take over.
1582        let _ = networks.networks.insert(DELEGATED_ID_2, delegated_properties());
1583        networks.starnix_default = Some(DELEGATED_ID_2);
1584        assert_eq!(
1585            networks.handle_default_network_update(),
1586            Some(DefaultChangedEvent { previous_default: Some(DELEGATED_ID_1) })
1587        );
1588        assert_eq!(networks.default_network, Some(DELEGATED_ID_2));
1589
1590        // Add a Fuchsia network. This Fuchsia network should take over because of
1591        // Fuchsia network priority.
1592        let _ = networks.networks.insert(FUCHSIA_ID_2, fuchsia_properties());
1593        assert_eq!(
1594            networks.handle_default_network_update(),
1595            Some(DefaultChangedEvent { previous_default: Some(DELEGATED_ID_2) })
1596        );
1597        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
1598
1599        // Add a Fuchsia network with a smaller ID. The smaller ID Fuchsia network
1600        // should take over.
1601        let _ = networks.networks.insert(FUCHSIA_ID_1, fuchsia_properties());
1602        assert_eq!(
1603            networks.handle_default_network_update(),
1604            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
1605        );
1606        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
1607
1608        // Remove FUCHSIA_ID_1. The next Fuchsia network should take over.
1609        let _ = networks.networks.remove(&FUCHSIA_ID_1);
1610        assert_eq!(
1611            networks.handle_default_network_update(),
1612            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_1) })
1613        );
1614        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
1615
1616        // Remove FUCHSIA_ID_2. There are no more Fuchsia networks, so the default
1617        // should fallback to the delegated network.
1618        let _ = networks.networks.remove(&FUCHSIA_ID_2);
1619        assert_eq!(
1620            networks.handle_default_network_update(),
1621            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
1622        );
1623        assert_eq!(networks.default_network, Some(DELEGATED_ID_2));
1624
1625        // Unset the default delegated network prior to removal.
1626        assert_eq!(
1627            networks.apply(PropertyUpdate::LoseDefaultNetwork),
1628            RegistryUpdateResult {
1629                event: UpdateApplied::None,
1630                default_changed: Some(DefaultChangedEvent {
1631                    previous_default: Some(DELEGATED_ID_2)
1632                })
1633            }
1634        );
1635        assert_eq!(networks.default_network, None);
1636
1637        // Remove the delegated network.
1638        assert_eq!(
1639            networks.apply(PropertyUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove)),
1640            RegistryUpdateResult {
1641                event: UpdateApplied::NetworkRemoved(DELEGATED_ID_1),
1642                default_changed: None
1643            }
1644        );
1645    }
1646
1647    // Tests the integration of `RegisteredNetworks::apply` updates, verifying
1648    // fallback priority from Fuchsia to Delegated networks and ensuring that
1649    // active default delegated networks cannot be removed.
1650    #[test]
1651    fn test_remove_fuchsia_network_fallback() {
1652        let mut networks = RegisteredNetworks::default();
1653        let marks = test_marks();
1654        let fuchsia_added = NetworkPropertiesChange { added: true, ..Default::default() };
1655
1656        // Add a Fuchsia network. This should become the default network.
1657        let result = networks.apply(PropertyUpdate::ChangeNetwork(
1658            FUCHSIA_ID_1,
1659            NetworkUpdate::Properties(fuchsia_added.clone()),
1660        ));
1661        assert_matches!(
1662            result.event,
1663            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
1664            if id == FUCHSIA_ID_1
1665        );
1666        assert_eq!(result.default_changed, Some(DefaultChangedEvent { previous_default: None }));
1667
1668        // Add a second Fuchsia network. This should not change the default network.
1669        let result = networks.apply(PropertyUpdate::ChangeNetwork(
1670            FUCHSIA_ID_2,
1671            NetworkUpdate::Properties(fuchsia_added),
1672        ));
1673        assert_matches!(
1674            result.event,
1675            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
1676            if id == FUCHSIA_ID_2
1677        );
1678        assert_eq!(result.default_changed, None);
1679
1680        // Add a delegated network. This should not change the default network.
1681        let result = networks.apply(PropertyUpdate::ChangeNetwork(
1682            DELEGATED_ID_1,
1683            NetworkUpdate::Properties(NetworkPropertiesChange {
1684                added: true,
1685                marks: Some(marks),
1686                ..Default::default()
1687            }),
1688        ));
1689        assert_matches!(
1690            result.event,
1691            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
1692            if id == DELEGATED_ID_1
1693        );
1694        assert_eq!(result.default_changed, None);
1695
1696        // Make the delegated network default (ignored because a Fuchsia
1697        // network is present).
1698        let result = networks
1699            .apply(PropertyUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::MakeDefault));
1700        assert_eq!(
1701            result,
1702            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
1703        );
1704        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
1705
1706        // Remove the first Fuchsia network (fallback to the second Fuchsia
1707        // network).
1708        let result =
1709            networks.apply(PropertyUpdate::ChangeNetwork(FUCHSIA_ID_1, NetworkUpdate::Remove));
1710        assert_eq!(result.event, UpdateApplied::NetworkRemoved(FUCHSIA_ID_1));
1711        assert_eq!(
1712            result.default_changed,
1713            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_1) })
1714        );
1715        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
1716
1717        // Remove the second Fuchsia network (fallback to the
1718        // delegated network since it is default).
1719        let result =
1720            networks.apply(PropertyUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove));
1721        assert_eq!(result.event, UpdateApplied::NetworkRemoved(FUCHSIA_ID_2));
1722        assert_eq!(
1723            result.default_changed,
1724            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
1725        );
1726        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
1727
1728        // Remove the delegated network (rejected because it is default).
1729        let result =
1730            networks.apply(PropertyUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove));
1731        assert_eq!(
1732            result,
1733            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
1734        );
1735        assert!(networks.networks.contains_key(&DELEGATED_ID_1));
1736        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
1737    }
1738
1739    #[test]
1740    fn remove_non_default_fuchsia_preserves_default() {
1741        let mut networks = RegisteredNetworks::default();
1742
1743        // Add both networks.
1744        let _ = networks.networks.insert(FUCHSIA_ID_1, NetworkProperties::default());
1745        let _ = networks.networks.insert(FUCHSIA_ID_2, NetworkProperties::default());
1746
1747        // On election, FUCHSIA_ID_1 (the smaller ID) is elected as default.
1748        assert_eq!(
1749            networks.handle_default_network_update(),
1750            Some(DefaultChangedEvent { previous_default: None })
1751        );
1752        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
1753
1754        // Remove the non-default network.
1755        assert_eq!(
1756            networks.apply(PropertyUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove)),
1757            RegistryUpdateResult {
1758                event: UpdateApplied::NetworkRemoved(FUCHSIA_ID_2),
1759                default_changed: None
1760            }
1761        );
1762
1763        // Verify that FUCHSIA_ID_1 is still the active default.
1764        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
1765    }
1766
1767    #[fuchsia::test]
1768    async fn remove_default_network_cleans_up_tokens() {
1769        let mut service = NetpolNetworksService::default();
1770
1771        // Add two Fuchsia networks via ChangeNetwork updates.
1772        service
1773            .update(PropertyUpdate::ChangeNetwork(
1774                FUCHSIA_ID_1,
1775                NetworkUpdate::Properties(added_properties(NAME_1)),
1776            ))
1777            .await;
1778
1779        service
1780            .update(PropertyUpdate::ChangeNetwork(
1781                FUCHSIA_ID_2,
1782                NetworkUpdate::Properties(added_properties(NAME_2)),
1783            ))
1784            .await;
1785
1786        // On election, FUCHSIA_ID_1 (the smaller ID) is elected as default.
1787        assert_eq!(service.default_network(), Some(FUCHSIA_ID_1));
1788
1789        // Ensure non-default tokens exist for both networks.
1790        assert!(service.has_token(FUCHSIA_ID_1, false /* is_default */));
1791        assert!(service.has_token(FUCHSIA_ID_2, false /* is_default */));
1792
1793        // Manually create default token for FUCHSIA_ID_1 to simulate a client WatchDefault call.
1794        service.ensure_token_for_test(FUCHSIA_ID_1, true /* is_default */);
1795        assert!(service.has_token(FUCHSIA_ID_1, true /* is_default */));
1796
1797        // Remove FUCHSIA_ID_1 (the default network). This should trigger fallback to FUCHSIA_ID_2
1798        // and clean up FUCHSIA_ID_1's tokens.
1799        service.update(PropertyUpdate::ChangeNetwork(FUCHSIA_ID_1, NetworkUpdate::Remove)).await;
1800
1801        // Verify fallback happened.
1802        assert_eq!(service.default_network(), Some(FUCHSIA_ID_2));
1803
1804        // Verify FUCHSIA_ID_1 tokens are gone.
1805        assert!(!service.has_token(FUCHSIA_ID_1, false /* is_default */));
1806        assert!(!service.has_token(FUCHSIA_ID_1, true /* is_default */));
1807
1808        // Verify FUCHSIA_ID_2 tokens still exist.
1809        assert!(service.has_token(FUCHSIA_ID_2, false /* is_default */));
1810    }
1811}