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(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            (_, _, NetworkId::Fuchsia(_), None) => Ok((
284                NetworkProperties {
285                    dns_servers: changed_dns_servers.unwrap_or_default(),
286                    ..Default::default()
287                },
288                added,
289            )),
290            (_, entry, NetworkId::Delegated(_), Some(socket_marks)) => {
291                let changed = if let Entry::Occupied(e) = entry {
292                    e.get().get_marks() != Some(&socket_marks)
293                } else {
294                    true
295                };
296                Ok((
297                    NetworkProperties {
298                        socket_marks: Some(socket_marks),
299                        dns_servers: changed_dns_servers.unwrap_or_default(),
300                        ..Default::default()
301                    },
302                    changed,
303                ))
304            }
305        };
306
307        match result {
308            Ok((mut properties, changed_marks)) => {
309                properties.connectivity_state = connectivity_state;
310                properties.network_type = network_type;
311                properties.name = name.clone();
312                let _ = entry.insert_entry(properties);
313                UpdateApplied::NetworkChanged {
314                    network_id,
315                    added,
316                    changed_marks,
317                    name,
318                    network_type,
319                }
320            }
321            Err(e) => {
322                error!("Cannot {e}. Update ignored.");
323                UpdateApplied::None
324            }
325        }
326    }
327
328    /// Returns the DNS servers for the default network if it is a Fuchsia network,
329    /// otherwise returns a concatenation of DNS servers from all delegated networks.
330    /// TODO(https://fxbug.dev/428712735): Remove once dns-resolver learns about DNS
331    /// via NetworkProperties.
332    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
333        if let Some(NetworkId::Fuchsia(if_id)) = self.default_network {
334            self.networks
335                .get(&NetworkId::Fuchsia(if_id))
336                .map(|p| p.dns_servers.clone())
337                .unwrap_or_default()
338        } else {
339            self.networks
340                .iter()
341                .filter(|(id, _)| matches!(id, NetworkId::Delegated(_)))
342                .flat_map(|(_, p)| &p.dns_servers)
343                .cloned()
344                .collect()
345        }
346    }
347
348    fn maybe_respond(
349        &self,
350        network: &NetworkTokenContents,
351        responder: NetworkPropertyResponder,
352    ) -> Option<NetworkPropertyResponder> {
353        let mut updates = Vec::new();
354        updates.add_socket_marks(self, network, &responder);
355        updates.add_dns(self, network, &responder);
356
357        if updates.is_empty() {
358            Some(responder)
359        } else {
360            if let Err(e) = responder.respond(Ok(&updates)) {
361                warn!("Could not send to responder: {e}");
362            }
363            None
364        }
365    }
366}
367
368trait PropertyUpdates {
369    fn add_socket_marks(
370        &mut self,
371        network_registry: &RegisteredNetworks,
372        network: &NetworkTokenContents,
373        responder: &NetworkPropertyResponder,
374    );
375    fn add_dns(
376        &mut self,
377        network_registry: &RegisteredNetworks,
378        network: &NetworkTokenContents,
379        responder: &NetworkPropertyResponder,
380    );
381}
382
383impl PropertyUpdates for Vec<fnp_properties::PropertyUpdate> {
384    fn add_socket_marks(
385        &mut self,
386        network_registry: &RegisteredNetworks,
387        network: &NetworkTokenContents,
388        responder: &NetworkPropertyResponder,
389    ) {
390        if !responder.watched_properties.contains(&fnp_properties::Property::SocketMarks) {
391            return;
392        }
393
394        match network_registry.networks.get(&network.network_id) {
395            Some(network) => {
396                if let Some(socket_marks) = network.get_marks() {
397                    self.push(fnp_properties::PropertyUpdate::SocketMarks(socket_marks.clone()));
398                }
399                return;
400            }
401            None => {
402                error!(
403                    "State is inconsistent. We attempted to add marks for a \
404            network that is not known: {:?}",
405                    network.network_id
406                );
407            }
408        }
409    }
410
411    fn add_dns(
412        &mut self,
413        network_registry: &RegisteredNetworks,
414        network: &NetworkTokenContents,
415        responder: &NetworkPropertyResponder,
416    ) {
417        if !responder.watched_properties.contains(&fnp_properties::Property::DnsConfiguration) {
418            return;
419        }
420
421        let interface_id = network.network_id;
422        self.push(fnp_properties::PropertyUpdate::DnsConfiguration(
423            fnp_properties::DnsConfiguration {
424                servers: Some(
425                    network_registry
426                        .dns_servers
427                        .iter()
428                        .filter(|d| {
429                            match &d.source {
430                                Some(source) => match source {
431                                    fnet_name::DnsServerSource::StaticSource(_) => true,
432                                    // `extract_dns_servers` prefers IPv4 DNS
433                                    // over IPv6 DNS when DNS servers are
434                                    // provided by the SocketProxy.
435                                    fnet_name::DnsServerSource::SocketProxy(
436                                        fnet_name::SocketProxyDnsServerSource {
437                                            source_interface,
438                                            ..
439                                        },
440                                    ) => match (interface_id, source_interface) {
441                                        (_, None) => true,
442                                        (id1, Some(id2)) => {
443                                            Ok(id1)
444                                                == InterfaceId::try_from(*id2)
445                                                    .map(|id| NetworkId::delegated(id))
446                                        }
447                                    },
448                                    fnet_name::DnsServerSource::Dhcp(
449                                        fnet_name::DhcpDnsServerSource { source_interface, .. },
450                                    )
451                                    | fnet_name::DnsServerSource::Ndp(
452                                        fnet_name::NdpDnsServerSource { source_interface, .. },
453                                    )
454                                    | fnet_name::DnsServerSource::Dhcpv6(
455                                        fnet_name::Dhcpv6DnsServerSource {
456                                            source_interface, ..
457                                        },
458                                    ) => match (interface_id, source_interface) {
459                                        (_, None) => true,
460                                        (id1, Some(id2)) => {
461                                            Ok(id1)
462                                                == InterfaceId::try_from(*id2)
463                                                    .map(|id| NetworkId::fuchsia(id))
464                                        }
465                                    },
466
467                                    _ => {
468                                        error!("unhandled DnsServerSource: {source:?}");
469                                        false
470                                    }
471                                },
472
473                                // No source, assume static source, so include it.
474                                None => true,
475                            }
476                        })
477                        .cloned()
478                        .collect::<Vec<_>>(),
479                ),
480                ..Default::default()
481            },
482        ));
483    }
484}
485
486/// An event representing the properties that changed for a network.
487#[derive(Clone, Debug, Default)]
488pub struct NetworkPropertiesChange {
489    /// When true, this is a new network being added. Otherwise, this is an
490    /// update to an existing network.
491    pub added: bool,
492    /// The new marks for the network.
493    pub marks: Option<fnet::Marks>,
494    /// If present, contains the new DNS servers for this network.
495    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
496    /// The new connectivity state of the network.
497    pub connectivity_state: Option<fnp_socketproxy::ConnectivityState>,
498    /// The name of the network.
499    pub name: Option<String>,
500    /// The transport type of the network.
501    pub network_type: Option<fnp_socketproxy::NetworkType>,
502}
503
504#[derive(Debug)]
505pub enum NetworkUpdate {
506    /// Change a network's properties.
507    Properties(NetworkPropertiesChange),
508    Remove,
509    MakeDefault,
510}
511
512#[derive(Debug, PartialEq, Eq, Clone)]
513struct DefaultChangedEvent {
514    previous_default: Option<NetworkId>,
515}
516
517#[derive(Debug, PartialEq, Eq)]
518struct RegistryUpdateResult {
519    event: UpdateApplied,
520    /// Stores whether the default network has changed, and the previous default
521    /// network, if any.
522    default_changed: Option<DefaultChangedEvent>,
523}
524
525#[derive(Debug, PartialEq, Eq, Clone)]
526enum UpdateApplied {
527    /// No update was performed.
528    None,
529
530    /// Whether the DNS servers changed.
531    DnsChanged,
532
533    /// Network was added or updated, contains the NetworkId of the added network.
534    NetworkChanged {
535        network_id: NetworkId,
536        added: bool,
537        changed_marks: bool,
538        name: Option<String>,
539        network_type: Option<fnp_socketproxy::NetworkType>,
540    },
541
542    /// Network was removed, contains the NetworkId of the removed network.
543    NetworkRemoved(NetworkId),
544}
545
546#[derive(Debug)]
547pub enum PropertyUpdate {
548    LoseDefaultNetwork,
549    ChangeNetwork(NetworkId, NetworkUpdate),
550    UpdateDns(Vec<fnet_name::DnsServer_>),
551}
552
553impl PropertyUpdate {
554    pub fn default_network_lost() -> Self {
555        PropertyUpdate::LoseDefaultNetwork
556    }
557
558    pub fn dns(dns_servers: &DnsServers) -> Self {
559        // TODO(https://fxbug.dev/477980011): Switch to deriving dns servers from
560        // NetworkRegistry updates.
561        PropertyUpdate::UpdateDns(dns_servers.consolidated_dns_servers())
562    }
563}
564
565/// The result of a delegated network update.
566///
567/// Returned to the main event loop to propagate system-wide configuration
568/// changes (such as DNS server updates) and notify active watchers.
569#[derive(Debug, PartialEq)]
570pub struct DelegatedNetworkUpdateResult {
571    /// If present, contains the new consolidated DNS servers known by the
572    /// network registry.
573    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
574}
575
576#[derive(Default)]
577pub struct NetpolNetworksService {
578    // The current generation
579    current_generation: UpdateGeneration,
580    // The last generation sent per connection
581    generations_by_connection: UpdateGenerations,
582    // Default Network Watchers
583    default_network_responders:
584        HashMap<ConnectionId, fnp_properties::NetworksWatchDefaultResponder>,
585    tokens: token_registry::TokenRegistry<NetworkTokenContents>,
586    // NetworkProperty Watchers
587    property_responders: HashMap<ConnectionId, NetworkPropertyResponder>,
588    // The networks known to the system
589    network_registry: RegisteredNetworks,
590    telemetry: Option<TelemetrySender>,
591}
592
593impl NetpolNetworksService {
594    pub fn set_telemetry(&mut self, telemetry: TelemetrySender) {
595        self.telemetry = Some(telemetry);
596    }
597
598    /// Returns the consolidated DNS servers from the Network Registry.
599    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
600        self.network_registry.consolidated_dns_servers()
601    }
602
603    pub async fn handle_network_attributes_request(
604        &mut self,
605        id: ConnectionId,
606        req: Result<fnp_properties::NetworksRequest, fidl::Error>,
607    ) -> Result<(), anyhow::Error> {
608        let req = req.context("network attributes request")?;
609        match req {
610            fnp_properties::NetworksRequest::WatchDefault { responder } => {
611                match self.default_network_responders.entry(id) {
612                    std::collections::hash_map::Entry::Occupied(_) => {
613                        warn!(
614                            "Only one call to fuchsia.net.policy.properties/Networks.WatchDefault \
615                             may be active per connection"
616                        );
617                        responder
618                            .control_handle()
619                            .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
620                    }
621                    std::collections::hash_map::Entry::Vacant(vacant_entry) => {
622                        let network_id = if self
623                            .generations_by_connection
624                            .default_network(&id)
625                            .unwrap_or_default()
626                            < self.current_generation.default_network
627                        {
628                            self.network_registry.default_network
629                        } else {
630                            None
631                        };
632                        if let Some(network_id) = network_id {
633                            self.generations_by_connection
634                                .set_default_network(id, self.current_generation);
635                            let token = self
636                                .tokens
637                                .ensure_token(NetworkTokenContents { network_id, is_default: true })
638                                .get()
639                                .duplicate()
640                                .context("could not duplicate token")?;
641                            responder.send(
642                                fnp_properties::NetworksWatchDefaultResponse::Network(token),
643                            )?;
644
645                            if let Some(responder) = self.property_responders.remove(&id) {
646                                let _: Option<_> = self.generations_by_connection.remove(&id);
647                                let _: Result<(), fidl::Error> =
648                                    responder.respond(Err(fnp_properties::WatchError::NetworkGone));
649                            }
650                        } else {
651                            let _: &mut _ = vacant_entry.insert(responder);
652                        }
653                    }
654                }
655            }
656            fnp_properties::NetworksRequest::WatchProperties {
657                payload: fnp_properties::NetworksWatchPropertiesRequest { network, properties, .. },
658                responder,
659            } => match (network, properties) {
660                (None, _) | (_, None) => {
661                    responder.send(Err(fnp_properties::WatchError::MissingRequiredArgument))?
662                }
663                (Some(network), Some(properties)) => {
664                    if properties.is_empty() {
665                        responder.send(Err(fnp_properties::WatchError::NoProperties))?;
666                    } else {
667                        match self.property_responders.entry(id) {
668                            std::collections::hash_map::Entry::Occupied(_) => {
669                                warn!(
670                                    "Only one call to \
671                                    fuchsia.net.policy.properties/Networks.WatchProperties may be \
672                                    active per connection"
673                                );
674                                responder
675                                    .control_handle()
676                                    .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
677                            }
678                            std::collections::hash_map::Entry::Vacant(vacant_entry) => {
679                                match self.tokens.get_contents(&network) {
680                                    Err(e) => {
681                                        warn!("Unknown network token. ({network:?}: {e})");
682                                        responder.send(Err(
683                                            fnp_properties::WatchError::InvalidNetworkToken,
684                                        ))?;
685                                    }
686                                    Ok(network_contents) => {
687                                        let responder = NetworkPropertyResponder {
688                                            token: network,
689                                            watched_properties: properties,
690                                            responder,
691                                        };
692                                        if self
693                                            .generations_by_connection
694                                            .properties(&id)
695                                            .unwrap_or_default()
696                                            < self.current_generation.properties
697                                        {
698                                            self.generations_by_connection
699                                                .set_properties(id, self.current_generation);
700                                            if let Some(responder) = self
701                                                .network_registry
702                                                .maybe_respond(&network_contents, responder)
703                                            {
704                                                let _: &mut NetworkPropertyResponder =
705                                                    vacant_entry.insert(responder);
706                                            }
707                                        } else {
708                                            let _: &mut NetworkPropertyResponder =
709                                                vacant_entry.insert(responder);
710                                        }
711                                    }
712                                }
713                            }
714                        }
715                    }
716                }
717            },
718            _ => {
719                warn!("Received unexpected request {req:?}");
720            }
721        }
722
723        Ok(())
724    }
725
726    /// Handles delegated network updates coming from Starnix.
727    ///
728    /// Resolves network events requested through the `NetworkRegistry` interface, applies the
729    /// corresponding properties changes, and yields the computed DNS configuration targets for
730    /// output updates.
731    ///
732    /// TODO(https://fxbug.dev/428712735): Stop returning DnsServer list once
733    /// dns-resolver learns about DNS via NetworkProperties.
734    pub async fn handle_delegated_networks_update(
735        &mut self,
736        update: Result<fnp_socketproxy::NetworkRegistryRequest, fidl::Error>,
737    ) -> Result<DelegatedNetworkUpdateResult, anyhow::Error> {
738        use fnp_socketproxy::{
739            NetworkInfo, NetworkRegistryAddError, NetworkRegistryRemoveError,
740            NetworkRegistryRequest, NetworkRegistrySetDefaultError, NetworkRegistryUpdateError,
741        };
742
743        let action_result = match update {
744            Err(e) => {
745                error!(
746                    "Encountered error watching for delegated network \
747                                    updates: {e:?}"
748                );
749                return Err(anyhow::anyhow!(e));
750            }
751            Ok(NetworkRegistryRequest::SetDefault { network_id, responder }) => {
752                let update_result = match network_id {
753                    fposix_socket::OptionalUint32::Value(interface_id) => {
754                        match InterfaceId::try_from(interface_id) {
755                            Ok(id) => {
756                                let delegated_id = NetworkId::delegated(id);
757                                self.update(PropertyUpdate::ChangeNetwork(
758                                    delegated_id,
759                                    NetworkUpdate::MakeDefault,
760                                ))
761                                .await;
762                                Ok(())
763                            }
764                            Err(_) => Err(NetworkRegistrySetDefaultError::NotFound),
765                        }
766                    }
767                    fposix_socket::OptionalUint32::Unset(_) => {
768                        self.update(PropertyUpdate::default_network_lost()).await;
769                        Ok(())
770                    }
771                };
772
773                self.respond_to_delegated_network_update(
774                    update_result,
775                    |reply| responder.send(reply),
776                    "failed to send SetDefault result",
777                )
778            }
779            Ok(NetworkRegistryRequest::Add { network, responder }) => {
780                let extracted_properties = (|| {
781                    let raw_network_id =
782                        network.network_id.ok_or(NetworkRegistryAddError::MissingNetworkId)?;
783                    let network_id = InterfaceId::try_from(raw_network_id)
784                        .map(|id| NetworkId::delegated(id))
785                        .map_err(|_| NetworkRegistryAddError::MissingNetworkId)?;
786                    let NetworkInfo::Starnix(info) =
787                        network.info.ok_or(NetworkRegistryAddError::MissingNetworkInfo)?
788                    else {
789                        return Err(NetworkRegistryAddError::MissingNetworkInfo);
790                    };
791
792                    let mut marks = fnet::Marks::default();
793                    marks.set_mark(fnet::MARK_DOMAIN_SO_MARK, info.mark);
794
795                    let dns_servers =
796                        Self::extract_dns_servers(&network.dns_servers, raw_network_id.into());
797
798                    Ok((network_id, marks, dns_servers))
799                })();
800
801                let update_result = match extracted_properties {
802                    Ok((network_id, marks, dns_servers)) => {
803                        self.update(PropertyUpdate::ChangeNetwork(
804                            network_id,
805                            NetworkUpdate::Properties(NetworkPropertiesChange {
806                                added: true,
807                                marks: Some(marks),
808                                dns_servers: Some(dns_servers.clone()),
809                                connectivity_state: network.connectivity,
810                                name: network.name,
811                                network_type: network.network_type,
812                            }),
813                        ))
814                        .await;
815                        Ok(())
816                    }
817                    Err(e) => Err(e),
818                };
819
820                self.respond_to_delegated_network_update(
821                    update_result,
822                    |reply| responder.send(reply),
823                    "failed to send Add result",
824                )
825            }
826            Ok(NetworkRegistryRequest::Update { network, responder }) => {
827                let extracted_properties = (|| {
828                    let raw_network_id =
829                        network.network_id.ok_or(NetworkRegistryUpdateError::MissingNetworkId)?;
830                    let network_id = InterfaceId::try_from(raw_network_id)
831                        .map(|id| NetworkId::delegated(id))
832                        .map_err(|_| NetworkRegistryUpdateError::MissingNetworkId)?;
833                    let NetworkInfo::Starnix(info) =
834                        network.info.ok_or(NetworkRegistryUpdateError::MissingNetworkInfo)?
835                    else {
836                        return Err(NetworkRegistryUpdateError::MissingNetworkInfo);
837                    };
838
839                    let mut marks = fnet::Marks::default();
840                    marks.set_mark(fnet::MARK_DOMAIN_SO_MARK, info.mark);
841
842                    let dns_servers =
843                        Self::extract_dns_servers(&network.dns_servers, raw_network_id.into());
844
845                    Ok((network_id, marks, dns_servers))
846                })();
847
848                let update_result = match extracted_properties {
849                    Ok((network_id, marks, dns_servers)) => {
850                        self.update(PropertyUpdate::ChangeNetwork(
851                            network_id,
852                            NetworkUpdate::Properties(NetworkPropertiesChange {
853                                added: false,
854                                marks: Some(marks),
855                                dns_servers: Some(dns_servers.clone()),
856                                connectivity_state: network.connectivity,
857                                name: network.name,
858                                network_type: network.network_type,
859                            }),
860                        ))
861                        .await;
862                        Ok(())
863                    }
864                    Err(e) => Err(e),
865                };
866
867                self.respond_to_delegated_network_update(
868                    update_result,
869                    |reply| responder.send(reply),
870                    "failed to send Update result",
871                )
872            }
873            Ok(NetworkRegistryRequest::Remove { network_id, responder }) => {
874                let update_result = match InterfaceId::try_from(network_id) {
875                    Ok(id) => {
876                        let delegated_id = NetworkId::delegated(id);
877                        self.update(PropertyUpdate::ChangeNetwork(
878                            delegated_id,
879                            NetworkUpdate::Remove,
880                        ))
881                        .await;
882                        Ok(())
883                    }
884                    Err(_) => Err(NetworkRegistryRemoveError::NotFound),
885                };
886
887                self.respond_to_delegated_network_update(
888                    update_result,
889                    |reply| responder.send(reply),
890                    "failed to send Remove result",
891                )
892            }
893        };
894
895        Ok(action_result)
896    }
897
898    // Resolves the operation result, sends the success or failure status to
899    // the FIDL responder, and returns the updated network registry settings.
900    fn respond_to_delegated_network_update<E, F>(
901        &self,
902        operation_result: Result<(), E>,
903        send_response: F,
904        context_message: &'static str,
905    ) -> DelegatedNetworkUpdateResult
906    where
907        F: FnOnce(Result<(), E>) -> Result<(), fidl::Error>,
908    {
909        // Return consolidated DNS servers if the operation was successful.
910        let dns_servers = operation_result
911            .as_ref()
912            .ok()
913            .map(|()| self.network_registry.consolidated_dns_servers());
914
915        // Send success or failure status to the FIDL responder.
916        if let Err(e) = send_response(operation_result) {
917            if !e.is_closed() {
918                error!(
919                    "Failed to send delegated network update result \
920                for {context_message}: {e}"
921                );
922            }
923        }
924
925        DelegatedNetworkUpdateResult { dns_servers }
926    }
927
928    // Converts `NetworkDnsServers` to `Vec<DnsServer_>` for a given network.
929    //
930    // Note: We prioritize IPv4 servers over IPv6 servers. This is impactful
931    // when sending DNS servers through NetworkProperties or to dns-resolver.
932    fn extract_dns_servers(
933        dns_servers: &Option<fnp_socketproxy::NetworkDnsServers>,
934        network_id: u64,
935    ) -> Vec<fnet_name::DnsServer_> {
936        let make_server = |address| fnet_name::DnsServer_ {
937            address: Some(address),
938            source: Some(fnet_name::DnsServerSource::SocketProxy(
939                fnet_name::SocketProxyDnsServerSource {
940                    source_interface: Some(network_id),
941                    ..Default::default()
942                },
943            )),
944            ..Default::default()
945        };
946
947        dns_servers
948            .as_ref()
949            .map(|dns| {
950                dns.v4
951                    .as_ref()
952                    .into_iter()
953                    .flatten()
954                    .map(|&address| {
955                        make_server(fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
956                            address,
957                            port: DNS_PORT,
958                        }))
959                    })
960                    .chain(dns.v6.as_ref().into_iter().flatten().map(|&address| {
961                        make_server(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
962                            address,
963                            port: DNS_PORT,
964                            zone_index: 0,
965                        }))
966                    }))
967                    .collect()
968            })
969            .unwrap_or_default()
970    }
971
972    pub(crate) async fn handle_network_token_resolver_request(
973        &mut self,
974        request: Result<fnp_properties::NetworkTokenResolverRequest, fidl::Error>,
975    ) -> Result<(), anyhow::Error> {
976        use fnp_properties::NetworkTokenResolverResolveTokenError as ResolveTokenError;
977
978        let request = request.context("while handling NetworkTokenResolver request")?;
979        match request {
980            fnp_properties::NetworkTokenResolverRequest::ResolveToken { token, responder } => {
981                let maybe_contents = self.tokens.get_contents(&token).copied();
982                match maybe_contents {
983                    Err(e) => {
984                        warn!("Unknown network token. ({token:?}: {e})");
985                        responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
986                    }
987                    Ok(contents) => {
988                        if contents.is_default {
989                            // This is a default network token, we need to grab
990                            // the non-default variant.
991                            let query = NetworkTokenContents { is_default: false, ..contents };
992                            if let Some(tok) = self.tokens.get_token(&query) {
993                                responder.send(tok.duplicate().map_err(|e| {
994                                    warn!("Encountered issue duplicating generated token. {e}");
995                                    ResolveTokenError::InvalidNetworkToken
996                                }))?;
997                            } else {
998                                warn!("Requested canonical version of unregistered network.");
999                                responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
1000                            }
1001                        } else {
1002                            responder.send(Ok(token))?;
1003                        }
1004                    }
1005                }
1006            }
1007            fidl_fuchsia_net_policy_properties::NetworkTokenResolverRequest::_UnknownMethod {
1008                ordinal,
1009                control_handle,
1010                method_type,
1011                ..
1012            } => warn!(
1013                "Encountered unknown method call on NetworkTokenResolver: {ordinal} \
1014                {control_handle:?} {method_type:?}"
1015            ),
1016        }
1017
1018        Ok(())
1019    }
1020
1021    async fn changed_default_network(
1022        &mut self,
1023        previous_default_network: Option<NetworkId>,
1024        responders: &mut HashMap<ConnectionId, NetworkPropertyResponder>,
1025    ) {
1026        let mut r = HashMap::new();
1027        std::mem::swap(&mut r, responders);
1028        r = r
1029            .into_iter()
1030            .filter_map(|(id, responder)| {
1031                match self.tokens.get_contents(&responder.token) {
1032                    Ok(contents) => {
1033                        // We only want to remove when watching a default token.
1034                        if contents.is_default {
1035                            let _: Option<_> = self.generations_by_connection.remove(&id);
1036                            let _: Result<(), fidl::Error> =
1037                                responder.respond(Err(fnp_properties::WatchError::NetworkGone));
1038                            return None;
1039                        }
1040                    }
1041                    Err(zx::Status::NOT_FOUND) => {
1042                        warn!("Token provided to get_contents is not valid.");
1043                    }
1044                    Err(e) => {
1045                        warn!("Encountered unknown issue while getting contents: {e}");
1046                    }
1047                }
1048                Some((id, responder))
1049            })
1050            .collect::<HashMap<_, _>>();
1051        std::mem::swap(&mut r, responders);
1052        self.tokens.drop_if(|&c| {
1053            c.is_default && previous_default_network.is_some_and(|i| i == c.network_id)
1054        });
1055    }
1056
1057    pub(crate) async fn remove_network(&mut self, network_id: NetworkId) {
1058        info!("Removing interface {network_id}. Reporting NETWORK_GONE to all clients.");
1059        let mut responders = HashMap::new();
1060        std::mem::swap(&mut self.property_responders, &mut responders);
1061        for (id, responder) in responders {
1062            let network = match self.tokens.get_contents(&responder.token) {
1063                Ok(network) => network,
1064                Err(e) => {
1065                    warn!("Could not fetch network data for responder: {e}");
1066                    continue;
1067                }
1068            };
1069            if network.network_id == network_id {
1070                // Report that this interface was removed
1071                if let Err(e) = responder.respond(Err(fnp_properties::WatchError::NetworkGone)) {
1072                    warn!("Could not send to responder: {e}");
1073                }
1074            } else {
1075                if self.property_responders.insert(id, responder).is_some() {
1076                    error!("Re-inserted in an existing responder slot. This should be impossible.");
1077                }
1078            }
1079        }
1080    }
1081
1082    pub async fn update(&mut self, update: PropertyUpdate) {
1083        self.current_generation.properties += 1;
1084        let RegistryUpdateResult { event, default_changed } = self.network_registry.apply(update);
1085
1086        if let UpdateApplied::None = event {
1087            if default_changed.is_none() {
1088                // Return early if there were absolutely no changes and the default stayed the same.
1089                return;
1090            }
1091        }
1092
1093        let mut property_responders = HashMap::new();
1094        std::mem::swap(&mut self.property_responders, &mut property_responders);
1095
1096        // Clean up or register tokens based on whether the network was added or removed.
1097        match event {
1098            UpdateApplied::NetworkChanged { network_id, added: true, .. } => {
1099                let _ = self
1100                    .tokens
1101                    .ensure_token(NetworkTokenContents { network_id, is_default: false });
1102            }
1103            UpdateApplied::NetworkRemoved(network_id) => {
1104                self.tokens.drop_if(|c| !c.is_default && c.network_id == network_id);
1105            }
1106            UpdateApplied::NetworkChanged { added: false, .. }
1107            | UpdateApplied::DnsChanged
1108            | UpdateApplied::None => {}
1109        }
1110
1111        // Notify watchers of default network changes if one occurred.
1112        if let Some(DefaultChangedEvent { previous_default }) = default_changed {
1113            self.notify_default_network_changed(previous_default, &mut property_responders).await;
1114            return;
1115        }
1116
1117        if let UpdateApplied::NetworkChanged { network_id, .. } = event {
1118            if let Some(telemetry) = &self.telemetry {
1119                if let Some(props) = self.network_registry.networks.get(&network_id) {
1120                    telemetry.send(TelemetryEvent::NetworkChanged(NetworkEventMetadata {
1121                        id: network_id.get().get(),
1122                        name: props.name.clone(),
1123                        transport: props
1124                            .network_type
1125                            .unwrap_or(fnp_socketproxy::NetworkType::Unknown),
1126                        is_fuchsia_provisioned: matches!(network_id, NetworkId::Fuchsia(_)),
1127                        connectivity_state: props.connectivity_state,
1128                    }));
1129                }
1130            }
1131        }
1132
1133        for (id, responder) in property_responders {
1134            let mut updates = Vec::new();
1135            let network = match self.tokens.get_contents(&responder.token) {
1136                Ok(network) => network,
1137                Err(e) => {
1138                    warn!("Could not fetch network data for responder: {e}");
1139                    continue;
1140                }
1141            };
1142
1143            if let UpdateApplied::NetworkChanged { network_id, changed_marks: true, .. } = event {
1144                if network.network_id == network_id {
1145                    updates.add_socket_marks(&self.network_registry, &network, &responder);
1146                }
1147            }
1148            if let UpdateApplied::DnsChanged = event {
1149                updates.add_dns(&self.network_registry, &network, &responder);
1150            }
1151
1152            self.generations_by_connection.set_properties(id, self.current_generation);
1153            if updates.is_empty() {
1154                if self.property_responders.insert(id, responder).is_some() {
1155                    warn!("Re-inserted in an existing responder slot. This should be impossible.");
1156                }
1157            } else {
1158                if let Err(e) = responder.respond(Ok(&updates)) {
1159                    warn!("Could not send to responder: {e}");
1160                }
1161            }
1162        }
1163    }
1164
1165    async fn notify_default_network_changed(
1166        &mut self,
1167        old_default: Option<NetworkId>,
1168        property_responders: &mut HashMap<ConnectionId, NetworkPropertyResponder>,
1169    ) {
1170        self.changed_default_network(old_default, property_responders).await;
1171        match self.network_registry.default_network {
1172            Some(default_network) => {
1173                if let Some(telemetry) = &self.telemetry {
1174                    if let Some(props) = self.network_registry.networks.get(&default_network) {
1175                        telemetry.send(TelemetryEvent::DefaultNetworkChanged(
1176                            NetworkEventMetadata {
1177                                id: default_network.get().get(),
1178                                name: props.name.clone(),
1179                                transport: props
1180                                    .network_type
1181                                    .unwrap_or(fnp_socketproxy::NetworkType::Unknown),
1182                                is_fuchsia_provisioned: matches!(
1183                                    default_network,
1184                                    NetworkId::Fuchsia(_)
1185                                ),
1186                                connectivity_state: props.connectivity_state,
1187                            },
1188                        ));
1189                    } else {
1190                        warn!("Could not fetch network data for default network.");
1191                    }
1192                }
1193                self.current_generation.default_network += 1;
1194                let mut responders = HashMap::new();
1195                std::mem::swap(&mut self.default_network_responders, &mut responders);
1196                for (id, responder) in responders {
1197                    self.generations_by_connection.set_default_network(id, self.current_generation);
1198                    match self
1199                        .tokens
1200                        .ensure_token(NetworkTokenContents {
1201                            network_id: default_network,
1202                            is_default: true,
1203                        })
1204                        .get()
1205                        .duplicate()
1206                    {
1207                        Ok(token) => {
1208                            if let Err(e) = responder
1209                                .send(fnp_properties::NetworksWatchDefaultResponse::Network(token))
1210                            {
1211                                warn!("Could not send to responder: {e}");
1212                            }
1213                        }
1214                        Err(e) => warn!("Could not duplicate token: {e}"),
1215                    };
1216                }
1217            }
1218            None => {
1219                if let Some(telemetry) = &self.telemetry {
1220                    telemetry.send(TelemetryEvent::DefaultNetworkLost);
1221                }
1222                // The default network has been lost.
1223                self.current_generation.default_network += 1;
1224                let mut responders = HashMap::new();
1225                std::mem::swap(&mut self.default_network_responders, &mut responders);
1226                for (id, responder) in responders {
1227                    self.generations_by_connection.set_default_network(id, self.current_generation);
1228                    if let Err(e) = responder.send(
1229                        fnp_properties::NetworksWatchDefaultResponse::NoDefaultNetwork(
1230                            fnp_properties::Empty,
1231                        ),
1232                    ) {
1233                        warn!("Could not send to responder: {e}");
1234                    }
1235                }
1236            }
1237        }
1238    }
1239}
1240
1241pub struct ConnectionTagged<Stream: futures::Stream + Unpin> {
1242    next_id: ConnectionId,
1243    streams: futures::stream::SelectAll<Tagged<ConnectionId, Stream>>,
1244}
1245
1246impl<Stream: futures::Stream + Unpin> Default for ConnectionTagged<Stream> {
1247    fn default() -> Self {
1248        Self { next_id: Default::default(), streams: Default::default() }
1249    }
1250}
1251
1252impl<Stream: futures::Stream + Unpin> ConnectionTagged<Stream> {
1253    pub fn push(&mut self, stream: Stream) {
1254        self.streams.push(stream.tagged(self.next_id));
1255        self.next_id.0 += 1;
1256    }
1257}
1258
1259impl<Stream: futures::Stream + Unpin> futures::Stream for ConnectionTagged<Stream> {
1260    type Item = (ConnectionId, <Stream as futures::Stream>::Item);
1261
1262    fn poll_next(
1263        mut self: std::pin::Pin<&mut Self>,
1264        cx: &mut std::task::Context<'_>,
1265    ) -> std::task::Poll<Option<Self::Item>> {
1266        std::pin::Pin::new(&mut self.streams).poll_next(cx)
1267    }
1268}
1269
1270impl<Stream: futures::Stream + Unpin> futures::stream::FusedStream for ConnectionTagged<Stream> {
1271    fn is_terminated(&self) -> bool {
1272        self.streams.is_terminated()
1273    }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278    use super::*;
1279    use std::num::NonZeroU64;
1280    const ID_1: InterfaceId = InterfaceId(NonZeroU64::new(1).unwrap());
1281    const ID_2: InterfaceId = InterfaceId(NonZeroU64::new(2).unwrap());
1282    const NAME_1: &str = "testif1";
1283    const NAME_2: &str = "testif2";
1284
1285    impl NetpolNetworksService {
1286        pub(crate) fn default_network(&self) -> Option<NetworkId> {
1287            self.network_registry.default_network
1288        }
1289
1290        pub(crate) fn has_network(&self, id: NetworkId) -> bool {
1291            self.network_registry.networks.contains_key(&id)
1292        }
1293    }
1294
1295    #[test]
1296    fn test_handle_changed_network_delegated() {
1297        let mut networks = RegisteredNetworks::default();
1298        let delegated_id = NetworkId::Delegated(ID_1);
1299
1300        // Add a new delegated network
1301        let marks = fnet::Marks { mark_1: Some(123), ..Default::default() };
1302        let event = NetworkPropertiesChange {
1303            added: true,
1304            marks: Some(marks.clone()),
1305            dns_servers: None,
1306            connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
1307            name: Some(NAME_1.to_string()),
1308            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1309        };
1310        assert_eq!(
1311            networks.handle_changed_network(delegated_id, event),
1312            UpdateApplied::NetworkChanged {
1313                network_id: delegated_id,
1314                added: true,
1315                changed_marks: true,
1316                name: Some(NAME_1.to_string()),
1317                network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1318            }
1319        );
1320        let properties = networks.networks.get(&delegated_id).expect("network should be present");
1321        assert_eq!(properties.socket_marks, Some(marks.clone()));
1322        assert_eq!(
1323            properties.connectivity_state,
1324            Some(fnp_socketproxy::ConnectivityState::FullConnectivity)
1325        );
1326
1327        // Update with different connectivity state but same marks
1328        let event = NetworkPropertiesChange {
1329            added: false,
1330            marks: Some(marks.clone()),
1331            dns_servers: None,
1332            connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
1333            name: Some(NAME_1.to_string()),
1334            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1335        };
1336        assert_eq!(
1337            networks.handle_changed_network(delegated_id, event),
1338            UpdateApplied::NetworkChanged {
1339                network_id: delegated_id,
1340                added: false,
1341                changed_marks: false,
1342                name: Some(NAME_1.to_string()),
1343                network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1344            }
1345        );
1346
1347        let properties = networks.networks.get(&delegated_id).expect("network should be present");
1348        assert_eq!(properties.socket_marks, Some(marks.clone()));
1349        assert_eq!(
1350            properties.connectivity_state,
1351            Some(fnp_socketproxy::ConnectivityState::NoConnectivity)
1352        );
1353
1354        // Update with different marks
1355        let new_marks = fnet::Marks { mark_1: Some(456), ..Default::default() };
1356        let event = NetworkPropertiesChange {
1357            added: false,
1358            marks: Some(new_marks.clone()),
1359            dns_servers: None,
1360            connectivity_state: Some(fnp_socketproxy::ConnectivityState::NoConnectivity),
1361            name: Some(NAME_1.to_string()),
1362            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1363        };
1364        assert_eq!(
1365            networks.handle_changed_network(delegated_id, event),
1366            UpdateApplied::NetworkChanged {
1367                network_id: delegated_id,
1368                added: false,
1369                changed_marks: true,
1370                name: Some(NAME_1.to_string()),
1371                network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1372            }
1373        );
1374        let properties = networks.networks.get(&delegated_id).expect("network should be present");
1375        assert_eq!(properties.socket_marks, Some(new_marks));
1376        assert_eq!(
1377            properties.connectivity_state,
1378            Some(fnp_socketproxy::ConnectivityState::NoConnectivity)
1379        );
1380    }
1381
1382    #[test]
1383    fn test_handle_changed_network_fuchsia() {
1384        let mut networks = RegisteredNetworks::default();
1385        let fuchsia_id = NetworkId::Fuchsia(ID_2);
1386
1387        // Add a Fuchsia network
1388        let event = NetworkPropertiesChange {
1389            added: true,
1390            marks: None,
1391            dns_servers: None,
1392            connectivity_state: Some(fnp_socketproxy::ConnectivityState::LocalConnectivity),
1393            name: Some(NAME_2.to_string()),
1394            network_type: Some(fnp_socketproxy::NetworkType::Wifi),
1395        };
1396        assert_eq!(
1397            networks.handle_changed_network(fuchsia_id, event),
1398            UpdateApplied::NetworkChanged {
1399                network_id: fuchsia_id,
1400                added: true,
1401                changed_marks: true,
1402                name: Some(NAME_2.to_string()),
1403                network_type: Some(fnp_socketproxy::NetworkType::Wifi),
1404            }
1405        );
1406        let properties = networks.networks.get(&fuchsia_id).expect("network should be present");
1407        assert_eq!(properties.socket_marks, None);
1408        assert_eq!(
1409            properties.connectivity_state,
1410            Some(fnp_socketproxy::ConnectivityState::LocalConnectivity)
1411        );
1412
1413        // Update Fuchsia network connectivity
1414        let event = NetworkPropertiesChange {
1415            added: false,
1416            marks: None,
1417            dns_servers: None,
1418            connectivity_state: Some(fnp_socketproxy::ConnectivityState::FullConnectivity),
1419            name: Some(NAME_2.to_string()),
1420            network_type: Some(fnp_socketproxy::NetworkType::Wifi),
1421        };
1422        assert_eq!(
1423            networks.handle_changed_network(fuchsia_id, event),
1424            UpdateApplied::NetworkChanged {
1425                network_id: fuchsia_id,
1426                added: false,
1427                changed_marks: false,
1428                name: Some(NAME_2.to_string()),
1429                network_type: Some(fnp_socketproxy::NetworkType::Wifi),
1430            }
1431        );
1432
1433        let properties = networks.networks.get(&fuchsia_id).expect("network should be present");
1434        assert_eq!(
1435            properties.connectivity_state,
1436            Some(fnp_socketproxy::ConnectivityState::FullConnectivity)
1437        );
1438    }
1439
1440    #[test]
1441    fn test_handle_changed_network_validation() {
1442        let mut networks = RegisteredNetworks::default();
1443        let fuchsia_id = NetworkId::Fuchsia(ID_1);
1444        let network_id = NetworkId::Delegated(ID_1);
1445        let marks = fnet::Marks { mark_1: Some(123), ..Default::default() };
1446
1447        // Update a non-added network
1448        let event = NetworkPropertiesChange {
1449            added: false,
1450            marks: Some(marks.clone()),
1451            dns_servers: None,
1452            connectivity_state: None,
1453            name: Some(NAME_1.to_string()),
1454            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1455        };
1456        assert_eq!(networks.handle_changed_network(network_id, event), UpdateApplied::None);
1457
1458        // Add the network
1459        let event = NetworkPropertiesChange {
1460            added: true,
1461            marks: Some(marks.clone()),
1462            dns_servers: None,
1463            connectivity_state: None,
1464            name: Some(NAME_1.to_string()),
1465            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1466        };
1467        assert_eq!(
1468            networks.handle_changed_network(network_id, event),
1469            UpdateApplied::NetworkChanged {
1470                network_id,
1471                added: true,
1472                changed_marks: true,
1473                name: Some(NAME_1.to_string()),
1474                network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1475            }
1476        );
1477
1478        // Add already added network
1479        let event = NetworkPropertiesChange {
1480            added: true,
1481            marks: Some(marks.clone()),
1482            dns_servers: None,
1483            connectivity_state: None,
1484            name: Some(NAME_1.to_string()),
1485            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1486        };
1487        assert_eq!(networks.handle_changed_network(network_id, event), UpdateApplied::None);
1488
1489        // Fuchsia network with marks
1490        let event = NetworkPropertiesChange {
1491            added: true,
1492            marks: Some(marks.clone()),
1493            dns_servers: None,
1494            connectivity_state: None,
1495            name: Some(NAME_1.to_string()),
1496            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1497        };
1498        assert_eq!(networks.handle_changed_network(fuchsia_id, event), UpdateApplied::None);
1499
1500        // Delegated network without marks
1501        let delegated_id = NetworkId::Delegated(ID_1);
1502        let event = NetworkPropertiesChange {
1503            added: true,
1504            marks: None,
1505            dns_servers: None,
1506            connectivity_state: None,
1507            name: Some(NAME_1.to_string()),
1508            network_type: Some(fnp_socketproxy::NetworkType::Ethernet),
1509        };
1510        assert_eq!(networks.handle_changed_network(delegated_id, event), UpdateApplied::None);
1511
1512        // Make the network default
1513        assert_eq!(
1514            networks.apply(PropertyUpdate::ChangeNetwork(network_id, NetworkUpdate::MakeDefault)),
1515            RegistryUpdateResult {
1516                event: UpdateApplied::None,
1517                default_changed: Some(DefaultChangedEvent { previous_default: None })
1518            }
1519        );
1520
1521        // Attempt to remove default network. This is invalid change since
1522        // the default must be unset prior to removal.
1523        assert_eq!(
1524            networks.apply(PropertyUpdate::ChangeNetwork(network_id, NetworkUpdate::Remove)),
1525            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
1526        );
1527
1528        // Verify it was not removed
1529        assert!(networks.networks.contains_key(&network_id));
1530        assert_eq!(networks.default_network, Some(network_id));
1531    }
1532
1533    #[test]
1534    fn test_remove_fuchsia_network_fallback() {
1535        let mut networks = RegisteredNetworks::default();
1536        let fuchsia_id1 = NetworkId::Fuchsia(ID_1);
1537        let fuchsia_id2 = NetworkId::Fuchsia(ID_2);
1538        let delegated_id = NetworkId::Delegated(ID_1);
1539
1540        let marks = fnet::Marks { mark_1: Some(123), ..Default::default() };
1541
1542        // Add two Fuchsia networks and one Delegated network to the registry.
1543        let fuchsia_added_network_change =
1544            NetworkPropertiesChange { added: true, ..Default::default() };
1545        assert_eq!(
1546            networks.apply(PropertyUpdate::ChangeNetwork(
1547                fuchsia_id1,
1548                NetworkUpdate::Properties(fuchsia_added_network_change.clone())
1549            )),
1550            RegistryUpdateResult {
1551                event: UpdateApplied::NetworkChanged {
1552                    network_id: fuchsia_id1,
1553                    added: true,
1554                    changed_marks: true,
1555                    name: None,
1556                    network_type: None,
1557                },
1558                default_changed: Some(DefaultChangedEvent { previous_default: None })
1559            }
1560        );
1561        assert_eq!(
1562            networks.apply(PropertyUpdate::ChangeNetwork(
1563                fuchsia_id2,
1564                NetworkUpdate::Properties(fuchsia_added_network_change)
1565            )),
1566            RegistryUpdateResult {
1567                event: UpdateApplied::NetworkChanged {
1568                    network_id: fuchsia_id2,
1569                    added: true,
1570                    changed_marks: true,
1571                    name: None,
1572                    network_type: None,
1573                },
1574                default_changed: None
1575            }
1576        );
1577        assert_eq!(
1578            networks.apply(PropertyUpdate::ChangeNetwork(
1579                delegated_id,
1580                NetworkUpdate::Properties(NetworkPropertiesChange {
1581                    added: true,
1582                    marks: Some(marks),
1583                    ..Default::default()
1584                })
1585            )),
1586            RegistryUpdateResult {
1587                event: UpdateApplied::NetworkChanged {
1588                    network_id: delegated_id,
1589                    added: true,
1590                    changed_marks: true,
1591                    name: None,
1592                    network_type: None,
1593                },
1594                default_changed: None
1595            }
1596        );
1597
1598        // Make the Delegated network default. Since Fuchsia networks are
1599        // present, they are prioritized, so the active default network does
1600        // not change.
1601        assert_eq!(
1602            networks.apply(PropertyUpdate::ChangeNetwork(delegated_id, NetworkUpdate::MakeDefault)),
1603            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
1604        );
1605
1606        // Verify the active default is Fuchsia's network with the lowest ID.
1607        assert_eq!(networks.default_network, Some(fuchsia_id1));
1608
1609        // Remove the Fuchsia active default network directly (allowed
1610        // statelessly for Fuchsia networks)
1611        assert_eq!(
1612            networks.apply(PropertyUpdate::ChangeNetwork(fuchsia_id1, NetworkUpdate::Remove)),
1613            RegistryUpdateResult {
1614                event: UpdateApplied::NetworkRemoved(fuchsia_id1),
1615                default_changed: Some(DefaultChangedEvent { previous_default: Some(fuchsia_id1) })
1616            }
1617        );
1618
1619        // Verify the fallback default is the next available Fuchsia network.
1620        assert_eq!(networks.default_network, Some(fuchsia_id2));
1621
1622        // Remove the fallback Fuchsia network.
1623        assert_eq!(
1624            networks.apply(PropertyUpdate::ChangeNetwork(fuchsia_id2, NetworkUpdate::Remove)),
1625            RegistryUpdateResult {
1626                event: UpdateApplied::NetworkRemoved(fuchsia_id2),
1627                default_changed: Some(DefaultChangedEvent { previous_default: Some(fuchsia_id2) })
1628            }
1629        );
1630
1631        // Verify the fallback default is the Delegated network since there are
1632        // no Fuchsia networks left.
1633        assert_eq!(networks.default_network, Some(delegated_id));
1634
1635        // Remove the Delegated network directly. This must be rejected because
1636        // it is the Starnix default.
1637        assert_eq!(
1638            networks.apply(PropertyUpdate::ChangeNetwork(delegated_id, NetworkUpdate::Remove)),
1639            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
1640        );
1641        assert!(networks.networks.contains_key(&delegated_id));
1642        assert_eq!(networks.default_network, Some(delegated_id));
1643    }
1644}