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