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
83pub(crate) struct NetworkPropertyResponder {
84    token: fidl::EventPair,
85    watched_properties: Vec<fnp_properties::Property>,
86    responder: fnp_properties::NetworksWatchPropertiesResponder,
87}
88
89#[derive(Default)]
90pub(crate) struct NetpolNetworksService {
91    // The current generation
92    current_generation: UpdateGeneration,
93    // The last generation sent per connection
94    generations_by_connection: UpdateGenerations,
95    // Default Network Watchers
96    default_network_responders:
97        HashMap<ConnectionId, fnp_properties::NetworksWatchDefaultResponder>,
98    tokens: token_map::TokenMap<NetworkTokenContents>,
99    // NetworkProperty Watchers
100    property_responders: HashMap<ConnectionId, NetworkPropertyResponder>,
101    // Network properties
102    network_properties: NetworkProperties,
103}
104
105#[derive(Default, Clone)]
106struct NetworkProperties {
107    // Current default network
108    default_network: Option<InterfaceId>,
109    // Network marks
110    socket_marks: HashMap<InterfaceId, fnet::Marks>,
111    // DNS Servers
112    dns_servers: Vec<fnet_name::DnsServer_>,
113}
114
115impl NetworkProperties {
116    fn apply(&mut self, update: PropertyUpdate) -> UpdatesApplied {
117        let mut updates = UpdatesApplied::default();
118
119        if let Some(netid) = update.default_network {
120            updates.default_network = Some(self.default_network);
121            self.default_network = Some(netid);
122        }
123
124        if let Some((netid, marks)) = update.socket_marks {
125            updates.socket_marks_network = Some(netid);
126            let _: Option<_> = self.socket_marks.insert(netid, marks);
127        }
128
129        if let Some(dns) = update.dns {
130            updates.dns_changed = self.dns_servers != dns;
131            self.dns_servers = dns;
132        }
133
134        updates
135    }
136
137    fn maybe_respond(
138        &self,
139        network: &NetworkTokenContents,
140        responder: NetworkPropertyResponder,
141    ) -> Option<NetworkPropertyResponder> {
142        let mut updates = Vec::new();
143        updates.add_socket_marks(self, network, &responder);
144        updates.add_dns(self, network, &responder);
145
146        if updates.is_empty() {
147            Some(responder)
148        } else {
149            if let Err(e) = responder.responder.send(Ok(&updates)) {
150                warn!("Could not send to responder: {e}");
151            }
152            None
153        }
154    }
155}
156
157trait PropertyUpdates {
158    fn add_socket_marks(
159        &mut self,
160        properties: &NetworkProperties,
161        network: &NetworkTokenContents,
162        responder: &NetworkPropertyResponder,
163    );
164    fn add_dns(
165        &mut self,
166        properties: &NetworkProperties,
167        network: &NetworkTokenContents,
168        responder: &NetworkPropertyResponder,
169    );
170}
171
172impl PropertyUpdates for Vec<fnp_properties::PropertyUpdate> {
173    fn add_socket_marks(
174        &mut self,
175        properties: &NetworkProperties,
176        network: &NetworkTokenContents,
177        responder: &NetworkPropertyResponder,
178    ) {
179        if !responder.watched_properties.contains(&fnp_properties::Property::SocketMarks) {
180            return;
181        }
182        match properties.socket_marks.get(&network.interface_id) {
183            Some(marks) => self.push(fnp_properties::PropertyUpdate::SocketMarks(marks.clone())),
184            None => {}
185        }
186    }
187
188    fn add_dns(
189        &mut self,
190        properties: &NetworkProperties,
191        network: &NetworkTokenContents,
192        responder: &NetworkPropertyResponder,
193    ) {
194        if !responder.watched_properties.contains(&fnp_properties::Property::DnsConfiguration) {
195            return;
196        }
197
198        let interface_id = network.interface_id;
199        self.push(fnp_properties::PropertyUpdate::DnsConfiguration(
200            fnp_properties::DnsConfiguration {
201                servers: Some(
202                    properties
203                        .dns_servers
204                        .iter()
205                        .filter(|d| {
206                            match &d.source {
207                                Some(source) => match source {
208                                    fnet_name::DnsServerSource::StaticSource(_) => true,
209                                    fnet_name::DnsServerSource::SocketProxy(
210                                        fnet_name::SocketProxyDnsServerSource {
211                                            source_interface,
212                                            ..
213                                        },
214                                    )
215                                    | fnet_name::DnsServerSource::Dhcp(
216                                        fnet_name::DhcpDnsServerSource { source_interface, .. },
217                                    )
218                                    | fnet_name::DnsServerSource::Ndp(
219                                        fnet_name::NdpDnsServerSource { source_interface, .. },
220                                    )
221                                    | fnet_name::DnsServerSource::Dhcpv6(
222                                        fnet_name::Dhcpv6DnsServerSource {
223                                            source_interface, ..
224                                        },
225                                    ) => match (interface_id, source_interface) {
226                                        (_, None) => true,
227                                        (id1, Some(id2)) => id1.get() == *id2,
228                                    },
229
230                                    _ => {
231                                        error!("unhandled DnsServerSource: {source:?}");
232                                        false
233                                    }
234                                },
235
236                                // No source, assume static source, so include it.
237                                None => true,
238                            }
239                        })
240                        .cloned()
241                        .collect::<Vec<_>>(),
242                ),
243                ..Default::default()
244            },
245        ));
246    }
247}
248
249#[derive(Default)]
250pub(crate) struct PropertyUpdate {
251    default_network: Option<InterfaceId>,
252    socket_marks: Option<(InterfaceId, fnet::Marks)>,
253    dns: Option<Vec<fnet_name::DnsServer_>>,
254}
255
256#[derive(Default, Debug)]
257struct UpdatesApplied {
258    // If Some, contains old network id
259    default_network: Option<Option<InterfaceId>>,
260
261    // If Some, contains the network ID for which the mark was set
262    socket_marks_network: Option<InterfaceId>,
263
264    // Was the DNS changed
265    dns_changed: bool,
266}
267
268impl PropertyUpdate {
269    pub fn default_network<N: TryInto<InterfaceId>>(
270        mut self,
271        network_id: N,
272    ) -> Result<Self, N::Error> {
273        self.default_network = Some(network_id.try_into()?);
274        Ok(self)
275    }
276
277    pub fn socket_marks<N: TryInto<InterfaceId>, Marks: Into<fnet::Marks>>(
278        mut self,
279        network_id: N,
280        marks: Marks,
281    ) -> Result<Self, N::Error> {
282        self.socket_marks = Some((network_id.try_into()?, marks.into()));
283        Ok(self)
284    }
285
286    pub fn dns(mut self, dns_servers: &DnsServers) -> Self {
287        self.dns = Some(dns_servers.consolidated_dns_servers());
288        self
289    }
290}
291
292impl NetpolNetworksService {
293    pub(crate) async fn handle_network_attributes_request(
294        &mut self,
295        id: ConnectionId,
296        req: Result<fnp_properties::NetworksRequest, fidl::Error>,
297    ) -> Result<(), anyhow::Error> {
298        let req = req.context("network attributes request")?;
299        match req {
300            fnp_properties::NetworksRequest::WatchDefault { responder } => {
301                match self.default_network_responders.entry(id) {
302                    std::collections::hash_map::Entry::Occupied(_) => {
303                        warn!(
304                            "Only one call to fuchsia.net.policy.properties/Networks.WatchDefault \
305                             may be active per connection"
306                        );
307                        responder
308                            .control_handle()
309                            .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
310                    }
311                    std::collections::hash_map::Entry::Vacant(vacant_entry) => {
312                        let interface_id = if self
313                            .generations_by_connection
314                            .default_network(&id)
315                            .unwrap_or_default()
316                            < self.current_generation.default_network
317                        {
318                            match self.network_properties.default_network {
319                                Some(interface_id) => Some(interface_id),
320                                None => None,
321                            }
322                        } else {
323                            None
324                        };
325                        if let Some(interface_id) = interface_id {
326                            self.generations_by_connection
327                                .set_default_network(id, self.current_generation);
328                            let token = self
329                                .tokens
330                                .insert_data(NetworkTokenContents {
331                                    connection_id: id,
332                                    interface_id,
333                                })
334                                .await;
335                            responder.send(fnp_properties::NetworkToken {
336                                value: Some(token),
337                                ..Default::default()
338                            })?;
339                        } else {
340                            let _ = vacant_entry.insert(responder);
341                        }
342
343                        if let Some(responder) = self.property_responders.remove(&id) {
344                            let _ = self.generations_by_connection.remove(&id);
345                            let _ = responder
346                                .responder
347                                .send(Err(fnp_properties::WatchError::DefaultNetworkChanged));
348                        }
349                    }
350                }
351            }
352            fnp_properties::NetworksRequest::WatchProperties {
353                payload: fnp_properties::NetworksWatchPropertiesRequest { network, properties, .. },
354                responder,
355            } => match (network, properties) {
356                (None, _) | (_, None) => {
357                    responder.send(Err(fnp_properties::WatchError::MissingRequiredArgument))?
358                }
359                (Some(network), Some(properties)) => {
360                    if properties.is_empty() {
361                        responder.send(Err(fnp_properties::WatchError::NoProperties))?;
362                    } else if let Some(token) = network.value {
363                        match self.property_responders.entry(id) {
364                            std::collections::hash_map::Entry::Occupied(_) => {
365                                warn!(
366                            "Only one call to fuchsia.net.policy.properties/Networks.WatchProperties \
367                             may be active per connection"
368                        );
369                                responder
370                                    .control_handle()
371                                    .shutdown_with_epitaph(zx::Status::CONNECTION_ABORTED)
372                            }
373                            std::collections::hash_map::Entry::Vacant(vacant_entry) => {
374                                match self.tokens.get(&token).await {
375                                    None => {
376                                        warn!("Unknown network token. ({token:?}");
377                                        responder.send(Err(
378                                            fnp_properties::WatchError::InvalidNetworkToken,
379                                        ))?;
380                                    }
381                                    Some(network_contents) => {
382                                        if network_contents.connection_id != id {
383                                            warn!(
384                                            "Cannot watch a NetworkToken that was not created by \
385                                             this connection."
386                                        );
387                                            responder.send(Err(
388                                                fnp_properties::WatchError::InvalidNetworkToken,
389                                            ))?;
390                                        } else {
391                                            let responder = NetworkPropertyResponder {
392                                                token,
393                                                watched_properties: properties,
394                                                responder,
395                                            };
396                                            if self
397                                                .generations_by_connection
398                                                .properties(&id)
399                                                .unwrap_or_default()
400                                                < self.current_generation.properties
401                                            {
402                                                self.generations_by_connection
403                                                    .set_properties(id, self.current_generation);
404                                                if let Some(responder) = self
405                                                    .network_properties
406                                                    .maybe_respond(&network_contents, responder)
407                                                {
408                                                    let _: &mut NetworkPropertyResponder =
409                                                        vacant_entry.insert(responder);
410                                                }
411                                            } else {
412                                                let _: &mut NetworkPropertyResponder =
413                                                    vacant_entry.insert(responder);
414                                            }
415                                        }
416                                    }
417                                }
418                            }
419                        }
420                    } else {
421                        responder.send(Err(fnp_properties::WatchError::InvalidNetworkToken))?;
422                    }
423                }
424            },
425            _ => {
426                warn!("Received unexpected request {req:?}");
427            }
428        }
429
430        Ok(())
431    }
432
433    pub(crate) async fn remove_network<ID: Into<InterfaceId>>(&mut self, interface_id: ID) {
434        let interface_id = interface_id.into();
435        info!("Removing interface {interface_id}. Reporting NETWORK_GONE to all clients.");
436        let mut responders = HashMap::new();
437        std::mem::swap(&mut self.property_responders, &mut responders);
438        for (id, responder) in responders {
439            let network = match self.tokens.get(&responder.token).await {
440                Some(network) => network,
441                None => {
442                    warn!("Could not fetch network data for responder");
443                    continue;
444                }
445            };
446            if network.interface_id == interface_id {
447                // Report that this interface was removed
448                if let Err(e) =
449                    responder.responder.send(Err(fnp_properties::WatchError::NetworkGone))
450                {
451                    warn!("Could not send to responder: {e}");
452                }
453            } else {
454                if self.property_responders.insert(id, responder).is_some() {
455                    error!("Re-inserted in an existing responder slot. This should be impossible.");
456                }
457            }
458        }
459    }
460
461    pub(crate) async fn update(&mut self, update: PropertyUpdate) {
462        self.current_generation.properties += 1;
463        let updates_applied = self.network_properties.apply(update);
464        let mut responders = HashMap::new();
465        std::mem::swap(&mut self.property_responders, &mut responders);
466
467        if updates_applied.default_network.is_some() {
468            if let Some(default_network) = self.network_properties.default_network {
469                self.current_generation.default_network += 1;
470                let mut responders = HashMap::new();
471                std::mem::swap(&mut self.default_network_responders, &mut responders);
472                for (id, responder) in responders {
473                    self.generations_by_connection.set_default_network(id, self.current_generation);
474
475                    let token = self
476                        .tokens
477                        .insert_data(NetworkTokenContents {
478                            connection_id: id,
479                            interface_id: default_network,
480                        })
481                        .await;
482
483                    if let Err(e) = responder.send(fnp_properties::NetworkToken {
484                        value: Some(token),
485                        ..Default::default()
486                    }) {
487                        warn!("Could not send to responder: {e}");
488                    }
489                }
490            }
491        }
492
493        for (id, responder) in responders {
494            let mut updates = Vec::new();
495            let network = match self.tokens.get(&responder.token).await {
496                Some(network) => network,
497                None => {
498                    warn!("Could not fetch network data for responder");
499                    continue;
500                }
501            };
502
503            if let Some(network_id) = updates_applied.socket_marks_network {
504                if network.interface_id == network_id {
505                    updates.add_socket_marks(&self.network_properties, &network, &responder);
506                }
507            }
508            if updates_applied.dns_changed {
509                updates.add_dns(&self.network_properties, &network, &responder);
510            }
511
512            self.generations_by_connection.set_properties(id, self.current_generation);
513            if updates.is_empty() {
514                if self.property_responders.insert(id, responder).is_some() {
515                    warn!("Re-inserted in an existing responder slot. This should be impossible.");
516                }
517            } else {
518                if let Err(e) = responder.responder.send(Ok(&updates)) {
519                    warn!("Could not send to responder: {e}");
520                }
521            }
522        }
523    }
524}
525
526#[derive(Default)]
527pub(crate) struct NetworksRequestStreams {
528    next_id: ConnectionId,
529    request_streams:
530        futures::stream::SelectAll<Tagged<ConnectionId, fnp_properties::NetworksRequestStream>>,
531}
532
533impl NetworksRequestStreams {
534    pub fn push(&mut self, stream: fnp_properties::NetworksRequestStream) {
535        self.request_streams.push(stream.tagged(self.next_id));
536        self.next_id.0 += 1;
537    }
538}
539
540impl futures::Stream for NetworksRequestStreams {
541    type Item = (ConnectionId, Result<fnp_properties::NetworksRequest, fidl::Error>);
542
543    fn poll_next(
544        mut self: std::pin::Pin<&mut Self>,
545        cx: &mut std::task::Context<'_>,
546    ) -> std::task::Poll<Option<Self::Item>> {
547        std::pin::Pin::new(&mut self.request_streams).poll_next(cx)
548    }
549}
550
551impl futures::stream::FusedStream for NetworksRequestStreams {
552    fn is_terminated(&self) -> bool {
553        self.request_streams.is_terminated()
554    }
555}