Skip to main content

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