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 anyhow::Context as _;
7use async_utils::stream::{Tagged, WithTag as _};
8use dns_server_watcher::DnsServers;
9use fidl::endpoints::{ControlHandle as _, Responder as _};
10use fidl::HandleBased as _;
11use log::{error, info, warn};
12use std::collections::HashMap;
13
14mod token_map;
15
16use {
17    fidl_fuchsia_net as fnet, fidl_fuchsia_net_name as fnet_name,
18    fidl_fuchsia_net_policy_properties as fnp_properties,
19};
20
21pub trait NetworkTokenExt: Sized {
22    fn duplicate(&self) -> Result<Self, zx::Status>;
23}
24
25impl NetworkTokenExt for fnp_properties::NetworkToken {
26    fn duplicate(&self) -> Result<fnp_properties::NetworkToken, zx::Status> {
27        Ok(fnp_properties::NetworkToken {
28            value: Some(
29                self.value
30                    .as_ref()
31                    .ok_or(zx::Status::NOT_FOUND)?
32                    .duplicate_handle(zx::Rights::SAME_RIGHTS)?,
33            ),
34            ..Default::default()
35        })
36    }
37}
38
39pub(crate) struct NetworkTokenContents {
40    connection_id: ConnectionId,
41    interface_id: InterfaceId,
42}
43
44#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub(crate) struct ConnectionId(usize);
46
47#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub(crate) struct UpdateGeneration {
49    /// The current generation for `fuchsia.net.policy.properties.WatchDefault`.
50    /// Incremented each time the default network changes.
51    default_network: usize,
52
53    /// The current generation for `fuchsia.net.policy.properties.WatchProperties`.
54    /// Incremented each time a network property changes.
55    properties: usize,
56}
57
58#[derive(Clone, Debug, Default)]
59pub(crate) struct UpdateGenerations(HashMap<ConnectionId, UpdateGeneration>);
60
61impl UpdateGenerations {
62    fn default_network(&self, id: &ConnectionId) -> Option<usize> {
63        self.0.get(id).map(|g| g.default_network)
64    }
65
66    fn set_default_network(&mut self, id: ConnectionId, generation: UpdateGeneration) {
67        self.0.entry(id).or_default().default_network = generation.default_network;
68    }
69
70    fn properties(&self, id: &ConnectionId) -> Option<usize> {
71        self.0.get(id).map(|g| g.properties)
72    }
73
74    fn set_properties(&mut self, id: ConnectionId, generation: UpdateGeneration) {
75        self.0.entry(id).or_default().properties = generation.properties;
76    }
77
78    fn remove(&mut self, id: &ConnectionId) -> Option<UpdateGeneration> {
79        self.0.remove(id)
80    }
81}
82
83#[derive(Debug)]
84pub(crate) struct NetworkPropertyResponder {
85    token: fidl::EventPair,
86    watched_properties: Vec<fnp_properties::Property>,
87    responder: fnp_properties::NetworksWatchPropertiesResponder,
88}
89
90#[derive(Default)]
91pub(crate) struct NetpolNetworksService {
92    // The current generation
93    current_generation: UpdateGeneration,
94    // The last generation sent per connection
95    generations_by_connection: UpdateGenerations,
96    // Default Network Watchers
97    default_network_responders:
98        HashMap<ConnectionId, fnp_properties::NetworksWatchDefaultResponder>,
99    tokens: token_map::TokenMap<NetworkTokenContents>,
100    // NetworkProperty Watchers
101    property_responders: HashMap<ConnectionId, NetworkPropertyResponder>,
102    // Network properties
103    network_properties: NetworkProperties,
104}
105
106#[derive(Default, Clone)]
107struct NetworkProperties {
108    // Current default network
109    default_network: Option<InterfaceId>,
110    // Network marks
111    socket_marks: HashMap<InterfaceId, fnet::Marks>,
112    // DNS Servers
113    dns_servers: Vec<fnet_name::DnsServer_>,
114}
115
116impl NetworkProperties {
117    fn apply(&mut self, update: PropertyUpdate) -> UpdatesApplied {
118        let mut updates = UpdatesApplied::default();
119
120        if let Some(new_default_network) = update.default_network {
121            updates.default_network = self.handle_default_network_update(new_default_network);
122        }
123
124        if let Some((netid, marks)) = update.socket_marks {
125            updates.socket_marks_network = self.handle_socket_marks_update(netid, marks);
126        }
127
128        if let Some(dns) = update.dns {
129            updates.dns_changed = self.dns_servers != dns;
130            self.dns_servers = dns;
131        }
132
133        updates
134    }
135
136    // Handle the `default_network` argument in a `PropertyUpdate`, determining
137    // whether the network changed as a result of the update.
138    //
139    // Returns the update to set for the `default_network` argument
140    // of UpdatesApplied.
141    fn handle_default_network_update(
142        &mut self,
143        new_default_network: Option<InterfaceId>,
144    ) -> Option<Option<InterfaceId>> {
145        // We do not need to send an update applied if the network stayed the same.
146        if new_default_network == self.default_network {
147            return None;
148        }
149
150        let old_default_network = self.default_network;
151        if let (None, Some(old_default_network_id)) = (new_default_network, old_default_network) {
152            let _: Option<_> = self.socket_marks.remove(&old_default_network_id);
153        }
154        self.default_network = new_default_network;
155        return Some(old_default_network);
156    }
157
158    // Handle the `socket_marks` argument in a `PropertyUpdate`, determining
159    // whether the socket marks changed as a result of the update.
160    //
161    // Returns the update to set for the `socket_marks_network` argument
162    // of UpdatesApplied.
163    fn handle_socket_marks_update(
164        &mut self,
165        netid: InterfaceId,
166        marks: fnet::Marks,
167    ) -> Option<InterfaceId> {
168        // We do not need to send an update applied if the marks for the
169        // provided netid stay the same.
170        if self.socket_marks.contains_key(&netid)
171            && self
172                .socket_marks
173                .get(&netid)
174                .and_then(|old_marks| Some(*old_marks == marks))
175                .unwrap_or_default()
176        {
177            return None;
178        }
179
180        let _: Option<_> = self.socket_marks.insert(netid, marks);
181        return Some(netid);
182    }
183
184    fn maybe_respond(
185        &self,
186        network: &NetworkTokenContents,
187        responder: NetworkPropertyResponder,
188    ) -> Option<NetworkPropertyResponder> {
189        let mut updates = Vec::new();
190        updates.add_socket_marks(self, network, &responder);
191        updates.add_dns(self, network, &responder);
192
193        if updates.is_empty() {
194            Some(responder)
195        } else {
196            if let Err(e) = responder.responder.send(Ok(&updates)) {
197                warn!("Could not send to responder: {e}");
198            }
199            None
200        }
201    }
202}
203
204trait PropertyUpdates {
205    fn add_socket_marks(
206        &mut self,
207        properties: &NetworkProperties,
208        network: &NetworkTokenContents,
209        responder: &NetworkPropertyResponder,
210    );
211    fn add_dns(
212        &mut self,
213        properties: &NetworkProperties,
214        network: &NetworkTokenContents,
215        responder: &NetworkPropertyResponder,
216    );
217}
218
219impl PropertyUpdates for Vec<fnp_properties::PropertyUpdate> {
220    fn add_socket_marks(
221        &mut self,
222        properties: &NetworkProperties,
223        network: &NetworkTokenContents,
224        responder: &NetworkPropertyResponder,
225    ) {
226        if !responder.watched_properties.contains(&fnp_properties::Property::SocketMarks) {
227            return;
228        }
229        match properties.socket_marks.get(&network.interface_id) {
230            Some(marks) => self.push(fnp_properties::PropertyUpdate::SocketMarks(marks.clone())),
231            None => {}
232        }
233    }
234
235    fn add_dns(
236        &mut self,
237        properties: &NetworkProperties,
238        network: &NetworkTokenContents,
239        responder: &NetworkPropertyResponder,
240    ) {
241        if !responder.watched_properties.contains(&fnp_properties::Property::DnsConfiguration) {
242            return;
243        }
244
245        let interface_id = network.interface_id;
246        self.push(fnp_properties::PropertyUpdate::DnsConfiguration(
247            fnp_properties::DnsConfiguration {
248                servers: Some(
249                    properties
250                        .dns_servers
251                        .iter()
252                        .filter(|d| {
253                            match &d.source {
254                                Some(source) => match source {
255                                    fnet_name::DnsServerSource::StaticSource(_) => true,
256                                    fnet_name::DnsServerSource::SocketProxy(
257                                        fnet_name::SocketProxyDnsServerSource {
258                                            source_interface,
259                                            ..
260                                        },
261                                    )
262                                    | fnet_name::DnsServerSource::Dhcp(
263                                        fnet_name::DhcpDnsServerSource { source_interface, .. },
264                                    )
265                                    | fnet_name::DnsServerSource::Ndp(
266                                        fnet_name::NdpDnsServerSource { source_interface, .. },
267                                    )
268                                    | fnet_name::DnsServerSource::Dhcpv6(
269                                        fnet_name::Dhcpv6DnsServerSource {
270                                            source_interface, ..
271                                        },
272                                    ) => match (interface_id, source_interface) {
273                                        (_, None) => true,
274                                        (id1, Some(id2)) => id1.get() == *id2,
275                                    },
276
277                                    _ => {
278                                        error!("unhandled DnsServerSource: {source:?}");
279                                        false
280                                    }
281                                },
282
283                                // No source, assume static source, so include it.
284                                None => true,
285                            }
286                        })
287                        .cloned()
288                        .collect::<Vec<_>>(),
289                ),
290                ..Default::default()
291            },
292        ));
293    }
294}
295
296#[derive(Default)]
297pub(crate) struct PropertyUpdate {
298    default_network: Option<Option<InterfaceId>>,
299    socket_marks: Option<(InterfaceId, fnet::Marks)>,
300    dns: Option<Vec<fnet_name::DnsServer_>>,
301}
302
303#[derive(Default, Debug)]
304struct UpdatesApplied {
305    // If Some, contains old network id
306    default_network: Option<Option<InterfaceId>>,
307
308    // If Some, contains the network ID for which the mark was set
309    socket_marks_network: Option<InterfaceId>,
310
311    // Was the DNS changed
312    dns_changed: bool,
313}
314
315impl PropertyUpdate {
316    pub fn default_network<N: TryInto<InterfaceId>>(
317        mut self,
318        network_id: N,
319    ) -> Result<Self, N::Error> {
320        self.default_network = Some(Some(network_id.try_into()?));
321        Ok(self)
322    }
323
324    pub fn default_network_lost(mut self) -> Self {
325        self.default_network = Some(None);
326        self
327    }
328
329    pub fn socket_marks<N: TryInto<InterfaceId>, Marks: Into<fnet::Marks>>(
330        mut self,
331        network_id: N,
332        marks: Marks,
333    ) -> Result<Self, N::Error> {
334        self.socket_marks = Some((network_id.try_into()?, marks.into()));
335        Ok(self)
336    }
337
338    pub fn dns(mut self, dns_servers: &DnsServers) -> Self {
339        self.dns = Some(dns_servers.consolidated_dns_servers());
340        self
341    }
342}
343
344impl NetpolNetworksService {
345    pub(crate) async fn handle_network_attributes_request(
346        &mut self,
347        id: ConnectionId,
348        req: Result<fnp_properties::NetworksRequest, fidl::Error>,
349    ) -> Result<(), anyhow::Error> {
350        let req = req.context("network attributes request")?;
351        match req {
352            fnp_properties::NetworksRequest::WatchDefault { responder } => {
353                match self.default_network_responders.entry(id) {
354                    std::collections::hash_map::Entry::Occupied(_) => {
355                        warn!(
356                            "Only one call to fuchsia.net.policy.properties/Networks.WatchDefault \
357                             may be active per connection"
358                        );
359                        responder
360                            .control_handle()
361                            .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
362                    }
363                    std::collections::hash_map::Entry::Vacant(vacant_entry) => {
364                        let interface_id = if self
365                            .generations_by_connection
366                            .default_network(&id)
367                            .unwrap_or_default()
368                            < self.current_generation.default_network
369                        {
370                            match self.network_properties.default_network {
371                                Some(interface_id) => Some(interface_id),
372                                None => None,
373                            }
374                        } else {
375                            None
376                        };
377                        if let Some(interface_id) = interface_id {
378                            self.generations_by_connection
379                                .set_default_network(id, self.current_generation);
380                            let token = self
381                                .tokens
382                                .insert_data(NetworkTokenContents {
383                                    connection_id: id,
384                                    interface_id,
385                                })
386                                .await;
387                            responder.send(
388                                fnp_properties::NetworksWatchDefaultResponse::Network(
389                                    fnp_properties::NetworkToken {
390                                        value: Some(token),
391                                        ..Default::default()
392                                    },
393                                ),
394                            )?;
395
396                            if let Some(responder) = self.property_responders.remove(&id) {
397                                let _ = self.generations_by_connection.remove(&id);
398                                let _ = responder
399                                    .responder
400                                    .send(Err(fnp_properties::WatchError::DefaultNetworkChanged));
401                            }
402                        } else {
403                            let _ = vacant_entry.insert(responder);
404                        }
405                    }
406                }
407            }
408            fnp_properties::NetworksRequest::WatchProperties {
409                payload: fnp_properties::NetworksWatchPropertiesRequest { network, properties, .. },
410                responder,
411            } => match (network, properties) {
412                (None, _) | (_, None) => {
413                    responder.send(Err(fnp_properties::WatchError::MissingRequiredArgument))?
414                }
415                (Some(network), Some(properties)) => {
416                    if properties.is_empty() {
417                        responder.send(Err(fnp_properties::WatchError::NoProperties))?;
418                    } else if let Some(token) = network.value {
419                        match self.property_responders.entry(id) {
420                            std::collections::hash_map::Entry::Occupied(_) => {
421                                warn!(
422                            "Only one call to fuchsia.net.policy.properties/Networks.WatchProperties \
423                             may be active per connection"
424                        );
425                                responder
426                                    .control_handle()
427                                    .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
428                            }
429                            std::collections::hash_map::Entry::Vacant(vacant_entry) => {
430                                match self.tokens.get(&token).await {
431                                    None => {
432                                        warn!("Unknown network token. ({token:?}");
433                                        responder.send(Err(
434                                            fnp_properties::WatchError::InvalidNetworkToken,
435                                        ))?;
436                                    }
437                                    Some(network_contents) => {
438                                        if network_contents.connection_id != id {
439                                            warn!(
440                                            "Cannot watch a NetworkToken that was not created by \
441                                             this connection."
442                                        );
443                                            responder.send(Err(
444                                                fnp_properties::WatchError::InvalidNetworkToken,
445                                            ))?;
446                                        } else {
447                                            let responder = NetworkPropertyResponder {
448                                                token,
449                                                watched_properties: properties,
450                                                responder,
451                                            };
452                                            if self
453                                                .generations_by_connection
454                                                .properties(&id)
455                                                .unwrap_or_default()
456                                                < self.current_generation.properties
457                                            {
458                                                self.generations_by_connection
459                                                    .set_properties(id, self.current_generation);
460                                                if let Some(responder) = self
461                                                    .network_properties
462                                                    .maybe_respond(&network_contents, responder)
463                                                {
464                                                    let _: &mut NetworkPropertyResponder =
465                                                        vacant_entry.insert(responder);
466                                                }
467                                            } else {
468                                                let _: &mut NetworkPropertyResponder =
469                                                    vacant_entry.insert(responder);
470                                            }
471                                        }
472                                    }
473                                }
474                            }
475                        }
476                    } else {
477                        responder.send(Err(fnp_properties::WatchError::InvalidNetworkToken))?;
478                    }
479                }
480            },
481            _ => {
482                warn!("Received unexpected request {req:?}");
483            }
484        }
485
486        Ok(())
487    }
488
489    async fn changed_default_network(
490        error: fnp_properties::WatchError,
491        responders: &mut HashMap<ConnectionId, NetworkPropertyResponder>,
492        generations: &mut UpdateGenerations,
493    ) {
494        let mut r = HashMap::new();
495        std::mem::swap(&mut r, responders);
496        r = r
497            .into_iter()
498            .filter_map(|(id, responder)| {
499                // NB: Currently all responders are for the default network.
500                let _ = generations.remove(&id);
501                let _ = responder.responder.send(Err(error));
502                None
503            })
504            .collect::<HashMap<_, _>>();
505        std::mem::swap(&mut r, responders);
506    }
507
508    pub(crate) async fn remove_network<ID: Into<InterfaceId>>(&mut self, interface_id: ID) {
509        let interface_id = interface_id.into();
510        info!("Removing interface {interface_id}. Reporting NETWORK_GONE to all clients.");
511        let mut responders = HashMap::new();
512        std::mem::swap(&mut self.property_responders, &mut responders);
513        for (id, responder) in responders {
514            let network = match self.tokens.get(&responder.token).await {
515                Some(network) => network,
516                None => {
517                    warn!("Could not fetch network data for responder");
518                    continue;
519                }
520            };
521            if network.interface_id == interface_id {
522                // Report that this interface was removed
523                if let Err(e) =
524                    responder.responder.send(Err(fnp_properties::WatchError::NetworkGone))
525                {
526                    warn!("Could not send to responder: {e}");
527                }
528            } else {
529                if self.property_responders.insert(id, responder).is_some() {
530                    error!("Re-inserted in an existing responder slot. This should be impossible.");
531                }
532            }
533        }
534    }
535
536    pub(crate) async fn update(&mut self, update: PropertyUpdate) {
537        self.current_generation.properties += 1;
538        let updates_applied = self.network_properties.apply(update);
539        let mut property_responders = HashMap::new();
540        std::mem::swap(&mut self.property_responders, &mut property_responders);
541
542        if updates_applied.default_network.is_some() {
543            if let Some(default_network) = self.network_properties.default_network {
544                self.current_generation.default_network += 1;
545                let mut responders = HashMap::new();
546                std::mem::swap(&mut self.default_network_responders, &mut responders);
547                for (id, responder) in responders {
548                    self.generations_by_connection.set_default_network(id, self.current_generation);
549
550                    let token = self
551                        .tokens
552                        .insert_data(NetworkTokenContents {
553                            connection_id: id,
554                            interface_id: default_network,
555                        })
556                        .await;
557
558                    if let Err(e) =
559                        responder.send(fnp_properties::NetworksWatchDefaultResponse::Network(
560                            fnp_properties::NetworkToken {
561                                value: Some(token),
562                                ..Default::default()
563                            },
564                        ))
565                    {
566                        warn!("Could not send to responder: {e}");
567                    }
568                }
569
570                NetpolNetworksService::changed_default_network(
571                    fnp_properties::WatchError::DefaultNetworkChanged,
572                    &mut property_responders,
573                    &mut self.generations_by_connection,
574                )
575                .await;
576            } else {
577                // The default network has been lost.
578                self.current_generation.default_network += 1;
579                let mut responders = HashMap::new();
580                std::mem::swap(&mut self.default_network_responders, &mut responders);
581                for (id, responder) in responders {
582                    self.generations_by_connection.set_default_network(id, self.current_generation);
583                    if let Err(e) = responder.send(
584                        fnp_properties::NetworksWatchDefaultResponse::NoDefaultNetwork(
585                            fnp_properties::Empty,
586                        ),
587                    ) {
588                        warn!("Could not send to responder: {e}");
589                    }
590                }
591
592                NetpolNetworksService::changed_default_network(
593                    fnp_properties::WatchError::DefaultNetworkLost,
594                    &mut property_responders,
595                    &mut self.generations_by_connection,
596                )
597                .await;
598            }
599        }
600
601        for (id, responder) in property_responders {
602            let mut updates = Vec::new();
603            let network = match self.tokens.get(&responder.token).await {
604                Some(network) => network,
605                None => {
606                    warn!("Could not fetch network data for responder");
607                    continue;
608                }
609            };
610
611            if let Some(network_id) = updates_applied.socket_marks_network {
612                if network.interface_id == network_id {
613                    updates.add_socket_marks(&self.network_properties, &network, &responder);
614                }
615            }
616            if updates_applied.dns_changed {
617                updates.add_dns(&self.network_properties, &network, &responder);
618            }
619
620            self.generations_by_connection.set_properties(id, self.current_generation);
621            if updates.is_empty() {
622                if self.property_responders.insert(id, responder).is_some() {
623                    warn!("Re-inserted in an existing responder slot. This should be impossible.");
624                }
625            } else {
626                if let Err(e) = responder.responder.send(Ok(&updates)) {
627                    warn!("Could not send to responder: {e}");
628                }
629            }
630        }
631    }
632}
633
634#[derive(Default)]
635pub(crate) struct NetworksRequestStreams {
636    next_id: ConnectionId,
637    request_streams:
638        futures::stream::SelectAll<Tagged<ConnectionId, fnp_properties::NetworksRequestStream>>,
639}
640
641impl NetworksRequestStreams {
642    pub fn push(&mut self, stream: fnp_properties::NetworksRequestStream) {
643        self.request_streams.push(stream.tagged(self.next_id));
644        self.next_id.0 += 1;
645    }
646}
647
648impl futures::Stream for NetworksRequestStreams {
649    type Item = (ConnectionId, Result<fnp_properties::NetworksRequest, fidl::Error>);
650
651    fn poll_next(
652        mut self: std::pin::Pin<&mut Self>,
653        cx: &mut std::task::Context<'_>,
654    ) -> std::task::Poll<Option<Self::Item>> {
655        std::pin::Pin::new(&mut self.request_streams).poll_next(cx)
656    }
657}
658
659impl futures::stream::FusedStream for NetworksRequestStreams {
660    fn is_terminated(&self) -> bool {
661        self.request_streams.is_terminated()
662    }
663}