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