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