netcfg/
lib.rs

1// Copyright 2018 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
5#![deny(unused)]
6
7mod devices;
8mod dhcpv4;
9mod dhcpv6;
10mod dns;
11mod errors;
12mod filter;
13mod interface;
14mod masquerade;
15mod network;
16mod socketproxy;
17mod virtualization;
18
19use ::dhcpv4::protocol::FromFidlExt as _;
20use std::collections::hash_map::Entry;
21use std::collections::{HashMap, HashSet};
22use std::fmt::{self, Display};
23use std::num::NonZeroU64;
24use std::pin::{Pin, pin};
25use std::str::FromStr;
26use std::{fs, io, path};
27
28use fidl::endpoints::{ProtocolMarker, RequestStream as _, Responder as _};
29use fidl_fuchsia_net_ext::{self as fnet_ext, DisplayExt as _, IpExt as _};
30use fidl_fuchsia_net_interfaces_ext::{self as fnet_interfaces_ext, Update as _};
31use fuchsia_component::client::{clone_namespace_svc, new_protocol_connector_in_dir};
32use fuchsia_component::server::{ServiceFs, ServiceFsDir};
33use fuchsia_fs::directory as fvfs_watcher;
34
35use {
36    fidl_fuchsia_io as fio, fidl_fuchsia_net as fnet, fidl_fuchsia_net_dhcp as fnet_dhcp,
37    fidl_fuchsia_net_dhcp_ext as fnet_dhcp_ext, fidl_fuchsia_net_dhcpv6 as fnet_dhcpv6,
38    fidl_fuchsia_net_filter as fnet_filter,
39    fidl_fuchsia_net_filter_deprecated as fnet_filter_deprecated,
40    fidl_fuchsia_net_interfaces as fnet_interfaces,
41    fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
42    fidl_fuchsia_net_masquerade as fnet_masquerade,
43    fidl_fuchsia_net_matchers_ext as fnet_matchers_ext, fidl_fuchsia_net_name as fnet_name,
44    fidl_fuchsia_net_ndp as fnet_ndp, fidl_fuchsia_net_policy_properties as fnp_properties,
45    fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy,
46    fidl_fuchsia_net_resources as fnet_resources,
47    fidl_fuchsia_net_routes_admin as fnet_routes_admin,
48    fidl_fuchsia_net_routes_ext as fnet_routes_ext, fidl_fuchsia_net_stack as fnet_stack,
49    fidl_fuchsia_net_virtualization as fnet_virtualization, fuchsia_async as fasync,
50};
51
52use anyhow::{Context as _, anyhow};
53use assert_matches::assert_matches;
54use async_trait::async_trait;
55use async_utils::stream::WithTag as _;
56use dns_server_watcher::{DEFAULT_DNS_PORT, DnsServers, DnsServersUpdateSource};
57use futures::stream::BoxStream;
58use futures::{FutureExt, StreamExt as _, TryFutureExt as _, TryStreamExt as _};
59use log::{debug, error, info, trace, warn};
60use net_declare::fidl_ip_v4;
61use net_declare::net::prefix_length_v4;
62use net_types::ip::{IpAddress as _, Ipv4, Ipv6, PrefixLength};
63use serde::{Deserialize, Serialize};
64use thiserror::Error;
65
66use self::devices::DeviceInfo;
67use self::errors::{ContextExt as _, accept_error};
68use self::filter::{FilterControl, FilterEnabledState};
69use self::interface::{
70    DeviceInfoRef, InterfaceNamingIdentifier, NetstackManagedRoutesDesignation, ProvisioningAction,
71    ProvisioningType,
72};
73use self::masquerade::MasqueradeHandler;
74use self::socketproxy::SocketProxyState;
75
76/// Interface Identifier
77#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
78pub struct InterfaceId(NonZeroU64);
79
80impl InterfaceId {
81    const fn new(raw: u64) -> Option<Self> {
82        // Note: use a match here because `Option::map` is non-const.
83        match NonZeroU64::new(raw) {
84            Some(id) => Some(InterfaceId(id)),
85            None => None,
86        }
87    }
88
89    const fn get(self) -> u64 {
90        self.0.get()
91    }
92}
93
94impl Display for InterfaceId {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "{}", self.0)
97    }
98}
99
100impl fnet_interfaces_ext::TryFromMaybeNonZero for InterfaceId {
101    fn try_from(value: u64) -> Result<Self, fnet_interfaces_ext::ZeroError> {
102        Self::new(value).ok_or(fnet_interfaces_ext::ZeroError {})
103    }
104}
105
106impl TryFrom<u64> for InterfaceId {
107    type Error = std::num::TryFromIntError;
108    fn try_from(val: u64) -> Result<Self, Self::Error> {
109        Ok(Self(NonZeroU64::try_from(val)?))
110    }
111}
112
113impl From<NonZeroU64> for InterfaceId {
114    fn from(val: NonZeroU64) -> Self {
115        Self(val)
116    }
117}
118
119impl From<InterfaceId> for NonZeroU64 {
120    fn from(InterfaceId(val): InterfaceId) -> Self {
121        val
122    }
123}
124
125impl From<InterfaceId> for u64 {
126    fn from(val: InterfaceId) -> Self {
127        val.get()
128    }
129}
130
131/// Interface metrics.
132///
133/// Interface metrics are used to sort the route table. An interface with a
134/// lower metric is favored over one with a higher metric.
135#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
136pub struct Metric(u32);
137
138impl Default for Metric {
139    // A default value of 600 is chosen for Metric: this provides plenty of space for routing
140    // policy on devices with many interfaces (physical or logical) while remaining in the same
141    // magnitude as our current default ethernet metric.
142    fn default() -> Self {
143        Self(600)
144    }
145}
146
147impl std::fmt::Display for Metric {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        let Metric(u) = self;
150        write!(f, "{}", u)
151    }
152}
153
154impl From<Metric> for u32 {
155    fn from(Metric(u): Metric) -> u32 {
156        u
157    }
158}
159
160/// A node that represents the directory it is in.
161///
162/// `/dir` and `/dir/.` point to the same directory.
163const THIS_DIRECTORY: &str = ".";
164
165/// The prefix length for the address assigned to a WLAN AP interface.
166const WLAN_AP_PREFIX_LEN: PrefixLength<Ipv4> = prefix_length_v4!(29);
167
168/// The address for the network the WLAN AP interface is a part of.
169const WLAN_AP_NETWORK_ADDR: fnet::Ipv4Address = fidl_ip_v4!("192.168.255.248");
170
171/// The lease time for a DHCP lease.
172///
173/// 1 day in seconds.
174const WLAN_AP_DHCP_LEASE_TIME_SECONDS: u32 = 24 * 60 * 60;
175
176/// A map of DNS server watcher streams that yields `DnsServerWatcherEvent` as DNS
177/// server updates become available.
178///
179/// DNS server watcher streams may be added or removed at runtime as the watchers
180/// are started or stopped.
181type DnsServerWatchers<'a> = async_utils::stream::StreamMap<
182    DnsServersUpdateSource,
183    BoxStream<'a, (DnsServersUpdateSource, Result<Vec<fnet_name::DnsServer_>, fidl::Error>)>,
184>;
185
186/// Defines log levels.
187#[derive(Debug, Copy, Clone)]
188pub struct LogLevel(diagnostics_log::Severity);
189
190impl Default for LogLevel {
191    fn default() -> Self {
192        Self(diagnostics_log::Severity::Info)
193    }
194}
195
196impl FromStr for LogLevel {
197    type Err = anyhow::Error;
198
199    fn from_str(s: &str) -> Result<Self, anyhow::Error> {
200        match s.to_uppercase().as_str() {
201            "TRACE" => Ok(Self(diagnostics_log::Severity::Trace)),
202            "DEBUG" => Ok(Self(diagnostics_log::Severity::Debug)),
203            "INFO" => Ok(Self(diagnostics_log::Severity::Info)),
204            "WARN" => Ok(Self(diagnostics_log::Severity::Warn)),
205            "ERROR" => Ok(Self(diagnostics_log::Severity::Error)),
206            "FATAL" => Ok(Self(diagnostics_log::Severity::Fatal)),
207            _ => Err(anyhow::anyhow!("unrecognized log level = {}", s)),
208        }
209    }
210}
211
212/// Network Configuration tool.
213///
214/// Configures network components in response to events.
215#[derive(argh::FromArgs, Debug)]
216struct Opt {
217    /// minimum severity for logs
218    #[argh(option, default = "Default::default()")]
219    min_severity: LogLevel,
220
221    /// config file to use
222    #[argh(option, default = "\"/netcfg-config/netcfg_default.json5\".to_string()")]
223    config_data: String,
224}
225
226#[derive(Debug, Deserialize)]
227#[serde(deny_unknown_fields)]
228pub struct DnsConfig {
229    pub servers: Vec<std::net::IpAddr>,
230}
231
232#[derive(Debug, Deserialize)]
233#[serde(deny_unknown_fields)]
234pub struct FilterConfig {
235    pub rules: Vec<String>,
236    pub nat_rules: Vec<String>,
237    pub rdr_rules: Vec<String>,
238}
239
240#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
241#[serde(deny_unknown_fields, rename_all = "lowercase")]
242pub enum InterfaceType {
243    Ethernet,
244    // NB: Serde alias provides backwards compatibility.
245    #[serde(alias = "wlan")]
246    WlanClient,
247    // NB: Serde alias provides backwards compatibility.
248    #[serde(alias = "ap")]
249    WlanAp,
250    Blackhole,
251}
252
253impl TryFrom<fidl_fuchsia_hardware_network::PortClass> for InterfaceType {
254    type Error = UnknownPortClassError;
255    fn try_from(port_class: fidl_fuchsia_hardware_network::PortClass) -> Result<Self, Self::Error> {
256        let device_class = DeviceClass::try_from(port_class)?;
257        Ok(device_class.into())
258    }
259}
260
261impl From<DeviceClass> for InterfaceType {
262    fn from(device_class: DeviceClass) -> Self {
263        match device_class {
264            DeviceClass::WlanClient => InterfaceType::WlanClient,
265            DeviceClass::Blackhole => InterfaceType::Blackhole,
266            DeviceClass::WlanAp => InterfaceType::WlanAp,
267            DeviceClass::Ethernet
268            | DeviceClass::Virtual
269            | DeviceClass::Ppp
270            | DeviceClass::Bridge => InterfaceType::Ethernet,
271            // TODO(https://fxbug.dev/349810498): Add a first party
272            // `InterfaceType` variant for lowpan interfaces.
273            DeviceClass::Lowpan => InterfaceType::Ethernet,
274        }
275    }
276}
277#[cfg_attr(test, derive(PartialEq))]
278#[derive(Debug, Default, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub struct InterfaceMetrics {
281    #[serde(default)]
282    pub wlan_metric: Metric,
283    #[serde(default)]
284    pub eth_metric: Metric,
285    #[serde(default)]
286    pub blackhole_metric: Metric,
287}
288
289#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
290#[serde(deny_unknown_fields, rename_all = "lowercase")]
291pub enum DeviceClass {
292    Virtual,
293    Ethernet,
294    // NB: Serde alias provides backwards compatibility.
295    #[serde(alias = "wlan")]
296    WlanClient,
297    Ppp,
298    Bridge,
299    WlanAp,
300    Lowpan,
301    Blackhole,
302}
303
304#[derive(Debug, Error)]
305#[error("unknown port class with ordinal: {unknown_ordinal}")]
306pub struct UnknownPortClassError {
307    unknown_ordinal: u16,
308}
309
310impl TryFrom<fidl_fuchsia_hardware_network::PortClass> for DeviceClass {
311    type Error = UnknownPortClassError;
312    fn try_from(port_class: fidl_fuchsia_hardware_network::PortClass) -> Result<Self, Self::Error> {
313        match port_class {
314            fidl_fuchsia_hardware_network::PortClass::Virtual => Ok(DeviceClass::Virtual),
315            fidl_fuchsia_hardware_network::PortClass::Ethernet => Ok(DeviceClass::Ethernet),
316            fidl_fuchsia_hardware_network::PortClass::WlanClient => Ok(DeviceClass::WlanClient),
317            fidl_fuchsia_hardware_network::PortClass::Ppp => Ok(DeviceClass::Ppp),
318            fidl_fuchsia_hardware_network::PortClass::Bridge => Ok(DeviceClass::Bridge),
319            fidl_fuchsia_hardware_network::PortClass::WlanAp => Ok(DeviceClass::WlanAp),
320            fidl_fuchsia_hardware_network::PortClass::Lowpan => Ok(DeviceClass::Lowpan),
321            fidl_fuchsia_hardware_network::PortClass::__SourceBreaking { unknown_ordinal } => {
322                Err(UnknownPortClassError { unknown_ordinal })
323            }
324        }
325    }
326}
327
328#[derive(Debug, PartialEq, Deserialize)]
329#[serde(transparent)]
330struct AllowedDeviceClasses(HashSet<DeviceClass>);
331
332impl Default for AllowedDeviceClasses {
333    fn default() -> Self {
334        // When new variants are added, this exhaustive match will cause a compilation failure as a
335        // reminder to add the new variant to the default array.
336        match DeviceClass::Virtual {
337            DeviceClass::Virtual
338            | DeviceClass::Ethernet
339            | DeviceClass::WlanClient
340            | DeviceClass::Ppp
341            | DeviceClass::Bridge
342            | DeviceClass::WlanAp
343            | DeviceClass::Lowpan
344            | DeviceClass::Blackhole => {}
345        }
346        Self(HashSet::from([
347            DeviceClass::Virtual,
348            DeviceClass::Ethernet,
349            DeviceClass::WlanClient,
350            DeviceClass::Ppp,
351            DeviceClass::Bridge,
352            DeviceClass::WlanAp,
353            DeviceClass::Lowpan,
354            DeviceClass::Blackhole,
355        ]))
356    }
357}
358
359// TODO(https://github.com/serde-rs/serde/issues/368): use an inline literal for the default value
360// rather than defining a one-off function.
361fn dhcpv6_enabled_default() -> bool {
362    true
363}
364
365#[derive(Debug, Default, Deserialize, PartialEq)]
366#[serde(deny_unknown_fields, rename_all = "lowercase")]
367struct ForwardedDeviceClasses {
368    #[serde(default)]
369    pub ipv4: HashSet<DeviceClass>,
370    #[serde(default)]
371    pub ipv6: HashSet<DeviceClass>,
372}
373
374#[derive(Debug, Deserialize)]
375#[serde(deny_unknown_fields)]
376struct Config {
377    pub dns_config: DnsConfig,
378    pub filter_config: FilterConfig,
379    pub filter_enabled_interface_types: HashSet<InterfaceType>,
380    // NB: `InterfaceMetrics` custom default behavior sets all included
381    // interface types to the same metric. It is encouraged to set at least one
382    // metric for predictable packet routing on a multinetworked system.
383    #[serde(default)]
384    pub interface_metrics: InterfaceMetrics,
385    // NB: `AllowedDeviceClasses` custom default behavior is permissive. To
386    // permit a subset of device classes, specify this in configuration.
387    #[serde(default)]
388    pub allowed_upstream_device_classes: AllowedDeviceClasses,
389    // NB: See above.
390    #[serde(default)]
391    pub allowed_bridge_upstream_device_classes: AllowedDeviceClasses,
392    // TODO(https://fxbug.dev/42173732): default to false.
393    #[serde(default = "dhcpv6_enabled_default")]
394    pub enable_dhcpv6: bool,
395    // NB: `ForwardedDeviceClasses` custom default behavior is restrictive. To
396    // permit a subset of device classes, specify this in configuration.
397    #[serde(default)]
398    pub forwarded_device_classes: ForwardedDeviceClasses,
399    #[serde(default)]
400    pub interface_naming_policy: Vec<interface::NamingRule>,
401    #[serde(default)]
402    pub interface_provisioning_policy: Vec<interface::ProvisioningRule>,
403    /// The names of blackhole interfaces to install.
404    ///
405    /// The interfaces are named exactly as specified in the configuration, and are not under
406    /// the influence of `interface_naming_policy`.
407    #[serde(default)]
408    pub blackhole_interfaces: Vec<String>,
409    // Whether to use protocols provided by the socketproxy component.
410    #[serde(default)]
411    pub enable_socket_proxy: bool,
412}
413
414impl Config {
415    pub fn load<P: AsRef<path::Path>>(path: P) -> Result<Self, anyhow::Error> {
416        let path = path.as_ref();
417        let file = fs::File::open(path)
418            .with_context(|| format!("could not open the config file {}", path.display()))?;
419        let config = serde_json5::from_reader(io::BufReader::new(file))
420            .with_context(|| format!("could not deserialize the config file {}", path.display()))?;
421        Ok(config)
422    }
423}
424
425#[derive(Clone, Debug)]
426struct InterfaceConfig {
427    name: String,
428    metric: u32,
429    netstack_managed_routes_designation: Option<NetstackManagedRoutesDesignation>,
430}
431
432#[derive(Debug)]
433struct InterfaceState {
434    // Hold on to control to enforce interface ownership, even if unused.
435    control: fidl_fuchsia_net_interfaces_ext::admin::Control,
436    device_class: DeviceClass,
437    config: InterfaceConfigState,
438    provisioning: interface::ProvisioningType,
439}
440
441/// State for an interface.
442#[derive(Debug)]
443enum InterfaceConfigState {
444    Host(HostInterfaceState),
445    WlanAp(WlanApInterfaceState),
446    Blackhole(BlackholeInterfaceState),
447}
448
449#[derive(Debug)]
450enum Dhcpv4ClientState {
451    NotRunning,
452    Running(dhcpv4::ClientState),
453    ScheduledRestart(Pin<Box<fasync::Timer>>),
454}
455
456#[derive(Debug)]
457struct HostInterfaceState {
458    dhcpv4_client: Dhcpv4ClientState,
459    dhcpv6_client_state: Option<dhcpv6::ClientState>,
460    // The PD configuration to use for the DHCPv6 client on this interface.
461    dhcpv6_pd_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
462    interface_admin_auth: fnet_resources::GrantForInterfaceAuthorization,
463    interface_naming_id: interface::InterfaceNamingIdentifier,
464}
465
466#[derive(Debug)]
467struct WlanApInterfaceState {
468    interface_naming_id: interface::InterfaceNamingIdentifier,
469}
470
471#[derive(Debug)]
472struct BlackholeInterfaceState;
473
474impl InterfaceState {
475    async fn new_host(
476        interface_naming_id: interface::InterfaceNamingIdentifier,
477        control: fidl_fuchsia_net_interfaces_ext::admin::Control,
478        device_class: DeviceClass,
479        dhcpv6_pd_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
480        provisioning: interface::ProvisioningType,
481    ) -> Result<Self, errors::Error> {
482        let interface_admin_auth =
483            control.get_authorization_for_interface().await.map_err(|e| {
484                errors::Error::NonFatal(anyhow::anyhow!(
485                    "error getting authorization for interface: {}",
486                    e
487                ))
488            })?;
489        Ok(Self {
490            control,
491            config: InterfaceConfigState::Host(HostInterfaceState {
492                dhcpv4_client: Dhcpv4ClientState::NotRunning,
493                dhcpv6_client_state: None,
494                dhcpv6_pd_config,
495                interface_admin_auth,
496                interface_naming_id,
497            }),
498            device_class,
499            provisioning,
500        })
501    }
502
503    fn new_wlan_ap(
504        interface_naming_id: interface::InterfaceNamingIdentifier,
505        control: fidl_fuchsia_net_interfaces_ext::admin::Control,
506        device_class: DeviceClass,
507        provisioning: interface::ProvisioningType,
508    ) -> Self {
509        Self {
510            control,
511            device_class,
512            config: InterfaceConfigState::WlanAp(WlanApInterfaceState { interface_naming_id }),
513            provisioning,
514        }
515    }
516
517    fn new_blackhole(
518        control: fidl_fuchsia_net_interfaces_ext::admin::Control,
519        provisioning: interface::ProvisioningType,
520    ) -> Self {
521        Self {
522            control,
523            device_class: DeviceClass::Blackhole,
524            config: InterfaceConfigState::Blackhole(BlackholeInterfaceState),
525            provisioning,
526        }
527    }
528
529    fn is_wlan_ap(&self) -> bool {
530        let Self { config, .. } = self;
531        match config {
532            InterfaceConfigState::Host(_) | InterfaceConfigState::Blackhole(_) => false,
533            InterfaceConfigState::WlanAp(_) => true,
534        }
535    }
536
537    fn interface_naming_id(&self) -> Option<&InterfaceNamingIdentifier> {
538        match &self.config {
539            InterfaceConfigState::Host(HostInterfaceState { interface_naming_id, .. })
540            | InterfaceConfigState::WlanAp(WlanApInterfaceState { interface_naming_id, .. }) => {
541                Some(interface_naming_id)
542            }
543            InterfaceConfigState::Blackhole(_) => None,
544        }
545    }
546
547    /// Handles the interface being discovered.
548    async fn on_discovery(
549        &mut self,
550        properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
551        dhcpv4_client_provider: Option<&fnet_dhcp::ClientProviderProxy>,
552        dhcpv6_client_provider: Option<&fnet_dhcpv6::ClientProviderProxy>,
553        dhcpv4_server: Option<&fnet_dhcp::Server_Proxy>,
554        route_set_provider: &fnet_routes_admin::RouteTableV4Proxy,
555        socket_proxy_state: &mut Option<SocketProxyState>,
556        watchers: &mut DnsServerWatchers<'_>,
557        dhcpv4_configuration_streams: &mut dhcpv4::ConfigurationStreamMap,
558        dhcpv6_prefixes_streams: &mut dhcpv6::PrefixesStreamMap,
559    ) -> Result<(), errors::Error> {
560        let Self { config, provisioning, .. } = self;
561        let fnet_interfaces_ext::Properties {
562            online,
563            has_default_ipv4_route,
564            has_default_ipv6_route,
565            ..
566        } = properties;
567
568        // Netcfg won't handle interface update results for a delegated
569        // interface.
570        debug_assert!(provisioning == &interface::ProvisioningType::Local);
571
572        // Note: No discovery actions are needed for offline interfaces.
573        if !online {
574            return Ok(());
575        }
576
577        // When the socketproxy is enabled, communicate the presence of the
578        // new network to the socketproxy.
579        if let Some(state) = socket_proxy_state {
580            // TODO(https://fxbug.dev/390709467): Involve Reachability state when evaluating whether
581            // to add discovered interfaces with Internet to the socketproxy.
582            //
583            // Verify that the interface has a v4 or v6 default route, as this
584            // is a signal that there might be a higher layer of connectivity.
585            if *has_default_ipv4_route || *has_default_ipv6_route {
586                state.handle_interface_new_candidate(properties).await;
587            }
588        }
589
590        match config {
591            InterfaceConfigState::Host(HostInterfaceState {
592                dhcpv4_client,
593                dhcpv6_client_state,
594                dhcpv6_pd_config,
595                interface_admin_auth,
596                interface_naming_id,
597            }) => {
598                let interface_id = properties.id.try_into().expect("should be nonzero");
599                NetCfg::handle_dhcpv4_client_start(
600                    interface_id,
601                    &properties.name,
602                    dhcpv4_client,
603                    dhcpv4_client_provider,
604                    route_set_provider,
605                    interface_admin_auth,
606                    dhcpv4_configuration_streams,
607                )
608                .await?;
609
610                if let Some(dhcpv6_client_provider) = dhcpv6_client_provider {
611                    let sockaddr = start_dhcpv6_client(
612                        properties,
613                        dhcpv6::duid(interface_naming_id.mac),
614                        dhcpv6_client_provider,
615                        dhcpv6_pd_config.clone(),
616                        watchers,
617                        dhcpv6_prefixes_streams,
618                    )?;
619                    *dhcpv6_client_state = sockaddr.map(dhcpv6::ClientState::new);
620                }
621            }
622            InterfaceConfigState::WlanAp(WlanApInterfaceState { interface_naming_id: _ }) => {
623                if let Some(dhcpv4_server) = dhcpv4_server {
624                    dhcpv4::start_server(dhcpv4_server)
625                        .await
626                        .context("error starting DHCP server")?
627                }
628            }
629            InterfaceConfigState::Blackhole(BlackholeInterfaceState) => {}
630        }
631
632        Ok(())
633    }
634}
635
636/// Network Configuration state.
637pub struct NetCfg<'a> {
638    stack: fnet_stack::StackProxy,
639    lookup_admin: fnet_name::LookupAdminProxy,
640    filter_control: FilterControl,
641    interface_state: fnet_interfaces::StateProxy,
642    installer: fidl_fuchsia_net_interfaces_admin::InstallerProxy,
643    dhcp_server: Option<fnet_dhcp::Server_Proxy>,
644    dhcpv4_client_provider: Option<fnet_dhcp::ClientProviderProxy>,
645    dhcpv6_client_provider: Option<fnet_dhcpv6::ClientProviderProxy>,
646    route_set_v4_provider: fnet_routes_admin::RouteTableV4Proxy,
647
648    socket_proxy_state: Option<SocketProxyState>,
649    locally_provisioned_network_rule_set:
650        Option<(fnet_routes_admin::RuleSetV4Proxy, fnet_routes_admin::RuleSetV6Proxy)>,
651
652    filter_enabled_state: FilterEnabledState,
653
654    // TODO(https://fxbug.dev/42146318): These hashmaps are all indexed by
655    // interface ID and store per-interface state, and should be merged.
656    interface_states: HashMap<InterfaceId, InterfaceState>,
657    interface_properties: HashMap<
658        InterfaceId,
659        fnet_interfaces_ext::PropertiesAndState<(), fnet_interfaces_ext::DefaultInterest>,
660    >,
661    interface_metrics: InterfaceMetrics,
662
663    dns_servers: DnsServers,
664    dns_server_watch_responders: dns::DnsServerWatchResponders,
665    route_advertisement_watcher_provider:
666        Option<fnet_ndp::RouterAdvertisementOptionWatcherProviderProxy>,
667
668    forwarded_device_classes: ForwardedDeviceClasses,
669
670    allowed_upstream_device_classes: &'a HashSet<DeviceClass>,
671
672    dhcpv4_configuration_streams: dhcpv4::ConfigurationStreamMap,
673
674    dhcpv6_prefix_provider_handler: Option<dhcpv6::PrefixProviderHandler>,
675    dhcpv6_prefixes_streams: dhcpv6::PrefixesStreamMap,
676
677    // Policy configuration to determine the name of an interface.
678    interface_naming_config: interface::InterfaceNamingConfig,
679    // Policy configuration to determine whether to provision an interface.
680    interface_provisioning_policy: Vec<interface::ProvisioningRule>,
681
682    // NetworkProperty Watchers
683    netpol_networks_service: network::NetpolNetworksService,
684
685    enable_socket_proxy: bool,
686}
687
688/// Returns a [`fnet_name::DnsServer_`] with a static source from a [`std::net::IpAddr`].
689fn static_source_from_ip(f: std::net::IpAddr) -> fnet_name::DnsServer_ {
690    let socket_addr = match fnet_ext::IpAddress(f).into() {
691        fnet::IpAddress::Ipv4(addr) => fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
692            address: addr,
693            port: DEFAULT_DNS_PORT,
694        }),
695        fnet::IpAddress::Ipv6(addr) => fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
696            address: addr,
697            port: DEFAULT_DNS_PORT,
698            zone_index: 0,
699        }),
700    };
701
702    fnet_name::DnsServer_ {
703        address: Some(socket_addr),
704        source: Some(fnet_name::DnsServerSource::StaticSource(
705            fnet_name::StaticDnsServerSource::default(),
706        )),
707        ..Default::default()
708    }
709}
710
711/// Connect to a service, returning an error if the service does not exist in
712/// the service directory.
713async fn svc_connect<S: fidl::endpoints::DiscoverableProtocolMarker>(
714    svc_dir: &fio::DirectoryProxy,
715) -> Result<S::Proxy, anyhow::Error> {
716    optional_svc_connect::<S>(svc_dir)
717        .await?
718        .ok_or_else(|| anyhow::anyhow!("service does not exist"))
719}
720
721/// Attempt to connect to a service, returning `None` if the service does not
722/// exist in the service directory.
723async fn optional_svc_connect<S: fidl::endpoints::DiscoverableProtocolMarker>(
724    svc_dir: &fio::DirectoryProxy,
725) -> Result<Option<S::Proxy>, anyhow::Error> {
726    let req = new_protocol_connector_in_dir::<S>(&svc_dir);
727    if !req.exists().await.context("error checking for service existence")? {
728        Ok(None)
729    } else {
730        req.connect().context("error connecting to service").map(Some)
731    }
732}
733
734/// Start a DHCPv6 client if there is a unicast link-local IPv6 address in `addresses` to use as
735/// the address.
736fn start_dhcpv6_client(
737    fnet_interfaces_ext::Properties {
738        id,
739        online,
740        name,
741        addresses,
742        port_class: _,
743        has_default_ipv4_route: _,
744        has_default_ipv6_route: _,
745    }: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
746    duid: fnet_dhcpv6::Duid,
747    dhcpv6_client_provider: &fnet_dhcpv6::ClientProviderProxy,
748    pd_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
749    watchers: &mut DnsServerWatchers<'_>,
750    dhcpv6_prefixes_streams: &mut dhcpv6::PrefixesStreamMap,
751) -> Result<Option<fnet::Ipv6SocketAddress>, errors::Error> {
752    let id = InterfaceId::from(*id);
753    if !online {
754        return Ok(None);
755    }
756
757    let sockaddr = if let Some(sockaddr) = addresses.iter().find_map(
758        |&fnet_interfaces_ext::Address {
759             addr: fnet::Subnet { addr, prefix_len: _ },
760             valid_until: _,
761             preferred_lifetime_info: _,
762             assignment_state,
763         }| {
764            assert_eq!(assignment_state, fnet_interfaces::AddressAssignmentState::Assigned);
765            match addr {
766                fnet::IpAddress::Ipv6(address) => {
767                    if address.is_unicast_link_local() {
768                        Some(fnet::Ipv6SocketAddress {
769                            address,
770                            port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
771                            zone_index: id.get(),
772                        })
773                    } else {
774                        None
775                    }
776                }
777                fnet::IpAddress::Ipv4(_) => None,
778            }
779        },
780    ) {
781        sockaddr
782    } else {
783        return Ok(None);
784    };
785
786    if matches!(pd_config, Some(fnet_dhcpv6::PrefixDelegationConfig::Prefix(_))) {
787        // We debug-log the `PrefixDelegationConfig` below. This is okay for
788        // now because we do not use the prefix variant, but if we did we need
789        // to support pretty-printing prefixes as it is considered PII and only
790        // pretty-printed prefixes/addresses are properly redacted.
791        todo!("https://fxbug.dev/42069036: Support pretty-printing configured prefix");
792    }
793
794    let source = DnsServersUpdateSource::Dhcpv6 { interface_id: id.get() };
795    assert!(
796        !watchers.contains_key(&source) && !dhcpv6_prefixes_streams.contains_key(&id),
797        "interface with id={} already has a DHCPv6 client",
798        id
799    );
800
801    let (dns_servers_stream, prefixes_stream) = dhcpv6::start_client(
802        dhcpv6_client_provider,
803        id,
804        sockaddr,
805        duid,
806        pd_config.clone(),
807    )
808        .with_context(|| {
809            format!(
810                "failed to start DHCPv6 client on interface {} (id={}) w/ sockaddr {} and PD config {:?}",
811                name,
812                id,
813                sockaddr.display_ext(),
814                pd_config,
815            )
816        })?;
817    if let Some(o) = watchers.insert(source, dns_servers_stream.tagged(source).boxed()) {
818        let _: Pin<Box<BoxStream<'_, _>>> = o;
819        unreachable!("DNS server watchers must not contain key {:?}", source);
820    }
821    if let Some(o) = dhcpv6_prefixes_streams.insert(id, prefixes_stream.tagged(id)) {
822        let _: Pin<Box<dhcpv6::InterfaceIdTaggedPrefixesStream>> = o;
823        unreachable!("DHCPv6 prefixes streams must not contain key {:?}", id);
824    }
825
826    info!(
827        "started DHCPv6 client on host interface {} (id={}) w/ sockaddr {} and PD config {:?}",
828        name,
829        id,
830        sockaddr.display_ext(),
831        pd_config,
832    );
833
834    Ok(Some(sockaddr))
835}
836
837enum RequestStream {
838    Virtualization(fnet_virtualization::ControlRequestStream),
839    Dhcpv6PrefixProvider(fnet_dhcpv6::PrefixProviderRequestStream),
840    Masquerade(fnet_masquerade::FactoryRequestStream),
841    DnsServerWatcher(fnet_name::DnsServerWatcherRequestStream),
842    NetworkAttributes(fnp_properties::NetworksRequestStream),
843}
844
845impl std::fmt::Debug for RequestStream {
846    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
847        match *self {
848            RequestStream::Virtualization(_) => write!(f, "Virtualization"),
849            RequestStream::Dhcpv6PrefixProvider(_) => write!(f, "Dhcpv6PrefixProvider"),
850            RequestStream::Masquerade(_) => write!(f, "Masquerade"),
851            RequestStream::DnsServerWatcher(_) => write!(f, "DnsServerWatcher"),
852            RequestStream::NetworkAttributes(_) => write!(f, "NetworkAttributes"),
853        }
854    }
855}
856
857#[derive(Debug, PartialEq)]
858enum AllowClientRestart {
859    No,
860    Yes,
861}
862
863#[must_use]
864#[derive(Debug, PartialEq)]
865enum Dhcpv4ConfigurationHandlerResult {
866    ContinueOperation,
867    ClientStopped(AllowClientRestart),
868}
869
870// Events associated with provisioning a device.
871#[derive(Debug)]
872enum ProvisioningEvent {
873    InterfaceWatcherResult(
874        Result<
875            Option<fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>>,
876            fidl::Error,
877        >,
878    ),
879    DnsWatcherResult(
880        Option<(
881            dns_server_watcher::DnsServersUpdateSource,
882            Result<Vec<fnet_name::DnsServer_>, fidl::Error>,
883        )>,
884    ),
885    RequestStream(Option<RequestStream>),
886    Dhcpv4Configuration(
887        Option<(InterfaceId, Result<fnet_dhcp_ext::Configuration, fnet_dhcp_ext::Error>)>,
888    ),
889    Dhcpv4ClientDelayedStart(InterfaceId),
890    Dhcpv6PrefixProviderRequest(Result<fnet_dhcpv6::PrefixProviderRequest, fidl::Error>),
891    Dhcpv6PrefixControlRequest(
892        Option<Result<Option<fnet_dhcpv6::PrefixControlRequest>, fidl::Error>>,
893    ),
894    Dhcpv6Prefixes(Option<(InterfaceId, Result<Vec<fnet_dhcpv6::Prefix>, fidl::Error>)>),
895    DnsServerWatcherRequest(
896        (dns::ConnectionId, Result<fnet_name::DnsServerWatcherRequest, fidl::Error>),
897    ),
898    VirtualizationEvent(virtualization::Event),
899    MasqueradeEvent(masquerade::Event),
900}
901
902// Per RFC 2131 "The client SHOULD wait a minimum of ten seconds before
903// restarting the configuration process to avoid excessive network traffic in
904// case of looping."
905const DHCP_CLIENT_RESTART_WAIT_TIME: std::time::Duration = std::time::Duration::from_secs(10);
906
907impl<'a> NetCfg<'a> {
908    async fn new(
909        filter_enabled_interface_types: HashSet<InterfaceType>,
910        interface_metrics: InterfaceMetrics,
911        enable_dhcpv6: bool,
912        forwarded_device_classes: ForwardedDeviceClasses,
913        allowed_upstream_device_classes: &'a HashSet<DeviceClass>,
914        interface_naming_policy: Vec<interface::NamingRule>,
915        interface_provisioning_policy: Vec<interface::ProvisioningRule>,
916        enable_socket_proxy: bool,
917    ) -> Result<NetCfg<'a>, anyhow::Error> {
918        let svc_dir = clone_namespace_svc().context("error cloning svc directory handle")?;
919        let stack = svc_connect::<fnet_stack::StackMarker>(&svc_dir)
920            .await
921            .context("could not connect to stack")?;
922        let lookup_admin = svc_connect::<fnet_name::LookupAdminMarker>(&svc_dir)
923            .await
924            .context("could not connect to lookup admin")?;
925
926        let filter_control = {
927            let filter_deprecated =
928                optional_svc_connect::<fnet_filter_deprecated::FilterMarker>(&svc_dir)
929                    .await
930                    .context("could not connect to filter deprecated")?;
931            let filter_current = optional_svc_connect::<fnet_filter::ControlMarker>(&svc_dir)
932                .await
933                .context("could not connect to filter")?;
934            filter::FilterControl::new(filter_deprecated, filter_current).await?
935        };
936
937        let interface_state = svc_connect::<fnet_interfaces::StateMarker>(&svc_dir)
938            .await
939            .context("could not connect to interfaces state")?;
940        let dhcp_server = optional_svc_connect::<fnet_dhcp::Server_Marker>(&svc_dir)
941            .await
942            .context("could not connect to DHCP Server")?;
943        let dhcpv4_client_provider = {
944            let provider = optional_svc_connect::<fnet_dhcp::ClientProviderMarker>(&svc_dir)
945                .await
946                .context("could not connect to DHCPv4 client provider")?;
947            match provider {
948                Some(provider) => dhcpv4::probe_for_presence(&provider).await.then_some(provider),
949                None => None,
950            }
951        };
952        let dhcpv6_client_provider = if enable_dhcpv6 {
953            let dhcpv6_client_provider = svc_connect::<fnet_dhcpv6::ClientProviderMarker>(&svc_dir)
954                .await
955                .context("could not connect to DHCPv6 client provider")?;
956            Some(dhcpv6_client_provider)
957        } else {
958            None
959        };
960        let route_set_v4_provider = svc_connect::<fnet_routes_admin::RouteTableV4Marker>(&svc_dir)
961            .await
962            .context("could not connect to fuchsia.net.routes.admin.RouteTableV4")?;
963        let installer = svc_connect::<fnet_interfaces_admin::InstallerMarker>(&svc_dir)
964            .await
965            .context("could not connect to installer")?;
966        let route_advertisement_watcher_provider = optional_svc_connect::<
967            fnet_ndp::RouterAdvertisementOptionWatcherProviderMarker,
968        >(&svc_dir)
969        .await
970        .context("could not connect to fuchsia.net.ndp.RouteAdvertisementOptionWatcherProvider")?;
971        let socket_proxy_state = if enable_socket_proxy {
972            let fuchsia_networks = fuchsia_component::client::connect_to_protocol::<
973                fnp_socketproxy::FuchsiaNetworksMarker,
974            >()
975            .context("could not connect to Fuchsia Networks Marker")?;
976            Some(SocketProxyState::new(fuchsia_networks))
977        } else {
978            None
979        };
980        let interface_naming_config =
981            interface::InterfaceNamingConfig::from_naming_rules(interface_naming_policy);
982
983        Ok(NetCfg {
984            stack,
985            lookup_admin,
986            filter_control,
987            interface_state,
988            dhcp_server,
989            installer,
990            dhcpv4_client_provider,
991            dhcpv6_client_provider,
992            route_set_v4_provider,
993            socket_proxy_state,
994            locally_provisioned_network_rule_set: None,
995            interface_naming_config,
996            filter_enabled_state: FilterEnabledState::new(filter_enabled_interface_types),
997            interface_properties: Default::default(),
998            interface_states: Default::default(),
999            interface_metrics,
1000            dns_servers: Default::default(),
1001            dns_server_watch_responders: Default::default(),
1002            route_advertisement_watcher_provider,
1003            forwarded_device_classes,
1004            dhcpv4_configuration_streams: dhcpv4::ConfigurationStreamMap::empty(),
1005            dhcpv6_prefix_provider_handler: None,
1006            dhcpv6_prefixes_streams: dhcpv6::PrefixesStreamMap::empty(),
1007            allowed_upstream_device_classes,
1008            interface_provisioning_policy,
1009            netpol_networks_service: Default::default(),
1010            enable_socket_proxy,
1011        })
1012    }
1013
1014    /// Updates the DNS servers used by the DNS resolver.
1015    async fn update_dns_servers(
1016        &mut self,
1017        source: DnsServersUpdateSource,
1018        servers: Vec<fnet_name::DnsServer_>,
1019    ) {
1020        dns::update_servers(
1021            &self.lookup_admin,
1022            &mut self.dns_servers,
1023            &mut self.dns_server_watch_responders,
1024            &mut self.netpol_networks_service,
1025            source,
1026            servers,
1027        )
1028        .await
1029    }
1030
1031    /// Handles the completion of the DNS server watcher associated with `source`.
1032    ///
1033    /// Clears the servers for `source` and removes the watcher from `dns_watchers`.
1034    async fn handle_dns_server_watcher_done(
1035        &mut self,
1036        source: DnsServersUpdateSource,
1037        dns_watchers: &mut DnsServerWatchers<'_>,
1038    ) -> Result<(), anyhow::Error> {
1039        match source {
1040            DnsServersUpdateSource::Default => {
1041                panic!("should not have a DNS server watcher for the default source");
1042            }
1043            DnsServersUpdateSource::Dhcpv4 { interface_id } => {
1044                unreachable!(
1045                    "DHCPv4 configurations are not obtained through DNS server watcher; \
1046                     interface_id={}",
1047                    interface_id,
1048                )
1049            }
1050            DnsServersUpdateSource::Netstack => Ok(()),
1051            DnsServersUpdateSource::Dhcpv6 { interface_id } => {
1052                let interface_id = interface_id.try_into().expect("should be nonzero");
1053                let InterfaceState { config, provisioning, .. } = self
1054                    .interface_states
1055                    .get_mut(&interface_id)
1056                    .unwrap_or_else(|| panic!("no interface state found for id={}", interface_id));
1057
1058                // Netcfg won't start a DHCPv6 client for a delegated interface.
1059                debug_assert!(provisioning == &interface::ProvisioningType::Local);
1060
1061                match config {
1062                    InterfaceConfigState::Host(HostInterfaceState {
1063                        dhcpv4_client: _,
1064                        dhcpv6_client_state,
1065                        dhcpv6_pd_config: _,
1066                        interface_admin_auth: _,
1067                        interface_naming_id: _,
1068                    }) => {
1069                        let _: dhcpv6::ClientState =
1070                            dhcpv6_client_state.take().unwrap_or_else(|| {
1071                                panic!(
1072                                    "DHCPv6 was not being performed on host interface with id={}",
1073                                    interface_id
1074                                )
1075                            });
1076
1077                        // If the DNS server watcher is done, that means the server-end
1078                        // of the channel is closed meaning DHCPv6 has been stopped.
1079                        // Perform the cleanup for our end of the DHCPv6 client
1080                        // (which will update DNS servers) and send an prefix update to any
1081                        // blocked watchers of fuchsia.net.dhcpv6/PrefixControl.WatchPrefix.
1082                        dhcpv6::stop_client(
1083                            &self.lookup_admin,
1084                            &mut self.dns_servers,
1085                            &mut self.dns_server_watch_responders,
1086                            &mut self.netpol_networks_service,
1087                            interface_id,
1088                            dns_watchers,
1089                            &mut self.dhcpv6_prefixes_streams,
1090                        )
1091                        .await;
1092
1093                        dhcpv6::maybe_send_watch_prefix_response(
1094                            &self.interface_states,
1095                            &self.allowed_upstream_device_classes,
1096                            self.dhcpv6_prefix_provider_handler.as_mut(),
1097                        )
1098                    }
1099                    InterfaceConfigState::WlanAp(WlanApInterfaceState {
1100                        interface_naming_id: _,
1101                    }) => {
1102                        panic!(
1103                            "should not have a DNS watcher for a WLAN AP interface with id={}",
1104                            interface_id
1105                        );
1106                    }
1107                    InterfaceConfigState::Blackhole(BlackholeInterfaceState) => {
1108                        panic!(
1109                            "should not have a DNS watcher for a blackhole interface with id={}",
1110                            interface_id
1111                        );
1112                    }
1113                }
1114            }
1115            DnsServersUpdateSource::Ndp { interface_id } => {
1116                let interface_id = interface_id.try_into().expect("should be nonzero");
1117                let InterfaceState { config, provisioning, .. } = self
1118                    .interface_states
1119                    .get_mut(&interface_id)
1120                    .unwrap_or_else(|| panic!("no interface state found for id={}", interface_id));
1121
1122                // Netcfg won't watch NDP servers for a delegated interface.
1123                debug_assert!(provisioning == &interface::ProvisioningType::Local);
1124
1125                match config {
1126                    InterfaceConfigState::Host(HostInterfaceState { .. }) => {
1127                        Ok(dns::remove_rdnss_watcher(
1128                            &self.lookup_admin,
1129                            &mut self.dns_servers,
1130                            &mut self.dns_server_watch_responders,
1131                            &mut self.netpol_networks_service,
1132                            interface_id,
1133                            dns_watchers,
1134                        )
1135                        .await)
1136                    }
1137                    InterfaceConfigState::WlanAp(WlanApInterfaceState { .. }) => {
1138                        panic!(
1139                            "should not have a NDP DNS watcher for a WLAN AP interface with id={}",
1140                            interface_id
1141                        );
1142                    }
1143                    InterfaceConfigState::Blackhole(BlackholeInterfaceState) => {
1144                        panic!(
1145                            "should not have a NDP DNS watcher for a blackhole interface with id={}",
1146                            interface_id
1147                        );
1148                    }
1149                }
1150            }
1151            DnsServersUpdateSource::SocketProxy => {
1152                // Remove the SocketProxy DNS servers when the server watcher is complete.
1153                Ok(self.update_dns_servers(source, vec![]).await)
1154            }
1155        }
1156    }
1157
1158    /// Run the network configuration eventloop.
1159    ///
1160    /// The device directory will be monitored for device events and the netstack will be
1161    /// configured with a new interface on new device discovery.
1162    async fn run(
1163        &mut self,
1164        mut virtualization_handler: impl virtualization::Handler,
1165    ) -> Result<(), anyhow::Error> {
1166        let netdev_stream =
1167            self.create_device_stream().await.context("create netdevice stream")?.fuse();
1168        let mut netdev_stream = pin!(netdev_stream);
1169
1170        let if_watcher_event_stream = fnet_interfaces_ext::event_stream_from_state(
1171            &self.interface_state,
1172            fnet_interfaces_ext::IncludedAddresses::OnlyAssigned,
1173        )
1174        .context("error creating interface watcher event stream")?
1175        .fuse();
1176        let mut if_watcher_event_stream = pin!(if_watcher_event_stream);
1177
1178        let dns_server_watcher =
1179            fuchsia_component::client::connect_to_protocol::<fnet_name::DnsServerWatcherMarker>()
1180                .context("error connecting to dns server watcher")?;
1181        let netstack_dns_server_stream = dns_server_watcher::new_dns_server_stream(
1182            DnsServersUpdateSource::Netstack,
1183            dns_server_watcher,
1184        )
1185        .boxed();
1186
1187        let dns_watchers = DnsServerWatchers::empty();
1188        // `Fuse` (the return of `fuse`) guarantees that once the underlying stream is
1189        // exhausted, future attempts to poll the stream will return `None`. This would
1190        // be undesirable if we needed to support a scenario where all streams are
1191        // exhausted before adding a new stream to the `StreamMap`. However,
1192        // `netstack_dns_server_stream` is not expected to end so we can fuse the
1193        // `StreamMap` without issue.
1194        let mut dns_watchers = dns_watchers.fuse();
1195        let default_network_updates: Pin<
1196            Box<
1197                dyn futures::Stream<
1198                        Item = Result<fnp_properties::DefaultNetworkUpdate, fidl::Error>,
1199                    >,
1200            >,
1201        >;
1202
1203        assert!(
1204            dns_watchers
1205                .get_mut()
1206                .insert(DnsServersUpdateSource::Netstack, netstack_dns_server_stream)
1207                .is_none(),
1208            "dns watchers should be empty"
1209        );
1210
1211        if self.enable_socket_proxy {
1212            let socketproxy_dns_server_watcher = fuchsia_component::client::connect_to_protocol::<
1213                fnp_socketproxy::DnsServerWatcherMarker,
1214            >()
1215            .context("error connecting to socketproxy dns server watcher")?;
1216            let socketproxy_dns_server_stream =
1217                dns_server_watcher::new_dns_server_stream_socketproxy(
1218                    socketproxy_dns_server_watcher,
1219                )
1220                .boxed();
1221
1222            assert!(
1223                dns_watchers
1224                    .get_mut()
1225                    .insert(DnsServersUpdateSource::SocketProxy, socketproxy_dns_server_stream)
1226                    .is_none(),
1227                "dns watchers should be empty"
1228            );
1229            default_network_updates = Box::pin(futures::stream::try_unfold(
1230                fuchsia_component::client::connect_to_protocol::<
1231                    fnp_properties::DefaultNetworkWatcherMarker,
1232                >()
1233                .context(
1234                    "failed to connect to fuchsia.network.policy.properties/DefaultNetworkWatcher",
1235                )?,
1236                move |proxy| proxy.watch().map_ok(move |s| Some((s, proxy))),
1237            ));
1238        } else {
1239            default_network_updates =
1240                Box::pin(futures::stream::try_unfold((), |_| async { Ok(None) }));
1241        }
1242        let mut default_network_updates = default_network_updates.fuse();
1243
1244        let mut masquerade_handler = MasqueradeHandler::default();
1245
1246        // Serve fuchsia.net.virtualization/Control.
1247        let mut fs = ServiceFs::new_local();
1248        let _: &mut ServiceFsDir<'_, _> = fs
1249            .dir("svc")
1250            .add_fidl_service(RequestStream::Virtualization)
1251            .add_fidl_service(RequestStream::Dhcpv6PrefixProvider)
1252            .add_fidl_service(RequestStream::Masquerade)
1253            .add_fidl_service(RequestStream::DnsServerWatcher)
1254            .add_fidl_service(RequestStream::NetworkAttributes);
1255        let _: &mut ServiceFs<_> =
1256            fs.take_and_serve_directory_handle().context("take and serve directory handle")?;
1257        let mut fs = fs.fuse();
1258
1259        let mut dhcpv6_prefix_provider_requests =
1260            futures::stream::SelectAll::<fnet_dhcpv6::PrefixProviderRequestStream>::new();
1261
1262        // Maintain a queue of virtualization events to be dispatched to the virtualization handler.
1263        let mut virtualization_events =
1264            futures::stream::SelectAll::<virtualization::EventStream>::new();
1265
1266        // Maintain a queue of masquerade events to be dispatched to the masquerade handler.
1267        let mut masquerade_events = futures::stream::SelectAll::<masquerade::EventStream>::new();
1268
1269        let mut dns_server_watcher_incoming_requests =
1270            dns::DnsServerWatcherRequestStreams::default();
1271
1272        let mut networks_request_streams = network::NetworksRequestStreams::default();
1273
1274        // Lifecycle handle takes no args, must be set to zero.
1275        // See zircon/processargs.h.
1276        const LIFECYCLE_HANDLE_ARG: u16 = 0;
1277        let lifecycle = fuchsia_runtime::take_startup_handle(fuchsia_runtime::HandleInfo::new(
1278            fuchsia_runtime::HandleType::Lifecycle,
1279            LIFECYCLE_HANDLE_ARG,
1280        ))
1281        .ok_or_else(|| anyhow::anyhow!("lifecycle handle not present"))?;
1282        let lifecycle = fuchsia_async::Channel::from_channel(lifecycle.into());
1283        let mut lifecycle = fidl_fuchsia_process_lifecycle::LifecycleRequestStream::from_channel(
1284            fidl::AsyncChannel::from(lifecycle),
1285        );
1286
1287        debug!("starting eventloop...");
1288
1289        enum Event {
1290            NetworkDeviceResult(Result<Option<devices::NetworkDeviceInstance>, anyhow::Error>),
1291            LifecycleRequest(
1292                Result<Option<fidl_fuchsia_process_lifecycle::LifecycleRequest>, fidl::Error>,
1293            ),
1294            NetworkAttributesRequest(
1295                (network::ConnectionId, Result<fnp_properties::NetworksRequest, fidl::Error>),
1296            ),
1297            DefaultNetworkUpdate(Result<fnp_properties::DefaultNetworkUpdate, fidl::Error>),
1298            ProvisioningEvent(ProvisioningEvent),
1299        }
1300
1301        loop {
1302            let mut dhcpv6_prefix_control_fut = futures::future::OptionFuture::from(
1303                self.dhcpv6_prefix_provider_handler
1304                    .as_mut()
1305                    .map(dhcpv6::PrefixProviderHandler::try_next_prefix_control_request),
1306            );
1307            let mut delayed_dhcpv4_client_starts = self
1308                .interface_states
1309                .iter_mut()
1310                .filter_map(|(id, InterfaceState { config, .. })| match config {
1311                    InterfaceConfigState::Host(HostInterfaceState {
1312                        dhcpv4_client,
1313                        dhcpv6_client_state: _,
1314                        dhcpv6_pd_config: _,
1315                        interface_admin_auth: _,
1316                        interface_naming_id: _,
1317                    }) => match dhcpv4_client {
1318                        Dhcpv4ClientState::NotRunning => None,
1319                        Dhcpv4ClientState::Running(_) => None,
1320                        Dhcpv4ClientState::ScheduledRestart(timer) => Some(timer.map(|()| *id)),
1321                    },
1322                    InterfaceConfigState::WlanAp(_) | InterfaceConfigState::Blackhole(_) => None,
1323                })
1324                .collect::<futures::stream::FuturesUnordered<_>>();
1325            let event = futures::select! {
1326                netdev_res = netdev_stream.try_next() => {
1327                    Event::NetworkDeviceResult(netdev_res)
1328                }
1329                req = lifecycle.try_next() => {
1330                    Event::LifecycleRequest(req)
1331                }
1332                if_watcher_res = if_watcher_event_stream.try_next() => {
1333                    Event::ProvisioningEvent(
1334                        ProvisioningEvent::InterfaceWatcherResult(if_watcher_res)
1335                    )
1336                }
1337                dns_watchers_res = dns_watchers.next() => {
1338                    Event::ProvisioningEvent(
1339                        ProvisioningEvent::DnsWatcherResult(dns_watchers_res)
1340                    )
1341                }
1342                req_stream = fs.next() => {
1343                    Event::ProvisioningEvent(
1344                        ProvisioningEvent::RequestStream(req_stream)
1345                    )
1346                }
1347                dhcpv4_configuration = self.dhcpv4_configuration_streams.next() => {
1348                    Event::ProvisioningEvent(
1349                        ProvisioningEvent::Dhcpv4Configuration(dhcpv4_configuration)
1350                    )
1351                }
1352                interface_id = delayed_dhcpv4_client_starts.select_next_some() => {
1353                    Event::ProvisioningEvent(
1354                        ProvisioningEvent::Dhcpv4ClientDelayedStart(interface_id)
1355                    )
1356                }
1357                dhcpv6_prefix_req = dhcpv6_prefix_provider_requests.select_next_some() => {
1358                    Event::ProvisioningEvent(
1359                        ProvisioningEvent::Dhcpv6PrefixProviderRequest(dhcpv6_prefix_req)
1360                    )
1361                }
1362                dhcpv6_prefix_control_req = dhcpv6_prefix_control_fut => {
1363                    Event::ProvisioningEvent(
1364                        ProvisioningEvent::Dhcpv6PrefixControlRequest(dhcpv6_prefix_control_req)
1365                    )
1366                }
1367                dhcpv6_prefixes = self.dhcpv6_prefixes_streams.next() => {
1368                    Event::ProvisioningEvent(
1369                        ProvisioningEvent::Dhcpv6Prefixes(dhcpv6_prefixes)
1370                    )
1371                }
1372                req = dns_server_watcher_incoming_requests.select_next_some() => {
1373                    Event::ProvisioningEvent(
1374                        ProvisioningEvent::DnsServerWatcherRequest(req)
1375                    )
1376                }
1377                virt_event = virtualization_events.select_next_some() => {
1378                    Event::ProvisioningEvent(
1379                        ProvisioningEvent::VirtualizationEvent(virt_event)
1380                    )
1381                }
1382                masq_event = masquerade_events.select_next_some() => {
1383                    Event::ProvisioningEvent(
1384                        ProvisioningEvent::MasqueradeEvent(
1385                            masq_event.context("error while receiving MasqueradeEvent")?)
1386                        )
1387                }
1388                net_attr_req = networks_request_streams.select_next_some() => {
1389                    Event::NetworkAttributesRequest(net_attr_req)
1390                }
1391                default_network_update = default_network_updates.select_next_some() => {
1392                    Event::DefaultNetworkUpdate(default_network_update)
1393                }
1394                complete => return Err(anyhow::anyhow!("eventloop ended unexpectedly")),
1395            };
1396
1397            // `delayed_dhcpv4_client_starts` mutably borrows the delayed-start timers from `self`'s
1398            // InterfaceState map, so we have to drop it here to regain access to `self`.
1399            drop(delayed_dhcpv4_client_starts);
1400            match event {
1401                Event::NetworkDeviceResult(netdev_res) => {
1402                    let instance =
1403                        netdev_res.context("error retrieving netdev instance")?.ok_or_else(
1404                            || anyhow::anyhow!("netdev instance watcher stream ended unexpectedly"),
1405                        )?;
1406                    // DNS watchers must be propagated to start an RA NDP watcher for the interface
1407                    // prior to the interface getting enabled in the Netstack.
1408                    self.handle_device_instance(instance, dns_watchers.get_mut())
1409                        .await
1410                        .context("handle netdev instance")?
1411                }
1412                Event::LifecycleRequest(req) => {
1413                    let req = req.context("lifecycle request")?.ok_or_else(|| {
1414                        anyhow::anyhow!("LifecycleRequestStream ended unexpectedly")
1415                    })?;
1416                    match req {
1417                        fidl_fuchsia_process_lifecycle::LifecycleRequest::Stop {
1418                            control_handle,
1419                        } => {
1420                            info!("received shutdown request");
1421                            // Shutdown request is acknowledged by the lifecycle
1422                            // channel shutting down. Intentionally leak the
1423                            // channel so it'll only be closed on process
1424                            // termination, allowing clean process termination
1425                            // to always be observed.
1426
1427                            // Must drop the control_handle to unwrap the
1428                            // lifecycle channel.
1429                            std::mem::drop(control_handle);
1430                            let (inner, _terminated): (_, bool) = lifecycle.into_inner();
1431                            let inner = std::sync::Arc::try_unwrap(inner).map_err(
1432                                |_: std::sync::Arc<_>| {
1433                                    anyhow::anyhow!("failed to retrieve lifecycle channel")
1434                                },
1435                            )?;
1436                            let inner: zx::Channel = inner.into_channel().into_zx_channel();
1437                            std::mem::forget(inner);
1438
1439                            return Ok(());
1440                        }
1441                    }
1442                }
1443                Event::NetworkAttributesRequest((id, req)) => {
1444                    self.netpol_networks_service.handle_network_attributes_request(id, req).await?
1445                }
1446                Event::DefaultNetworkUpdate(update) => match update {
1447                    Err(e) => error!("Encountered error watching for network updates: {e:?}"),
1448                    Ok(fnp_properties::DefaultNetworkUpdate {
1449                        interface_id, socket_marks, ..
1450                    }) => match (interface_id, socket_marks) {
1451                        (None, Some(sm)) => {
1452                            error!(
1453                                "Default network update malformed: missing interface_id, while \
1454                                 socket_marks are present {sm:?}"
1455                            )
1456                        }
1457                        (Some(ii), None) => {
1458                            error!(
1459                                "Default network update malformed: missing socket_marks, while \
1460                                 interface_id is present {ii:?}"
1461                            )
1462                        }
1463                        (None, None) => {
1464                            self.netpol_networks_service
1465                                .update(network::PropertyUpdate::default().default_network_lost())
1466                                .await
1467                        }
1468                        (Some(interface_id), Some(socket_marks)) => {
1469                            if let Err(e) = (async || {
1470                                self.netpol_networks_service
1471                                    .update(
1472                                        network::PropertyUpdate::default()
1473                                            .default_network(interface_id)
1474                                            .context("while setting default network")?
1475                                            .socket_marks(interface_id, socket_marks)
1476                                            .context("while setting socket_marks")?,
1477                                    )
1478                                    .await;
1479                                Ok::<(), anyhow::Error>(())
1480                            })()
1481                            .await
1482                            {
1483                                error!("Could not handle default network update: {e:?}");
1484                            }
1485                        }
1486                    },
1487                },
1488                Event::ProvisioningEvent(event) => {
1489                    self.handle_provisioning_event(
1490                        event,
1491                        dns_watchers.get_mut(),
1492                        &mut dhcpv6_prefix_provider_requests,
1493                        &mut dns_server_watcher_incoming_requests,
1494                        &mut virtualization_handler,
1495                        &mut virtualization_events,
1496                        &mut masquerade_handler,
1497                        &mut masquerade_events,
1498                        &mut networks_request_streams,
1499                    )
1500                    .await?
1501                }
1502            }
1503        }
1504    }
1505
1506    async fn handle_provisioning_event(
1507        &mut self,
1508        event: ProvisioningEvent,
1509        dns_watchers: &mut DnsServerWatchers<'_>,
1510        dhcpv6_prefix_provider_requests: &mut futures::stream::SelectAll<
1511            fnet_dhcpv6::PrefixProviderRequestStream,
1512        >,
1513        dns_server_watcher_incoming_requests: &mut dns::DnsServerWatcherRequestStreams,
1514        virtualization_handler: &mut impl virtualization::Handler,
1515        virtualization_events: &mut futures::stream::SelectAll<virtualization::EventStream>,
1516        masquerade_handler: &mut MasqueradeHandler,
1517        masquerade_events: &mut futures::stream::SelectAll<masquerade::EventStream>,
1518        networks_request_streams: &mut network::NetworksRequestStreams,
1519    ) -> Result<(), anyhow::Error> {
1520        match event {
1521            ProvisioningEvent::InterfaceWatcherResult(if_watcher_res) => {
1522                let event = if_watcher_res
1523                    .unwrap_or_else(|err| exit_with_fidl_error(err))
1524                    .expect("watcher stream never returns None");
1525                trace!("got interfaces watcher event = {:?}", event);
1526
1527                self.handle_interface_watcher_event(event, dns_watchers, virtualization_handler)
1528                    .await
1529                    .context("handle interface watcher event")?;
1530            }
1531            ProvisioningEvent::DnsWatcherResult(dns_watchers_res) => {
1532                let (source, res) = dns_watchers_res.ok_or_else(|| {
1533                    anyhow::anyhow!("dns watchers stream should never be exhausted")
1534                })?;
1535                let servers = match res {
1536                    Ok(s) => s,
1537                    Err(e) => {
1538                        // TODO(https://fxbug.dev/42135335): Restart the DNS server watcher.
1539                        warn!(
1540                            "non-fatal error getting next event from DNS server watcher stream
1541                            with source = {:?}: {:?}",
1542                            source, e
1543                        );
1544                        self.handle_dns_server_watcher_done(source, dns_watchers)
1545                            .await
1546                            .with_context(|| {
1547                                format!(
1548                                    "error handling completion of DNS server watcher for \
1549                                    {:?}",
1550                                    source
1551                                )
1552                            })?;
1553                        return Ok(());
1554                    }
1555                };
1556
1557                self.update_dns_servers(source, servers).await;
1558            }
1559            // TODO(https://fxbug.dev/42080722): Add tests to ensure we do not offer
1560            // these services when interface has ProvisioningType::Delegated
1561            // state.
1562            ProvisioningEvent::RequestStream(req_stream) => {
1563                match req_stream.context("ServiceFs ended unexpectedly")? {
1564                    RequestStream::Virtualization(req_stream) => virtualization_handler
1565                        .handle_event(
1566                            virtualization::Event::ControlRequestStream(req_stream),
1567                            virtualization_events,
1568                        )
1569                        .await
1570                        .context("handle virtualization event")
1571                        .or_else(errors::Error::accept_non_fatal)?,
1572                    RequestStream::Dhcpv6PrefixProvider(req_stream) => {
1573                        dhcpv6_prefix_provider_requests.push(req_stream);
1574                    }
1575                    RequestStream::Masquerade(req_stream) => {
1576                        masquerade_handler
1577                            .handle_event(
1578                                masquerade::Event::FactoryRequestStream(req_stream),
1579                                masquerade_events,
1580                                &mut self.filter_control,
1581                                &mut self.filter_enabled_state,
1582                                &self.interface_states,
1583                            )
1584                            .await
1585                    }
1586                    RequestStream::DnsServerWatcher(req_stream) => {
1587                        dns_server_watcher_incoming_requests.handle_request_stream(req_stream);
1588                    }
1589                    RequestStream::NetworkAttributes(req_stream) => {
1590                        networks_request_streams.push(req_stream)
1591                    }
1592                };
1593            }
1594            ProvisioningEvent::Dhcpv4Configuration(config) => {
1595                let (interface_id, config) =
1596                    config.expect("DHCPv4 configuration stream is never exhausted");
1597                match self.handle_dhcpv4_configuration(interface_id, config).await {
1598                    Dhcpv4ConfigurationHandlerResult::ContinueOperation => (),
1599                    Dhcpv4ConfigurationHandlerResult::ClientStopped(allow_restart) => {
1600                        let interface_name = self
1601                            .interface_properties
1602                            .get(&interface_id)
1603                            .map(
1604                                |fnet_interfaces_ext::PropertiesAndState {
1605                                     state: (),
1606                                     properties: fnet_interfaces_ext::Properties { name, .. },
1607                                 }| name.as_str(),
1608                            )
1609                            .unwrap_or("<removed>");
1610                        let state = self
1611                            .interface_states
1612                            .get_mut(&interface_id)
1613                            .map(
1614                                |InterfaceState {
1615                                     control,
1616                                     device_class: _,
1617                                     config,
1618                                     provisioning: _,
1619                                 }| {
1620                                    match config {
1621                                        InterfaceConfigState::Host(HostInterfaceState {
1622                                            dhcpv4_client,
1623                                            dhcpv6_client_state: _,
1624                                            dhcpv6_pd_config: _,
1625                                            interface_admin_auth: _,
1626                                            interface_naming_id: _,
1627                                        }) => Some((dhcpv4_client, control)),
1628                                        InterfaceConfigState::WlanAp(_)
1629                                        | InterfaceConfigState::Blackhole(_) => None,
1630                                    }
1631                                },
1632                            )
1633                            .flatten();
1634
1635                        match state {
1636                            None => {
1637                                log::error!(
1638                                    "Trying to handle DHCPv4 client shutdown \
1639                                (id={interface_id}), but no client is running on that interface"
1640                                );
1641                            }
1642                            Some((dhcpv4_client, control)) => {
1643                                Self::handle_dhcpv4_client_stop(
1644                                    interface_id,
1645                                    interface_name,
1646                                    dhcpv4_client,
1647                                    &mut self.dhcpv4_configuration_streams,
1648                                    &mut self.dns_servers,
1649                                    &mut self.dns_server_watch_responders,
1650                                    &mut self.netpol_networks_service,
1651                                    control,
1652                                    &self.lookup_admin,
1653                                    dhcpv4::AlreadyObservedClientExit::Yes,
1654                                )
1655                                .await;
1656
1657                                match allow_restart {
1658                                    AllowClientRestart::No => (),
1659                                    AllowClientRestart::Yes => {
1660                                        // The client exited due to an unexpected error. Schedule it
1661                                        // to be restarted after waiting a backoff period.
1662                                        *dhcpv4_client =
1663                                            Dhcpv4ClientState::ScheduledRestart(Box::pin(
1664                                                fasync::Timer::new(DHCP_CLIENT_RESTART_WAIT_TIME),
1665                                            ));
1666                                    }
1667                                }
1668                            }
1669                        }
1670                    }
1671                }
1672            }
1673            ProvisioningEvent::Dhcpv4ClientDelayedStart(interface_id) => {
1674                self.on_delayed_dhcpv4_client_start(interface_id)
1675                    .await
1676                    .or_else(errors::Error::accept_non_fatal)?;
1677            }
1678            ProvisioningEvent::Dhcpv6PrefixProviderRequest(res) => {
1679                match res {
1680                    Ok(fnet_dhcpv6::PrefixProviderRequest::AcquirePrefix {
1681                        config,
1682                        prefix,
1683                        control_handle: _,
1684                    }) => {
1685                        self.handle_dhcpv6_acquire_prefix(config, prefix, dns_watchers)
1686                            .await
1687                            .or_else(errors::Error::accept_non_fatal)?;
1688                    }
1689                    Err(e) => {
1690                        error!("fuchsia.net.dhcpv6/PrefixProvider request error: {:?}", e)
1691                    }
1692                };
1693            }
1694            ProvisioningEvent::Dhcpv6PrefixControlRequest(req) => {
1695                let res = req.context(
1696                    "PrefixControl OptionFuture will only be selected if it is not None",
1697                )?;
1698                match res {
1699                    Err(e) => {
1700                        error!("fuchsia.net.dhcpv6/PrefixControl request stream error: {:?}", e);
1701                        self.on_dhcpv6_prefix_control_close(dns_watchers).await;
1702                    }
1703                    Ok(None) => {
1704                        info!("fuchsia.net.dhcpv6/PrefixControl closed by client");
1705                        self.on_dhcpv6_prefix_control_close(dns_watchers).await;
1706                    }
1707                    Ok(Some(fnet_dhcpv6::PrefixControlRequest::WatchPrefix { responder })) => {
1708                        self.handle_watch_prefix(responder, dns_watchers)
1709                            .await
1710                            .context("handle PrefixControl.WatchPrefix")
1711                            .unwrap_or_else(accept_error);
1712                    }
1713                };
1714            }
1715            ProvisioningEvent::Dhcpv6Prefixes(prefixes) => {
1716                let (interface_id, res) =
1717                    prefixes.context("DHCPv6 watch prefixes stream map can never be exhausted")?;
1718                self.handle_dhcpv6_prefixes(interface_id, res, dns_watchers)
1719                    .await
1720                    .unwrap_or_else(accept_error);
1721            }
1722            ProvisioningEvent::DnsServerWatcherRequest((id, req)) => {
1723                self.dns_server_watch_responders.handle_request(id, req, &self.dns_servers)?;
1724            }
1725            ProvisioningEvent::VirtualizationEvent(event) => {
1726                virtualization_handler
1727                    .handle_event(event, virtualization_events)
1728                    .await
1729                    .context("handle virtualization event")
1730                    .or_else(errors::Error::accept_non_fatal)?;
1731            }
1732            ProvisioningEvent::MasqueradeEvent(event) => {
1733                masquerade_handler
1734                    .handle_event(
1735                        event,
1736                        masquerade_events,
1737                        &mut self.filter_control,
1738                        &mut self.filter_enabled_state,
1739                        &self.interface_states,
1740                    )
1741                    .await
1742            }
1743        };
1744        return Ok(());
1745    }
1746
1747    async fn handle_dhcpv4_client_stop(
1748        id: InterfaceId,
1749        name: &str,
1750        dhcpv4_client: &mut Dhcpv4ClientState,
1751        configuration_streams: &mut dhcpv4::ConfigurationStreamMap,
1752        dns_servers: &mut DnsServers,
1753        dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
1754        netpol_networks_service: &mut network::NetpolNetworksService,
1755        control: &fnet_interfaces_ext::admin::Control,
1756        lookup_admin: &fnet_name::LookupAdminProxy,
1757        already_observed_client_exit: dhcpv4::AlreadyObservedClientExit,
1758    ) {
1759        match std::mem::replace(dhcpv4_client, Dhcpv4ClientState::NotRunning) {
1760            Dhcpv4ClientState::NotRunning => (),
1761            Dhcpv4ClientState::Running(c) => {
1762                dhcpv4::stop_client(
1763                    id,
1764                    name,
1765                    c,
1766                    configuration_streams,
1767                    dns_servers,
1768                    dns_server_watch_responders,
1769                    netpol_networks_service,
1770                    control,
1771                    lookup_admin,
1772                    already_observed_client_exit,
1773                )
1774                .await;
1775            }
1776            Dhcpv4ClientState::ScheduledRestart(_) => (),
1777        }
1778    }
1779
1780    async fn handle_dhcpv4_client_start(
1781        id: InterfaceId,
1782        name: &str,
1783        dhcpv4_client: &mut Dhcpv4ClientState,
1784        dhcpv4_client_provider: Option<&fnet_dhcp::ClientProviderProxy>,
1785        route_set_provider: &fnet_routes_admin::RouteTableV4Proxy,
1786        interface_admin_auth: &fnet_resources::GrantForInterfaceAuthorization,
1787        configuration_streams: &mut dhcpv4::ConfigurationStreamMap,
1788    ) -> Result<(), errors::Error> {
1789        *dhcpv4_client = match dhcpv4_client_provider {
1790            None => Dhcpv4ClientState::NotRunning,
1791            Some(p) => Dhcpv4ClientState::Running(
1792                dhcpv4::start_client(
1793                    id,
1794                    name,
1795                    p,
1796                    route_set_provider,
1797                    interface_admin_auth,
1798                    configuration_streams,
1799                )
1800                .await?,
1801            ),
1802        };
1803        Ok(())
1804    }
1805
1806    async fn handle_dhcpv4_client_update(
1807        id: InterfaceId,
1808        name: &str,
1809        online: bool,
1810        dhcpv4_client: &mut Dhcpv4ClientState,
1811        dhcpv4_client_provider: Option<&fnet_dhcp::ClientProviderProxy>,
1812        configuration_streams: &mut dhcpv4::ConfigurationStreamMap,
1813        dns_servers: &mut DnsServers,
1814        dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
1815        netpol_networks_service: &mut network::NetpolNetworksService,
1816        control: &fnet_interfaces_ext::admin::Control,
1817        lookup_admin: &fnet_name::LookupAdminProxy,
1818        route_set_provider: &fnet_routes_admin::RouteTableV4Proxy,
1819        interface_admin_auth: &fnet_resources::GrantForInterfaceAuthorization,
1820    ) -> Result<(), errors::Error> {
1821        if online {
1822            Self::handle_dhcpv4_client_start(
1823                id,
1824                name,
1825                dhcpv4_client,
1826                dhcpv4_client_provider,
1827                route_set_provider,
1828                interface_admin_auth,
1829                configuration_streams,
1830            )
1831            .await?
1832        } else {
1833            Self::handle_dhcpv4_client_stop(
1834                id,
1835                name,
1836                dhcpv4_client,
1837                configuration_streams,
1838                dns_servers,
1839                dns_server_watch_responders,
1840                netpol_networks_service,
1841                control,
1842                lookup_admin,
1843                dhcpv4::AlreadyObservedClientExit::No,
1844            )
1845            .await
1846        }
1847
1848        Ok(())
1849    }
1850
1851    async fn on_delayed_dhcpv4_client_start(
1852        &mut self,
1853        interface_id: InterfaceId,
1854    ) -> Result<(), errors::Error> {
1855        let Self {
1856            dhcpv4_client_provider,
1857            route_set_v4_provider,
1858            interface_states,
1859            interface_properties,
1860            dhcpv4_configuration_streams,
1861            ..
1862        } = self;
1863
1864        let (dhcpv4_client, interface_admin_auth) = match interface_states
1865            .get_mut(&interface_id)
1866            .and_then(|InterfaceState { config, .. }| match config {
1867                InterfaceConfigState::Host(HostInterfaceState {
1868                    dhcpv4_client,
1869                    dhcpv6_client_state: _,
1870                    dhcpv6_pd_config: _,
1871                    interface_admin_auth,
1872                    interface_naming_id: _,
1873                }) => Some((dhcpv4_client, interface_admin_auth)),
1874                InterfaceConfigState::WlanAp(_) | InterfaceConfigState::Blackhole(_) => None,
1875            }) {
1876            Some(state) => state,
1877            None => {
1878                // It's fine for the interface to have been removed before we
1879                // got around to restarting the client.
1880                return Ok(());
1881            }
1882        };
1883
1884        match dhcpv4_client {
1885            Dhcpv4ClientState::NotRunning => (),
1886            Dhcpv4ClientState::Running(_) => {
1887                // We already restarted the client before reaching this point.
1888                return Ok(());
1889            }
1890            Dhcpv4ClientState::ScheduledRestart(_) => {
1891                *dhcpv4_client = Dhcpv4ClientState::NotRunning;
1892            }
1893        };
1894
1895        let properties = match interface_properties.get(&interface_id) {
1896            Some(fnet_interfaces_ext::PropertiesAndState { properties, state: () }) => properties,
1897            None => return Ok(()),
1898        };
1899
1900        Self::handle_dhcpv4_client_start(
1901            properties.id.into(),
1902            &properties.name,
1903            dhcpv4_client,
1904            dhcpv4_client_provider.as_ref(),
1905            route_set_v4_provider,
1906            interface_admin_auth,
1907            dhcpv4_configuration_streams,
1908        )
1909        .await
1910    }
1911
1912    /// Handles an interface watcher event (existing, added, changed, or removed).
1913    async fn handle_interface_watcher_event(
1914        &mut self,
1915        event: fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
1916        watchers: &mut DnsServerWatchers<'_>,
1917        virtualization_handler: &mut impl virtualization::Handler,
1918    ) -> Result<(), anyhow::Error> {
1919        let Self {
1920            interface_properties,
1921            dns_servers,
1922            dns_server_watch_responders,
1923            netpol_networks_service,
1924            interface_states,
1925            lookup_admin,
1926            dhcp_server,
1927            dhcpv4_client_provider,
1928            dhcpv6_client_provider,
1929            route_set_v4_provider,
1930            socket_proxy_state,
1931            dhcpv4_configuration_streams,
1932            dhcpv6_prefixes_streams,
1933            allowed_upstream_device_classes,
1934            dhcpv6_prefix_provider_handler,
1935            ..
1936        } = self;
1937        let update_result = interface_properties
1938            .update(event)
1939            .context("failed to update interface properties with watcher event")?;
1940
1941        // When the underlying interface id for the event represents an
1942        // interface netcfg installed, determine whether the event should
1943        // be ignored given the interface's provisioning policy.
1944        if let Some(id) = match &update_result {
1945            fnet_interfaces_ext::UpdateResult::NoChange => None,
1946            fnet_interfaces_ext::UpdateResult::Existing { properties, state: _ } => {
1947                Some(properties.id)
1948            }
1949            fnet_interfaces_ext::UpdateResult::Added { properties, state: _ } => {
1950                Some(properties.id)
1951            }
1952            fnet_interfaces_ext::UpdateResult::Changed { previous: _, current, state: _ } => {
1953                Some(current.id)
1954            }
1955            fnet_interfaces_ext::UpdateResult::Removed(
1956                fnet_interfaces_ext::PropertiesAndState { properties, state: _ },
1957            ) => Some(properties.id),
1958        } {
1959            if let Some(&InterfaceState { provisioning, .. }) = interface_states.get(&id.into()) {
1960                if provisioning == interface::ProvisioningType::Delegated {
1961                    // Ignore result handling, which prevents provisioning
1962                    // activity from starting such as DHCP and DHCPv6 clients.
1963                    debug!(
1964                        "ignoring interface watcher event because provisioning \
1965                    is delegated for this interface: {:?}",
1966                        &update_result
1967                    );
1968                    return Ok(());
1969                }
1970            }
1971        }
1972
1973        Self::handle_interface_update_result(
1974            &update_result,
1975            watchers,
1976            dns_servers,
1977            dns_server_watch_responders,
1978            netpol_networks_service,
1979            interface_states,
1980            dhcpv4_configuration_streams,
1981            dhcpv6_prefixes_streams,
1982            lookup_admin,
1983            dhcp_server,
1984            dhcpv4_client_provider,
1985            dhcpv6_client_provider,
1986            route_set_v4_provider,
1987            socket_proxy_state,
1988        )
1989        .await
1990        .context("handle interface update")
1991        .or_else(errors::Error::accept_non_fatal)?;
1992
1993        // The interface watcher event may have disabled DHCPv6 on the interface
1994        // so respond accordingly.
1995        dhcpv6::maybe_send_watch_prefix_response(
1996            interface_states,
1997            allowed_upstream_device_classes,
1998            dhcpv6_prefix_provider_handler.as_mut(),
1999        )
2000        .context("maybe send PrefixControl.WatchPrefix response")?;
2001
2002        virtualization_handler
2003            .handle_interface_update_result(&update_result)
2004            .await
2005            .context("handle interface update for virtualization")
2006            .or_else(errors::Error::accept_non_fatal)
2007    }
2008
2009    // This method takes mutable references to several fields of `NetCfg` separately as parameters,
2010    // rather than `&mut self` directly, because `update_result` already holds a reference into
2011    // `self.interface_properties`.
2012    async fn handle_interface_update_result(
2013        update_result: &fnet_interfaces_ext::UpdateResult<
2014            '_,
2015            (),
2016            fnet_interfaces_ext::DefaultInterest,
2017        >,
2018        watchers: &mut DnsServerWatchers<'_>,
2019        dns_servers: &mut DnsServers,
2020        dns_server_watch_responders: &mut dns::DnsServerWatchResponders,
2021        netpol_networks_service: &mut network::NetpolNetworksService,
2022        interface_states: &mut HashMap<InterfaceId, InterfaceState>,
2023        dhcpv4_configuration_streams: &mut dhcpv4::ConfigurationStreamMap,
2024        dhcpv6_prefixes_streams: &mut dhcpv6::PrefixesStreamMap,
2025        lookup_admin: &fnet_name::LookupAdminProxy,
2026        dhcp_server: &Option<fnet_dhcp::Server_Proxy>,
2027        dhcpv4_client_provider: &Option<fnet_dhcp::ClientProviderProxy>,
2028        dhcpv6_client_provider: &Option<fnet_dhcpv6::ClientProviderProxy>,
2029        route_set_v4_provider: &fnet_routes_admin::RouteTableV4Proxy,
2030        socket_proxy_state: &mut Option<SocketProxyState>,
2031    ) -> Result<(), errors::Error> {
2032        match update_result {
2033            fnet_interfaces_ext::UpdateResult::Added { properties, state: _ } => {
2034                match interface_states.get_mut(&properties.id.into()) {
2035                    Some(state) => state
2036                        .on_discovery(
2037                            properties,
2038                            dhcpv4_client_provider.as_ref(),
2039                            dhcpv6_client_provider.as_ref(),
2040                            dhcp_server.as_ref(),
2041                            route_set_v4_provider,
2042                            socket_proxy_state,
2043                            watchers,
2044                            dhcpv4_configuration_streams,
2045                            dhcpv6_prefixes_streams,
2046                        )
2047                        .await
2048                        .context("failed to handle interface added event"),
2049                    // An interface netcfg won't be configuring was added, do nothing.
2050                    None => Ok(()),
2051                }
2052            }
2053            fnet_interfaces_ext::UpdateResult::Existing { properties, state: _ } => {
2054                match interface_states.get_mut(&properties.id.into()) {
2055                    Some(state) => state
2056                        .on_discovery(
2057                            properties,
2058                            dhcpv4_client_provider.as_ref(),
2059                            dhcpv6_client_provider.as_ref(),
2060                            dhcp_server.as_ref(),
2061                            route_set_v4_provider,
2062                            socket_proxy_state,
2063                            watchers,
2064                            dhcpv4_configuration_streams,
2065                            dhcpv6_prefixes_streams,
2066                        )
2067                        .await
2068                        .context("failed to handle existing interface event"),
2069                    // An interface netcfg won't be configuring was discovered, do nothing.
2070                    None => Ok(()),
2071                }
2072            }
2073            fnet_interfaces_ext::UpdateResult::Changed {
2074                previous: previous_properties,
2075                current: current_properties,
2076                state: _,
2077            } => {
2078                let fnet_interfaces::Properties { online: previous_online, .. } =
2079                    previous_properties;
2080                let &fnet_interfaces_ext::Properties {
2081                    id, ref name, online, ref addresses, ..
2082                } = current_properties;
2083                match interface_states.get_mut(&(*id).into()) {
2084                    // An interface netcfg is not configuring was changed, do nothing.
2085                    None => return Ok(()),
2086                    Some(InterfaceState {
2087                        config:
2088                            InterfaceConfigState::Host(HostInterfaceState {
2089                                dhcpv4_client,
2090                                dhcpv6_client_state,
2091                                dhcpv6_pd_config,
2092                                interface_admin_auth,
2093                                interface_naming_id,
2094                            }),
2095                        control,
2096                        ..
2097                    }) => {
2098                        if previous_online.is_some() {
2099                            Self::handle_dhcpv4_client_update(
2100                                (*id).into(),
2101                                name,
2102                                *online,
2103                                dhcpv4_client,
2104                                dhcpv4_client_provider.as_ref(),
2105                                dhcpv4_configuration_streams,
2106                                dns_servers,
2107                                dns_server_watch_responders,
2108                                netpol_networks_service,
2109                                control,
2110                                lookup_admin,
2111                                route_set_v4_provider,
2112                                interface_admin_auth,
2113                            )
2114                            .await?;
2115                        }
2116
2117                        // When the socket proxy is present, communicate whether the
2118                        // interface has gained or lost candidacy.
2119                        if let Some(state) = socket_proxy_state {
2120                            match socketproxy::determine_interface_state_changed(
2121                                &previous_properties,
2122                                &current_properties,
2123                            ) {
2124                                Some(true) => {
2125                                    state.handle_interface_new_candidate(&current_properties).await
2126                                }
2127                                Some(false) => {
2128                                    state
2129                                        .handle_interface_no_longer_candidate(InterfaceId(*id))
2130                                        .await
2131                                }
2132                                None => (),
2133                            }
2134                        }
2135
2136                        let dhcpv6_client_provider =
2137                            if let Some(dhcpv6_client_provider) = dhcpv6_client_provider {
2138                                dhcpv6_client_provider
2139                            } else {
2140                                return Ok(());
2141                            };
2142
2143                        if !online {
2144                            // Stop DHCPv6 client if interface went down.
2145                            let dhcpv6::ClientState { sockaddr, prefixes: _ } =
2146                                match dhcpv6_client_state.take() {
2147                                    Some(s) => s,
2148                                    None => return Ok(()),
2149                                };
2150
2151                            info!(
2152                                "host interface {} (id={}) went down \
2153                                so stopping DHCPv6 client w/ sockaddr = {}",
2154                                name,
2155                                id,
2156                                sockaddr.display_ext(),
2157                            );
2158
2159                            return Ok(dhcpv6::stop_client(
2160                                &lookup_admin,
2161                                dns_servers,
2162                                dns_server_watch_responders,
2163                                netpol_networks_service,
2164                                (*id).into(),
2165                                watchers,
2166                                dhcpv6_prefixes_streams,
2167                            )
2168                            .await);
2169                        }
2170
2171                        // Stop the DHCPv6 client if its address can no longer be found on the
2172                        // interface.
2173                        if let Some(dhcpv6::ClientState { sockaddr, prefixes: _ }) =
2174                            dhcpv6_client_state
2175                        {
2176                            let &mut fnet::Ipv6SocketAddress { address, port: _, zone_index: _ } =
2177                                sockaddr;
2178                            if !addresses.iter().any(
2179                                |&fnet_interfaces_ext::Address {
2180                                     addr: fnet::Subnet { addr, prefix_len: _ },
2181                                     valid_until: _,
2182                                     preferred_lifetime_info: _,
2183                                     assignment_state,
2184                                 }| {
2185                                    assert_eq!(
2186                                        assignment_state,
2187                                        fnet_interfaces::AddressAssignmentState::Assigned
2188                                    );
2189                                    addr == fnet::IpAddress::Ipv6(address)
2190                                },
2191                            ) {
2192                                let sockaddr = *sockaddr;
2193                                *dhcpv6_client_state = None;
2194
2195                                info!(
2196                                    "stopping DHCPv6 client on host interface {} (id={}) \
2197                                    w/ removed sockaddr = {}",
2198                                    name,
2199                                    id,
2200                                    sockaddr.display_ext(),
2201                                );
2202
2203                                dhcpv6::stop_client(
2204                                    &lookup_admin,
2205                                    dns_servers,
2206                                    dns_server_watch_responders,
2207                                    netpol_networks_service,
2208                                    (*id).into(),
2209                                    watchers,
2210                                    dhcpv6_prefixes_streams,
2211                                )
2212                                .await;
2213                            }
2214                        }
2215
2216                        // Start a DHCPv6 client if there isn't one.
2217                        if dhcpv6_client_state.is_none() {
2218                            let sockaddr = start_dhcpv6_client(
2219                                current_properties,
2220                                dhcpv6::duid(interface_naming_id.mac),
2221                                &dhcpv6_client_provider,
2222                                dhcpv6_pd_config.clone(),
2223                                watchers,
2224                                dhcpv6_prefixes_streams,
2225                            )?;
2226                            *dhcpv6_client_state = sockaddr.map(dhcpv6::ClientState::new);
2227                        }
2228
2229                        Ok(())
2230                    }
2231                    Some(InterfaceState {
2232                        config:
2233                            InterfaceConfigState::WlanAp(WlanApInterfaceState {
2234                                interface_naming_id: _,
2235                            }),
2236                        ..
2237                    }) => {
2238                        // TODO(https://fxbug.dev/42133555): Stop the DHCP server when the address it is
2239                        // listening on is removed.
2240                        let dhcp_server = if let Some(dhcp_server) = dhcp_server {
2241                            dhcp_server
2242                        } else {
2243                            return Ok(());
2244                        };
2245
2246                        if previous_online
2247                            .map_or(true, |previous_online| previous_online == *online)
2248                        {
2249                            return Ok(());
2250                        }
2251
2252                        if *online {
2253                            info!(
2254                                "WLAN AP interface {} (id={}) came up so starting DHCP server",
2255                                name, id
2256                            );
2257                            dhcpv4::start_server(&dhcp_server)
2258                                .await
2259                                .context("error starting DHCP server")
2260                        } else {
2261                            info!(
2262                                "WLAN AP interface {} (id={}) went down so stopping DHCP server",
2263                                name, id
2264                            );
2265                            dhcpv4::stop_server(&dhcp_server)
2266                                .await
2267                                .context("error stopping DHCP server")
2268                        }
2269                    }
2270                    Some(InterfaceState {
2271                        config: InterfaceConfigState::Blackhole(BlackholeInterfaceState),
2272                        ..
2273                    }) => return Ok(()),
2274                }
2275            }
2276            fnet_interfaces_ext::UpdateResult::Removed(
2277                fnet_interfaces_ext::PropertiesAndState {
2278                    properties: fnet_interfaces_ext::Properties { id, name, .. },
2279                    state: (),
2280                },
2281            ) => {
2282                netpol_networks_service.remove_network(*id).await;
2283                match interface_states.remove(&(*id).into()) {
2284                    // An interface netcfg was not responsible for configuring was removed, do
2285                    // nothing.
2286                    None => Ok(()),
2287                    Some(InterfaceState { config, control, .. }) => {
2288                        // When the socket proxy is present, communicate the
2289                        // removal of the Fuchsia network.
2290                        if let Some(state) = socket_proxy_state {
2291                            state.handle_interface_no_longer_candidate(InterfaceId(*id)).await;
2292                        }
2293                        match config {
2294                            InterfaceConfigState::Host(HostInterfaceState {
2295                                mut dhcpv4_client,
2296                                mut dhcpv6_client_state,
2297                                dhcpv6_pd_config: _,
2298                                interface_admin_auth: _,
2299                                interface_naming_id: _,
2300                            }) => {
2301                                let interface_id: InterfaceId = (*id).into();
2302                                Self::handle_dhcpv4_client_stop(
2303                                    interface_id,
2304                                    name,
2305                                    &mut dhcpv4_client,
2306                                    dhcpv4_configuration_streams,
2307                                    dns_servers,
2308                                    dns_server_watch_responders,
2309                                    netpol_networks_service,
2310                                    &control,
2311                                    lookup_admin,
2312                                    dhcpv4::AlreadyObservedClientExit::No,
2313                                )
2314                                    .await;
2315
2316
2317                                let dhcpv6::ClientState {
2318                                    sockaddr,
2319                                    prefixes: _,
2320                                } = match dhcpv6_client_state.take() {
2321                                    Some(s) => s,
2322                                    None => return Ok(()),
2323                                };
2324
2325                                info!(
2326                                    "host interface {} (id={}) removed \
2327                                    so stopping DHCPv6 client w/ sockaddr = {}",
2328                                    name,
2329                                    id,
2330                                    sockaddr.display_ext()
2331                                );
2332
2333                                dhcpv6::stop_client(
2334                                    &lookup_admin,
2335                                    dns_servers,
2336                                    dns_server_watch_responders,
2337                                    netpol_networks_service,
2338                                    interface_id,
2339                                    watchers,
2340                                    dhcpv6_prefixes_streams,
2341                                )
2342                                .await;
2343
2344                                Ok(dns::remove_rdnss_watcher(
2345                                    &lookup_admin,
2346                                    dns_servers,
2347                                    dns_server_watch_responders,
2348                                    netpol_networks_service,
2349                                    interface_id,
2350                                    watchers,
2351                                )
2352                                .await)
2353                            }
2354                            InterfaceConfigState::WlanAp(WlanApInterfaceState {
2355                                interface_naming_id: _,
2356                            }) => {
2357                                if let Some(dhcp_server) = dhcp_server {
2358                                    // The DHCP server should only run on the WLAN AP interface, so stop it
2359                                    // since the AP interface is removed.
2360                                    info!(
2361                                        "WLAN AP interface {} (id={}) is removed, stopping DHCP server",
2362                                        name, id
2363                                    );
2364                                    dhcpv4::stop_server(&dhcp_server)
2365                                        .await
2366                                        .context("error stopping DHCP server")
2367                                } else {
2368                                    Ok(())
2369                                }
2370                            }
2371                            InterfaceConfigState::Blackhole(BlackholeInterfaceState) => {
2372                                Ok(())
2373                            }
2374                        }
2375                        .context("failed to handle interface removed event")
2376                    }
2377                }
2378            }
2379            fnet_interfaces_ext::UpdateResult::NoChange => Ok(()),
2380        }
2381    }
2382
2383    /// Handle an event from `D`'s device directory.
2384    async fn create_device_stream(
2385        &self,
2386    ) -> Result<
2387        impl futures::Stream<Item = Result<devices::NetworkDeviceInstance, anyhow::Error>>,
2388        anyhow::Error,
2389    > {
2390        let installer = self.installer.clone();
2391        let directory = fuchsia_fs::directory::open_in_namespace(
2392            devices::NetworkDeviceInstance::PATH,
2393            fio::PERM_READABLE,
2394        )
2395        .with_context(|| format!("error opening netdevice directory"))?;
2396        let stream_of_streams = fvfs_watcher::Watcher::new(&directory)
2397            .await
2398            .with_context(|| {
2399                format!("creating watcher for {}", devices::NetworkDeviceInstance::PATH)
2400            })?
2401            .err_into()
2402            .try_filter_map(move |fvfs_watcher::WatchMessage { event, filename }| {
2403                let installer = installer.clone();
2404                async move {
2405                    trace!("got {:?} event for {}", event, filename.display());
2406
2407                    if filename.to_str() == Some(THIS_DIRECTORY) {
2408                        debug!("skipping device w/ filename = {}", filename.display());
2409                        return Ok(None);
2410                    }
2411
2412                    match event {
2413                        fvfs_watcher::WatchEvent::ADD_FILE | fvfs_watcher::WatchEvent::EXISTING => {
2414                            let filepath = path::Path::new(devices::NetworkDeviceInstance::PATH)
2415                                .join(filename);
2416                            info!("found new network device at {:?}", filepath);
2417                            match devices::NetworkDeviceInstance::get_instance_stream(
2418                                &installer, &filepath,
2419                            )
2420                            .await
2421                            .context("create instance stream")
2422                            {
2423                                Ok(stream) => Ok(Some(
2424                                    stream
2425                                        .filter_map(move |r| {
2426                                            futures::future::ready(match r {
2427                                                Ok(instance) => Some(Ok(instance)),
2428                                                Err(errors::Error::NonFatal(nonfatal)) => {
2429                                                    error!(
2430                                        "non-fatal error operating device stream for {:?}: {:?}",
2431                                        filepath,
2432                                        nonfatal
2433                                    );
2434                                                    None
2435                                                }
2436                                                Err(errors::Error::Fatal(fatal)) => {
2437                                                    Some(Err(fatal))
2438                                                }
2439                                            })
2440                                        })
2441                                        // Need to box the stream to combine it
2442                                        // with flatten_unordered because it's
2443                                        // not Unpin.
2444                                        .boxed(),
2445                                )),
2446                                Err(errors::Error::NonFatal(nonfatal)) => {
2447                                    error!(
2448                                        "non-fatal error fetching device stream for {:?}: {:?}",
2449                                        filepath, nonfatal
2450                                    );
2451                                    Ok(None)
2452                                }
2453                                Err(errors::Error::Fatal(fatal)) => Err(fatal),
2454                            }
2455                        }
2456                        fvfs_watcher::WatchEvent::IDLE | fvfs_watcher::WatchEvent::REMOVE_FILE => {
2457                            Ok(None)
2458                        }
2459                        event => Err(anyhow::anyhow!(
2460                            "unrecognized event {:?} for device filename {}",
2461                            event,
2462                            filename.display()
2463                        )),
2464                    }
2465                }
2466            })
2467            .fuse()
2468            .try_flatten_unordered(None);
2469        Ok(stream_of_streams)
2470    }
2471
2472    async fn handle_device_instance(
2473        &mut self,
2474        instance: devices::NetworkDeviceInstance,
2475        dns_watchers: &mut DnsServerWatchers<'_>,
2476    ) -> Result<(), anyhow::Error> {
2477        // Produce the identifier for the device to determine if it is already
2478        // known to Netcfg.
2479        let interface_naming_id =
2480            match self.get_interface_naming_identifier_for_instance(&instance).await {
2481                Ok(id) => id,
2482                Err(e) => {
2483                    error!("non-fatal error getting interface naming id {instance:?}: {e:?}");
2484
2485                    return Ok(());
2486                }
2487            };
2488
2489        // TODO(https://fxbug.dev/42086636): Add metrics/inspect data for devices
2490        // that encounter the situations below
2491        // Check if this device is known to Netcfg.
2492        // The device creation process removes an existing interface with
2493        // the same InterfaceNamingIdentifier. Therefore, two interfaces cannot
2494        // exist concurrently with the same InterfaceNamingIdentifier. This
2495        // should result in 0 or 1 interface to exist with the
2496        // provided identifier.
2497        match self.interface_states.iter().find_map(|(_id, state)| {
2498            // A device with the same PersistentId is considered to be
2499            // the same logical interface.
2500            if state.interface_naming_id() == Some(&interface_naming_id) {
2501                Some(state)
2502            } else {
2503                None
2504            }
2505        }) {
2506            // If Netcfg knows about the device already, then it's likely
2507            // flapped and we should uninstall the existing interface prior
2508            // to adding the new one.
2509            Some(interface_state @ InterfaceState { control, .. }) => {
2510                let interface_naming_id = interface_state.interface_naming_id();
2511                warn!(
2512                    "interface likely flapped. attempting to remove interface with \
2513                    interface naming id ({interface_naming_id:?}) prior to adding \
2514                    instance: {instance:?}"
2515                );
2516                match control.remove().await {
2517                    Ok(Ok(())) => {
2518                        // Successfully removed interface.
2519                        // It may have been that Netstack responded to the
2520                        // `remove()` request or Netstack noticed the flap
2521                        // and removed the interface on its own.
2522                        info!(
2523                            "interface removed due to reason: {:?}",
2524                            control.clone().wait_termination().await
2525                        );
2526                    }
2527                    Ok(Err(e)) => {
2528                        panic!("expected to be able to call remove on this interface: {e:?}")
2529                    }
2530                    Err(e) => {
2531                        // Do nothing. The interface was already removed.
2532                        info!("interface was already removed prior to calling `remove()`: {e:?}");
2533                    }
2534                }
2535            }
2536            None => {
2537                // Do nothing. The device is not known to Netcfg so
2538                // we should attempt to add the device.
2539            }
2540        }
2541
2542        match self.add_new_device(&instance, dns_watchers).await {
2543            Ok(()) => {
2544                return Ok(());
2545            }
2546            Err(devices::AddDeviceError::AlreadyExists(name)) => {
2547                // Either the interface with this name was not installed
2548                // by Netcfg or another device installed by Netcfg used
2549                // the same name already. We will reject interface
2550                // installation.
2551                error!(
2552                    "interface with name ({name}) is already present in the Netstack, \
2553                    and the device was either not installed by Netcfg or a different \
2554                    device installed by Netcfg used the same name. rejecting \
2555                    installation for instance: {instance:?}"
2556                );
2557                return Ok(());
2558            }
2559            Err(devices::AddDeviceError::Other(errors::Error::NonFatal(e))) => {
2560                error!("non-fatal error adding device {:?}: {:?}", instance, e);
2561
2562                return Ok(());
2563            }
2564            Err(devices::AddDeviceError::Other(errors::Error::Fatal(e))) => {
2565                return Err(e.context(format!("error adding new device {:?}", instance)));
2566            }
2567        }
2568    }
2569
2570    /// Add a blackhole interface to the netstack.
2571    async fn add_blackhole_interface(&mut self, name: &str) -> Result<(), devices::AddDeviceError> {
2572        let (control, control_server_end) =
2573            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
2574                .context("create Control proxy")
2575                .map_err(errors::Error::NonFatal)?;
2576        let Metric(metric) = self.interface_metrics.blackhole_metric;
2577        self.installer
2578            .install_blackhole_interface(
2579                control_server_end,
2580                fnet_interfaces_admin::Options {
2581                    name: Some(name.to_owned()),
2582                    metric: Some(metric),
2583                    netstack_managed_routes_designation: None,
2584                    __source_breaking: fidl::marker::SourceBreaking,
2585                },
2586            )
2587            .context("error installing blackhole interface")
2588            .map_err(errors::Error::NonFatal)?;
2589        let interface_id = control.get_id().await.map_err(|err| {
2590            let other = match err {
2591                fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Fidl(err) => err.into(),
2592                fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(terminal_error) => {
2593                    match terminal_error {
2594                        fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::DuplicateName => {
2595                            return devices::AddDeviceError::AlreadyExists(name.to_owned());
2596                        }
2597                        reason => {
2598                            anyhow::anyhow!("received terminal event {:?}", reason)
2599                        }
2600                    }
2601                }
2602            };
2603            devices::AddDeviceError::Other(
2604                errors::Error::NonFatal(other).context("calling Control get_id"),
2605            )
2606        })?;
2607        let _did_enable: bool = control
2608            .enable()
2609            .await
2610            .map_err(map_control_error("error sending enable request"))
2611            .and_then(|res| {
2612                // ControlEnableError is an empty *flexible* enum, so we can't match on it, but
2613                // the operation is infallible at the time of writing.
2614                res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlEnableError| {
2615                    errors::Error::Fatal(anyhow::anyhow!("enable interface: {:?}", e))
2616                })
2617            })?;
2618        let interface_state =
2619            InterfaceState::new_blackhole(control, interface::ProvisioningType::Local);
2620
2621        assert_matches!(
2622            self.interface_states
2623                .insert(InterfaceId::new(interface_id).expect("must be nonzero"), interface_state),
2624            None
2625        );
2626
2627        info!("installed blackhole interface (id={} name={})", interface_id, name);
2628
2629        Ok(())
2630    }
2631
2632    /// Add a device at `filepath` to the netstack.
2633    async fn add_new_device(
2634        &mut self,
2635        device_instance: &devices::NetworkDeviceInstance,
2636        dns_watchers: &mut DnsServerWatchers<'_>,
2637    ) -> Result<(), devices::AddDeviceError> {
2638        let device_info =
2639            device_instance.get_device_info().await.context("error getting device info and MAC")?;
2640
2641        let DeviceInfo { mac, port_class, topological_path } = &device_info;
2642
2643        let mac = mac.ok_or_else(|| {
2644            warn!("devices without mac address not supported yet");
2645            devices::AddDeviceError::Other(errors::Error::NonFatal(anyhow::anyhow!(
2646                "device without mac not supported"
2647            )))
2648        })?;
2649
2650        let device_class =
2651            DeviceClass::try_from(*port_class).map_err(|e: UnknownPortClassError| {
2652                devices::AddDeviceError::Other(errors::Error::NonFatal(anyhow::Error::new(e)))
2653            })?;
2654        let device_info =
2655            DeviceInfoRef { device_class, mac: &mac, topological_path: &topological_path };
2656
2657        let interface_type = device_info.interface_type();
2658        let metric = match interface_type {
2659            InterfaceType::WlanClient | InterfaceType::WlanAp => self.interface_metrics.wlan_metric,
2660            InterfaceType::Ethernet => self.interface_metrics.eth_metric,
2661            InterfaceType::Blackhole => self.interface_metrics.blackhole_metric,
2662        }
2663        .into();
2664        let (interface_name, interface_naming_id) = match self
2665            .interface_naming_config
2666            .generate_stable_name(&topological_path, &mac, device_class)
2667        {
2668            Ok((name, interface_naming_id)) => (name.to_string(), interface_naming_id),
2669            Err(interface::NameGenerationError::GenerationError(e)) => {
2670                return Err(devices::AddDeviceError::Other(errors::Error::Fatal(
2671                    e.context("error getting stable name"),
2672                )));
2673            }
2674        };
2675
2676        info!(
2677            "adding {device_instance:?} to stack with name = {interface_name} and \
2678        naming id = {interface_naming_id:?}"
2679        );
2680
2681        let provisioning_action = interface::find_provisioning_action_from_provisioning_rules(
2682            &self.interface_provisioning_policy,
2683            &device_info,
2684            &interface_name,
2685        );
2686        info!(
2687            "interface with name {:?} will have {:?} provisioning",
2688            &interface_name, provisioning_action
2689        );
2690
2691        let ProvisioningAction { provisioning, netstack_managed_routes_designation } =
2692            provisioning_action;
2693
2694        let (interface_id, control) = device_instance
2695            .add_to_stack(
2696                self,
2697                InterfaceConfig {
2698                    name: interface_name.clone(),
2699                    metric,
2700                    netstack_managed_routes_designation,
2701                },
2702            )
2703            .await
2704            .context("error adding to stack")?;
2705
2706        self.configure_eth_interface(
2707            interface_id.try_into().expect("interface ID should be nonzero"),
2708            control,
2709            interface_name,
2710            interface_naming_id,
2711            &device_info,
2712            dns_watchers,
2713            provisioning,
2714        )
2715        .await
2716        .context("error configuring ethernet interface")
2717        .map_err(devices::AddDeviceError::Other)
2718    }
2719
2720    /// Configure an ethernet interface.
2721    ///
2722    /// If the device is a WLAN AP, it will be configured as a WLAN AP (see
2723    /// `configure_wlan_ap_and_dhcp_server` for more details). Otherwise, it
2724    /// will be configured as a host (see `configure_host` for more details).
2725    async fn configure_eth_interface(
2726        &mut self,
2727        interface_id: InterfaceId,
2728        control: fidl_fuchsia_net_interfaces_ext::admin::Control,
2729        interface_name: String,
2730        interface_naming_id: interface::InterfaceNamingIdentifier,
2731        device_info: &DeviceInfoRef<'_>,
2732        dns_watchers: &mut DnsServerWatchers<'_>,
2733        provisioning_type: ProvisioningType,
2734    ) -> Result<(), errors::Error> {
2735        let ForwardedDeviceClasses { ipv4, ipv6 } = &self.forwarded_device_classes;
2736        let ipv4_forwarding = ipv4.contains(&device_info.device_class);
2737        let ipv6_forwarding = ipv6.contains(&device_info.device_class);
2738        let config: fnet_interfaces_admin::Configuration = control
2739            .set_configuration(&fnet_interfaces_admin::Configuration {
2740                ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2741                    unicast_forwarding: Some(ipv6_forwarding),
2742                    multicast_forwarding: Some(ipv6_forwarding),
2743                    ..Default::default()
2744                }),
2745                ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2746                    unicast_forwarding: Some(ipv4_forwarding),
2747                    multicast_forwarding: Some(ipv4_forwarding),
2748                    ..Default::default()
2749                }),
2750                ..Default::default()
2751            })
2752            .await
2753            .map_err(map_control_error("setting configuration"))
2754            .and_then(|res| {
2755                res.map_err(|e: fnet_interfaces_admin::ControlSetConfigurationError| {
2756                    errors::Error::Fatal(anyhow::anyhow!("{:?}", e))
2757                })
2758            })?;
2759        info!("installed configuration with result {:?}", config);
2760
2761        if device_info.is_wlan_ap() {
2762            if let Some(id) = self
2763                .interface_states
2764                .iter()
2765                .find_map(|(id, state)| if state.is_wlan_ap() { Some(id) } else { None })
2766            {
2767                return Err(errors::Error::NonFatal(anyhow::anyhow!(
2768                    "multiple WLAN AP interfaces are not supported, \
2769                        have WLAN AP interface with id = {}",
2770                    id
2771                )));
2772            }
2773            let InterfaceState { control, .. } = match self.interface_states.entry(interface_id) {
2774                Entry::Occupied(entry) => {
2775                    panic!(
2776                        "multiple interfaces with the same ID = {}; \
2777                                attempting to add state for a WLAN AP, existing state = {:?}",
2778                        entry.key(),
2779                        entry.get(),
2780                    );
2781                }
2782                Entry::Vacant(entry) => entry.insert(InterfaceState::new_wlan_ap(
2783                    interface_naming_id,
2784                    control,
2785                    device_info.device_class,
2786                    provisioning_type,
2787                )),
2788            };
2789
2790            info!("discovered WLAN AP (interface ID={})", interface_id);
2791
2792            if let Some(dhcp_server) = &self.dhcp_server {
2793                info!("configuring DHCP server for WLAN AP (interface ID={})", interface_id);
2794                Self::configure_wlan_ap_and_dhcp_server(
2795                    &mut self.filter_enabled_state,
2796                    &mut self.filter_control,
2797                    interface_id,
2798                    dhcp_server,
2799                    control,
2800                    interface_name,
2801                    device_info,
2802                )
2803                .await
2804                .context("error configuring wlan ap and dhcp server")?;
2805            } else {
2806                warn!(
2807                    "cannot configure DHCP server for WLAN AP (interface ID={}) \
2808                        since DHCP server service is not available",
2809                    interface_id
2810                );
2811            }
2812        } else {
2813            let InterfaceState { control, .. } = match self.interface_states.entry(interface_id) {
2814                Entry::Occupied(entry) => {
2815                    panic!(
2816                        "multiple interfaces with the same ID = {}; \
2817                            attempting to add state for a host, existing state = {:?}",
2818                        entry.key(),
2819                        entry.get()
2820                    );
2821                }
2822                Entry::Vacant(entry) => {
2823                    let dhcpv6_pd_config = if !self
2824                        .allowed_upstream_device_classes
2825                        .contains(&device_info.device_class)
2826                    {
2827                        None
2828                    } else {
2829                        self.dhcpv6_prefix_provider_handler.as_ref().map(
2830                            |dhcpv6::PrefixProviderHandler {
2831                                 preferred_prefix_len,
2832                                 prefix_control_request_stream: _,
2833                                 watch_prefix_responder: _,
2834                                 interface_config: _,
2835                                 current_prefix: _,
2836                             }| {
2837                                preferred_prefix_len.map_or(
2838                                    fnet_dhcpv6::PrefixDelegationConfig::Empty(fnet_dhcpv6::Empty),
2839                                    |preferred_prefix_len| {
2840                                        fnet_dhcpv6::PrefixDelegationConfig::PrefixLength(
2841                                            preferred_prefix_len,
2842                                        )
2843                                    },
2844                                )
2845                            },
2846                        )
2847                    };
2848                    entry.insert(
2849                        InterfaceState::new_host(
2850                            interface_naming_id,
2851                            control,
2852                            device_info.device_class,
2853                            dhcpv6_pd_config,
2854                            provisioning_type,
2855                        )
2856                        .await?,
2857                    )
2858                }
2859            };
2860
2861            info!("discovered host interface with id={}, configuring interface", interface_id);
2862
2863            Self::configure_host(
2864                &mut self.filter_enabled_state,
2865                &mut self.filter_control,
2866                &self.stack,
2867                interface_id,
2868                device_info,
2869                // Disable in-stack DHCPv4 when provisioning is ignored.
2870                self.dhcpv4_client_provider.is_none()
2871                    && provisioning_type == interface::ProvisioningType::Local,
2872            )
2873            .await
2874            .context("error configuring host")?;
2875
2876            if let Some(watcher_provider) = &self.route_advertisement_watcher_provider {
2877                dns::add_rdnss_watcher(&watcher_provider, interface_id, dns_watchers)
2878                    .await
2879                    .map_err(errors::Error::NonFatal)?;
2880            }
2881
2882            if self.socket_proxy_state.is_some()
2883                && provisioning_type == interface::ProvisioningType::Local
2884            {
2885                if self.locally_provisioned_network_rule_set.is_none() {
2886                    self.locally_provisioned_network_rule_set = futures::try_join!(
2887                        install_locally_provisioned_network_rule_set::<Ipv4>(),
2888                        install_locally_provisioned_network_rule_set::<Ipv6>(),
2889                    )
2890                    .inspect_err(|err| {
2891                        log::error!(
2892                            "failed to create route rules for locally provisioined networks: {:?}",
2893                            err
2894                        );
2895                    })
2896                    .ok();
2897                }
2898            }
2899
2900            let _did_enable: bool = control
2901                .enable()
2902                .await
2903                .map_err(map_control_error("error sending enable request"))
2904                .and_then(|res| {
2905                    // ControlEnableError is an empty *flexible* enum, so we can't match on it, but
2906                    // the operation is infallible at the time of writing.
2907                    res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlEnableError| {
2908                        errors::Error::Fatal(anyhow::anyhow!("enable interface: {:?}", e))
2909                    })
2910                })?;
2911        }
2912
2913        Ok(())
2914    }
2915
2916    /// Configure host interface.
2917    async fn configure_host(
2918        filter_enabled_state: &mut FilterEnabledState,
2919        filter_control: &mut FilterControl,
2920        stack: &fnet_stack::StackProxy,
2921        interface_id: InterfaceId,
2922        device_info: &DeviceInfoRef<'_>,
2923        start_in_stack_dhcpv4: bool,
2924    ) -> Result<(), errors::Error> {
2925        // Handle on-demand interface enabling by using either implementation of Filter.
2926        filter_enabled_state
2927            .maybe_update(Some(device_info.interface_type()), interface_id, filter_control)
2928            .await
2929            .map_err(|e| {
2930                anyhow::anyhow!("failed to update filter on nic {interface_id} with error = {e:?}")
2931            })
2932            .map_err(errors::Error::NonFatal)?;
2933
2934        // Enable DHCP.
2935        if start_in_stack_dhcpv4 {
2936            stack
2937                .set_dhcp_client_enabled(interface_id.get(), true)
2938                .await
2939                .unwrap_or_else(|err| exit_with_fidl_error(err))
2940                .map_err(|e| anyhow!("failed to start dhcp client: {:?}", e))
2941                .map_err(errors::Error::NonFatal)?;
2942        }
2943
2944        Ok(())
2945    }
2946
2947    /// Configure the WLAN AP and the DHCP server to serve requests on its network.
2948    ///
2949    /// Note, this method will not start the DHCP server, it will only be configured
2950    /// with the parameters so it is ready to be started when an interface UP event
2951    /// is received for the WLAN AP.
2952    async fn configure_wlan_ap_and_dhcp_server(
2953        filter_enabled_state: &mut FilterEnabledState,
2954        filter_control: &mut FilterControl,
2955        interface_id: InterfaceId,
2956        dhcp_server: &fnet_dhcp::Server_Proxy,
2957        control: &fidl_fuchsia_net_interfaces_ext::admin::Control,
2958        name: String,
2959        device_info: &DeviceInfoRef<'_>,
2960    ) -> Result<(), errors::Error> {
2961        let (address_state_provider, server_end) = fidl::endpoints::create_proxy::<
2962            fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker,
2963        >();
2964
2965        // Handle on-demand interface enabling by using either implementation of Filter.
2966        filter_enabled_state
2967            .maybe_update(Some(device_info.interface_type()), interface_id, filter_control)
2968            .await
2969            .map_err(|e| {
2970                anyhow::anyhow!("failed to update filter on nic {interface_id} with error = {e:?}")
2971            })
2972            .map_err(errors::Error::NonFatal)?;
2973
2974        // Calculate and set the interface address based on the network address.
2975        // The interface address should be the first available address.
2976        let network_addr_as_u32 = u32::from_be_bytes(WLAN_AP_NETWORK_ADDR.addr);
2977        let interface_addr_as_u32 = network_addr_as_u32 + 1;
2978        let ipv4 = fnet::Ipv4Address { addr: interface_addr_as_u32.to_be_bytes() };
2979        let addr = fidl_fuchsia_net::Subnet {
2980            addr: fnet::IpAddress::Ipv4(ipv4.clone()),
2981            prefix_len: WLAN_AP_PREFIX_LEN.get(),
2982        };
2983
2984        control
2985            .add_address(
2986                &addr,
2987                &fidl_fuchsia_net_interfaces_admin::AddressParameters {
2988                    add_subnet_route: Some(true),
2989                    ..Default::default()
2990                },
2991                server_end,
2992            )
2993            .map_err(map_control_error("error sending add address request"))?;
2994
2995        // Allow the address to outlive this scope. At the time of writing its lifetime is
2996        // identical to the interface's lifetime and no updates to its properties are made. We may
2997        // wish to retain the handle in the future to allow external address removal (e.g. by a
2998        // user) to be observed so that an error can be emitted (as such removal would break a
2999        // critical user journey).
3000        address_state_provider
3001            .detach()
3002            .map_err(Into::into)
3003            .map_err(map_address_state_provider_error("error sending detach request"))?;
3004
3005        // Enable the interface to allow DAD to proceed.
3006        let _did_enable: bool = control
3007            .enable()
3008            .await
3009            .map_err(map_control_error("error sending enable request"))
3010            .and_then(|res| {
3011                // ControlEnableError is an empty *flexible* enum, so we can't match on it, but the
3012                // operation is infallible at the time of writing.
3013                res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlEnableError| {
3014                    errors::Error::Fatal(anyhow::anyhow!("enable interface: {:?}", e))
3015                })
3016            })?;
3017
3018        let state_stream =
3019            fidl_fuchsia_net_interfaces_ext::admin::assignment_state_stream(address_state_provider);
3020        let mut state_stream = pin!(state_stream);
3021        fidl_fuchsia_net_interfaces_ext::admin::wait_assignment_state(
3022            &mut state_stream,
3023            fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned,
3024        )
3025        .await
3026        .map_err(map_address_state_provider_error("failed to add interface address for WLAN AP"))?;
3027
3028        // First we clear any leases that the server knows about since the server
3029        // will be used on a new interface. If leases exist, configuring the DHCP
3030        // server parameters may fail (AddressPool).
3031        debug!("clearing DHCP leases");
3032        dhcp_server
3033            .clear_leases()
3034            .await
3035            .context("error sending clear DHCP leases request")
3036            .map_err(errors::Error::NonFatal)?
3037            .map_err(zx::Status::from_raw)
3038            .context("error clearing DHCP leases request")
3039            .map_err(errors::Error::NonFatal)?;
3040
3041        // Configure the DHCP server.
3042        let v = vec![ipv4];
3043        debug!("setting DHCP IpAddrs parameter to {:?}", v);
3044        dhcp_server
3045            .set_parameter(&fnet_dhcp::Parameter::IpAddrs(v))
3046            .await
3047            .context("error sending set DHCP IpAddrs parameter request")
3048            .map_err(errors::Error::NonFatal)?
3049            .map_err(zx::Status::from_raw)
3050            .context("error setting DHCP IpAddrs parameter")
3051            .map_err(errors::Error::NonFatal)?;
3052
3053        let v = vec![name];
3054        debug!("setting DHCP BoundDeviceNames parameter to {:?}", v);
3055        dhcp_server
3056            .set_parameter(&fnet_dhcp::Parameter::BoundDeviceNames(v))
3057            .await
3058            .context("error sending set DHCP BoundDeviceName parameter request")
3059            .map_err(errors::Error::NonFatal)?
3060            .map_err(zx::Status::from_raw)
3061            .context("error setting DHCP BoundDeviceNames parameter")
3062            .map_err(errors::Error::NonFatal)?;
3063
3064        let v = fnet_dhcp::LeaseLength {
3065            default: Some(WLAN_AP_DHCP_LEASE_TIME_SECONDS),
3066            max: Some(WLAN_AP_DHCP_LEASE_TIME_SECONDS),
3067            ..Default::default()
3068        };
3069        debug!("setting DHCP LeaseLength parameter to {:?}", v);
3070        dhcp_server
3071            .set_parameter(&fnet_dhcp::Parameter::Lease(v))
3072            .await
3073            .context("error sending set DHCP LeaseLength parameter request")
3074            .map_err(errors::Error::NonFatal)?
3075            .map_err(zx::Status::from_raw)
3076            .context("error setting DHCP LeaseLength parameter")
3077            .map_err(errors::Error::NonFatal)?;
3078
3079        let host_mask = ::dhcpv4::configuration::SubnetMask::new(WLAN_AP_PREFIX_LEN);
3080        let broadcast_addr =
3081            host_mask.broadcast_of(&std::net::Ipv4Addr::from_fidl(WLAN_AP_NETWORK_ADDR));
3082        let broadcast_addr_as_u32: u32 = broadcast_addr.into();
3083        // The start address of the DHCP pool should be the first address after the WLAN AP
3084        // interface address.
3085        let dhcp_pool_start =
3086            fnet::Ipv4Address { addr: (interface_addr_as_u32 + 1).to_be_bytes() }.into();
3087        // The last address of the DHCP pool should be the last available address
3088        // in the interface's network. This is the address immediately before the
3089        // network's broadcast address.
3090        let dhcp_pool_end = fnet::Ipv4Address { addr: (broadcast_addr_as_u32 - 1).to_be_bytes() };
3091
3092        let v = fnet_dhcp::AddressPool {
3093            prefix_length: Some(WLAN_AP_PREFIX_LEN.get()),
3094            range_start: Some(dhcp_pool_start),
3095            range_stop: Some(dhcp_pool_end),
3096            ..Default::default()
3097        };
3098        debug!("setting DHCP AddressPool parameter to {:?}", v);
3099        dhcp_server
3100            .set_parameter(&fnet_dhcp::Parameter::AddressPool(v))
3101            .await
3102            .context("error sending set DHCP AddressPool parameter request")
3103            .map_err(errors::Error::NonFatal)?
3104            .map_err(zx::Status::from_raw)
3105            .context("error setting DHCP AddressPool parameter")
3106            .map_err(errors::Error::NonFatal)
3107    }
3108
3109    async fn handle_dhcpv6_acquire_prefix(
3110        &mut self,
3111        fnet_dhcpv6::AcquirePrefixConfig {
3112            interface_id,
3113            preferred_prefix_len,
3114            ..
3115        }: fnet_dhcpv6::AcquirePrefixConfig,
3116        prefix: fidl::endpoints::ServerEnd<fnet_dhcpv6::PrefixControlMarker>,
3117        dns_watchers: &mut DnsServerWatchers<'_>,
3118    ) -> Result<(), errors::Error> {
3119        let (prefix_control_request_stream, control_handle) =
3120            prefix.into_stream_and_control_handle();
3121
3122        let dhcpv6_client_provider = if let Some(s) = self.dhcpv6_client_provider.as_ref() {
3123            s
3124        } else {
3125            warn!(
3126                "Attempted to acquire prefix when DHCPv6 is not \
3127                supported; interface_id={:?}, preferred_prefix_len={:?}",
3128                interface_id, preferred_prefix_len,
3129            );
3130            return control_handle
3131                .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::NotSupported)
3132                .context("failed to send NotSupported terminal event")
3133                .map_err(errors::Error::NonFatal);
3134        };
3135
3136        let interface_config = if let Some(interface_id) = interface_id {
3137            match self
3138                .interface_states
3139                .get(&interface_id.try_into().expect("interface ID should be nonzero"))
3140            {
3141                None => {
3142                    warn!(
3143                        "Attempted to acquire a prefix on unmanaged interface; \
3144                        id={:?}, preferred_prefix_len={:?}",
3145                        interface_id, preferred_prefix_len,
3146                    );
3147                    return control_handle
3148                        .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::InvalidInterface)
3149                        .context("failed to send InvalidInterface terminal event")
3150                        .map_err(errors::Error::NonFatal);
3151                }
3152                Some(InterfaceState { config: InterfaceConfigState::WlanAp(_), .. }) => {
3153                    warn!(
3154                        "Attempted to acquire a prefix on AP interface; \
3155                        id={:?}, preferred_prefix_len={:?}",
3156                        interface_id, preferred_prefix_len,
3157                    );
3158                    return control_handle
3159                        .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::InvalidInterface)
3160                        .context("failed to send InvalidInterface terminal event")
3161                        .map_err(errors::Error::NonFatal);
3162                }
3163                Some(InterfaceState { config: InterfaceConfigState::Blackhole(_), .. }) => {
3164                    warn!(
3165                        "Attempted to acquire a prefix on blackhole interface; \
3166                        id={:?}, preferred_prefix_len={:?}",
3167                        interface_id, preferred_prefix_len,
3168                    );
3169                    return control_handle
3170                        .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::InvalidInterface)
3171                        .context("failed to send InvalidInterface terminal event")
3172                        .map_err(errors::Error::NonFatal);
3173                }
3174                Some(InterfaceState {
3175                    config:
3176                        InterfaceConfigState::Host(HostInterfaceState {
3177                            dhcpv4_client: _,
3178                            dhcpv6_client_state: _,
3179                            dhcpv6_pd_config: _,
3180                            interface_admin_auth: _,
3181                            interface_naming_id: _,
3182                        }),
3183                    ..
3184                }) => dhcpv6::AcquirePrefixInterfaceConfig::Id(interface_id),
3185            }
3186        } else {
3187            dhcpv6::AcquirePrefixInterfaceConfig::Upstreams
3188        };
3189        let pd_config = if let Some(preferred_prefix_len) = preferred_prefix_len {
3190            if preferred_prefix_len > net_types::ip::Ipv6Addr::BYTES * 8 {
3191                warn!(
3192                    "Preferred prefix length exceeds bits in IPv6 address; \
3193                    interface_id={:?}, preferred_prefix_len={:?}",
3194                    interface_id, preferred_prefix_len,
3195                );
3196                return control_handle
3197                    .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::InvalidPrefixLength)
3198                    .context("failed to send InvalidPrefixLength terminal event")
3199                    .map_err(errors::Error::NonFatal);
3200            }
3201            fnet_dhcpv6::PrefixDelegationConfig::PrefixLength(preferred_prefix_len)
3202        } else {
3203            fnet_dhcpv6::PrefixDelegationConfig::Empty(fnet_dhcpv6::Empty)
3204        };
3205
3206        // TODO(https://fxbug.dev/142065403): Support multiple clients asking
3207        // for IPv6 prefixes.
3208        if self.dhcpv6_prefix_provider_handler.is_some() {
3209            warn!(
3210                "Attempted to acquire a prefix while a prefix is already being acquired for \
3211                another iface; interface_id={:?}, preferred_prefix_len={:?}",
3212                interface_id, preferred_prefix_len,
3213            );
3214            return control_handle
3215                .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::AlreadyAcquiring)
3216                .context("failed to send AlreadyAcquiring terminal event")
3217                .map_err(errors::Error::NonFatal);
3218        }
3219
3220        let interface_state_iter = match interface_config {
3221            dhcpv6::AcquirePrefixInterfaceConfig::Id(want_id) => {
3222                let want_id = want_id.try_into().expect("interface ID should be nonzero");
3223                either::Either::Left(std::iter::once((
3224                    want_id,
3225                    self.interface_states
3226                        .get_mut(&want_id)
3227                        .unwrap_or_else(|| panic!("interface {} state not present", want_id)),
3228                )))
3229            }
3230            dhcpv6::AcquirePrefixInterfaceConfig::Upstreams => either::Either::Right(
3231                self.interface_states.iter_mut().filter_map(|(id, if_state)| {
3232                    self.allowed_upstream_device_classes
3233                        .contains(&if_state.device_class)
3234                        .then_some((*id, if_state))
3235                }),
3236            ),
3237        };
3238        // Look for all eligible interfaces and start/restart DHCPv6 client as needed.
3239        for (id, InterfaceState { config, .. }) in interface_state_iter {
3240            let HostInterfaceState {
3241                dhcpv4_client: _,
3242                dhcpv6_client_state,
3243                dhcpv6_pd_config,
3244                interface_admin_auth: _,
3245                interface_naming_id,
3246            } = match config {
3247                InterfaceConfigState::Host(state) => state,
3248                InterfaceConfigState::WlanAp(WlanApInterfaceState { interface_naming_id: _ })
3249                | InterfaceConfigState::Blackhole(_) => {
3250                    continue;
3251                }
3252            };
3253
3254            // Save the config so that future DHCPv6 clients are started with PD.
3255            *dhcpv6_pd_config = Some(pd_config.clone());
3256
3257            let fnet_interfaces_ext::PropertiesAndState { properties, state: _ } =
3258                if let Some(properties) = self.interface_properties.get(&id) {
3259                    properties
3260                } else {
3261                    // There is a delay between when netcfg installs and when said interface's
3262                    // properties are received via the interface watcher and ends up in
3263                    // `interface_properties`, so if the properties are not yet known, simply
3264                    // continue. The DHCPv6 client on such an interface will be started with PD
3265                    // configured per the usual process when handling interface watcher events.
3266                    continue;
3267                };
3268
3269            // TODO(https://fxbug.dev/42068818): Reload configuration in-place rather than
3270            // restarting the DHCPv6 client with different configuration.
3271            // Stop DHCPv6 client if it's running.
3272            if let Some::<dhcpv6::ClientState>(_) = dhcpv6_client_state.take() {
3273                dhcpv6::stop_client(
3274                    &self.lookup_admin,
3275                    &mut self.dns_servers,
3276                    &mut self.dns_server_watch_responders,
3277                    &mut self.netpol_networks_service,
3278                    id,
3279                    dns_watchers,
3280                    &mut self.dhcpv6_prefixes_streams,
3281                )
3282                .await;
3283            }
3284
3285            // Restart DHCPv6 client and configure it to perform PD.
3286            let sockaddr = match start_dhcpv6_client(
3287                properties,
3288                dhcpv6::duid(interface_naming_id.mac),
3289                &dhcpv6_client_provider,
3290                Some(pd_config.clone()),
3291                dns_watchers,
3292                &mut self.dhcpv6_prefixes_streams,
3293            )
3294            .context("starting DHCPv6 client with PD")
3295            {
3296                Ok(dhcpv6_client_addr) => dhcpv6_client_addr,
3297                Err(errors::Error::NonFatal(e)) => {
3298                    warn!("error restarting DHCPv6 client to perform PD: {:?}", e);
3299                    None
3300                }
3301                Err(errors::Error::Fatal(e)) => {
3302                    panic!("error restarting DHCPv6 client to perform PD: {:?}", e);
3303                }
3304            };
3305            *dhcpv6_client_state = sockaddr.map(dhcpv6::ClientState::new);
3306        }
3307        self.dhcpv6_prefix_provider_handler = Some(dhcpv6::PrefixProviderHandler {
3308            prefix_control_request_stream,
3309            watch_prefix_responder: None,
3310            current_prefix: None,
3311            interface_config,
3312            preferred_prefix_len,
3313        });
3314        Ok(())
3315    }
3316
3317    async fn handle_watch_prefix(
3318        &mut self,
3319        responder: fnet_dhcpv6::PrefixControlWatchPrefixResponder,
3320        dns_watchers: &mut DnsServerWatchers<'_>,
3321    ) -> Result<(), anyhow::Error> {
3322        let dhcpv6::PrefixProviderHandler {
3323            watch_prefix_responder,
3324            interface_config: _,
3325            prefix_control_request_stream: _,
3326            preferred_prefix_len: _,
3327            current_prefix: _,
3328        } = self
3329            .dhcpv6_prefix_provider_handler
3330            .as_mut()
3331            .expect("DHCPv6 prefix provider handler must be present to handle WatchPrefix");
3332        if let Some(responder) = watch_prefix_responder.take() {
3333            warn!("Attempted to call WatchPrefix twice on PrefixControl channel, closing channel");
3334            match responder
3335                .control_handle()
3336                .send_on_exit(fnet_dhcpv6::PrefixControlExitReason::DoubleWatch)
3337            {
3338                Err(e) => {
3339                    warn!(
3340                        "failed to send DoubleWatch terminal event on PrefixControl channel: {:?}",
3341                        e
3342                    );
3343                }
3344                Ok(()) => {}
3345            }
3346            self.on_dhcpv6_prefix_control_close(dns_watchers).await;
3347            Ok(())
3348        } else {
3349            *watch_prefix_responder = Some(responder);
3350
3351            dhcpv6::maybe_send_watch_prefix_response(
3352                &self.interface_states,
3353                &self.allowed_upstream_device_classes,
3354                self.dhcpv6_prefix_provider_handler.as_mut(),
3355            )
3356        }
3357    }
3358
3359    async fn on_dhcpv6_prefix_control_close(&mut self, dns_watchers: &mut DnsServerWatchers<'_>) {
3360        let _: dhcpv6::PrefixProviderHandler = self
3361            .dhcpv6_prefix_provider_handler
3362            .take()
3363            .expect("DHCPv6 prefix provider handler must be present");
3364        let dhcpv6_client_provider =
3365            self.dhcpv6_client_provider.as_ref().expect("DHCPv6 client provider must be present");
3366        for (id, InterfaceState { config, .. }) in self.interface_states.iter_mut() {
3367            let (dhcpv6_client_state, interface_naming_id) = match config {
3368                InterfaceConfigState::WlanAp(WlanApInterfaceState { interface_naming_id: _ })
3369                | InterfaceConfigState::Blackhole(_) => {
3370                    continue;
3371                }
3372                InterfaceConfigState::Host(HostInterfaceState {
3373                    dhcpv4_client: _,
3374                    dhcpv6_client_state,
3375                    dhcpv6_pd_config,
3376                    interface_admin_auth: _,
3377                    interface_naming_id,
3378                }) => {
3379                    if dhcpv6_pd_config.take().is_none() {
3380                        continue;
3381                    }
3382                    match dhcpv6_client_state.take() {
3383                        Some(_) => (dhcpv6_client_state, interface_naming_id),
3384                        None => continue,
3385                    }
3386                }
3387            };
3388
3389            // TODO(https://fxbug.dev/42068818): Reload configuration in-place rather than
3390            // restarting the DHCPv6 client with different configuration.
3391            // Stop DHCPv6 client if it's running.
3392            dhcpv6::stop_client(
3393                &self.lookup_admin,
3394                &mut self.dns_servers,
3395                &mut self.dns_server_watch_responders,
3396                &mut self.netpol_networks_service,
3397                *id,
3398                dns_watchers,
3399                &mut self.dhcpv6_prefixes_streams,
3400            )
3401            .await;
3402
3403            let fnet_interfaces_ext::PropertiesAndState { properties, state: _ } =
3404                self.interface_properties.get(id).unwrap_or_else(|| {
3405                    panic!("interface {} has DHCPv6 client but properties unknown", id)
3406                });
3407
3408            // Restart DHCPv6 client without PD.
3409            let sockaddr = match start_dhcpv6_client(
3410                properties,
3411                dhcpv6::duid(interface_naming_id.mac),
3412                &dhcpv6_client_provider,
3413                None,
3414                dns_watchers,
3415                &mut self.dhcpv6_prefixes_streams,
3416            )
3417            .context("starting DHCPv6 client with PD")
3418            {
3419                Ok(dhcpv6_client_addr) => dhcpv6_client_addr,
3420                Err(errors::Error::NonFatal(e)) => {
3421                    warn!("restarting DHCPv6 client to stop PD: {:?}", e);
3422                    None
3423                }
3424                Err(e @ errors::Error::Fatal(_)) => {
3425                    panic!("restarting DHCPv6 client to stop PD: {:?}", e);
3426                }
3427            };
3428            *dhcpv6_client_state = sockaddr.map(dhcpv6::ClientState::new);
3429        }
3430    }
3431
3432    async fn handle_dhcpv4_configuration(
3433        &mut self,
3434        interface_id: InterfaceId,
3435        res: Result<fnet_dhcp_ext::Configuration, fnet_dhcp_ext::Error>,
3436    ) -> Dhcpv4ConfigurationHandlerResult {
3437        let configuration = match res {
3438            Err(error) => {
3439                let allow_restart = match error {
3440                    fnet_dhcp_ext::Error::UnexpectedExit(reason) => match reason {
3441                        Some(reason) => match reason {
3442                            fnet_dhcp::ClientExitReason::AddressRemovedByUser => {
3443                                log::warn!(
3444                                    "DHCP client exited because its \
3445                                    bound address was removed (iface={interface_id})"
3446                                );
3447                                // The user intentionally removed the DHCP client's address, so we
3448                                // shouldn't automatically restart the client.
3449                                AllowClientRestart::No
3450                            }
3451                            reason @ (
3452                                fnet_dhcp::ClientExitReason::ClientAlreadyExistsOnInterface
3453                                | fnet_dhcp::ClientExitReason::WatchConfigurationAlreadyPending
3454                                | fnet_dhcp::ClientExitReason::InvalidInterface
3455                                | fnet_dhcp::ClientExitReason::InvalidParams
3456                                | fnet_dhcp::ClientExitReason::NetworkUnreachable
3457                                | fnet_dhcp::ClientExitReason::UnableToOpenSocket
3458                                | fnet_dhcp::ClientExitReason::GracefulShutdown
3459                                | fnet_dhcp::ClientExitReason::AddressStateProviderError
3460                            ) => {
3461                                log::error!("DHCP client unexpectedly exited \
3462                                                (iface={interface_id}, reason={reason:?})");
3463                                // The exit was unexpected, so we should restart the client.
3464                                AllowClientRestart::Yes
3465                            }
3466                        },
3467                        None => {
3468                            log::error!(
3469                                "DHCP client unexpectedly exited without \
3470                                giving a reason (iface={interface_id})"
3471                            );
3472                            AllowClientRestart::Yes
3473                        }
3474                    },
3475                    error @ (fnet_dhcp_ext::Error::ApiViolation(_)
3476                    | fnet_dhcp_ext::Error::RouteSet(_)
3477                    | fnet_dhcp_ext::Error::Fidl(_)
3478                    | fnet_dhcp_ext::Error::WrongExitReason(_)
3479                    | fnet_dhcp_ext::Error::MissingExitReason) => {
3480                        log::error!("DHCP client exited due to error \
3481                                        (iface={interface_id}, error={error:?})");
3482                                        AllowClientRestart::Yes
3483                    }
3484                };
3485                return Dhcpv4ConfigurationHandlerResult::ClientStopped(allow_restart);
3486            }
3487            Ok(configuration) => configuration,
3488        };
3489
3490        let (dhcpv4_client, control) = {
3491            let InterfaceState { config, control, .. } = self
3492                .interface_states
3493                .get_mut(&interface_id)
3494                .unwrap_or_else(|| panic!("interface {} not found", interface_id));
3495
3496            match config {
3497                InterfaceConfigState::Host(HostInterfaceState {
3498                    dhcpv4_client,
3499                    dhcpv6_client_state: _,
3500                    dhcpv6_pd_config: _,
3501                    interface_admin_auth: _,
3502                    interface_naming_id: _,
3503                }) => (
3504                    match dhcpv4_client {
3505                        Dhcpv4ClientState::Running(client) => client,
3506                        Dhcpv4ClientState::NotRunning | Dhcpv4ClientState::ScheduledRestart(_) => {
3507                            panic!(
3508                                "interface {} does not have a running DHCPv4 client",
3509                                interface_id
3510                            )
3511                        }
3512                    },
3513                    control,
3514                ),
3515                InterfaceConfigState::WlanAp(wlan_ap_state) => {
3516                    panic!(
3517                        "interface {} expected to be host but is WLAN AP with state {:?}",
3518                        interface_id, wlan_ap_state
3519                    );
3520                }
3521                InterfaceConfigState::Blackhole(state) => {
3522                    panic!(
3523                        "interface {} expected to be host but is blackhole with state {:?}",
3524                        interface_id, state
3525                    );
3526                }
3527            }
3528        };
3529
3530        dhcpv4::update_configuration(
3531            interface_id,
3532            dhcpv4_client,
3533            configuration,
3534            &mut self.dns_servers,
3535            &mut self.dns_server_watch_responders,
3536            &mut self.netpol_networks_service,
3537            control,
3538            &self.lookup_admin,
3539        )
3540        .await;
3541
3542        Dhcpv4ConfigurationHandlerResult::ContinueOperation
3543    }
3544
3545    async fn handle_dhcpv6_prefixes(
3546        &mut self,
3547        interface_id: InterfaceId,
3548        res: Result<Vec<fnet_dhcpv6::Prefix>, fidl::Error>,
3549        dns_watchers: &mut DnsServerWatchers<'_>,
3550    ) -> Result<(), anyhow::Error> {
3551        let new_prefixes = match res
3552            .context("DHCPv6 prefixes stream FIDL error")
3553            .and_then(|prefixes| dhcpv6::from_fidl_prefixes(&prefixes))
3554        {
3555            Err(e) => {
3556                warn!(
3557                    "DHCPv6 client on interface={} error on watch_prefixes: {:?}",
3558                    interface_id, e
3559                );
3560                dhcpv6::stop_client(
3561                    &self.lookup_admin,
3562                    &mut self.dns_servers,
3563                    &mut self.dns_server_watch_responders,
3564                    &mut self.netpol_networks_service,
3565                    interface_id,
3566                    dns_watchers,
3567                    &mut self.dhcpv6_prefixes_streams,
3568                )
3569                .await;
3570                HashMap::new()
3571            }
3572            Ok(prefixes_map) => prefixes_map,
3573        };
3574        let InterfaceState { config, .. } = self
3575            .interface_states
3576            .get_mut(&interface_id)
3577            .unwrap_or_else(|| panic!("interface {} not found", interface_id));
3578        let dhcpv6::ClientState { prefixes, sockaddr: _ } = match config {
3579            InterfaceConfigState::Host(HostInterfaceState {
3580                dhcpv4_client: _,
3581                dhcpv6_client_state,
3582                dhcpv6_pd_config: _,
3583                interface_admin_auth: _,
3584                interface_naming_id: _,
3585            }) => dhcpv6_client_state.as_mut().unwrap_or_else(|| {
3586                panic!("interface {} does not have a running DHCPv6 client", interface_id)
3587            }),
3588            InterfaceConfigState::WlanAp(wlan_ap_state) => {
3589                panic!(
3590                    "interface {} expected to be host but is WLAN AP with state {:?}",
3591                    interface_id, wlan_ap_state
3592                );
3593            }
3594            InterfaceConfigState::Blackhole(state) => {
3595                panic!(
3596                    "interface {} expected to be host but is blackhole with state {:?}",
3597                    interface_id, state
3598                );
3599            }
3600        };
3601        *prefixes = new_prefixes;
3602
3603        dhcpv6::maybe_send_watch_prefix_response(
3604            &self.interface_states,
3605            &self.allowed_upstream_device_classes,
3606            self.dhcpv6_prefix_provider_handler.as_mut(),
3607        )
3608    }
3609
3610    async fn get_interface_naming_identifier_for_instance(
3611        &self,
3612        device_instance: &devices::NetworkDeviceInstance,
3613    ) -> Result<interface::InterfaceNamingIdentifier, anyhow::Error> {
3614        let device_info = device_instance
3615            .get_device_info()
3616            .await
3617            .context("error getting device info and MAC")
3618            .map_err(|e| match e {
3619                errors::Error::NonFatal(e) | errors::Error::Fatal(e) => e,
3620            })?;
3621
3622        let DeviceInfo { mac, port_class: _, topological_path: _ } = &device_info;
3623        let mac = mac.ok_or_else(|| anyhow!("devices without mac not supported"))?;
3624
3625        Ok(interface::generate_identifier(&mac))
3626    }
3627}
3628
3629pub async fn run<M: Mode>() -> Result<(), anyhow::Error> {
3630    let opt: Opt = argh::from_env();
3631    let Opt { min_severity: LogLevel(min_severity), config_data } = &opt;
3632
3633    // Use the diagnostics_log library directly rather than e.g. the #[fuchsia::main] macro on
3634    // the main function, so that we can specify the logging severity level at runtime based on a
3635    // command line argument.
3636    diagnostics_log::initialize(
3637        diagnostics_log::PublishOptions::default().minimum_severity(*min_severity),
3638    )?;
3639
3640    info!("starting");
3641    debug!("starting with options = {:?}", opt);
3642
3643    let Config {
3644        dns_config: DnsConfig { servers },
3645        filter_config,
3646        filter_enabled_interface_types,
3647        interface_metrics,
3648        allowed_upstream_device_classes: AllowedDeviceClasses(allowed_upstream_device_classes),
3649        allowed_bridge_upstream_device_classes:
3650            AllowedDeviceClasses(allowed_bridge_upstream_device_classes),
3651        enable_dhcpv6,
3652        forwarded_device_classes,
3653        interface_naming_policy,
3654        interface_provisioning_policy,
3655        blackhole_interfaces,
3656        enable_socket_proxy,
3657    } = Config::load(config_data)?;
3658
3659    info!("using naming policy: {interface_naming_policy:?}");
3660    info!("using provisioning policy: {interface_provisioning_policy:?}");
3661
3662    let mut netcfg = NetCfg::new(
3663        filter_enabled_interface_types,
3664        interface_metrics,
3665        enable_dhcpv6,
3666        forwarded_device_classes,
3667        &allowed_upstream_device_classes,
3668        interface_naming_policy,
3669        interface_provisioning_policy,
3670        enable_socket_proxy,
3671    )
3672    .await
3673    .context("error creating new netcfg instance")?;
3674
3675    for name in &blackhole_interfaces {
3676        let result = netcfg.add_blackhole_interface(name).await;
3677
3678        match result {
3679            Ok(()) => {}
3680            Err(devices::AddDeviceError::AlreadyExists(name)) => {
3681                // Either the interface with this name was not installed
3682                // by Netcfg or another device installed by Netcfg used
3683                // the same name already. We will reject interface
3684                // installation.
3685                error!(
3686                    "interface with name ({name}) is already present in the Netstack, \
3687                    so we're rejecting installation of a blackhole interface with that name."
3688                );
3689            }
3690            Err(devices::AddDeviceError::Other(errors::Error::NonFatal(e))) => {
3691                error!("non-fatal error adding blackhole interface {:?}: {:?}", name, e);
3692            }
3693            Err(devices::AddDeviceError::Other(errors::Error::Fatal(e))) => {
3694                return Err(e.context(format!("error adding new blackhole interface {:?}", name)));
3695            }
3696        }
3697    }
3698
3699    // TODO(https://fxbug.dev/42080661): Once non-Fuchsia components can control filtering rules, disable
3700    // setting filters when interfaces are in Delegated provisioning mode.
3701    netcfg
3702        .filter_control
3703        .update_filters(filter_config)
3704        .await
3705        .context("update filters based on config")?;
3706
3707    // TODO(https://fxbug.dev/42080096): Once non-Fuchsia components can control DNS servers, disable
3708    // setting default DNS servers when interfaces are in Delegated provisioning mode.
3709    let servers = servers.into_iter().map(static_source_from_ip).collect();
3710    debug!("updating default servers to {:?}", servers);
3711    netcfg.update_dns_servers(DnsServersUpdateSource::Default, servers).await;
3712
3713    M::run(netcfg, allowed_bridge_upstream_device_classes)
3714        .map_err(|e| {
3715            let err_str = format!("fatal error running main: {:?}", e);
3716            error!("{}", err_str);
3717            anyhow!(err_str)
3718        })
3719        .await
3720}
3721
3722/// Allows callers of `netcfg::run` to configure at compile time which features
3723/// should be enabled.
3724///
3725/// This trait may be expanded to support combinations of features that can be
3726/// assembled together for specific netcfg builds.
3727#[async_trait(?Send)]
3728pub trait Mode {
3729    async fn run<'a>(
3730        netcfg: NetCfg<'a>,
3731        allowed_bridge_upstream_device_classes: HashSet<DeviceClass>,
3732    ) -> Result<(), anyhow::Error>;
3733}
3734
3735/// In this configuration, netcfg acts as the policy manager for netstack,
3736/// watching for device events and configuring the netstack with new interfaces
3737/// as needed on new device discovery. It does not implement any FIDL protocols.
3738pub enum BasicMode {}
3739
3740#[async_trait(?Send)]
3741impl Mode for BasicMode {
3742    async fn run<'a>(
3743        mut netcfg: NetCfg<'a>,
3744        _allowed_bridge_upstream_device_classes: HashSet<DeviceClass>,
3745    ) -> Result<(), anyhow::Error> {
3746        netcfg.run(virtualization::Stub).await.context("event loop")
3747    }
3748}
3749
3750/// In this configuration, netcfg implements the base functionality included in
3751/// `BasicMode`, and also serves the `fuchsia.net.virtualization/Control`
3752/// protocol, allowing clients to create virtual networks.
3753pub enum VirtualizationEnabled {}
3754
3755#[async_trait(?Send)]
3756impl Mode for VirtualizationEnabled {
3757    async fn run<'a>(
3758        mut netcfg: NetCfg<'a>,
3759        allowed_bridge_upstream_device_classes: HashSet<DeviceClass>,
3760    ) -> Result<(), anyhow::Error> {
3761        let handler = virtualization::Virtualization::new(
3762            netcfg.allowed_upstream_device_classes,
3763            allowed_bridge_upstream_device_classes,
3764            virtualization::BridgeHandlerImpl::new(netcfg.stack.clone()),
3765            netcfg.installer.clone(),
3766        );
3767        netcfg.run(handler).await.context("event loop")
3768    }
3769}
3770
3771fn map_control_error(
3772    ctx: &'static str,
3773) -> impl FnOnce(
3774    fnet_interfaces_ext::admin::TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
3775) -> errors::Error {
3776    move |e| {
3777        let severity = match &e {
3778            fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Fidl(e) => {
3779                if e.is_closed() {
3780                    // Control handle can close when interface is
3781                    // removed; not a fatal error.
3782                    errors::Error::NonFatal
3783                } else {
3784                    errors::Error::Fatal
3785                }
3786            }
3787            fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(e) => match e {
3788                fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::DuplicateName
3789                | fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortAlreadyBound
3790                | fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::BadPort => {
3791                    errors::Error::Fatal
3792                }
3793                fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::PortClosed
3794                | fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::User
3795                | _ => errors::Error::NonFatal,
3796            },
3797        };
3798        severity(anyhow::Error::new(e).context(ctx))
3799    }
3800}
3801
3802fn map_address_state_provider_error(
3803    ctx: &'static str,
3804) -> impl FnOnce(fnet_interfaces_ext::admin::AddressStateProviderError) -> errors::Error {
3805    move |e| {
3806        let severity = match &e {
3807            fnet_interfaces_ext::admin::AddressStateProviderError::Fidl(e) => {
3808                if e.is_closed() {
3809                    // TODO(https://fxbug.dev/42170615): Reconsider whether this
3810                    // should be a fatal error, as it can be caused by a
3811                    // netstack bug.
3812                    errors::Error::NonFatal
3813                } else {
3814                    errors::Error::Fatal
3815                }
3816            }
3817            fnet_interfaces_ext::admin::AddressStateProviderError::ChannelClosed => {
3818                // TODO(https://fxbug.dev/42170615): Reconsider whether this should
3819                // be a fatal error, as it can be caused by a netstack bug.
3820                errors::Error::NonFatal
3821            }
3822            fnet_interfaces_ext::admin::AddressStateProviderError::AddressRemoved(e) => match e {
3823                fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::Invalid
3824                | fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::InvalidProperties => {
3825                    errors::Error::Fatal
3826                }
3827                fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::AlreadyAssigned
3828                | fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::DadFailed
3829                | fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::Forfeited
3830                | fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::InterfaceRemoved
3831                | fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::UserRemoved => {
3832                    errors::Error::NonFatal
3833                }
3834            },
3835        };
3836        severity(anyhow::Error::new(e).context(ctx))
3837    }
3838}
3839
3840/// If we can't reach netstack via fidl, log an error and exit.
3841//
3842// TODO(https://fxbug.dev/42070352): add a test that works as intended.
3843pub(crate) fn exit_with_fidl_error(cause: fidl::Error) -> ! {
3844    error!(cause:%; "exiting due to fidl error");
3845    std::process::exit(1);
3846}
3847
3848/// Installs the rule that directs all unmarked sockets to lookup the main table
3849/// where all the locally-provisioned network related routes live.
3850///
3851/// This needs to have a high priority so that it is not overridden later.
3852async fn install_locally_provisioned_network_rule_set<
3853    I: fnet_routes_ext::rules::FidlRuleIpExt
3854        + fnet_routes_ext::rules::FidlRuleAdminIpExt
3855        + fnet_routes_ext::FidlRouteIpExt
3856        + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3857>() -> Result<<I::RuleSetMarker as ProtocolMarker>::Proxy, anyhow::Error> {
3858    let rule_table = fuchsia_component::client::connect_to_protocol::<I::RuleTableMarker>()?;
3859    // The `RouteTableV{4,6}` protocol provides access to the main table.
3860    let main_route_table = fuchsia_component::client::connect_to_protocol::<I::RouteTableMarker>()?;
3861
3862    let fidl_fuchsia_net_routes_admin::GrantForRouteTableAuthorization { table_id, token } =
3863        fnet_routes_ext::admin::get_authorization_for_route_table::<I>(&main_route_table)
3864            .await
3865            .context("failed to get authorization for the main table")?;
3866
3867    const LOCALLY_PROVISIONED_NETWORK_RULE_SET_PRIORITY: u32 = 0;
3868    let rule_set = fnet_routes_ext::rules::new_rule_set::<I>(
3869        &rule_table,
3870        fnet_routes_ext::rules::RuleSetPriority::from(
3871            LOCALLY_PROVISIONED_NETWORK_RULE_SET_PRIORITY,
3872        ),
3873    )
3874    .context("failed to create a new rule set")?;
3875
3876    fnet_routes_ext::rules::authenticate_for_route_table::<I>(&rule_set, table_id, token)
3877        .await
3878        .context("fidl error authenticating for route table")?
3879        .map_err(|err| anyhow::anyhow!("failed to authenticate for the route table: {err:?}"))?;
3880
3881    const LOCALLY_PROVISIONED_NETWORK_RULE_INDEX: u32 = 0;
3882    fnet_routes_ext::rules::add_rule::<I>(
3883        &rule_set,
3884        fnet_routes_ext::rules::RuleIndex::from(LOCALLY_PROVISIONED_NETWORK_RULE_INDEX),
3885        fnet_routes_ext::rules::RuleMatcher {
3886            mark_1: Some(fnet_matchers_ext::Mark::Unmarked),
3887            mark_2: Some(fnet_matchers_ext::Mark::Unmarked),
3888            ..Default::default()
3889        },
3890        fnet_routes_ext::rules::RuleAction::Lookup(fnet_routes_ext::TableId::new(table_id)),
3891    )
3892    .await
3893    .context("fidl error adding rule")?
3894    .map_err(|err| anyhow::anyhow!("failed to add the rule: {err:?}"))?;
3895
3896    Ok(rule_set)
3897}
3898
3899#[cfg(test)]
3900mod tests {
3901    use fidl_fuchsia_net_ext::FromExt as _;
3902    use {
3903        fidl_fuchsia_net_dhcpv6_ext as fnet_dhcpv6_ext, fidl_fuchsia_net_routes as fnet_routes,
3904        fidl_fuchsia_net_routes_admin as fnet_routes_admin,
3905        fidl_fuchsia_net_routes_ext as fnet_routes_ext,
3906    };
3907
3908    use assert_matches::assert_matches;
3909    use futures::future::{self, FutureExt as _};
3910    use futures::stream::{FusedStream as _, TryStreamExt as _};
3911    use net_declare::{
3912        fidl_ip, fidl_ip_v4_with_prefix, fidl_ip_v6, fidl_ip_v6_with_prefix, fidl_mac, fidl_subnet,
3913    };
3914    use pretty_assertions::assert_eq;
3915    use test_case::test_case;
3916
3917    use super::*;
3918    use crate::interface::ProvisioningType::{Delegated, Local};
3919    use crate::socketproxy::socketproxy_utils::respond_to_socketproxy;
3920
3921    impl Config {
3922        pub fn load_str(s: &str) -> Result<Self, anyhow::Error> {
3923            let config = serde_json5::from_str(s)
3924                .with_context(|| format!("could not deserialize the config data {}", s))?;
3925            Ok(config)
3926        }
3927    }
3928
3929    struct ServerEnds {
3930        lookup_admin: fnet_name::LookupAdminRequestStream,
3931        dhcpv4_client_provider: fnet_dhcp::ClientProviderRequestStream,
3932        dhcpv6_client_provider: fnet_dhcpv6::ClientProviderRequestStream,
3933        route_set_v4_provider: fidl::endpoints::ServerEnd<fnet_routes_admin::RouteTableV4Marker>,
3934        dhcpv4_server: fidl::endpoints::ServerEnd<fnet_dhcp::Server_Marker>,
3935        fuchsia_networks: fidl::endpoints::ServerEnd<fnp_socketproxy::FuchsiaNetworksMarker>,
3936    }
3937
3938    impl Into<anyhow::Error> for errors::Error {
3939        fn into(self) -> anyhow::Error {
3940            match self {
3941                errors::Error::NonFatal(e) => e,
3942                errors::Error::Fatal(e) => e,
3943            }
3944        }
3945    }
3946
3947    impl Into<fidl_fuchsia_hardware_network::PortClass> for DeviceClass {
3948        fn into(self) -> fidl_fuchsia_hardware_network::PortClass {
3949            match self {
3950                Self::Virtual => fidl_fuchsia_hardware_network::PortClass::Virtual,
3951                Self::Ethernet => fidl_fuchsia_hardware_network::PortClass::Ethernet,
3952                Self::WlanClient => fidl_fuchsia_hardware_network::PortClass::WlanClient,
3953                Self::Ppp => fidl_fuchsia_hardware_network::PortClass::Ppp,
3954                Self::Bridge => fidl_fuchsia_hardware_network::PortClass::Bridge,
3955                Self::WlanAp => fidl_fuchsia_hardware_network::PortClass::WlanAp,
3956                Self::Lowpan => fidl_fuchsia_hardware_network::PortClass::Lowpan,
3957                Self::Blackhole => fidl_fuchsia_hardware_network::PortClass::Virtual,
3958            }
3959        }
3960    }
3961
3962    static DEFAULT_ALLOWED_UPSTREAM_DEVICE_CLASSES: std::sync::LazyLock<HashSet<DeviceClass>> =
3963        std::sync::LazyLock::new(HashSet::new);
3964
3965    struct NetcfgTestArgs {
3966        with_dhcpv4_client_provider: bool,
3967        with_fuchsia_networks: bool,
3968    }
3969
3970    fn test_netcfg(args: NetcfgTestArgs) -> Result<(NetCfg<'static>, ServerEnds), anyhow::Error> {
3971        let (stack, _stack_server) = fidl::endpoints::create_proxy::<fnet_stack::StackMarker>();
3972        let (lookup_admin, lookup_admin_server) =
3973            fidl::endpoints::create_proxy::<fnet_name::LookupAdminMarker>();
3974        let (filter, _filter_server) =
3975            fidl::endpoints::create_proxy::<fnet_filter_deprecated::FilterMarker>();
3976        let filter_control = FilterControl::Deprecated(filter);
3977        let (interface_state, _interface_state_server) =
3978            fidl::endpoints::create_proxy::<fnet_interfaces::StateMarker>();
3979        let (dhcp_server, dhcp_server_server_end) =
3980            fidl::endpoints::create_proxy::<fnet_dhcp::Server_Marker>();
3981        let (dhcpv4_client_provider, dhcpv4_client_provider_server) =
3982            fidl::endpoints::create_proxy::<fnet_dhcp::ClientProviderMarker>();
3983        let (dhcpv6_client_provider, dhcpv6_client_provider_server) =
3984            fidl::endpoints::create_proxy::<fnet_dhcpv6::ClientProviderMarker>();
3985        let (installer, _installer_server) =
3986            fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces_admin::InstallerMarker>();
3987        let (route_set_v4_provider, route_set_v4_provider_server) =
3988            fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV4Marker>();
3989        let (fuchsia_networks, fuchsia_networks_server) =
3990            fidl::endpoints::create_proxy::<fnp_socketproxy::FuchsiaNetworksMarker>();
3991        Ok((
3992            NetCfg {
3993                stack,
3994                lookup_admin,
3995                filter_control,
3996                interface_state,
3997                installer,
3998                dhcp_server: Some(dhcp_server),
3999                dhcpv4_client_provider: args
4000                    .with_dhcpv4_client_provider
4001                    .then_some(dhcpv4_client_provider),
4002                dhcpv6_client_provider: Some(dhcpv6_client_provider),
4003                route_set_v4_provider,
4004                socket_proxy_state: args
4005                    .with_fuchsia_networks
4006                    .then_some(SocketProxyState::new(fuchsia_networks)),
4007                locally_provisioned_network_rule_set: None,
4008                filter_enabled_state: Default::default(),
4009                interface_properties: Default::default(),
4010                interface_states: Default::default(),
4011                interface_metrics: Default::default(),
4012                dns_servers: Default::default(),
4013                dns_server_watch_responders: Default::default(),
4014                route_advertisement_watcher_provider: Default::default(),
4015                forwarded_device_classes: Default::default(),
4016                dhcpv6_prefix_provider_handler: Default::default(),
4017                allowed_upstream_device_classes: &DEFAULT_ALLOWED_UPSTREAM_DEVICE_CLASSES,
4018                dhcpv4_configuration_streams: dhcpv4::ConfigurationStreamMap::empty(),
4019                dhcpv6_prefixes_streams: dhcpv6::PrefixesStreamMap::empty(),
4020                interface_naming_config: interface::InterfaceNamingConfig::from_naming_rules(
4021                    vec![],
4022                ),
4023                interface_provisioning_policy: Default::default(),
4024                netpol_networks_service: Default::default(),
4025                enable_socket_proxy: false,
4026            },
4027            ServerEnds {
4028                lookup_admin: lookup_admin_server.into_stream(),
4029                dhcpv4_client_provider: dhcpv4_client_provider_server.into_stream(),
4030                dhcpv6_client_provider: dhcpv6_client_provider_server.into_stream(),
4031                route_set_v4_provider: route_set_v4_provider_server,
4032                dhcpv4_server: dhcp_server_server_end,
4033                fuchsia_networks: fuchsia_networks_server,
4034            },
4035        ))
4036    }
4037
4038    const INTERFACE_ID: InterfaceId = InterfaceId::new(1).unwrap();
4039    const DHCPV6_DNS_SOURCE: DnsServersUpdateSource =
4040        DnsServersUpdateSource::Dhcpv6 { interface_id: INTERFACE_ID.get() };
4041    const LINK_LOCAL_SOCKADDR1: fnet::Ipv6SocketAddress = fnet::Ipv6SocketAddress {
4042        address: fidl_ip_v6!("fe80::1"),
4043        port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
4044        zone_index: INTERFACE_ID.get(),
4045    };
4046    const LINK_LOCAL_SOCKADDR2: fnet::Ipv6SocketAddress = fnet::Ipv6SocketAddress {
4047        address: fidl_ip_v6!("fe80::2"),
4048        port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
4049        zone_index: INTERFACE_ID.get(),
4050    };
4051    const GLOBAL_ADDR: fnet::Subnet = fnet::Subnet { addr: fidl_ip!("2000::1"), prefix_len: 64 };
4052    const DNS_SERVER1: fnet::SocketAddress = fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
4053        address: fidl_ip_v6!("2001::1"),
4054        port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
4055        zone_index: 0,
4056    });
4057    const DNS_SERVER2: fnet::SocketAddress = fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
4058        address: fidl_ip_v6!("2001::2"),
4059        port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
4060        zone_index: 0,
4061    });
4062
4063    fn test_addr(addr: fnet::Subnet) -> fnet_interfaces::Address {
4064        fnet_interfaces_ext::Address::<fnet_interfaces_ext::DefaultInterest> {
4065            addr,
4066            valid_until: fnet_interfaces_ext::NoInterest,
4067            preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
4068            assignment_state: fnet_interfaces::AddressAssignmentState::Assigned,
4069        }
4070        .into()
4071    }
4072
4073    fn ipv6addrs(a: Option<fnet::Ipv6SocketAddress>) -> Vec<fnet_interfaces::Address> {
4074        // The DHCPv6 client will only use a link-local address but we include a global address
4075        // and expect it to not be used.
4076        std::iter::once(test_addr(GLOBAL_ADDR))
4077            .chain(a.map(|fnet::Ipv6SocketAddress { address, port: _, zone_index: _ }| {
4078                test_addr(fnet::Subnet { addr: fnet::IpAddress::Ipv6(address), prefix_len: 64 })
4079            }))
4080            .collect()
4081    }
4082
4083    /// Handle receiving a netstack interface changed event.
4084    async fn handle_interface_changed_event(
4085        netcfg: &mut NetCfg<'_>,
4086        dns_watchers: &mut DnsServerWatchers<'_>,
4087        online: Option<bool>,
4088        addresses: Option<Vec<fnet_interfaces::Address>>,
4089    ) -> Result<(), anyhow::Error> {
4090        let event = fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
4091            id: Some(INTERFACE_ID.get()),
4092            online,
4093            addresses,
4094            ..Default::default()
4095        });
4096        netcfg
4097            .handle_interface_watcher_event(event.into(), dns_watchers, &mut virtualization::Stub)
4098            .await
4099    }
4100
4101    /// Make sure that a new DHCPv6 client was requested, and verify its parameters.
4102    async fn check_new_dhcpv6_client(
4103        server: &mut fnet_dhcpv6::ClientProviderRequestStream,
4104        id: InterfaceId,
4105        sockaddr: fnet::Ipv6SocketAddress,
4106        prefix_delegation_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
4107        dns_watchers: &mut DnsServerWatchers<'_>,
4108    ) -> Result<fnet_dhcpv6::ClientRequestStream, anyhow::Error> {
4109        let evt =
4110            server.try_next().await.context("error getting next dhcpv6 client provider event")?;
4111        let mut client_server =
4112            match evt.ok_or(anyhow::anyhow!("expected dhcpv6 client provider request"))? {
4113                fnet_dhcpv6::ClientProviderRequest::NewClient {
4114                    params,
4115                    request,
4116                    control_handle: _,
4117                } => {
4118                    let stateful = prefix_delegation_config.is_some();
4119                    let params: fnet_dhcpv6_ext::NewClientParams = params.try_into()?;
4120                    assert_eq!(
4121                        params,
4122                        fnet_dhcpv6_ext::NewClientParams {
4123                            interface_id: id.get(),
4124                            address: sockaddr,
4125                            config: fnet_dhcpv6_ext::ClientConfig {
4126                                information_config: fnet_dhcpv6_ext::InformationConfig {
4127                                    dns_servers: true,
4128                                },
4129                                prefix_delegation_config,
4130                                non_temporary_address_config: fnet_dhcpv6_ext::AddressConfig {
4131                                    address_count: 0,
4132                                    preferred_addresses: None,
4133                                }
4134                            },
4135                            duid: stateful.then_some(fnet_dhcpv6::Duid::LinkLayerAddress(
4136                                fnet_dhcpv6::LinkLayerAddress::Ethernet(TEST_MAC)
4137                            )),
4138                        }
4139                    );
4140
4141                    request.into_stream()
4142                }
4143            };
4144        assert!(dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should have a watcher");
4145        // NetCfg always keeps the server hydrated with a pending hanging-get.
4146        expect_watch_prefixes(&mut client_server).await;
4147        Ok(client_server)
4148    }
4149
4150    fn expect_watch_dhcpv4_configuration(
4151        stream: &mut fnet_dhcp::ClientRequestStream,
4152    ) -> fnet_dhcp::ClientWatchConfigurationResponder {
4153        assert_matches::assert_matches!(
4154            stream.try_next().now_or_never(),
4155            Some(Ok(Some(fnet_dhcp::ClientRequest::WatchConfiguration { responder }))) => {
4156                responder
4157            },
4158            "expect a watch_configuration call immediately"
4159        )
4160    }
4161
4162    const TEST_MAC: fidl_fuchsia_net::MacAddress = fidl_mac!("00:01:02:03:04:05");
4163
4164    fn test_interface_naming_id() -> interface::InterfaceNamingIdentifier {
4165        interface::generate_identifier(&TEST_MAC.into())
4166    }
4167
4168    async fn expect_get_interface_auth(control: &mut fnet_interfaces_admin::ControlRequestStream) {
4169        let responder = control
4170            .by_ref()
4171            .try_next()
4172            .await
4173            .expect("get next interface control request")
4174            .expect("interface control request")
4175            .into_get_authorization_for_interface()
4176            .expect("should be interface authorization request");
4177        responder
4178            .send(fnet_resources::GrantForInterfaceAuthorization {
4179                interface_id: INTERFACE_ID.get(),
4180                token: zx::Event::create(),
4181            })
4182            .expect("respond should succeed");
4183    }
4184
4185    #[fuchsia::test]
4186    async fn test_stopping_dhcpv6_with_down_lookup_admin() {
4187        let (
4188            mut netcfg,
4189            ServerEnds {
4190                lookup_admin,
4191                dhcpv4_client_provider: _,
4192                mut dhcpv6_client_provider,
4193                route_set_v4_provider: _,
4194                dhcpv4_server: _,
4195                fuchsia_networks: _,
4196            },
4197        ) = test_netcfg(NetcfgTestArgs {
4198            with_dhcpv4_client_provider: false,
4199            with_fuchsia_networks: false,
4200        })
4201        .expect("error creating test netcfg");
4202        let mut dns_watchers = DnsServerWatchers::empty();
4203
4204        // Mock a new interface being discovered by NetCfg (we only need to make NetCfg aware of a
4205        // NIC with ID `INTERFACE_ID` to test DHCPv6).
4206        let (control, control_server_end) =
4207            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
4208                .expect("create endpoints");
4209
4210        let mut control_request_stream = control_server_end.into_stream();
4211
4212        let port_class = fidl_fuchsia_hardware_network::PortClass::Virtual;
4213        let (new_host_result, ()) = futures::join!(
4214            InterfaceState::new_host(
4215                test_interface_naming_id(),
4216                control,
4217                port_class.try_into().unwrap(),
4218                None,
4219                interface::ProvisioningType::Local,
4220            ),
4221            expect_get_interface_auth(&mut control_request_stream)
4222        );
4223
4224        assert_matches::assert_matches!(
4225            netcfg
4226                .interface_states
4227                .insert(INTERFACE_ID, new_host_result.expect("new_host should succeed")),
4228            None
4229        );
4230
4231        // Should start the DHCPv6 client when we get an interface changed event that shows the
4232        // interface as up with an link-local address.
4233        netcfg
4234            .handle_interface_watcher_event(
4235                fnet_interfaces::Event::Added(fnet_interfaces::Properties {
4236                    id: Some(INTERFACE_ID.get()),
4237                    name: Some("testif01".to_string()),
4238                    port_class: Some(fnet_interfaces::PortClass::Device(port_class)),
4239                    online: Some(true),
4240                    addresses: Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR1))),
4241                    has_default_ipv4_route: Some(false),
4242                    has_default_ipv6_route: Some(false),
4243                    ..Default::default()
4244                })
4245                .into(),
4246                &mut dns_watchers,
4247                &mut virtualization::Stub,
4248            )
4249            .await
4250            .expect("error handling interface added event with interface up and sockaddr1");
4251        let _: fnet_dhcpv6::ClientRequestStream = check_new_dhcpv6_client(
4252            &mut dhcpv6_client_provider,
4253            INTERFACE_ID,
4254            LINK_LOCAL_SOCKADDR1,
4255            None,
4256            &mut dns_watchers,
4257        )
4258        .await
4259        .expect("error checking for new client with sockaddr1");
4260
4261        // Drop the server-end of the lookup admin to simulate a down lookup admin service.
4262        std::mem::drop(lookup_admin);
4263
4264        // Not having any more link local IPv6 addresses should terminate the client.
4265        handle_interface_changed_event(&mut netcfg, &mut dns_watchers, None, Some(ipv6addrs(None)))
4266            .await
4267            .expect("error handling interface changed event with sockaddr1 removed");
4268
4269        // Another update without any link-local IPv6 addresses should do nothing
4270        // since the DHCPv6 client was already stopped.
4271        handle_interface_changed_event(&mut netcfg, &mut dns_watchers, None, Some(Vec::new()))
4272            .await
4273            .expect("error handling interface changed event with sockaddr1 removed");
4274
4275        // Update interface with a link-local address to create a new DHCPv6 client.
4276        handle_interface_changed_event(
4277            &mut netcfg,
4278            &mut dns_watchers,
4279            None,
4280            Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR1))),
4281        )
4282        .await
4283        .expect("error handling interface changed event with sockaddr1 removed");
4284        let _: fnet_dhcpv6::ClientRequestStream = check_new_dhcpv6_client(
4285            &mut dhcpv6_client_provider,
4286            INTERFACE_ID,
4287            LINK_LOCAL_SOCKADDR1,
4288            None,
4289            &mut dns_watchers,
4290        )
4291        .await
4292        .expect("error checking for new client with sockaddr1");
4293
4294        // Update offline status to down to stop DHCPv6 client.
4295        handle_interface_changed_event(&mut netcfg, &mut dns_watchers, Some(false), None)
4296            .await
4297            .expect("error handling interface changed event with sockaddr1 removed");
4298
4299        // Update interface with new addresses but leave offline status as down.
4300        handle_interface_changed_event(&mut netcfg, &mut dns_watchers, None, Some(ipv6addrs(None)))
4301            .await
4302            .expect("error handling interface changed event with sockaddr1 removed")
4303    }
4304
4305    async fn expect_watch_prefixes(client_server: &mut fnet_dhcpv6::ClientRequestStream) {
4306        assert_matches::assert_matches!(
4307            client_server.try_next().now_or_never(),
4308            Some(Ok(Some(fnet_dhcpv6::ClientRequest::WatchPrefixes { responder }))) => {
4309                responder.drop_without_shutdown();
4310            },
4311            "expect a watch_prefixes call immediately"
4312        )
4313    }
4314
4315    async fn handle_update(
4316        netcfg: &mut NetCfg<'_>,
4317        online: Option<bool>,
4318        addresses: Option<Vec<fnet_interfaces::Address>>,
4319        dns_watchers: &mut DnsServerWatchers<'_>,
4320    ) {
4321        netcfg
4322            .handle_interface_watcher_event(
4323                fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
4324                    id: Some(INTERFACE_ID.get()),
4325                    online,
4326                    addresses,
4327                    ..Default::default()
4328                })
4329                .into(),
4330                dns_watchers,
4331                &mut virtualization::Stub,
4332            )
4333            .await
4334            .expect("error handling interface change event with interface online")
4335    }
4336    const DHCP_ADDRESS: fnet::Ipv4AddressWithPrefix = fidl_ip_v4_with_prefix!("192.0.2.254/24");
4337
4338    fn dhcp_address_parameters() -> fnet_interfaces_admin::AddressParameters {
4339        fnet_interfaces_admin::AddressParameters {
4340            initial_properties: Some(fnet_interfaces_admin::AddressProperties {
4341                preferred_lifetime_info: None,
4342                valid_lifetime_end: Some(zx::MonotonicInstant::INFINITE.into_nanos()),
4343                ..Default::default()
4344            }),
4345            temporary: Some(true),
4346            add_subnet_route: Some(false),
4347            ..Default::default()
4348        }
4349    }
4350
4351    // Verify that the DHCPv4 server for a WlanAP is started when the interface
4352    // is observed to be online, regardless of whether that happens in an
4353    // `Added` or `Changed` event from the Netstack's interfaces watcher.
4354    #[test_case(true; "added online")]
4355    #[test_case(false; "added offline")]
4356    #[fuchsia::test]
4357    async fn test_dhcpv4_server_started(added_online: bool) {
4358        let (mut netcfg, ServerEnds { dhcpv4_server, .. }) = test_netcfg(NetcfgTestArgs {
4359            with_dhcpv4_client_provider: false,
4360            with_fuchsia_networks: false,
4361        })
4362        .expect("error creating test netcfg");
4363
4364        // A future representing the DHCPv4 server. Must be polled while feeding
4365        // updates to Netcfg.
4366        let mut dhcpv4_server_req_stream = dhcpv4_server.into_stream();
4367        let start_serving_fut = dhcpv4_server_req_stream.next().map(|req| {
4368            match req.expect("dhcpv4 request stream ended").expect("dhcpv4 request") {
4369                fnet_dhcp::Server_Request::StartServing { responder } => {
4370                    responder.send(Ok(())).expect("failed to respond");
4371                }
4372                _ => panic!("unexpected DHCPv4 server request"),
4373            }
4374        });
4375
4376        // Mock a new WlanAP interface being discovered by NetCfg.
4377        let port_class = fidl_fuchsia_hardware_network::PortClass::WlanAp;
4378        let (control_client, _control_server_end) =
4379            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
4380                .expect("create endpoints");
4381        let wlan_ap = InterfaceState::new_wlan_ap(
4382            test_interface_naming_id(),
4383            control_client,
4384            port_class.try_into().unwrap(),
4385            interface::ProvisioningType::Local,
4386        );
4387        assert_matches::assert_matches!(
4388            netcfg.interface_states.insert(INTERFACE_ID, wlan_ap),
4389            None
4390        );
4391
4392        // Have Netcfg observe the new interface being added.
4393        let mut dns_watchers = DnsServerWatchers::empty();
4394        let mut virt_stub = virtualization::Stub;
4395        let start_serving_fut = {
4396            let netcfg_fut = netcfg
4397                .handle_interface_watcher_event(
4398                    fnet_interfaces::Event::Added(fnet_interfaces::Properties {
4399                        id: Some(INTERFACE_ID.get()),
4400                        name: Some("testif01".to_string()),
4401                        port_class: Some(fnet_interfaces::PortClass::Device(port_class)),
4402                        online: Some(added_online),
4403                        addresses: Some(Vec::new()),
4404                        has_default_ipv4_route: Some(false),
4405                        has_default_ipv6_route: Some(false),
4406                        ..Default::default()
4407                    })
4408                    .into(),
4409                    &mut dns_watchers,
4410                    &mut virt_stub,
4411                )
4412                .map(|result| result.expect("handling interfaces watcher event"))
4413                .fuse();
4414            let netcfg_fut = pin!(netcfg_fut);
4415            if added_online {
4416                // Serving should be started. Expect both futures to terminate.
4417                let ((), ()) = futures::join!(netcfg_fut, start_serving_fut);
4418                None
4419            } else {
4420                match futures::future::select(netcfg_fut, start_serving_fut).await {
4421                    futures::future::Either::Left(((), start_serving_fut)) => {
4422                        Some(start_serving_fut)
4423                    }
4424                    futures::future::Either::Right(((), _netcfg_fut)) => {
4425                        panic!("serving unexpectedly started")
4426                    }
4427                }
4428            }
4429        };
4430        if let Some(start_serving_fut) = start_serving_fut {
4431            let netcfg_fut = handle_update(
4432                &mut netcfg,
4433                Some(true), /* online */
4434                None,       /* addresses */
4435                &mut dns_watchers,
4436            )
4437            .fuse();
4438            let netcfg_fut = pin!(netcfg_fut);
4439            // Serving should be started. Expect both futures to terminate.
4440            let ((), ()) = futures::join!(netcfg_fut, start_serving_fut);
4441        }
4442    }
4443
4444    #[test_case(true, true; "added online and removed interface")]
4445    #[test_case(false, true; "added offline and removed interface")]
4446    #[test_case(true, false; "added online and disabled interface")]
4447    #[test_case(false, false; "added offline and disabled interface")]
4448    #[fuchsia::test]
4449    async fn test_dhcpv4(added_online: bool, remove_interface: bool) {
4450        let (
4451            mut netcfg,
4452            ServerEnds {
4453                mut lookup_admin,
4454                mut dhcpv4_client_provider,
4455                dhcpv6_client_provider: _,
4456                route_set_v4_provider,
4457                dhcpv4_server: _,
4458                fuchsia_networks: _,
4459            },
4460        ) = test_netcfg(NetcfgTestArgs {
4461            with_dhcpv4_client_provider: true,
4462            with_fuchsia_networks: false,
4463        })
4464        .expect("error creating test netcfg");
4465        let mut dns_watchers = DnsServerWatchers::empty();
4466
4467        let mut route_set_request_stream =
4468            fnet_routes_ext::testutil::admin::serve_one_route_set::<Ipv4>(route_set_v4_provider);
4469
4470        // Mock a new interface being discovered by NetCfg (we only need to make NetCfg aware of a
4471        // NIC with ID `INTERFACE_ID` to test DHCPv4).
4472        let (control_client, control_server_end) =
4473            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
4474                .expect("create endpoints");
4475        let port_class = fidl_fuchsia_hardware_network::PortClass::Virtual;
4476        let mut control = control_server_end.into_stream();
4477
4478        let (new_host_result, ()) = futures::join!(
4479            InterfaceState::new_host(
4480                test_interface_naming_id(),
4481                control_client,
4482                port_class.try_into().unwrap(),
4483                None,
4484                interface::ProvisioningType::Local,
4485            ),
4486            expect_get_interface_auth(&mut control)
4487        );
4488
4489        assert_matches::assert_matches!(
4490            netcfg
4491                .interface_states
4492                .insert(INTERFACE_ID, new_host_result.expect("new_host should succeed")),
4493            None
4494        );
4495
4496        let handle_route_set_fut = async {
4497            let (_proof, responder) = route_set_request_stream
4498                .try_next()
4499                .await
4500                .expect("get next route set request")
4501                .expect("route set request stream should not have ended")
4502                .into_authenticate_for_interface()
4503                .expect("should be AuthenticateForInterface request");
4504            responder.send(Ok(())).expect("responding should succeed");
4505        };
4506
4507        let handle_interface_watcher_event_fut = async {
4508            netcfg
4509                .handle_interface_watcher_event(
4510                    fnet_interfaces::Event::Added(fnet_interfaces::Properties {
4511                        id: Some(INTERFACE_ID.get()),
4512                        name: Some("testif01".to_string()),
4513                        port_class: Some(fnet_interfaces::PortClass::Device(
4514                            fidl_fuchsia_hardware_network::PortClass::Virtual,
4515                        )),
4516                        online: Some(added_online),
4517                        addresses: Some(Vec::new()),
4518                        has_default_ipv4_route: Some(false),
4519                        has_default_ipv6_route: Some(false),
4520                        ..Default::default()
4521                    })
4522                    .into(),
4523                    &mut dns_watchers,
4524                    &mut virtualization::Stub,
4525                )
4526                .await
4527                .expect("error handling interface added event");
4528
4529            if !added_online {
4530                assert_matches::assert_matches!(
4531                    dhcpv4_client_provider.try_next().now_or_never(),
4532                    None
4533                );
4534                handle_update(
4535                    &mut netcfg,
4536                    Some(true), /* online */
4537                    None,       /* addresses */
4538                    &mut dns_watchers,
4539                )
4540                .await;
4541            }
4542
4543            let mut client_req_stream = match dhcpv4_client_provider
4544                .try_next()
4545                .await
4546                .expect("get next dhcpv4 client provider event")
4547                .expect("dhcpv4 client provider request")
4548            {
4549                fnet_dhcp::ClientProviderRequest::NewClient {
4550                    interface_id,
4551                    params,
4552                    request,
4553                    control_handle: _,
4554                } => {
4555                    assert_eq!(interface_id, INTERFACE_ID.get());
4556                    assert_eq!(params, dhcpv4::new_client_params());
4557                    request.into_stream()
4558                }
4559                fnet_dhcp::ClientProviderRequest::CheckPresence { responder: _ } => {
4560                    unreachable!("only called at startup")
4561                }
4562            };
4563
4564            let responder = expect_watch_dhcpv4_configuration(&mut client_req_stream);
4565
4566            (client_req_stream, responder)
4567        };
4568
4569        // Make sure the DHCPv4 client is created on interface up.
4570        let ((mut client_stream, responder), ()) =
4571            futures::join!(handle_interface_watcher_event_fut, handle_route_set_fut);
4572
4573        let check_route = |fnet_routes::RouteV4 { destination, action, properties },
4574                           routers: &mut HashSet<_>| {
4575            assert_eq!(destination, fnet_dhcp_ext::DEFAULT_ADDR_PREFIX);
4576            let (outbound_interface, next_hop) = assert_matches!(action, fnet_routes::RouteActionV4::Forward(
4577                fnet_routes::RouteTargetV4 {
4578                    outbound_interface,
4579                    next_hop
4580                }
4581            ) => (outbound_interface, next_hop));
4582            assert_eq!(outbound_interface, INTERFACE_ID.get());
4583
4584            assert!(routers.insert(*next_hop.expect("specified next hop")));
4585
4586            assert_matches!(
4587                properties,
4588                fnet_routes::RoutePropertiesV4 {
4589                    specified_properties: Some(fnet_routes::SpecifiedRouteProperties {
4590                        metric: Some(fnet_routes::SpecifiedMetric::InheritedFromInterface(
4591                            fnet_routes::Empty
4592                        )),
4593                        ..
4594                    }),
4595                    ..
4596                }
4597            );
4598        };
4599
4600        let (expected_routers, route_set_request_stream) = {
4601            let dns_servers = vec![fidl_ip_v4!("192.0.2.1"), fidl_ip_v4!("192.0.2.2")];
4602            let routers = vec![fidl_ip_v4!("192.0.2.3"), fidl_ip_v4!("192.0.2.4")];
4603
4604            let (_asp_client, asp_server) = fidl::endpoints::create_proxy();
4605
4606            responder
4607                .send(fnet_dhcp::ClientWatchConfigurationResponse {
4608                    address: Some(fnet_dhcp::Address {
4609                        address: Some(DHCP_ADDRESS),
4610                        address_parameters: Some(dhcp_address_parameters()),
4611                        address_state_provider: Some(asp_server),
4612                        ..Default::default()
4613                    }),
4614                    dns_servers: Some(dns_servers.clone()),
4615                    routers: Some(routers.clone()),
4616                    ..Default::default()
4617                })
4618                .expect("send configuration update");
4619
4620            let (got_interface_id, got_response) = netcfg
4621                .dhcpv4_configuration_streams
4622                .next()
4623                .await
4624                .expect("DHCPv4 configuration streams should never be exhausted");
4625            assert_eq!(got_interface_id, INTERFACE_ID);
4626
4627            let dns_servers = dns_servers
4628                .into_iter()
4629                .map(|address| {
4630                    fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
4631                        address,
4632                        port: DEFAULT_DNS_PORT,
4633                    })
4634                })
4635                .collect::<Vec<_>>();
4636
4637            let expect_add_default_routers = futures::stream::repeat(()).take(routers.len()).fold(
4638                (HashSet::new(), route_set_request_stream),
4639                |(mut routers, mut route_set), ()| async move {
4640                    let (route, responder) = assert_matches!(
4641                        route_set.next().await.expect("route set request stream should not be exhausted"),
4642                        Ok(fnet_routes_admin::RouteSetV4Request::AddRoute { route, responder}) => {
4643                            (route, responder)
4644                        }
4645                    );
4646                    check_route(route, &mut routers);
4647                    responder.send(Ok(true)).expect("send add route response");
4648                    (routers, route_set)
4649                },
4650            );
4651
4652            let expect_add_address_called = async {
4653                assert_matches!(
4654                    control.next().await.expect("control request stream should not be exhausted"),
4655                    Ok(fnet_interfaces_admin::ControlRequest::AddAddress {
4656                        address,
4657                        parameters,
4658                        address_state_provider: _,
4659                        control_handle: _,
4660                    })  => {
4661                        assert_eq!(address, fnet::Subnet::from_ext(DHCP_ADDRESS));
4662                        assert_eq!(parameters, dhcp_address_parameters());
4663                    }
4664                );
4665            };
4666
4667            let (dhcpv4_result, (), (added_routers, route_set_request_stream), ()) = future::join4(
4668                netcfg.handle_dhcpv4_configuration(got_interface_id, got_response),
4669                run_lookup_admin_once(&mut lookup_admin, &dns_servers),
4670                expect_add_default_routers,
4671                expect_add_address_called,
4672            )
4673            .await;
4674            assert_eq!(netcfg.dns_servers.consolidated(), dns_servers);
4675            assert_matches!(dhcpv4_result, Dhcpv4ConfigurationHandlerResult::ContinueOperation);
4676            let expected_routers = routers.iter().cloned().collect::<HashSet<_>>();
4677            assert_eq!(added_routers, expected_routers);
4678            (expected_routers, route_set_request_stream)
4679        };
4680
4681        // Netcfg always keeps the server hydrated with a WatchConfiguration
4682        // request.
4683        let _responder: fnet_dhcp::ClientWatchConfigurationResponder =
4684            expect_watch_dhcpv4_configuration(&mut client_stream);
4685
4686        let expect_delete_default_routers = futures::stream::repeat(())
4687            .take(expected_routers.len())
4688            .fold((HashSet::new(), route_set_request_stream), |(mut routers, mut route_set), ()| async move {
4689                let (route, responder) = assert_matches!(
4690                    route_set.next().await.expect("route set request stream should not be exhausted"),
4691                    Ok(fnet_routes_admin::RouteSetV4Request::RemoveRoute { route, responder }) => {
4692                        (route, responder)
4693                    }
4694                );
4695                check_route(route, &mut routers);
4696                responder.send(Ok(true)).expect("send del fwd entry response");
4697                (routers, route_set)
4698            });
4699
4700        // Make sure the DHCPv4 client is shutdown on interface disable/removal.
4701        let ((), (), (), (deleted_routers, mut route_set_request_stream)) = future::join4(
4702            async {
4703                if remove_interface {
4704                    netcfg
4705                        .handle_interface_watcher_event(
4706                            fnet_interfaces::Event::Removed(INTERFACE_ID.get()).into(),
4707                            &mut dns_watchers,
4708                            &mut virtualization::Stub,
4709                        )
4710                        .await
4711                        .expect("error handling interface removed event")
4712                } else {
4713                    handle_update(
4714                        &mut netcfg,
4715                        Some(false), /* online */
4716                        None,        /* addresses */
4717                        &mut dns_watchers,
4718                    )
4719                    .await
4720                }
4721            },
4722            async {
4723                match client_stream
4724                    .try_next()
4725                    .await
4726                    .expect("wait for next shutdown request")
4727                    .expect("netcfg should send shutdown request before closing client")
4728                {
4729                    req @ fnet_dhcp::ClientRequest::WatchConfiguration { responder: _ } => {
4730                        panic!("unexpected request = {:?}", req);
4731                    }
4732                    fnet_dhcp::ClientRequest::Shutdown { control_handle } => control_handle
4733                        .send_on_exit(fnet_dhcp::ClientExitReason::GracefulShutdown)
4734                        .expect("send client exit reason"),
4735                }
4736            },
4737            run_lookup_admin_once(&mut lookup_admin, &Vec::new()),
4738            expect_delete_default_routers,
4739        )
4740        .await;
4741        assert_eq!(netcfg.dns_servers.consolidated(), []);
4742        assert_eq!(deleted_routers, expected_routers);
4743
4744        // No more requests sent by NetCfg.
4745        assert_matches!(lookup_admin.next().now_or_never(), None);
4746        assert_matches!(route_set_request_stream.next().now_or_never(), None);
4747        let control_next = control.next().now_or_never();
4748        if remove_interface {
4749            assert_matches!(control_next, Some(None));
4750        } else {
4751            assert_matches!(control_next, None);
4752        }
4753    }
4754
4755    #[fuchsia::test]
4756    async fn test_dhcpv4_ignores_address_change() {
4757        let (
4758            mut netcfg,
4759            ServerEnds {
4760                lookup_admin: _,
4761                mut dhcpv4_client_provider,
4762                dhcpv6_client_provider: _,
4763                route_set_v4_provider,
4764                dhcpv4_server: _,
4765                fuchsia_networks: _,
4766            },
4767        ) = test_netcfg(NetcfgTestArgs {
4768            with_dhcpv4_client_provider: true,
4769            with_fuchsia_networks: false,
4770        })
4771        .expect("error creating test netcfg");
4772        let mut dns_watchers = DnsServerWatchers::empty();
4773
4774        let _noop_route_sets_task = fasync::Task::local(
4775            fnet_routes_ext::testutil::admin::serve_noop_route_sets::<Ipv4>(route_set_v4_provider),
4776        );
4777
4778        // Mock a new interface being discovered by NetCfg (we only need to make NetCfg aware of a
4779        // NIC with ID `INTERFACE_ID` to test DHCPv4).
4780        let (control, control_server_end) =
4781            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
4782                .expect("create endpoints");
4783        let port_class = fidl_fuchsia_hardware_network::PortClass::Virtual;
4784
4785        let new_host_fut = InterfaceState::new_host(
4786            test_interface_naming_id(),
4787            control,
4788            port_class.try_into().unwrap(),
4789            None,
4790            interface::ProvisioningType::Local,
4791        );
4792        let mut control = control_server_end.into_stream();
4793        let (new_host_result, ()) =
4794            futures::join!(new_host_fut, expect_get_interface_auth(&mut control));
4795
4796        assert_matches::assert_matches!(
4797            netcfg
4798                .interface_states
4799                .insert(INTERFACE_ID, new_host_result.expect("new_host should succeed")),
4800            None
4801        );
4802
4803        // Make sure the DHCPv4 client is created on interface up.
4804        netcfg
4805            .handle_interface_watcher_event(
4806                fnet_interfaces::Event::Added(fnet_interfaces::Properties {
4807                    id: Some(INTERFACE_ID.get()),
4808                    name: Some("testif01".to_string()),
4809                    port_class: Some(fnet_interfaces::PortClass::Device(
4810                        fidl_fuchsia_hardware_network::PortClass::Virtual,
4811                    )),
4812                    online: Some(true),
4813                    addresses: Some(Vec::new()),
4814                    has_default_ipv4_route: Some(false),
4815                    has_default_ipv6_route: Some(false),
4816                    ..Default::default()
4817                })
4818                .into(),
4819                &mut dns_watchers,
4820                &mut virtualization::Stub,
4821            )
4822            .await
4823            .expect("error handling interface added event");
4824
4825        let mut client_req_stream = match dhcpv4_client_provider
4826            .try_next()
4827            .await
4828            .expect("get next dhcpv4 client provider event")
4829            .expect("dhcpv4 client provider request")
4830        {
4831            fnet_dhcp::ClientProviderRequest::NewClient {
4832                interface_id,
4833                params,
4834                request,
4835                control_handle: _,
4836            } => {
4837                assert_eq!(interface_id, INTERFACE_ID.get());
4838                assert_eq!(params, dhcpv4::new_client_params());
4839                request.into_stream()
4840            }
4841            fnet_dhcp::ClientProviderRequest::CheckPresence { responder: _ } => {
4842                unreachable!("only called at startup")
4843            }
4844        };
4845
4846        let responder = expect_watch_dhcpv4_configuration(&mut client_req_stream);
4847        let (_asp_client, asp_server) = fidl::endpoints::create_proxy();
4848        responder
4849            .send(fnet_dhcp::ClientWatchConfigurationResponse {
4850                address: Some(fnet_dhcp::Address {
4851                    address: Some(DHCP_ADDRESS),
4852                    address_parameters: Some(dhcp_address_parameters()),
4853                    address_state_provider: Some(asp_server),
4854                    ..Default::default()
4855                }),
4856                ..Default::default()
4857            })
4858            .expect("send configuration update");
4859
4860        // A DHCP client should only be started or stopped when the interface is
4861        // enabled or disabled. If we change the addresses seen on the
4862        // interface, we shouldn't see requests to create a new DHCP client, and
4863        // the existing one should remain alive (netcfg shouldn't have sent a
4864        // shutdown request for it).
4865        for addresses in [
4866            vec![fidl_subnet!("192.2.2.2/28")],
4867            vec![],
4868            vec![fidl_subnet!("192.2.2.2/28"), fidl_subnet!("fe80::1234/64")],
4869        ] {
4870            handle_update(
4871                &mut netcfg,
4872                None,
4873                Some(addresses.into_iter().map(test_addr).collect()),
4874                &mut dns_watchers,
4875            )
4876            .await;
4877        }
4878        assert_matches!(dhcpv4_client_provider.next().now_or_never(), None);
4879        assert_matches!(client_req_stream.next().now_or_never(), None);
4880    }
4881
4882    /// Waits for a `SetDnsServers` request with the specified servers.
4883    async fn run_lookup_admin_once(
4884        server: &mut fnet_name::LookupAdminRequestStream,
4885        expected_servers: &Vec<fnet::SocketAddress>,
4886    ) {
4887        let req = server
4888            .try_next()
4889            .await
4890            .expect("get next lookup admin request")
4891            .expect("lookup admin request stream should not be exhausted");
4892        let (servers, responder) = assert_matches!(
4893            req,
4894            fnet_name::LookupAdminRequest::SetDnsServers { servers, responder } => {
4895                (servers, responder)
4896            }
4897        );
4898
4899        assert_eq!(expected_servers, &servers);
4900        responder.send(Ok(())).expect("send set dns servers response");
4901    }
4902
4903    #[test_case(Some(fnet_dhcp::ClientExitReason::UnableToOpenSocket), AllowClientRestart::Yes)]
4904    #[test_case(Some(fnet_dhcp::ClientExitReason::NetworkUnreachable), AllowClientRestart::Yes)]
4905    #[test_case(None, AllowClientRestart::Yes)]
4906    #[test_case(Some(fnet_dhcp::ClientExitReason::AddressRemovedByUser), AllowClientRestart::No)]
4907    #[fuchsia::test]
4908    async fn dhcpv4_handles_client_exit(
4909        exit_reason: Option<fnet_dhcp::ClientExitReason>,
4910        expected_allow_restart: AllowClientRestart,
4911    ) {
4912        let (
4913            mut netcfg,
4914            ServerEnds {
4915                lookup_admin: _,
4916                mut dhcpv4_client_provider,
4917                dhcpv6_client_provider: _,
4918                route_set_v4_provider,
4919                dhcpv4_server: _,
4920                fuchsia_networks: _,
4921            },
4922        ) = test_netcfg(NetcfgTestArgs {
4923            with_dhcpv4_client_provider: true,
4924            with_fuchsia_networks: false,
4925        })
4926        .expect("error creating test netcfg");
4927        let mut dns_watchers = DnsServerWatchers::empty();
4928
4929        let _noop_route_sets_task = fasync::Task::local(
4930            fnet_routes_ext::testutil::admin::serve_noop_route_sets::<Ipv4>(route_set_v4_provider),
4931        );
4932
4933        // Mock a new interface being discovered by NetCfg (we only need to make NetCfg aware of a
4934        // NIC with ID `INTERFACE_ID` to test DHCPv4).
4935        let (control, control_server_end) =
4936            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
4937                .expect("create endpoints");
4938        let port_class = fidl_fuchsia_hardware_network::PortClass::Virtual;
4939        let new_host_fut = InterfaceState::new_host(
4940            test_interface_naming_id(),
4941            control,
4942            port_class.try_into().unwrap(),
4943            None,
4944            interface::ProvisioningType::Local,
4945        );
4946        let mut control = control_server_end.into_stream();
4947        let (new_host_result, ()) =
4948            futures::join!(new_host_fut, expect_get_interface_auth(&mut control));
4949        assert_matches::assert_matches!(
4950            netcfg
4951                .interface_states
4952                .insert(INTERFACE_ID, new_host_result.expect("new_host should succeed")),
4953            None
4954        );
4955
4956        // Make sure the DHCPv4 client is created on interface up.
4957        let (client_stream, control_handle, responder) = {
4958            netcfg
4959                .handle_interface_watcher_event(
4960                    fnet_interfaces::Event::Added(fnet_interfaces::Properties {
4961                        id: Some(INTERFACE_ID.get()),
4962                        name: Some("testif01".to_string()),
4963                        port_class: Some(fnet_interfaces::PortClass::Device(
4964                            fidl_fuchsia_hardware_network::PortClass::Virtual,
4965                        )),
4966                        online: Some(true),
4967                        addresses: Some(Vec::new()),
4968                        has_default_ipv4_route: Some(false),
4969                        has_default_ipv6_route: Some(false),
4970                        ..Default::default()
4971                    })
4972                    .into(),
4973                    &mut dns_watchers,
4974                    &mut virtualization::Stub,
4975                )
4976                .await
4977                .expect("error handling interface added event");
4978
4979            let (mut client_req_stream, control_handle) = match dhcpv4_client_provider
4980                .try_next()
4981                .await
4982                .expect("get next dhcpv4 client provider event")
4983                .expect("dhcpv4 client provider request")
4984            {
4985                fnet_dhcp::ClientProviderRequest::NewClient {
4986                    interface_id,
4987                    params,
4988                    request,
4989                    control_handle: _,
4990                } => {
4991                    assert_eq!(interface_id, INTERFACE_ID.get());
4992                    assert_eq!(params, dhcpv4::new_client_params());
4993                    request.into_stream_and_control_handle()
4994                }
4995                fnet_dhcp::ClientProviderRequest::CheckPresence { responder: _ } => {
4996                    unreachable!("only called at startup")
4997                }
4998            };
4999
5000            let responder = expect_watch_dhcpv4_configuration(&mut client_req_stream);
5001            (client_req_stream, control_handle, responder)
5002        };
5003
5004        // Simulate the client exiting with an error.
5005        match exit_reason {
5006            Some(reason) => {
5007                control_handle.send_on_exit(reason).expect("sending OnExit should succeed");
5008            }
5009            None => {}
5010        }
5011
5012        drop((client_stream, control_handle, responder));
5013
5014        let (got_interface_id, got_response) = netcfg
5015            .dhcpv4_configuration_streams
5016            .next()
5017            .await
5018            .expect("DHCPv4 configuration streams should never be exhausted");
5019        assert_eq!(got_interface_id, INTERFACE_ID);
5020
5021        let dhcpv4_result =
5022            netcfg.handle_dhcpv4_configuration(got_interface_id, got_response).await;
5023        assert_eq!(
5024            dhcpv4_result,
5025            Dhcpv4ConfigurationHandlerResult::ClientStopped(expected_allow_restart)
5026        );
5027    }
5028
5029    #[fuchsia::test]
5030    async fn test_dhcpv6() {
5031        let (mut netcfg, mut servers) = test_netcfg(NetcfgTestArgs {
5032            with_dhcpv4_client_provider: false,
5033            with_fuchsia_networks: false,
5034        })
5035        .expect("error creating test netcfg");
5036        let mut dns_watchers = DnsServerWatchers::empty();
5037
5038        // Mock a fake DNS update from the DHCPv6 client.
5039        async fn update_dns(netcfg: &mut NetCfg<'static>, servers: &mut ServerEnds) {
5040            let ((), ()) = future::join(
5041                netcfg.update_dns_servers(
5042                    DHCPV6_DNS_SOURCE,
5043                    vec![fnet_name::DnsServer_ {
5044                        address: Some(DNS_SERVER2),
5045                        source: Some(fnet_name::DnsServerSource::Dhcpv6(
5046                            fnet_name::Dhcpv6DnsServerSource {
5047                                source_interface: Some(INTERFACE_ID.get()),
5048                                ..Default::default()
5049                            },
5050                        )),
5051                        ..Default::default()
5052                    }],
5053                ),
5054                run_lookup_admin_once(&mut servers.lookup_admin, &vec![DNS_SERVER2, DNS_SERVER1]),
5055            )
5056            .await;
5057        }
5058
5059        // Mock a fake DNS update from the netstack.
5060        let netstack_servers = vec![DNS_SERVER1];
5061        let ((), ()) = future::join(
5062            netcfg.update_dns_servers(
5063                DnsServersUpdateSource::Netstack,
5064                vec![fnet_name::DnsServer_ {
5065                    address: Some(DNS_SERVER1),
5066                    source: Some(fnet_name::DnsServerSource::StaticSource(
5067                        fnet_name::StaticDnsServerSource::default(),
5068                    )),
5069                    ..Default::default()
5070                }],
5071            ),
5072            run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers),
5073        )
5074        .await;
5075
5076        // Mock a new interface being discovered by NetCfg (we only need to make NetCfg aware of a
5077        // NIC with ID `INTERFACE_ID` to test DHCPv6).
5078        let (control, control_server_end) =
5079            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
5080                .expect("create endpoints");
5081        let mut control_request_stream = control_server_end.into_stream();
5082        let port_class = fidl_fuchsia_hardware_network::PortClass::Virtual;
5083        let (new_host_result, ()) = futures::join!(
5084            InterfaceState::new_host(
5085                test_interface_naming_id(),
5086                control,
5087                port_class.try_into().unwrap(),
5088                None,
5089                interface::ProvisioningType::Local,
5090            ),
5091            expect_get_interface_auth(&mut control_request_stream)
5092        );
5093        assert_matches::assert_matches!(
5094            netcfg
5095                .interface_states
5096                .insert(INTERFACE_ID, new_host_result.expect("new_host should succeed")),
5097            None
5098        );
5099
5100        // Should start the DHCPv6 client when we get an interface changed event that shows the
5101        // interface as up with an link-local address.
5102        netcfg
5103            .handle_interface_watcher_event(
5104                fnet_interfaces::Event::Added(fnet_interfaces::Properties {
5105                    id: Some(INTERFACE_ID.get()),
5106                    name: Some("testif01".to_string()),
5107                    port_class: Some(fnet_interfaces::PortClass::Device(port_class)),
5108                    online: Some(true),
5109                    addresses: Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR1))),
5110                    has_default_ipv4_route: Some(false),
5111                    has_default_ipv6_route: Some(false),
5112                    ..Default::default()
5113                })
5114                .into(),
5115                &mut dns_watchers,
5116                &mut virtualization::Stub,
5117            )
5118            .await
5119            .expect("error handling interface added event with interface up and sockaddr1");
5120        let mut client_server = check_new_dhcpv6_client(
5121            &mut servers.dhcpv6_client_provider,
5122            INTERFACE_ID,
5123            LINK_LOCAL_SOCKADDR1,
5124            None,
5125            &mut dns_watchers,
5126        )
5127        .await
5128        .expect("error checking for new client with sockaddr1");
5129        update_dns(&mut netcfg, &mut servers).await;
5130
5131        // Not having any more link local IPv6 addresses should terminate the client.
5132        let ((), ()) = future::join(
5133            handle_interface_changed_event(
5134                &mut netcfg,
5135                &mut dns_watchers,
5136                None,
5137                Some(ipv6addrs(None)),
5138            )
5139            .map(|r| r.expect("error handling interface changed event with sockaddr1 removed")),
5140            run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers),
5141        )
5142        .await;
5143        assert!(!dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should not have a watcher");
5144        assert_matches::assert_matches!(client_server.try_next().await, Ok(None));
5145
5146        // Should start a new DHCPv6 client when we get an interface changed event that shows the
5147        // interface as up with an link-local address.
5148        handle_interface_changed_event(
5149            &mut netcfg,
5150            &mut dns_watchers,
5151            None,
5152            Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR2))),
5153        )
5154        .await
5155        .expect("error handling netstack event with sockaddr2 added");
5156        let mut client_server = check_new_dhcpv6_client(
5157            &mut servers.dhcpv6_client_provider,
5158            INTERFACE_ID,
5159            LINK_LOCAL_SOCKADDR2,
5160            None,
5161            &mut dns_watchers,
5162        )
5163        .await
5164        .expect("error checking for new client with sockaddr2");
5165        update_dns(&mut netcfg, &mut servers).await;
5166
5167        // Interface being down should terminate the client.
5168        let ((), ()) = future::join(
5169            handle_interface_changed_event(
5170                &mut netcfg,
5171                &mut dns_watchers,
5172                Some(false), /* down */
5173                None,
5174            )
5175            .map(|r| r.expect("error handling interface changed event with interface down")),
5176            run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers),
5177        )
5178        .await;
5179        assert!(!dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should not have a watcher");
5180        assert_matches::assert_matches!(client_server.try_next().await, Ok(None));
5181
5182        // Should start a new DHCPv6 client when we get an interface changed event that shows the
5183        // interface as up with an link-local address.
5184        handle_interface_changed_event(
5185            &mut netcfg,
5186            &mut dns_watchers,
5187            Some(true), /* up */
5188            None,
5189        )
5190        .await
5191        .expect("error handling interface up event");
5192        let mut client_server = check_new_dhcpv6_client(
5193            &mut servers.dhcpv6_client_provider,
5194            INTERFACE_ID,
5195            LINK_LOCAL_SOCKADDR2,
5196            None,
5197            &mut dns_watchers,
5198        )
5199        .await
5200        .expect("error checking for new client with sockaddr2 after interface up again");
5201        update_dns(&mut netcfg, &mut servers).await;
5202
5203        // Should start a new DHCPv6 client when we get an interface changed event that shows the
5204        // interface as up with a new link-local address.
5205        let ((), ()) = future::join(
5206            handle_interface_changed_event(
5207                &mut netcfg,
5208                &mut dns_watchers,
5209                None,
5210                Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR1))),
5211            )
5212            .map(|r| {
5213                r.expect("error handling interface change event with sockaddr1 replacing sockaddr2")
5214            }),
5215            run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers),
5216        )
5217        .await;
5218        assert_matches::assert_matches!(client_server.try_next().await, Ok(None));
5219        let _client_server: fnet_dhcpv6::ClientRequestStream = check_new_dhcpv6_client(
5220            &mut servers.dhcpv6_client_provider,
5221            INTERFACE_ID,
5222            LINK_LOCAL_SOCKADDR1,
5223            None,
5224            &mut dns_watchers,
5225        )
5226        .await
5227        .expect("error checking for new client with sockaddr1 after address change");
5228        update_dns(&mut netcfg, &mut servers).await;
5229
5230        // Complete the DNS server watcher then start a new one.
5231        let ((), ()) = future::join(
5232            netcfg
5233                .handle_dns_server_watcher_done(DHCPV6_DNS_SOURCE, &mut dns_watchers)
5234                .map(|r| r.expect("error handling completion of dns server watcher")),
5235            run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers),
5236        )
5237        .await;
5238        assert!(!dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should not have a watcher");
5239        handle_interface_changed_event(
5240            &mut netcfg,
5241            &mut dns_watchers,
5242            None,
5243            Some(ipv6addrs(Some(LINK_LOCAL_SOCKADDR2))),
5244        )
5245        .await
5246        .expect("error handling interface change event with sockaddr2 replacing sockaddr1");
5247        let mut client_server = check_new_dhcpv6_client(
5248            &mut servers.dhcpv6_client_provider,
5249            INTERFACE_ID,
5250            LINK_LOCAL_SOCKADDR2,
5251            None,
5252            &mut dns_watchers,
5253        )
5254        .await
5255        .expect("error checking for new client with sockaddr2 after completing dns watcher");
5256        update_dns(&mut netcfg, &mut servers).await;
5257
5258        // An event that indicates the interface is removed should stop the client.
5259        let ((), ()) = future::join(
5260            netcfg
5261                .handle_interface_watcher_event(
5262                    fnet_interfaces::Event::Removed(INTERFACE_ID.get()).into(),
5263                    &mut dns_watchers,
5264                    &mut virtualization::Stub,
5265                )
5266                .map(|r| r.expect("error handling interface removed event")),
5267            run_lookup_admin_once(&mut servers.lookup_admin, &netstack_servers),
5268        )
5269        .await;
5270        assert!(!dns_watchers.contains_key(&DHCPV6_DNS_SOURCE), "should not have a watcher");
5271        assert_matches::assert_matches!(client_server.try_next().await, Ok(None));
5272        assert!(!netcfg.interface_states.contains_key(&INTERFACE_ID));
5273    }
5274
5275    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
5276    enum InterfaceKind {
5277        Unowned,
5278        NonHost,
5279        Host { upstream: bool },
5280    }
5281
5282    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
5283    struct InterfaceConfig {
5284        id: InterfaceId,
5285        kind: InterfaceKind,
5286    }
5287
5288    const UPSTREAM_INTERFACE_CONFIG: InterfaceConfig = InterfaceConfig {
5289        id: InterfaceId::new(1).unwrap(),
5290        kind: InterfaceKind::Host { upstream: true },
5291    };
5292
5293    const ALLOWED_UPSTREAM_DEVICE_CLASS: DeviceClass = DeviceClass::Ethernet;
5294    const DISALLOWED_UPSTREAM_DEVICE_CLASS: DeviceClass = DeviceClass::Virtual;
5295
5296    fn dhcpv6_sockaddr(interface_id: InterfaceId) -> fnet::Ipv6SocketAddress {
5297        let mut address = fidl_ip_v6!("fe80::");
5298        let interface_id: u8 =
5299            interface_id.get().try_into().expect("interface ID should fit into u8");
5300        *address.addr.last_mut().expect("IPv6 address is empty") = interface_id;
5301        fnet::Ipv6SocketAddress {
5302            address: address,
5303            port: fnet_dhcpv6::DEFAULT_CLIENT_PORT,
5304            zone_index: interface_id.into(),
5305        }
5306    }
5307
5308    #[test_case(
5309        Some(UPSTREAM_INTERFACE_CONFIG.id),
5310        None; "specific_interface_no_preferred_prefix_len")]
5311    #[test_case(
5312        None,
5313        None; "all_upstreams_no_preferred_prefix_len")]
5314    #[test_case(
5315        Some(UPSTREAM_INTERFACE_CONFIG.id),
5316        Some(2); "specific_interface_preferred_prefix_len")]
5317    #[test_case(
5318        None,
5319        Some(1); "all_upstreams_preferred_prefix_len")]
5320    #[fuchsia::test]
5321    async fn test_dhcpv6_acquire_prefix(
5322        interface_id: Option<InterfaceId>,
5323        preferred_prefix_len: Option<u8>,
5324    ) {
5325        const INTERFACE_CONFIGS: [InterfaceConfig; 5] = [
5326            UPSTREAM_INTERFACE_CONFIG,
5327            InterfaceConfig {
5328                id: InterfaceId::new(2).unwrap(),
5329                kind: InterfaceKind::Host { upstream: true },
5330            },
5331            InterfaceConfig {
5332                id: InterfaceId::new(3).unwrap(),
5333                kind: InterfaceKind::Host { upstream: false },
5334            },
5335            InterfaceConfig { id: InterfaceId::new(4).unwrap(), kind: InterfaceKind::Unowned },
5336            InterfaceConfig { id: InterfaceId::new(5).unwrap(), kind: InterfaceKind::NonHost },
5337        ];
5338
5339        let (
5340            mut netcfg,
5341            ServerEnds {
5342                lookup_admin: lookup_admin_request_stream,
5343                dhcpv4_client_provider: _,
5344                dhcpv6_client_provider: mut dhcpv6_client_provider_request_stream,
5345                route_set_v4_provider: _,
5346                dhcpv4_server: _,
5347                fuchsia_networks: _,
5348            },
5349        ) = test_netcfg(NetcfgTestArgs {
5350            with_dhcpv4_client_provider: false,
5351            with_fuchsia_networks: false,
5352        })
5353        .expect("error creating test netcfg");
5354        let allowed_upstream_device_classes = HashSet::from([ALLOWED_UPSTREAM_DEVICE_CLASS]);
5355        netcfg.allowed_upstream_device_classes = &allowed_upstream_device_classes;
5356        let mut dns_watchers = DnsServerWatchers::empty();
5357
5358        struct TestInterfaceState {
5359            _control_server_end:
5360                Option<fidl::endpoints::ServerEnd<fnet_interfaces_admin::ControlMarker>>,
5361            dhcpv6_client_request_stream: Option<fnet_dhcpv6::ClientRequestStream>,
5362            kind: InterfaceKind,
5363        }
5364        let mut interface_states = HashMap::new();
5365        for InterfaceConfig { id, kind } in INTERFACE_CONFIGS.into_iter() {
5366            // Mock new interfaces being discovered by NetCfg as needed.
5367            let (device_class, control_server_end) = match kind {
5368                InterfaceKind::Unowned => (DISALLOWED_UPSTREAM_DEVICE_CLASS, None),
5369                InterfaceKind::NonHost => {
5370                    let (control, control_server_end) =
5371                        fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
5372                            .expect("create endpoints");
5373                    let device_class = DeviceClass::WlanAp;
5374                    assert_matches::assert_matches!(
5375                        netcfg.interface_states.insert(
5376                            id.try_into().expect("interface ID should be nonzero"),
5377                            InterfaceState::new_wlan_ap(
5378                                test_interface_naming_id(),
5379                                control,
5380                                device_class,
5381                                interface::ProvisioningType::Local
5382                            )
5383                        ),
5384                        None
5385                    );
5386                    (device_class, Some(control_server_end))
5387                }
5388                InterfaceKind::Host { upstream } => {
5389                    let (control, control_server_end) =
5390                        fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
5391                            .expect("create endpoints");
5392                    let device_class = if upstream {
5393                        ALLOWED_UPSTREAM_DEVICE_CLASS
5394                    } else {
5395                        DISALLOWED_UPSTREAM_DEVICE_CLASS
5396                    };
5397
5398                    let mut control_request_stream = control_server_end.into_stream();
5399
5400                    let (new_host_result, ()) = futures::join!(
5401                        InterfaceState::new_host(
5402                            test_interface_naming_id(),
5403                            control,
5404                            device_class,
5405                            None,
5406                            interface::ProvisioningType::Local,
5407                        ),
5408                        expect_get_interface_auth(&mut control_request_stream)
5409                    );
5410
5411                    assert_matches::assert_matches!(
5412                        netcfg.interface_states.insert(
5413                            id.try_into().expect("interface ID should be nonzero"),
5414                            new_host_result.expect("new_host should succeed")
5415                        ),
5416                        None
5417                    );
5418
5419                    let (control_inner, _is_terminated) = control_request_stream.into_inner();
5420                    let control_inner = std::sync::Arc::try_unwrap(control_inner)
5421                        .expect("recover original server end");
5422
5423                    (
5424                        device_class,
5425                        Some(
5426                            fidl::endpoints::ServerEnd
5427                                ::<fnet_interfaces_admin::ControlMarker>
5428                                ::from(control_inner.into_channel().into_zx_channel()),
5429                        ),
5430                    )
5431                }
5432            };
5433
5434            let sockaddr = dhcpv6_sockaddr(id);
5435
5436            // Fake an interface added event.
5437            netcfg
5438                .handle_interface_watcher_event(
5439                    fnet_interfaces::Event::Added(fnet_interfaces::Properties {
5440                        id: Some(id.get()),
5441                        name: Some(format!("testif{}", id)),
5442                        port_class: Some(fnet_interfaces::PortClass::Device(
5443                            device_class.into(),
5444                        )),
5445                        online: Some(true),
5446                        addresses: Some(ipv6addrs(Some(sockaddr))),
5447                        has_default_ipv4_route: Some(false),
5448                        has_default_ipv6_route: Some(false),
5449                        ..Default::default()
5450                    }).into(),
5451                    &mut dns_watchers,
5452                    &mut virtualization::Stub,
5453                )
5454                .await
5455                .unwrap_or_else(|e| panic!("error handling interface added event for {} with interface up and link-local addr: {}", id, e));
5456
5457            // Expect DHCPv6 client to have started on host interfaces.
5458            let dhcpv6_client_request_stream = match kind {
5459                InterfaceKind::Unowned | InterfaceKind::NonHost => None,
5460                InterfaceKind::Host { upstream: _ } => {
5461                    let request_stream = check_new_dhcpv6_client(
5462                        &mut dhcpv6_client_provider_request_stream,
5463                        id,
5464                        sockaddr,
5465                        None,
5466                        &mut dns_watchers,
5467                    )
5468                    .await
5469                    .unwrap_or_else(|e| {
5470                        panic!("error checking for new DHCPv6 client on interface {}: {}", id, e)
5471                    });
5472                    Some(request_stream)
5473                }
5474            };
5475            assert!(
5476                interface_states
5477                    .insert(
5478                        id,
5479                        TestInterfaceState {
5480                            _control_server_end: control_server_end,
5481                            dhcpv6_client_request_stream,
5482                            kind,
5483                        }
5484                    )
5485                    .is_none()
5486            );
5487        }
5488
5489        async fn assert_dhcpv6_clients_stopped(
5490            interface_states: &mut HashMap<InterfaceId, TestInterfaceState>,
5491            interface_id: Option<InterfaceId>,
5492        ) -> HashSet<InterfaceId> {
5493            futures::stream::iter(
5494                interface_states.iter_mut(),
5495            ).filter_map(|(
5496                id,
5497                TestInterfaceState { _control_server_end, dhcpv6_client_request_stream, kind },
5498            )| async move {
5499                // Expect DHCPv6 to be restarted iff the interface matches the
5500                // interface used to acquire prefix on, or is an upstream
5501                // capable host if no interface was specified to acquire
5502                // prefixes from.
5503                let expect_restart = interface_id.map_or_else(
5504                    || *kind == InterfaceKind::Host { upstream: true },
5505                    |want_id| want_id == *id,
5506                );
5507                if expect_restart {
5508                    let res = dhcpv6_client_request_stream
5509                        .take()
5510                        .unwrap_or_else(|| panic!("interface {} DHCPv6 client provider request stream missing when expecting restart", id))
5511                        .try_next().await;
5512                    assert_matches!(res, Ok(None));
5513                    Some(*id)
5514                } else {
5515                    if let Some(req_stream) = dhcpv6_client_request_stream.as_mut() {
5516                        // We do not expect DHCPv6 to restart and want to make
5517                        // sure that the stream has not ended. We can't `.await`
5518                        // because that will result in us blocking forever on
5519                        // the next event (since the DHCPv6 client did not close
5520                        // so the stream is still open and blocked).
5521                        assert_matches!(req_stream.try_next().now_or_never(), None, "interface_id={:?}, kind={:?}, id={:?}", interface_id, kind, id);
5522                        assert!(!req_stream.is_terminated());
5523                    }
5524                    None
5525                }
5526            })
5527            .collect().await
5528        }
5529
5530        async fn assert_dhcpv6_clients_started(
5531            count: usize,
5532            dhcpv6_client_provider_request_stream: &mut fnet_dhcpv6::ClientProviderRequestStream,
5533            interface_states: &mut HashMap<InterfaceId, TestInterfaceState>,
5534            want_pd_config: Option<fnet_dhcpv6::PrefixDelegationConfig>,
5535        ) -> HashSet<InterfaceId> {
5536            dhcpv6_client_provider_request_stream
5537                .map(|res| res.expect("DHCPv6 ClientProvider request stream error"))
5538                .take(count)
5539                .then(
5540                    |fnet_dhcpv6::ClientProviderRequest::NewClient {
5541                         params:
5542                             fnet_dhcpv6::NewClientParams { interface_id, address: _, config, .. },
5543                         request,
5544                         control_handle: _,
5545                     }| async move {
5546                        let mut new_stream = request.into_stream();
5547                        // NetCfg always keeps the server hydrated with a pending hanging-get.
5548                        expect_watch_prefixes(&mut new_stream).await;
5549                        (interface_id, config, new_stream)
5550                    },
5551                )
5552                .map(|(interface_id, config, new_stream)| {
5553                    let interface_id = interface_id
5554                        .expect("interface ID missing in new DHCPv6 client request")
5555                        .try_into()
5556                        .expect("interface ID should be nonzero");
5557                    let TestInterfaceState {
5558                        _control_server_end,
5559                        dhcpv6_client_request_stream,
5560                        kind: _,
5561                    } = interface_states.get_mut(&interface_id).unwrap_or_else(|| {
5562                        panic!("interface {} must be present in map", interface_id)
5563                    });
5564                    assert!(
5565                        std::mem::replace(dhcpv6_client_request_stream, Some(new_stream)).is_none()
5566                    );
5567
5568                    let config = fnet_dhcpv6_ext::ClientConfig::try_from(
5569                        config.expect("ClientConfig must be present"),
5570                    )
5571                    .expect("ClientConfig should pass FIDL table validation");
5572                    assert_eq!(
5573                        config,
5574                        fnet_dhcpv6_ext::ClientConfig {
5575                            information_config: fnet_dhcpv6_ext::InformationConfig {
5576                                dns_servers: true,
5577                            },
5578                            non_temporary_address_config: Default::default(),
5579                            prefix_delegation_config: want_pd_config.clone(),
5580                        }
5581                    );
5582                    interface_id
5583                })
5584                .collect()
5585                .await
5586        }
5587
5588        async fn handle_watch_prefix_with_fake(
5589            netcfg: &mut NetCfg<'_>,
5590            dns_watchers: &mut DnsServerWatchers<'_>,
5591            interface_id: InterfaceId,
5592            fake_prefixes: Vec<fnet_dhcpv6::Prefix>,
5593            // Used to test handling PrefixControl.WatchPrefix & Client.WatchPrefixes
5594            // events in different orders.
5595            handle_dhcpv6_prefixes_before_watch_prefix: bool,
5596        ) {
5597            let responder = netcfg
5598                .dhcpv6_prefix_provider_handler
5599                .as_mut()
5600                .map(dhcpv6::PrefixProviderHandler::try_next_prefix_control_request)
5601                .expect("DHCPv6 prefix provider handler must be present")
5602                .await
5603                .expect("prefix provider request")
5604                .expect("PrefixProvider request stream exhausted")
5605                .into_watch_prefix()
5606                .expect("request not WatchPrefix");
5607
5608            async fn handle_dhcpv6_prefixes(
5609                netcfg: &mut NetCfg<'_>,
5610                dns_watchers: &mut DnsServerWatchers<'_>,
5611                interface_id: InterfaceId,
5612                fake_prefixes: Vec<fnet_dhcpv6::Prefix>,
5613            ) {
5614                netcfg
5615                    .handle_dhcpv6_prefixes(interface_id, Ok(fake_prefixes), dns_watchers)
5616                    .await
5617                    .expect("handle DHCPv6 prefixes")
5618            }
5619
5620            async fn handle_watch_prefix(
5621                netcfg: &mut NetCfg<'_>,
5622                dns_watchers: &mut DnsServerWatchers<'_>,
5623                responder: fnet_dhcpv6::PrefixControlWatchPrefixResponder,
5624            ) {
5625                netcfg
5626                    .handle_watch_prefix(responder, dns_watchers)
5627                    .await
5628                    .expect("handle watch prefix")
5629            }
5630
5631            if handle_dhcpv6_prefixes_before_watch_prefix {
5632                handle_dhcpv6_prefixes(netcfg, dns_watchers, interface_id, fake_prefixes).await;
5633                handle_watch_prefix(netcfg, dns_watchers, responder).await;
5634            } else {
5635                handle_watch_prefix(netcfg, dns_watchers, responder).await;
5636                handle_dhcpv6_prefixes(netcfg, dns_watchers, interface_id, fake_prefixes).await;
5637            }
5638        }
5639
5640        // Making an AcquirePrefix call should trigger restarting DHCPv6 w/ PD.
5641        let (prefix_control, server_end) =
5642            fidl::endpoints::create_proxy::<fnet_dhcpv6::PrefixControlMarker>();
5643        let mut lookup_admin_fut = lookup_admin_request_stream
5644            .try_for_each(|req| {
5645                let (_, responder): (Vec<fnet::SocketAddress>, _) =
5646                    req.into_set_dns_servers().expect("request must be SetDnsServers");
5647                responder.send(Ok(())).expect("send SetDnsServers response");
5648                futures::future::ok(())
5649            })
5650            .fuse();
5651
5652        {
5653            let acquire_prefix_fut = netcfg.handle_dhcpv6_acquire_prefix(
5654                fnet_dhcpv6::AcquirePrefixConfig {
5655                    interface_id: interface_id.map(InterfaceId::get),
5656                    preferred_prefix_len,
5657                    ..Default::default()
5658                },
5659                server_end,
5660                &mut dns_watchers,
5661            );
5662            futures::select! {
5663                res = acquire_prefix_fut.fuse() => {
5664                    res.expect("acquire DHCPv6 prefix")
5665                }
5666                res = lookup_admin_fut => {
5667                    panic!("fuchsia.net.name/LookupAdmin request stream exhausted unexpectedly: {:?}", res)
5668                },
5669            };
5670            // Expect DHCPv6 client to have been restarted on the appropriate
5671            // interfaces with PD configured.
5672            let stopped = assert_dhcpv6_clients_stopped(&mut interface_states, interface_id).await;
5673            let started = assert_dhcpv6_clients_started(
5674                stopped.len(),
5675                dhcpv6_client_provider_request_stream.by_ref(),
5676                &mut interface_states,
5677                Some(preferred_prefix_len.map_or(
5678                    fnet_dhcpv6::PrefixDelegationConfig::Empty(fnet_dhcpv6::Empty),
5679                    fnet_dhcpv6::PrefixDelegationConfig::PrefixLength,
5680                )),
5681            )
5682            .await;
5683            assert_eq!(started, stopped);
5684
5685            // Yield a prefix to the PrefixControl.
5686            let prefix = fnet_dhcpv6::Prefix {
5687                prefix: fidl_ip_v6_with_prefix!("abcd::/64"),
5688                lifetimes: fnet_dhcpv6::Lifetimes { valid_until: 123, preferred_until: 456 },
5689            };
5690            let any_eligible_interface = *started.iter().next().expect(
5691                "must have configured DHCPv6 client to perform PD on at least one interface",
5692            );
5693            let (watch_prefix_res, ()) = futures::future::join(
5694                prefix_control.watch_prefix(),
5695                handle_watch_prefix_with_fake(
5696                    &mut netcfg,
5697                    &mut dns_watchers,
5698                    any_eligible_interface,
5699                    vec![prefix.clone()],
5700                    true, /* handle_dhcpv6_prefixes_before_watch_prefix */
5701                ),
5702            )
5703            .await;
5704            assert_matches!(watch_prefix_res, Ok(fnet_dhcpv6::PrefixEvent::Assigned(got_prefix)) => {
5705                assert_eq!(got_prefix, prefix);
5706            });
5707
5708            // Yield a different prefix from what was yielded before.
5709            let renewed_prefix = fnet_dhcpv6::Prefix {
5710                lifetimes: fnet_dhcpv6::Lifetimes {
5711                    valid_until: 123_123,
5712                    preferred_until: 456_456,
5713                },
5714                ..prefix
5715            };
5716            let (watch_prefix_res, ()) = futures::future::join(
5717                prefix_control.watch_prefix(),
5718                handle_watch_prefix_with_fake(
5719                    &mut netcfg,
5720                    &mut dns_watchers,
5721                    any_eligible_interface,
5722                    vec![renewed_prefix.clone()],
5723                    false, /* handle_dhcpv6_prefixes_before_watch_prefix */
5724                ),
5725            )
5726            .await;
5727            assert_matches!(watch_prefix_res, Ok(fnet_dhcpv6::PrefixEvent::Assigned(
5728                got_prefix,
5729            )) => {
5730                assert_eq!(got_prefix, renewed_prefix);
5731            });
5732            let (watch_prefix_res, ()) = futures::future::join(
5733                prefix_control.watch_prefix(),
5734                handle_watch_prefix_with_fake(
5735                    &mut netcfg,
5736                    &mut dns_watchers,
5737                    any_eligible_interface,
5738                    vec![],
5739                    true, /* handle_dhcpv6_prefixes_before_watch_prefix */
5740                ),
5741            )
5742            .await;
5743            assert_matches!(
5744                watch_prefix_res,
5745                Ok(fnet_dhcpv6::PrefixEvent::Unassigned(fnet_dhcpv6::Empty))
5746            );
5747        }
5748
5749        // Closing the PrefixControl should trigger clients running DHCPv6-PD to
5750        // be restarted.
5751        {
5752            futures::select! {
5753                () = netcfg.on_dhcpv6_prefix_control_close(&mut dns_watchers).fuse() => {},
5754                res = lookup_admin_fut => {
5755                    panic!(
5756                        "fuchsia.net.name/LookupAdmin request stream exhausted unexpectedly: {:?}",
5757                        res,
5758                    )
5759                },
5760            }
5761            let stopped = assert_dhcpv6_clients_stopped(&mut interface_states, interface_id).await;
5762            let started = assert_dhcpv6_clients_started(
5763                stopped.len(),
5764                dhcpv6_client_provider_request_stream.by_ref(),
5765                &mut interface_states,
5766                None,
5767            )
5768            .await;
5769            assert_eq!(started, stopped);
5770        }
5771    }
5772
5773    // Tests that DHCPv6 clients are configured to perform PD on eligible
5774    // upstream-providing interfaces while a `PrefixControl` channel is open.
5775    #[fuchsia::test]
5776    async fn test_dhcpv6_pd_on_added_upstream() {
5777        let (
5778            mut netcfg,
5779            ServerEnds {
5780                lookup_admin: _,
5781                dhcpv4_client_provider: _,
5782                dhcpv6_client_provider: mut dhcpv6_client_provider_request_stream,
5783                route_set_v4_provider: _,
5784                dhcpv4_server: _,
5785                fuchsia_networks: _,
5786            },
5787        ) = test_netcfg(NetcfgTestArgs {
5788            with_dhcpv4_client_provider: false,
5789            with_fuchsia_networks: false,
5790        })
5791        .expect("error creating test netcfg");
5792        let allowed_upstream_device_classes = HashSet::from([ALLOWED_UPSTREAM_DEVICE_CLASS]);
5793        netcfg.allowed_upstream_device_classes = &allowed_upstream_device_classes;
5794        let mut dns_watchers = DnsServerWatchers::empty();
5795
5796        let (_prefix_control, server_end) =
5797            fidl::endpoints::create_proxy::<fnet_dhcpv6::PrefixControlMarker>();
5798        netcfg
5799            .handle_dhcpv6_acquire_prefix(
5800                fnet_dhcpv6::AcquirePrefixConfig::default(),
5801                server_end,
5802                &mut dns_watchers,
5803            )
5804            .await
5805            .expect("handle DHCPv6 acquire prefix");
5806
5807        for (id, upstream) in
5808            [(InterfaceId::new(1).unwrap(), true), (InterfaceId::new(2).unwrap(), false)]
5809        {
5810            // Mock interface being discovered by NetCfg.
5811            let (control, control_server_end) =
5812                fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
5813                    .expect("create endpoints");
5814            let device_class = if upstream {
5815                ALLOWED_UPSTREAM_DEVICE_CLASS
5816            } else {
5817                DISALLOWED_UPSTREAM_DEVICE_CLASS
5818            };
5819            let mut control_request_stream = control_server_end.into_stream();
5820
5821            let (new_host_result, ()) = futures::join!(
5822                InterfaceState::new_host(
5823                    test_interface_naming_id(),
5824                    control,
5825                    device_class,
5826                    upstream
5827                        .then_some(fnet_dhcpv6::PrefixDelegationConfig::Empty(fnet_dhcpv6::Empty)),
5828                    interface::ProvisioningType::Local,
5829                ),
5830                expect_get_interface_auth(&mut control_request_stream)
5831            );
5832
5833            assert_matches::assert_matches!(
5834                netcfg
5835                    .interface_states
5836                    .insert(id, new_host_result.expect("new_host should succeed")),
5837                None
5838            );
5839
5840            let sockaddr = dhcpv6_sockaddr(id);
5841
5842            // Fake an interface added event.
5843            netcfg
5844                .handle_interface_watcher_event(
5845                    fnet_interfaces::Event::Added(fnet_interfaces::Properties {
5846                        id: Some(id.get()),
5847                        name: Some(format!("testif{}", id)),
5848                        port_class: Some(fnet_interfaces::PortClass::Device(
5849                            device_class.into(),
5850                        )),
5851                        online: Some(true),
5852                        addresses: Some(ipv6addrs(Some(sockaddr))),
5853                        has_default_ipv4_route: Some(false),
5854                        has_default_ipv6_route: Some(false),
5855                        ..Default::default()
5856                    }).into(),
5857                    &mut dns_watchers,
5858                    &mut virtualization::Stub,
5859                )
5860                .await
5861                .unwrap_or_else(|e| panic!("error handling interface added event for {} with interface up and link-local addr: {:?}", id, e));
5862
5863            // Expect DHCPv6 client to have started with PD configuration.
5864            let _: fnet_dhcpv6::ClientRequestStream = check_new_dhcpv6_client(
5865                &mut dhcpv6_client_provider_request_stream,
5866                id,
5867                sockaddr,
5868                upstream.then_some(fnet_dhcpv6::PrefixDelegationConfig::Empty(fnet_dhcpv6::Empty)),
5869                &mut dns_watchers,
5870            )
5871            .await
5872            .unwrap_or_else(|e| {
5873                panic!("error checking for new DHCPv6 client on interface {}: {:?}", id, e)
5874            });
5875        }
5876    }
5877
5878    struct InterfacePropertiesHelper {
5879        id: InterfaceId,
5880        online: Option<bool>,
5881        has_default_ipv4_route: Option<bool>,
5882        has_default_ipv6_route: Option<bool>,
5883        addresses: Option<Vec<fnet_interfaces::Address>>,
5884    }
5885
5886    // Create a `fnet_interfaces::Properties` object using
5887    // a small subset of fields that influence whether an
5888    // interface is shared with the socketproxy.
5889    fn create_properties(
5890        InterfacePropertiesHelper {
5891            id,
5892            online,
5893            has_default_ipv4_route,
5894            has_default_ipv6_route,
5895            addresses,
5896        }: InterfacePropertiesHelper,
5897    ) -> fnet_interfaces::Properties {
5898        fnet_interfaces::Properties {
5899            id: Some(id.get()),
5900            name: Some(format!("testif{}", id)),
5901            port_class: Some(fnet_interfaces::PortClass::Device(
5902                ALLOWED_UPSTREAM_DEVICE_CLASS.into(),
5903            )),
5904            online,
5905            has_default_ipv4_route,
5906            has_default_ipv6_route,
5907            addresses,
5908            ..Default::default()
5909        }
5910    }
5911
5912    // Given a list of interface ids and provisioning actions,
5913    // get the nth interface with `Local` provisioning.
5914    //
5915    // Returns None if there is no nth matching interface.
5916    fn get_nth_interface_id_locally_provisioned(
5917        interfaces: &[(InterfaceId, interface::ProvisioningType)],
5918        nth: usize,
5919    ) -> Option<InterfaceId> {
5920        interfaces.into_iter().filter_map(|(id, action)| (*action == Local).then_some(*id)).nth(nth)
5921    }
5922
5923    const INTERFACE_ID2: InterfaceId = InterfaceId::new(2).unwrap();
5924    const INTERFACE_ID3: InterfaceId = InterfaceId::new(3).unwrap();
5925
5926    async fn fuchsia_networks_fallback_helper(
5927        interfaces: &[(InterfaceId, interface::ProvisioningType)],
5928        final_event: fnet_interfaces::Event,
5929    ) {
5930        // The provided interface id list must be non-empty.
5931        assert!(interfaces.len() > 0);
5932
5933        let (mut netcfg, ServerEnds { fuchsia_networks, .. }) = test_netcfg(NetcfgTestArgs {
5934            with_dhcpv4_client_provider: false,
5935            with_fuchsia_networks: true,
5936        })
5937        .expect("error creating test netcfg");
5938        let mut fuchsia_networks_stream = fuchsia_networks.into_stream();
5939        let mut dns_watchers = DnsServerWatchers::empty();
5940
5941        assert_eq!(netcfg.socket_proxy_state.as_ref().expect("should be set").default_id(), None);
5942
5943        // The initial default interface is the first one that is
5944        // locally provisioned. There must be at least one interface
5945        // that has ProvisioningAction::Local set.
5946        let initial_default_interface =
5947            get_nth_interface_id_locally_provisioned(interfaces, 0).unwrap();
5948
5949        // Mock interfaces being discovered by NetCfg.
5950        for (id, provisioning_action) in interfaces.iter() {
5951            let (control, control_server_end) =
5952                fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
5953                    .expect("create endpoints");
5954            let mut control_request_stream = control_server_end.into_stream();
5955
5956            let (new_host_result, ()) = futures::join!(
5957                InterfaceState::new_host(
5958                    test_interface_naming_id(),
5959                    control,
5960                    ALLOWED_UPSTREAM_DEVICE_CLASS,
5961                    None,
5962                    *provisioning_action,
5963                ),
5964                expect_get_interface_auth(&mut control_request_stream)
5965            );
5966
5967            assert_matches::assert_matches!(
5968                netcfg
5969                    .interface_states
5970                    .insert(*id, new_host_result.expect("new_host should succeed")),
5971                None
5972            );
5973
5974            // Fake an interface added event. As `has_default_ipv4_route` and
5975            // `has_default_ipv6_route` are true, the network should be added to
5976            // the socketproxy state and made as default.
5977            let (watcher_result, ()) = futures::future::join(
5978                netcfg.handle_interface_watcher_event(
5979                    fnet_interfaces::Event::Added(create_properties(InterfacePropertiesHelper {
5980                        id: *id,
5981                        online: Some(true),
5982                        has_default_ipv4_route: Some(true),
5983                        has_default_ipv6_route: Some(true),
5984                        addresses: Some(ipv6addrs(None)),
5985                    }))
5986                    .into(),
5987                    &mut dns_watchers,
5988                    &mut virtualization::Stub,
5989                ),
5990                async {
5991                    // The call to `add`. Only locally provisioned networks
5992                    // are added in FuchsiaNetworks.
5993                    if *provisioning_action == Local {
5994                        respond_to_socketproxy(&mut fuchsia_networks_stream, Ok(())).await;
5995                    }
5996
5997                    // The call to `set_default`. Only the first viable network
5998                    // will be set to default.
5999                    if *id == initial_default_interface {
6000                        respond_to_socketproxy(&mut fuchsia_networks_stream, Ok(())).await;
6001                    }
6002                },
6003            )
6004            .await;
6005            assert_matches::assert_matches!(watcher_result, Ok(()));
6006        }
6007
6008        assert_eq!(
6009            netcfg.socket_proxy_state.as_ref().expect("should be set").default_id(),
6010            Some(initial_default_interface)
6011        );
6012
6013        // Send an interface changed event with `has_default_ipv4_route`
6014        // becoming false. This results in no calls against FuchsiaNetworks
6015        // due to one of the default routes still being present.
6016        assert_matches::assert_matches!(
6017            netcfg
6018                .handle_interface_watcher_event(
6019                    fnet_interfaces::Event::Changed(create_properties(InterfacePropertiesHelper {
6020                        id: initial_default_interface,
6021                        online: None,
6022                        has_default_ipv4_route: Some(false),
6023                        has_default_ipv6_route: None,
6024                        addresses: None,
6025                    }))
6026                    .into(),
6027                    &mut dns_watchers,
6028                    &mut virtualization::Stub,
6029                )
6030                .await,
6031            Ok(())
6032        );
6033
6034        // Send an interface changed event with an address update
6035        // that does not impact the network being a valid candidate.
6036        // This results in no calls against FuchsiaNetworks.
6037        assert_matches::assert_matches!(
6038            netcfg
6039                .handle_interface_watcher_event(
6040                    fnet_interfaces::Event::Changed(create_properties(InterfacePropertiesHelper {
6041                        id: initial_default_interface,
6042                        online: None,
6043                        has_default_ipv4_route: None,
6044                        has_default_ipv6_route: None,
6045                        addresses: Some(vec![fnet_interfaces::Address {
6046                            addr: Some(fidl_subnet!("192.0.2.0/24")),
6047                            assignment_state: Some(
6048                                fnet_interfaces::AddressAssignmentState::Assigned
6049                            ),
6050                            ..Default::default()
6051                        }]),
6052                    }))
6053                    .into(),
6054                    &mut dns_watchers,
6055                    &mut virtualization::Stub,
6056                )
6057                .await,
6058            Ok(())
6059        );
6060
6061        // Send an interface changed event that should cause the network
6062        // to be removed from the socketproxy.
6063        let (watcher_result, ()) = futures::future::join(
6064            netcfg.handle_interface_watcher_event(
6065                final_event.into(),
6066                &mut dns_watchers,
6067                &mut virtualization::Stub,
6068            ),
6069            async {
6070                // The call to `set_default`.
6071                respond_to_socketproxy(&mut fuchsia_networks_stream, Ok(())).await;
6072
6073                // The call to `remove`.
6074                respond_to_socketproxy(&mut fuchsia_networks_stream, Ok(())).await;
6075            },
6076        )
6077        .await;
6078        assert_matches::assert_matches!(watcher_result, Ok(()));
6079
6080        // The fallback default interface is the second one that is locally
6081        // provisioned (or None if one does not exist).
6082        assert_eq!(
6083            netcfg.socket_proxy_state.as_ref().expect("should be set").default_id(),
6084            get_nth_interface_id_locally_provisioned(interfaces, 1)
6085        );
6086    }
6087
6088    // Test the default network functionality of the FuchsiaNetworks
6089    // integration. Determine the fallback behavior when there is another
6090    // valid network and when no alternative network exists.
6091    #[test_case(
6092        &[INTERFACE_ID],
6093            fnet_interfaces::Event::Changed(create_properties(InterfacePropertiesHelper {
6094                id: INTERFACE_ID,
6095                online: None,
6096                has_default_ipv4_route: None,
6097                has_default_ipv6_route: Some(false),
6098                addresses: None,
6099        }))
6100    ; "one_iface_update_lost_candidacy_v6")]
6101    #[test_case(
6102        &[INTERFACE_ID],
6103            fnet_interfaces::Event::Changed(create_properties(InterfacePropertiesHelper {
6104                id: INTERFACE_ID,
6105                online: Some(false),
6106                has_default_ipv4_route: None,
6107                has_default_ipv6_route: None,
6108                addresses: None,
6109        }))
6110    ; "one_iface_update_lost_candidacy_offline")]
6111    #[test_case(
6112        &[INTERFACE_ID],
6113        fnet_interfaces::Event::Removed(INTERFACE_ID.get())
6114    ; "one_iface_remove_default")]
6115    #[test_case(
6116        &[INTERFACE_ID, INTERFACE_ID2],
6117            fnet_interfaces::Event::Changed(create_properties(InterfacePropertiesHelper {
6118                id: INTERFACE_ID,
6119                online: None,
6120                has_default_ipv4_route: None,
6121                has_default_ipv6_route: Some(false),
6122                addresses: None,
6123        }))
6124    ; "two_iface_update_lost_candidacy")]
6125    #[test_case(
6126        &[INTERFACE_ID, INTERFACE_ID2],
6127        fnet_interfaces::Event::Removed(INTERFACE_ID.get())
6128    ; "two_iface_remove_default")]
6129    #[test_case(
6130        &[INTERFACE_ID, INTERFACE_ID2, INTERFACE_ID3],
6131            fnet_interfaces::Event::Changed(create_properties(InterfacePropertiesHelper {
6132                id: INTERFACE_ID,
6133                online: None,
6134                has_default_ipv4_route: None,
6135                has_default_ipv6_route: Some(false),
6136                addresses: None,
6137        }))
6138    ; "three_iface_update_lost_candidacy")]
6139    #[test_case(
6140        &[INTERFACE_ID, INTERFACE_ID2, INTERFACE_ID3],
6141        fnet_interfaces::Event::Removed(INTERFACE_ID.get())
6142    ; "three_iface_remove_default")]
6143    #[fuchsia::test]
6144    async fn test_fuchsia_networks_fallback_all_local(
6145        interface_ids: &[InterfaceId],
6146        final_event: fnet_interfaces::Event,
6147    ) {
6148        let interfaces: Vec<(InterfaceId, interface::ProvisioningType)> =
6149            interface_ids.into_iter().map(|id| (*id, Local)).collect();
6150        fuchsia_networks_fallback_helper(&interfaces, final_event).await
6151    }
6152
6153    // Test the default network functionality of the FuchsiaNetworks
6154    // integration. Determine the fallback behavior when there are delegated
6155    // interfaces present.
6156    #[test_case(
6157        &[(INTERFACE_ID, Local), (INTERFACE_ID2, Delegated)],
6158        fnet_interfaces::Event::Removed(INTERFACE_ID.get())
6159    ; "one_local_iface_first_iface")]
6160    #[test_case(
6161        &[(INTERFACE_ID, Delegated), (INTERFACE_ID2, Delegated), (INTERFACE_ID3, Local)],
6162        fnet_interfaces::Event::Removed(INTERFACE_ID3.get())
6163    ; "one_local_iface_not_first_iface")]
6164    #[test_case(
6165        &[(INTERFACE_ID, Local), (INTERFACE_ID2, Delegated), (INTERFACE_ID3, Local)],
6166        fnet_interfaces::Event::Removed(INTERFACE_ID.get())
6167    ; "two_local_iface_first_iface")]
6168    #[test_case(
6169        &[(INTERFACE_ID, Delegated), (INTERFACE_ID2, Local), (INTERFACE_ID3, Local)],
6170        fnet_interfaces::Event::Removed(INTERFACE_ID2.get())
6171    ; "two_local_iface_not_first_iface")]
6172    #[fuchsia::test]
6173    async fn test_fuchsia_networks_fallback_mixed_provisioning(
6174        interfaces: &[(InterfaceId, interface::ProvisioningType)],
6175        final_event: fnet_interfaces::Event,
6176    ) {
6177        fuchsia_networks_fallback_helper(&interfaces, final_event).await
6178    }
6179
6180    #[test]
6181    fn test_config() {
6182        let config_str = r#"
6183{
6184  "dns_config": {
6185    "servers": ["8.8.8.8"]
6186  },
6187  "filter_config": {
6188    "rules": [],
6189    "nat_rules": [],
6190    "rdr_rules": []
6191  },
6192  "filter_enabled_interface_types": ["wlanclient", "wlanap"],
6193  "interface_metrics": {
6194    "wlan_metric": 100,
6195    "eth_metric": 10,
6196    "blackhole_metric": 123
6197  },
6198  "allowed_upstream_device_classes": ["ethernet", "wlanclient"],
6199  "allowed_bridge_upstream_device_classes": ["ethernet"],
6200  "enable_dhcpv6": true,
6201  "forwarded_device_classes": { "ipv4": [ "ethernet" ], "ipv6": [ "wlanclient" ] },
6202  "interface_naming_policy": [ { "matchers": [
6203        {"bus_types": ["usb", "pci", "sdio"]},
6204        {"device_classes": ["ethernet", "wlanclient", "wlanap"]},
6205        {"topological_path": "abcde"},
6206        {"any": true}
6207    ], "naming_scheme": [
6208        { "type": "dynamic", "rule": "device_class" },
6209        { "type": "static", "value": "x" },
6210        { "type": "dynamic", "rule": "normalized_mac" },
6211        { "type": "dynamic", "rule": "bus_type" },
6212        { "type": "dynamic", "rule": "bus_path" },
6213        { "type": "default" }
6214    ] } ],
6215    "interface_provisioning_policy": [ {
6216        "matchers": [ {"any": false } ],
6217        "provisioning": "delegated",
6218        "netstack_managed_routes_designation": "interface_local"
6219    }, {
6220        "matchers": [ {"interface_name": "xyz" } ],
6221        "provisioning": "local"
6222    } ],
6223   "blackhole_interfaces": [ "ifb0" ],
6224   "enable_socket_proxy": true
6225}
6226"#;
6227
6228        let Config {
6229            dns_config: DnsConfig { servers },
6230            filter_config,
6231            filter_enabled_interface_types,
6232            interface_metrics,
6233            allowed_upstream_device_classes,
6234            allowed_bridge_upstream_device_classes,
6235            enable_dhcpv6,
6236            forwarded_device_classes,
6237            interface_naming_policy,
6238            interface_provisioning_policy,
6239            blackhole_interfaces,
6240            enable_socket_proxy,
6241        } = Config::load_str(config_str).unwrap();
6242
6243        assert_eq!(vec!["8.8.8.8".parse::<std::net::IpAddr>().unwrap()], servers);
6244        let FilterConfig { rules, nat_rules, rdr_rules } = filter_config;
6245        assert_eq!(Vec::<String>::new(), rules);
6246        assert_eq!(Vec::<String>::new(), nat_rules);
6247        assert_eq!(Vec::<String>::new(), rdr_rules);
6248
6249        assert_eq!(
6250            HashSet::from([InterfaceType::WlanClient, InterfaceType::WlanAp]),
6251            filter_enabled_interface_types
6252        );
6253
6254        let expected_metrics = InterfaceMetrics {
6255            wlan_metric: Metric(100),
6256            eth_metric: Metric(10),
6257            blackhole_metric: Metric(123),
6258        };
6259        assert_eq!(interface_metrics, expected_metrics);
6260
6261        assert_eq!(
6262            AllowedDeviceClasses(HashSet::from([DeviceClass::Ethernet, DeviceClass::WlanClient])),
6263            allowed_upstream_device_classes
6264        );
6265        assert_eq!(
6266            AllowedDeviceClasses(HashSet::from([DeviceClass::Ethernet])),
6267            allowed_bridge_upstream_device_classes
6268        );
6269
6270        assert_eq!(enable_dhcpv6, true);
6271
6272        let expected_classes = ForwardedDeviceClasses {
6273            ipv4: HashSet::from([DeviceClass::Ethernet]),
6274            ipv6: HashSet::from([DeviceClass::WlanClient]),
6275        };
6276        assert_eq!(forwarded_device_classes, expected_classes);
6277
6278        let expected_naming_policy = Vec::from([interface::NamingRule {
6279            matchers: HashSet::from([
6280                interface::MatchingRule::BusTypes(vec![
6281                    interface::BusType::USB,
6282                    interface::BusType::PCI,
6283                    interface::BusType::SDIO,
6284                ]),
6285                interface::MatchingRule::DeviceClasses(vec![
6286                    DeviceClass::Ethernet,
6287                    DeviceClass::WlanClient,
6288                    DeviceClass::WlanAp,
6289                ]),
6290                interface::MatchingRule::TopologicalPath(glob::Pattern::new("abcde").unwrap()),
6291                interface::MatchingRule::Any(true),
6292            ]),
6293            naming_scheme: vec![
6294                interface::NameCompositionRule::Dynamic {
6295                    rule: interface::DynamicNameCompositionRule::DeviceClass,
6296                },
6297                interface::NameCompositionRule::Static { value: "x".to_owned() },
6298                interface::NameCompositionRule::Dynamic {
6299                    rule: interface::DynamicNameCompositionRule::NormalizedMac,
6300                },
6301                interface::NameCompositionRule::Dynamic {
6302                    rule: interface::DynamicNameCompositionRule::BusType,
6303                },
6304                interface::NameCompositionRule::Dynamic {
6305                    rule: interface::DynamicNameCompositionRule::BusPath,
6306                },
6307                interface::NameCompositionRule::Default,
6308            ],
6309        }]);
6310        assert_eq!(interface_naming_policy, expected_naming_policy);
6311
6312        let expected_provisioning_policy = Vec::from([
6313            interface::ProvisioningRule {
6314                matchers: HashSet::from([interface::ProvisioningMatchingRule::Common(
6315                    interface::MatchingRule::Any(false),
6316                )]),
6317                action: interface::ProvisioningAction {
6318                    provisioning: interface::ProvisioningType::Delegated,
6319                    netstack_managed_routes_designation: Some(
6320                        interface::NetstackManagedRoutesDesignation::InterfaceLocal,
6321                    ),
6322                },
6323            },
6324            interface::ProvisioningRule {
6325                matchers: HashSet::from([interface::ProvisioningMatchingRule::InterfaceName {
6326                    pattern: glob::Pattern::new("xyz").unwrap(),
6327                }]),
6328                action: interface::ProvisioningAction {
6329                    provisioning: interface::ProvisioningType::Local,
6330                    netstack_managed_routes_designation: None,
6331                },
6332            },
6333        ]);
6334        assert_eq!(interface_provisioning_policy, expected_provisioning_policy);
6335
6336        assert_eq!(blackhole_interfaces, vec!["ifb0".to_string()]);
6337
6338        assert_eq!(enable_socket_proxy, true)
6339    }
6340
6341    #[test]
6342    fn test_config_defaults() {
6343        let config_str = r#"
6344{
6345  "dns_config": { "servers": [] },
6346  "filter_config": {
6347    "rules": [],
6348    "nat_rules": [],
6349    "rdr_rules": []
6350  },
6351  "filter_enabled_interface_types": []
6352}
6353"#;
6354
6355        let Config {
6356            dns_config: _,
6357            filter_config: _,
6358            filter_enabled_interface_types: _,
6359            allowed_upstream_device_classes,
6360            allowed_bridge_upstream_device_classes,
6361            interface_metrics,
6362            enable_dhcpv6,
6363            forwarded_device_classes: _,
6364            interface_naming_policy,
6365            interface_provisioning_policy,
6366            blackhole_interfaces,
6367            enable_socket_proxy,
6368        } = Config::load_str(config_str).unwrap();
6369
6370        assert_eq!(allowed_upstream_device_classes, Default::default());
6371        assert_eq!(allowed_bridge_upstream_device_classes, Default::default());
6372        assert_eq!(interface_metrics, Default::default());
6373        assert_eq!(enable_dhcpv6, true);
6374        assert_eq!(interface_naming_policy.len(), 0);
6375        assert_eq!(interface_provisioning_policy.len(), 0);
6376        assert_eq!(blackhole_interfaces, Vec::<String>::new());
6377        assert_eq!(enable_socket_proxy, false);
6378    }
6379
6380    #[test_case(
6381        "eth_metric", Default::default(), Metric(1), Metric(1);
6382        "wlan assumes default metric when unspecified")]
6383    #[test_case("wlan_metric", Metric(1), Default::default(), Metric(1);
6384        "eth assumes default metric when unspecified")]
6385    fn test_config_metric_individual_defaults(
6386        metric_name: &'static str,
6387        wlan_metric: Metric,
6388        eth_metric: Metric,
6389        expect_metric: Metric,
6390    ) {
6391        let config_str = format!(
6392            r#"
6393{{
6394  "dns_config": {{ "servers": [] }},
6395  "filter_config": {{
6396    "rules": [],
6397    "nat_rules": [],
6398    "rdr_rules": []
6399  }},
6400  "filter_enabled_interface_types": [],
6401  "interface_metrics": {{ "{}": {} }}
6402}}
6403"#,
6404            metric_name, expect_metric
6405        );
6406
6407        let Config {
6408            dns_config: _,
6409            filter_config: _,
6410            filter_enabled_interface_types: _,
6411            allowed_upstream_device_classes: _,
6412            allowed_bridge_upstream_device_classes: _,
6413            enable_dhcpv6: _,
6414            interface_metrics,
6415            forwarded_device_classes: _,
6416            interface_naming_policy: _,
6417            interface_provisioning_policy: _,
6418            blackhole_interfaces: _,
6419            enable_socket_proxy: _,
6420        } = Config::load_str(&config_str).unwrap();
6421
6422        let expected_metrics =
6423            InterfaceMetrics { wlan_metric, eth_metric, blackhole_metric: Default::default() };
6424        assert_eq!(interface_metrics, expected_metrics);
6425    }
6426
6427    #[test]
6428    fn test_config_denies_unknown_fields() {
6429        let config_str = r#"{
6430            "filter_enabled_interface_types": ["wlanclient"],
6431            "foobar": "baz"
6432        }"#;
6433
6434        let err = Config::load_str(config_str).expect_err("config shouldn't accept unknown fields");
6435        let err = err.downcast::<serde_json5::Error>().expect("downcast error");
6436        assert_matches!(
6437            err,
6438            serde_json5::Error::Message {
6439                location: Some(serde_json5::Location { line: 3, .. }),
6440                ..
6441            }
6442        );
6443        // Ensure the error is complaining about unknown field.
6444        assert!(format!("{:?}", err).contains("foobar"));
6445    }
6446
6447    #[test]
6448    fn test_config_denies_unknown_fields_nested() {
6449        let bad_configs = vec![
6450            r#"
6451{
6452  "dns_config": { "speling": [] },
6453  "filter_config": {
6454    "rules": [],
6455    "nat_rules": [],
6456    "rdr_rules": []
6457  },
6458  "filter_enabled_interface_types": []
6459}
6460"#,
6461            r#"
6462{
6463  "dns_config": { "servers": [] },
6464  "filter_config": {
6465    "speling": [],
6466    "nat_rules": [],
6467    "rdr_rules": []
6468  },
6469  "filter_enabled_interface_types": []
6470}
6471"#,
6472            r#"
6473{
6474  "dns_config": { "servers": [] },
6475  "filter_config": {
6476    "rules": [],
6477    "nat_rules": [],
6478    "rdr_rules": []
6479  },
6480  "filter_enabled_interface_types": ["speling"]
6481}
6482"#,
6483            r#"
6484{
6485  "dns_config": { "servers": [] },
6486  "filter_config": {
6487    "rules": [],
6488    "nat_rules": [],
6489    "rdr_rules": []
6490  },
6491  "interface_metrics": {
6492    "eth_metric": 1,
6493    "wlan_metric": 2,
6494    "speling": 3,
6495  },
6496  "filter_enabled_interface_types": []
6497}
6498"#,
6499            r#"
6500{
6501  "dns_config": { "servers": [] },
6502  "filter_config": {
6503    "rules": [],
6504    "nat_rules": [],
6505    "rdr_rules": []
6506  },
6507  "filter_enabled_interface_types": ["speling"]
6508}
6509"#,
6510            r#"
6511{
6512  "dns_config": { "servers": [] },
6513  "filter_config": {
6514    "rules": [],
6515    "nat_rules": [],
6516    "rdr_rules": []
6517  },
6518  "filter_enabled_interface_types": [],
6519  "allowed_upstream_device_classes": ["speling"]
6520}
6521"#,
6522            r#"
6523{
6524  "dns_config": { "servers": [] },
6525  "filter_config": {
6526    "rules": [],
6527    "nat_rules": [],
6528    "rdr_rules": []
6529  },
6530  "filter_enabled_interface_types": [],
6531  "allowed_bridge_upstream_device_classes": ["speling"]
6532}
6533"#,
6534            r#"
6535{
6536  "dns_config": { "servers": [] },
6537  "filter_config": {
6538    "rules": [],
6539    "nat_rules": [],
6540    "rdr_rules": []
6541  },
6542  "filter_enabled_interface_types": [],
6543  "allowed_upstream_device_classes": [],
6544  "forwarded_device_classes": { "ipv4": [], "ipv6": [], "speling": [] }
6545}
6546"#,
6547            r#"
6548{
6549  "dns_config": { "servers": [] },
6550  "filter_config": {
6551    "rules": [],
6552    "nat_rules": [],
6553    "rdr_rules": []
6554  },
6555  "filter_enabled_interface_types": [],
6556  "allowed_upstream_device_classes": [],
6557  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6558  "interface_naming_policy": [{
6559    "matchers": [],
6560    "naming_scheme": [],
6561    "speling": []
6562  }]
6563}
6564"#,
6565            r#"
6566{
6567  "dns_config": { "servers": [] },
6568  "filter_config": {
6569    "rules": [],
6570    "nat_rules": [],
6571    "rdr_rules": []
6572  },
6573  "filter_enabled_interface_types": [],
6574  "allowed_upstream_device_classes": [],
6575  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6576  "interface_naming_policy": [{
6577    "matchers": [ { "speling": [] } ],
6578    "naming_scheme": []
6579  }]
6580}
6581"#,
6582            r#"
6583{
6584  "dns_config": { "servers": [] },
6585  "filter_config": {
6586    "rules": [],
6587    "nat_rules": [],
6588    "rdr_rules": []
6589  },
6590  "filter_enabled_interface_types": [],
6591  "allowed_upstream_device_classes": [],
6592  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6593  "interface_naming_policy": [{
6594    "matchers": [ { "bus_types": ["speling"] } ],
6595    "naming_scheme": []
6596  }]
6597}
6598"#,
6599            r#"
6600{
6601  "dns_config": { "servers": [] },
6602  "filter_config": {
6603    "rules": [],
6604    "nat_rules": [],
6605    "rdr_rules": []
6606  },
6607  "filter_enabled_interface_types": [],
6608  "allowed_upstream_device_classes": [],
6609  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6610  "interface_naming_policy": [{
6611    "matchers": [ { "device_classes": ["speling"] } ],
6612    "naming_scheme": []
6613  }]
6614}
6615"#,
6616            r#"
6617{
6618  "dns_config": { "servers": [] },
6619  "filter_config": {
6620    "rules": [],
6621    "nat_rules": [],
6622    "rdr_rules": []
6623  },
6624  "filter_enabled_interface_types": [],
6625  "allowed_upstream_device_classes": [],
6626  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6627  "interface_naming_policy": [{
6628    "matchers": [ { "any": "speling" } ],
6629    "naming_scheme": []
6630  }]
6631}
6632"#,
6633            r#"
6634{
6635  "dns_config": { "servers": [] },
6636  "filter_config": {
6637    "rules": [],
6638    "nat_rules": [],
6639    "rdr_rules": []
6640  },
6641  "filter_enabled_interface_types": [],
6642  "allowed_upstream_device_classes": [],
6643  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6644  "interface_naming_policy": [{
6645    "matchers": [ { "any": true } ],
6646    "naming_scheme": [ { "type": "speling" } ]
6647  }]
6648}
6649"#,
6650            r#"
6651{
6652  "dns_config": { "servers": [] },
6653  "filter_config": {
6654    "rules": [],
6655    "nat_rules": [],
6656    "rdr_rules": []
6657  },
6658  "filter_enabled_interface_types": [],
6659  "allowed_upstream_device_classes": [],
6660  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6661  "interface_naming_policy": [{
6662    "matchers": [ { "any": true } ],
6663    "naming_scheme": [ { "type": "dynamic", "rule": "speling" } ]
6664  }]
6665}
6666"#,
6667            r#"
6668{
6669  "dns_config": { "servers": [] },
6670  "filter_config": {
6671    "rules": [],
6672    "nat_rules": [],
6673    "rdr_rules": []
6674  },
6675  "filter_enabled_interface_types": [],
6676  "allowed_upstream_device_classes": [],
6677  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6678  "interface_provisioning_policy": [{
6679    "matchers": [ { "any": true } ],
6680    "speling": ""
6681  }]
6682}
6683"#,
6684            r#"
6685{
6686  "dns_config": { "servers": [] },
6687  "filter_config": {
6688    "rules": [],
6689    "nat_rules": [],
6690    "rdr_rules": []
6691  },
6692  "filter_enabled_interface_types": [],
6693  "allowed_upstream_device_classes": [],
6694  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6695  "interface_provisioning_policy": [{
6696    "matchers": [ { "any": true } ],
6697    "provisioning": "speling"
6698  }]
6699}
6700"#,
6701            r#"
6702{
6703  "dns_config": { "servers": [] },
6704  "filter_config": {
6705    "rules": [],
6706    "nat_rules": [],
6707    "rdr_rules": []
6708  },
6709  "filter_enabled_interface_types": [],
6710  "allowed_upstream_device_classes": [],
6711  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6712  "interface_provisioning_policy": [{
6713    "matchers": [ { "any": true } ],
6714    "provisioning": "delegated",
6715    "netstack_managed_routes_designation": "speling"
6716  }]
6717}
6718"#,
6719        ];
6720
6721        for config_str in bad_configs {
6722            let err =
6723                Config::load_str(config_str).expect_err("config shouldn't accept unknown fields");
6724            let err = err.downcast::<serde_json5::Error>().expect("downcast error");
6725            let err_str = format!("{:?}", err);
6726            // Ensure the error is complaining about unknown field, or complaining
6727            // about the missing field.
6728            assert!(err_str.contains("speling") || err_str.contains("missing field"));
6729        }
6730    }
6731
6732    #[test_case(
6733        r#"
6734{
6735  "dns_config": { "servers": [] },
6736  "filter_config": {
6737    "rules": [],
6738    "nat_rules": [],
6739    "rdr_rules": []
6740  },
6741  "filter_enabled_interface_types": [],
6742  "allowed_upstream_device_classes": [],
6743  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6744  "interface_naming_policy": [{
6745    "matchers": [ { "topological_path": "[speling" } ],
6746    "naming_scheme": []
6747  }]
6748}
6749"#,
6750        "invalid range";
6751        "topological_path"
6752    )]
6753    #[test_case(
6754        r#"
6755{
6756  "dns_config": { "servers": [] },
6757  "filter_config": {
6758    "rules": [],
6759    "nat_rules": [],
6760    "rdr_rules": []
6761  },
6762  "filter_enabled_interface_types": [],
6763  "allowed_upstream_device_classes": [],
6764  "forwarded_device_classes": { "ipv4": [], "ipv6": [] },
6765  "interface_provisioning_policy": [{
6766    "matchers": [ { "interface_name": "[speling" } ],
6767    "provisioning": "delegated"
6768  }]
6769}
6770"#,
6771        "did not match any variant";
6772        "interface_name"
6773    )]
6774    fn test_config_denies_invalid_glob(bad_config: &'static str, err_text: &'static str) {
6775        // Should fail on improper glob: square braces not closed.
6776        let err =
6777            Config::load_str(bad_config).expect_err("config shouldn't accept invalid pattern");
6778        let err = err.downcast::<serde_json5::Error>().expect("downcast error");
6779        // Ensure the error is complaining about invalid glob.
6780        assert!(format!("{:?}", err).contains(err_text));
6781    }
6782
6783    #[test]
6784    fn test_config_legacy_wlan_name() {
6785        let config_str = r#"
6786{
6787  "dns_config": { "servers": [] },
6788  "filter_config": {
6789    "rules": [],
6790    "nat_rules": [],
6791    "rdr_rules": []
6792  },
6793  "filter_enabled_interface_types": ["wlan", "ap"],
6794  "allowed_upstream_device_classes": ["wlan"]
6795}
6796"#;
6797        let Config { filter_enabled_interface_types, allowed_upstream_device_classes, .. } =
6798            Config::load_str(config_str).unwrap();
6799        assert_eq!(
6800            HashSet::from([InterfaceType::WlanClient, InterfaceType::WlanAp]),
6801            filter_enabled_interface_types
6802        );
6803        assert_eq!(
6804            AllowedDeviceClasses(HashSet::from([DeviceClass::WlanClient])),
6805            allowed_upstream_device_classes
6806        );
6807    }
6808
6809    #[test]
6810    fn test_config_supports_json5() {
6811        let config_str = r#"{
6812            "dns_config": { "servers": [] },
6813            // A comment on the config
6814            "filter_config": {
6815                'rules': [], // Single quoted string
6816                "nat_rules": [],
6817                "rdr_rules": [], // Trailing comma
6818            },
6819            "filter_enabled_interface_types": [],
6820            "allowed_upstream_device_classes": []
6821        }"#;
6822        let _ = Config::load_str(config_str).unwrap();
6823    }
6824}