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