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