Skip to main content

netlink/
interfaces.rs

1// Copyright 2023 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//! A module for managing RTM_LINK and RTM_ADDR information by generating
6//! RTM_LINK and RTM_ADDR Netlink messages based on events received from
7//! Netstack's interface watcher.
8
9use std::collections::BTreeMap;
10use std::fmt::Debug;
11use std::net::IpAddr;
12use std::num::{NonZeroU32, NonZeroU64};
13
14use fidl_fuchsia_net as fnet;
15use fidl_fuchsia_net_ext::IntoExt as _;
16use fidl_fuchsia_net_interfaces as fnet_interfaces;
17use fidl_fuchsia_net_interfaces_admin::{
18    self as fnet_interfaces_admin, AddressRemovalReason, InterfaceRemovedReason,
19};
20use fidl_fuchsia_net_interfaces_ext::admin::{
21    AddressStateProviderError, TerminalError, wait_for_address_added_event,
22};
23use fidl_fuchsia_net_interfaces_ext::{self as fnet_interfaces_ext, Update as _};
24use fidl_fuchsia_net_root as fnet_root;
25
26use derivative::Derivative;
27use either::Either;
28use futures::StreamExt as _;
29use futures::channel::oneshot;
30use linux_uapi::{
31    ARPHRD_6LOWPAN, ARPHRD_ETHER, ARPHRD_LOOPBACK, ARPHRD_PPP, ARPHRD_VOID,
32    net_device_flags_IFF_LOOPBACK, net_device_flags_IFF_LOWER_UP, net_device_flags_IFF_RUNNING,
33    net_device_flags_IFF_UP, rtnetlink_groups_RTNLGRP_IPV4_IFADDR,
34    rtnetlink_groups_RTNLGRP_IPV6_IFADDR, rtnetlink_groups_RTNLGRP_LINK,
35};
36use net_types::ip::{AddrSubnetEither, IpVersion, Ipv4, Ipv6};
37use netlink_packet_core::{NLM_F_MULTIPART, NetlinkMessage};
38use netlink_packet_route::address::{
39    AddressAttribute, AddressFlags, AddressHeader, AddressHeaderFlags, AddressMessage,
40};
41use netlink_packet_route::link::{
42    LinkAttribute, LinkFlags, LinkHeader, LinkLayerType, LinkMessage, State,
43};
44use netlink_packet_route::{AddressFamily, RouteNetlinkMessage};
45
46use crate::SysctlError;
47use crate::client::{ClientTable, InternalClient};
48use crate::logging::{log_debug, log_error, log_warn};
49use crate::messaging::Sender;
50use crate::multicast_groups::ModernGroup;
51use crate::netlink_packet::UNSPECIFIED_SEQUENCE_NUMBER;
52use crate::netlink_packet::errno::Errno;
53use crate::protocol_family::ProtocolFamily;
54use crate::protocol_family::route::NetlinkRoute;
55use crate::route_tables::{NetlinkRouteTableIndex, RouteTable, RouteTableMap, UnmanagedTable};
56use crate::util::respond_to_completer;
57
58/// A handler for interface events.
59pub trait InterfacesHandler: Send + Sync + 'static {
60    /// Handle a new link.
61    fn handle_new_link(&mut self, name: &str, interface_id: NonZeroU64);
62
63    /// Handle a deleted link.
64    fn handle_deleted_link(&mut self, name: &str);
65
66    /// Handle the idle event.
67    fn handle_idle_event(&mut self) {}
68}
69
70/// Represents the ways RTM_*LINK messages may specify an individual link.
71#[derive(Clone, Debug, PartialEq, Eq)]
72pub(crate) enum LinkSpecifier {
73    Index(NonZeroU32),
74    Name(String),
75}
76
77/// Arguments for an RTM_GETLINK [`Request`].
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub(crate) enum GetLinkArgs {
80    /// Dump state for all the links.
81    Dump,
82    /// Get a specific link.
83    Get(LinkSpecifier),
84}
85
86/// Arguments for an RTM_SETLINK ['Request`].
87#[derive(Clone, Debug, PartialEq, Eq)]
88pub(crate) struct SetLinkArgs {
89    /// The link to update.
90    pub(crate) link: LinkSpecifier,
91    /// `Some` if the link's admin enabled state should be updated to the
92    /// provided `bool`.
93    pub(crate) enable: Option<bool>,
94}
95
96/// [`Request`] arguments associated with links.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub(crate) enum LinkRequestArgs {
99    /// RTM_GETLINK
100    Get(GetLinkArgs),
101    /// RTM_SETLINK
102    Set(SetLinkArgs),
103}
104
105/// Arguments for an RTM_GETADDR [`Request`].
106#[derive(Copy, Clone, Debug, PartialEq, Eq)]
107pub(crate) enum GetAddressArgs {
108    /// Dump state for all addresses with the optional IP version filter.
109    Dump { ip_version_filter: Option<IpVersion> },
110    // TODO(https://issues.fuchsia.dev/296616404): Support get requests w/
111    // filter.
112}
113
114/// The address and interface ID arguments for address requests.
115#[derive(Copy, Clone, Debug, PartialEq, Eq)]
116pub(crate) struct AddressAndInterfaceArgs {
117    pub address: AddrSubnetEither,
118    pub interface_id: NonZeroU32,
119}
120
121/// Arguments for an RTM_NEWADDR [`Request`].
122#[derive(Copy, Clone, Debug, PartialEq, Eq)]
123pub(crate) struct NewAddressArgs {
124    /// The address to be added and the interface to add it to.
125    pub address_and_interface_id: AddressAndInterfaceArgs,
126    /// Indicates whether or not an on-link route should be added for the
127    /// address's subnet.
128    pub add_subnet_route: bool,
129    /// Indicates whether or not an existing address should be replaced.
130    pub replace: bool,
131}
132
133/// Arguments for an RTM_DELADDR [`Request`].
134#[derive(Copy, Clone, Debug, PartialEq, Eq)]
135pub(crate) struct DelAddressArgs {
136    /// The address to be removed and the interface to remove it from.
137    pub address_and_interface_id: AddressAndInterfaceArgs,
138}
139
140/// [`Request`] arguments associated with addresses.
141#[derive(Copy, Clone, Debug, PartialEq, Eq)]
142pub(crate) enum AddressRequestArgs {
143    /// RTM_GETADDR
144    Get(GetAddressArgs),
145    /// RTM_NEWADDR
146    New(NewAddressArgs),
147    /// RTM_DELADDR
148    Del(DelAddressArgs),
149}
150
151/// The argument(s) for a [`Request`].
152#[derive(Clone, Debug, PartialEq, Eq)]
153pub(crate) enum RequestArgs {
154    Link(LinkRequestArgs),
155    Address(AddressRequestArgs),
156}
157
158/// An error encountered while handling a [`Request`].
159#[derive(Copy, Clone, Debug, PartialEq, Eq)]
160pub(crate) enum RequestError {
161    Unknown,
162    InvalidRequest,
163    UnrecognizedInterface,
164    AlreadyExists,
165    AddressNotFound,
166}
167
168impl RequestError {
169    pub(crate) fn into_errno(self) -> Errno {
170        match self {
171            RequestError::Unknown => {
172                log_error!("observed an unknown error, reporting `EINVAL` as the best guess");
173                Errno::EINVAL
174            }
175            RequestError::InvalidRequest => Errno::EINVAL,
176            RequestError::UnrecognizedInterface => Errno::ENODEV,
177            RequestError::AlreadyExists => Errno::EEXIST,
178            RequestError::AddressNotFound => Errno::EADDRNOTAVAIL,
179        }
180    }
181}
182
183fn map_existing_interface_terminal_error(
184    e: TerminalError<InterfaceRemovedReason>,
185    interface_id: NonZeroU64,
186) -> RequestError {
187    match e {
188        TerminalError::Fidl(e) => {
189            // If the channel was closed, then we likely tried to get a control
190            // chandle to an interface that does not exist.
191            if !e.is_closed() {
192                log_error!(
193                    "unexpected interface terminal error for interface ({:?}): {:?}",
194                    interface_id,
195                    e,
196                )
197            }
198        }
199        TerminalError::Terminal(reason) => match reason {
200            reason @ (InterfaceRemovedReason::DuplicateName
201            | InterfaceRemovedReason::PortAlreadyBound
202            | InterfaceRemovedReason::BadPort) => {
203                // These errors are only expected when the interface fails to
204                // be installed.
205                unreachable!(
206                    "unexpected interface removed reason {:?} for interface ({:?})",
207                    reason, interface_id,
208                )
209            }
210            InterfaceRemovedReason::PortClosed | InterfaceRemovedReason::User => {
211                // The interface was removed. Treat this scenario as if the
212                // interface did not exist.
213            }
214            reason => {
215                // `InterfaceRemovedReason` is a flexible FIDL enum so we
216                // cannot exhaustively match.
217                //
218                // We don't know what the reason is but we know the interface
219                // was removed so just assume that the unrecognized reason is
220                // valid and return the same error as if it was removed with
221                // `PortClosed`/`User` reasons.
222                log_error!(
223                    "unrecognized removal reason {:?} from interface {:?}",
224                    reason,
225                    interface_id
226                )
227            }
228        },
229    }
230
231    RequestError::UnrecognizedInterface
232}
233
234/// A request associated with links or addresses.
235#[derive(Derivative)]
236#[derivative(Debug(bound = ""))]
237pub(crate) struct Request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>> {
238    /// The resource and operation-specific argument(s) for this request.
239    pub args: RequestArgs,
240    /// The request's sequence number.
241    ///
242    /// This value will be copied verbatim into any message sent as a result of
243    /// this request.
244    pub sequence_number: u32,
245    /// The client that made the request.
246    pub client: InternalClient<NetlinkRoute, S>,
247    /// A completer that will have the result of the request sent over.
248    pub completer: oneshot::Sender<Result<(), RequestError>>,
249}
250
251/// Handles asynchronous work related to RTM_LINK and RTM_ADDR messages.
252///
253/// Can respond to interface watcher events and RTM_LINK and RTM_ADDR
254/// message requests.
255pub(crate) struct InterfacesWorkerState<H, S: Sender<<NetlinkRoute as ProtocolFamily>::Response>> {
256    /// A handler for interface events.
257    interfaces_handler: H,
258    /// An `InterfacesProxy` to get controlling access to interfaces.
259    interfaces_proxy: fnet_root::InterfacesProxy,
260    /// The current set of clients of NETLINK_ROUTE protocol family.
261    route_clients: ClientTable<NetlinkRoute, S>,
262    /// The table of interfaces and associated state discovered through the
263    /// interfaces watcher.
264    pub(crate) interface_properties: BTreeMap<
265        u64,
266        fnet_interfaces_ext::PropertiesAndState<
267            InterfaceState,
268            fnet_interfaces_ext::DefaultInterest,
269        >,
270    >,
271    /// Corresponds to the `/proc/sys/net/ipv6/conf/default/accept_ra_rt_table`.
272    /// It is the default sysctl value for the interfaces to be added.
273    pub(crate) default_accept_ra_rt_table: AcceptRaRtTable,
274    /// Corresponds to the `/proc/sys/net/ipv6/conf/all/accept_ra_rt_table`.
275    /// It does _nothing_ upon write, same as Linux.
276    pub(crate) all_accept_ra_rt_table: AcceptRaRtTable,
277}
278
279/// This models the `accept_ra_rt_table` sysctl.
280///
281/// The sysctl behaves as follows:
282///   - = 0: default. Put routes into RT6_TABLE_MAIN if the interface
283///     is not in a VRF, or into the VRF table if it is.
284///   - > 0: manual. Put routes into the specified table.
285///   - < 0: automatic. Add the absolute value of the sysctl to the
286///     device's ifindex, and use that table.
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
288pub(crate) enum AcceptRaRtTable {
289    /// Installs routes in the main table.
290    #[default]
291    Main,
292    /// Installs routes in the specified table.
293    Manual(u32),
294    /// Installs routes in table ID that is interface ID plus the diff.
295    Auto(u32),
296}
297
298impl From<i32> for AcceptRaRtTable {
299    fn from(val: i32) -> Self {
300        if val == 0 {
301            Self::Main
302        } else if val > 0 {
303            Self::Manual(val.unsigned_abs())
304        } else {
305            Self::Auto(val.unsigned_abs())
306        }
307    }
308}
309
310impl From<AcceptRaRtTable> for i32 {
311    fn from(val: AcceptRaRtTable) -> Self {
312        match val {
313            AcceptRaRtTable::Main => 0,
314            AcceptRaRtTable::Manual(val) => i32::try_from(val).expect("larger than i32::MAX"),
315            AcceptRaRtTable::Auto(val) => {
316                0i32.checked_sub_unsigned(val).expect("less than i32::MIN")
317            }
318        }
319    }
320}
321
322#[derive(Debug, Default, Clone)]
323pub(crate) struct InterfaceState {
324    // `BTreeMap` so that addresses are iterated in deterministic order
325    // (useful for tests).
326    addresses: BTreeMap<fnet::IpAddress, NetlinkAddressMessage>,
327    link_address: Option<Vec<u8>>,
328    control: Option<fnet_interfaces_ext::admin::Control>,
329    accept_ra_rt_table: AcceptRaRtTable,
330}
331
332impl InterfaceState {
333    pub(crate) fn accept_ra_rt_table(&self) -> AcceptRaRtTable {
334        self.accept_ra_rt_table
335    }
336
337    /// Sets the sysctl for the interface, creates a new netlink table for the
338    /// interface-local route table if needed.
339    ///
340    /// If successful, this method returns [`Some`] if a new interface-local
341    /// table became mapped. Otherwise, [`None`] is returned.
342    pub(crate) async fn set_accept_ra_rt_table(
343        &mut self,
344        new_accept_ra_rt_table: AcceptRaRtTable,
345        interfaces_proxy: &fnet_root::InterfacesProxy,
346        interface_id: NonZeroU64,
347        route_table_maps: Option<(&mut RouteTableMap<Ipv4>, &mut RouteTableMap<Ipv6>)>,
348    ) -> Result<Option<NetlinkRouteTableIndex>, SysctlError> {
349        let old_accept_ra_rt_table = self.accept_ra_rt_table;
350        if old_accept_ra_rt_table == new_accept_ra_rt_table {
351            return Ok(None);
352        }
353
354        enum InsertOrRemove {
355            Insert,
356            Remove,
357        }
358
359        let (delta, insert_or_remove) = match (old_accept_ra_rt_table, new_accept_ra_rt_table) {
360            (AcceptRaRtTable::Main, AcceptRaRtTable::Auto(delta)) => {
361                (delta, InsertOrRemove::Insert)
362            }
363            (AcceptRaRtTable::Auto(delta), AcceptRaRtTable::Main) => {
364                (delta, InsertOrRemove::Remove)
365            }
366            (from, to) => {
367                log::error!("unsupported transition from {from:?} to {to:?}");
368                return Err(SysctlError::Unsupported);
369            }
370        };
371
372        let netlink_id = match u32::try_from(interface_id.get()) {
373            Ok(i) => {
374                NetlinkRouteTableIndex::new(i.checked_add(delta).ok_or(SysctlError::Unsupported)?)
375            }
376            Err(std::num::TryFromIntError { .. }) => {
377                log::error!(
378                    "not using local route table for interface \
379                {interface_id:?} because it is not representable in u32"
380                );
381                return Err(SysctlError::Unsupported);
382            }
383        };
384
385        self.accept_ra_rt_table = new_accept_ra_rt_table;
386
387        let Some((v4_route_table_map, v6_route_table_map)) = route_table_maps else {
388            return Ok(None);
389        };
390        let control = self.control(interfaces_proxy, interface_id);
391        match insert_or_remove {
392            InsertOrRemove::Insert => {
393                let result = futures::future::try_join(
394                    UnmanagedTable::<Ipv4>::interface_local(
395                        control,
396                        &v4_route_table_map.route_table_provider(),
397                    ),
398                    UnmanagedTable::<Ipv6>::interface_local(
399                        control,
400                        &v6_route_table_map.route_table_provider(),
401                    ),
402                )
403                .await;
404                match result {
405                    Ok((local_table_v4, local_table_v6)) => {
406                        let fidl_table_id_v4 = local_table_v4.fidl_table_id;
407                        let fidl_table_id_v6 = local_table_v6.fidl_table_id;
408                        log::info!(
409                            "local table mapping for {interface_id}: \
410                            {netlink_id:?} -> ({:?}, {:?})",
411                            fidl_table_id_v4,
412                            fidl_table_id_v6,
413                        );
414                        v4_route_table_map
415                            .insert(netlink_id, RouteTable::Unmanaged(local_table_v4));
416                        v6_route_table_map
417                            .insert(netlink_id, RouteTable::Unmanaged(local_table_v6));
418                        Ok(Some(netlink_id))
419                    }
420                    Err(err) => {
421                        log::error!("failed to get a local table for {interface_id}: {err:?}");
422                        Ok(None)
423                    }
424                }
425            }
426            InsertOrRemove::Remove => {
427                let _: Option<_> = v4_route_table_map.remove(netlink_id);
428                let _: Option<_> = v6_route_table_map.remove(netlink_id);
429                Ok(None)
430            }
431        }
432    }
433
434    pub(crate) fn control(
435        &mut self,
436        interfaces_proxy: &fnet_root::InterfacesProxy,
437        interface_id: NonZeroU64,
438    ) -> &fnet_interfaces_ext::admin::Control {
439        self.control.get_or_insert_with(|| {
440            let (control, server_end) = fnet_interfaces_ext::admin::Control::create_endpoints()
441                .expect("create Control endpoints");
442            interfaces_proxy
443                .get_admin(interface_id.get(), server_end)
444                .expect("send get admin request");
445            control
446        })
447    }
448}
449
450async fn set_link_address(
451    interfaces_proxy: &fnet_root::InterfacesProxy,
452    id: NonZeroU64,
453    link_address: &mut Option<Vec<u8>>,
454) {
455    match interfaces_proxy
456        .get_mac(id.get())
457        .await
458        .expect("netstack should never close its end of `fuchsia.net.root/Interfaces`")
459    {
460        Ok(None) => {
461            // The request succeeded but the interface has no address.
462            log_debug!("no MAC address for interface ({id:?})")
463        }
464        Ok(Some(mac)) => {
465            let fnet::MacAddress { octets } = *mac;
466            assert_eq!(link_address.replace(octets.to_vec()), None)
467        }
468        Err(fnet_root::InterfacesGetMacError::NotFound) => {
469            // We only get here if the interface has been removed after we
470            // learned about it through the interfaces watcher. Do nothing as
471            // a removed event should come for this interface shortly.
472            log_warn!("failed to get MAC address for interface ({id:?}) with not found error")
473        }
474    }
475}
476
477#[derive(Clone, Copy, Debug)]
478enum PendingRequestKind {
479    AddAddress(AddressAndInterfaceArgs),
480    DelAddress(AddressAndInterfaceArgs),
481    DisableInterface(NonZeroU64),
482    // TODO(https://issues.fuchsia.dev/290372180): Support Pending
483    // "EnableInterface" requests once link_state is available via a FIDL API
484}
485
486#[derive(Derivative)]
487#[derivative(Debug(bound = ""))]
488pub(crate) struct PendingRequest<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>> {
489    kind: PendingRequestKind,
490    client: InternalClient<NetlinkRoute, S>,
491    completer: oneshot::Sender<Result<(), RequestError>>,
492}
493
494impl<H: InterfacesHandler, S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>
495    InterfacesWorkerState<H, S>
496{
497    /// Create the Netlink Interfaces Worker.
498    ///
499    /// # Panics
500    ///
501    /// Panics if an unexpected error is encountered on one of the FIDL
502    /// connections with the netstack.
503    pub(crate) async fn create(
504        mut interfaces_handler: H,
505        route_clients: ClientTable<NetlinkRoute, S>,
506        interfaces_proxy: fnet_root::InterfacesProxy,
507        interfaces_state_proxy: fnet_interfaces::StateProxy,
508    ) -> (
509        Self,
510        impl futures::Stream<
511            Item = Result<
512                fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
513                fidl::Error,
514            >,
515        > + 'static,
516    ) {
517        let mut if_event_stream = Box::pin(
518            fnet_interfaces_ext::event_stream_from_state(
519                &interfaces_state_proxy,
520                fnet_interfaces_ext::WatchOptions {
521                    included_addresses: fnet_interfaces_ext::IncludedAddresses::All,
522                    ..Default::default()
523                },
524            )
525            .expect("connecting to fuchsia.net.interfaces.State FIDL should succeed"),
526        );
527
528        let mut interface_properties = fnet_interfaces_ext::existing(
529            if_event_stream.by_ref(),
530            BTreeMap::<u64, fnet_interfaces_ext::PropertiesAndState<InterfaceState, _>>::new(),
531        )
532        .await
533        .expect("determining already installed interfaces should succeed");
534
535        for fnet_interfaces_ext::PropertiesAndState { properties, state } in
536            interface_properties.values_mut()
537        {
538            set_link_address(&interfaces_proxy, properties.id, &mut state.link_address).await;
539
540            if let Some(interface_addresses) =
541                addresses_optionally_from_interface_properties(properties)
542            {
543                state.addresses = interface_addresses;
544            }
545
546            interfaces_handler.handle_new_link(&properties.name, properties.id);
547        }
548
549        interfaces_handler.handle_idle_event();
550
551        (
552            InterfacesWorkerState {
553                interfaces_handler,
554                interfaces_proxy,
555                route_clients,
556                interface_properties,
557                default_accept_ra_rt_table: Default::default(),
558                all_accept_ra_rt_table: Default::default(),
559            },
560            if_event_stream,
561        )
562    }
563
564    /// Handles events observed from the interface watcher by updating the
565    /// table of discovered interfaces.
566    ///
567    /// # Panics
568    ///
569    /// Panics if an unexpected Interface Watcher Event is published by the
570    /// Netstack.
571    ///
572    /// Returns [`Some`] if a new interface-local table is mapped. The caller
573    /// should use this to process stashed routes for this new table.
574    pub(crate) async fn handle_interface_watcher_event(
575        &mut self,
576        event: fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
577        route_table_maps: Option<(&mut RouteTableMap<Ipv4>, &mut RouteTableMap<Ipv6>)>,
578    ) -> Option<NetlinkRouteTableIndex> {
579        let update = self
580            .interface_properties
581            .update(event)
582            .expect("Netstack interface event resulted in an invalid update");
583
584        match update {
585            fnet_interfaces_ext::UpdateResult::Added { properties, state } => {
586                set_link_address(&self.interfaces_proxy, properties.id, &mut state.link_address)
587                    .await;
588
589                let interface_id = properties.id;
590
591                // The newly added device should have the default sysctl.
592                let initial_value = self.default_accept_ra_rt_table;
593                let new_table = state
594                    .set_accept_ra_rt_table(
595                        initial_value,
596                        &self.interfaces_proxy,
597                        interface_id,
598                        route_table_maps,
599                    )
600                    .await
601                    .unwrap_or_else(|_err| {
602                        log::error!("failed to update the accept_ra_rt_table for {interface_id:?}");
603                        None
604                    });
605
606                if let Some(message) =
607                    NetlinkLinkMessage::optionally_from(properties, &state.link_address)
608                {
609                    self.route_clients.send_message_to_group(
610                        message.into_rtnl_new_link(UNSPECIFIED_SEQUENCE_NUMBER, false),
611                        ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
612                    )
613                }
614
615                // Send address messages after the link message for newly added links
616                // so that netlink clients are aware of the interface before sending
617                // address messages for an interface.
618                if let Some(updated_addresses) =
619                    addresses_optionally_from_interface_properties(properties)
620                {
621                    update_addresses(&mut state.addresses, updated_addresses, &self.route_clients);
622                }
623
624                self.interfaces_handler.handle_new_link(&properties.name, properties.id);
625
626                log_debug!("processed add/existing event for id {}", properties.id);
627
628                new_table
629            }
630            fnet_interfaces_ext::UpdateResult::Changed {
631                previous:
632                    fnet_interfaces::Properties {
633                        online,
634                        addresses,
635                        id: _,
636                        name: _,
637                        has_default_ipv4_route: _,
638                        has_default_ipv6_route: _,
639                        port_class: _,
640                        ..
641                    },
642                current:
643                    current @ fnet_interfaces_ext::Properties {
644                        id,
645                        addresses: _,
646                        name: _,
647                        port_class: _,
648                        online: _,
649                        has_default_ipv4_route: _,
650                        has_default_ipv6_route: _,
651                        port_identity_koid: _,
652                    },
653                state:
654                    InterfaceState {
655                        addresses: interface_addresses,
656                        link_address,
657                        control: _,
658                        accept_ra_rt_table: _,
659                    },
660            } => {
661                if online.is_some() {
662                    if let Some(message) =
663                        NetlinkLinkMessage::optionally_from(current, link_address)
664                    {
665                        self.route_clients.send_message_to_group(
666                            message.into_rtnl_new_link(UNSPECIFIED_SEQUENCE_NUMBER, false),
667                            ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
668                        )
669                    }
670
671                    log_debug!("processed interface link change event for id {}", id);
672                };
673
674                // The `is_some` check is not strictly necessary because
675                // `update_addresses` will calculate the delta before sending
676                // updates but is useful as an optimization when addresses don't
677                // change (<avoid allocations and message comparisons that will net
678                // no updates).
679                if addresses.is_some() {
680                    if let Some(updated_addresses) =
681                        addresses_optionally_from_interface_properties(current)
682                    {
683                        update_addresses(
684                            interface_addresses,
685                            updated_addresses,
686                            &self.route_clients,
687                        );
688                    }
689
690                    log_debug!("processed interface address change event for id {}", id);
691                }
692                None
693            }
694            fnet_interfaces_ext::UpdateResult::Removed(
695                fnet_interfaces_ext::PropertiesAndState {
696                    properties,
697                    state:
698                        InterfaceState {
699                            mut addresses,
700                            link_address,
701                            control: _,
702                            accept_ra_rt_table: _,
703                        },
704                },
705            ) => {
706                update_addresses(&mut addresses, BTreeMap::new(), &self.route_clients);
707
708                // Send link messages after the address message for removed links
709                // so that netlink clients are aware of the interface throughout the
710                // address messages.
711                if let Some(message) =
712                    NetlinkLinkMessage::optionally_from(&properties, &link_address)
713                {
714                    self.route_clients.send_message_to_group(
715                        message.into_rtnl_del_link(UNSPECIFIED_SEQUENCE_NUMBER),
716                        ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
717                    )
718                }
719
720                self.interfaces_handler.handle_deleted_link(&properties.name);
721
722                log_debug!("processed interface remove event for id {}", properties.id);
723                None
724            }
725            fnet_interfaces_ext::UpdateResult::Existing { properties, state: _ } => {
726                panic!("Netstack reported the addition of an existing interface: {properties:?}");
727            }
728            fnet_interfaces_ext::UpdateResult::NoChange => None,
729        }
730    }
731
732    /// Checks whether a `PendingRequest` can be marked completed given the current state of the
733    /// worker. If so, notifies the request's completer and returns `None`. If not, returns
734    /// the `PendingRequest` as `Some`.
735    ///
736    /// TODO(https://fxbug.dev/488124265): Use synchronization primitives to
737    /// more robustly match requests to their corresponding watch events.
738    pub(crate) fn handle_pending_request(
739        &self,
740        pending_request: PendingRequest<S>,
741    ) -> Option<PendingRequest<S>> {
742        let PendingRequest { kind, client: _, completer: _ } = &pending_request;
743        let contains_addr = |&AddressAndInterfaceArgs { address, interface_id }| {
744            // NB: The interface must exist, because we were able to
745            // successfully add/remove an address (hence the pending
746            // request). The Netstack will send a 'changed' event to
747            // reflect the address add/remove before sending a `removed`
748            // event for the interface.
749            let fnet_interfaces_ext::PropertiesAndState {
750                properties: _,
751                state:
752                    InterfaceState { addresses, link_address: _, control: _, accept_ra_rt_table: _ },
753            } = self
754                .interface_properties
755                .get(&interface_id.get().into())
756                .expect("interfaces with pending address change should exist");
757            let fnet::Subnet { addr, prefix_len: _ } = address.clone().into_ext();
758            addresses.contains_key(&addr)
759        };
760
761        let done = match kind {
762            PendingRequestKind::AddAddress(address_and_interface_args) => {
763                contains_addr(address_and_interface_args)
764            }
765            PendingRequestKind::DelAddress(address_and_interface_args) => {
766                !contains_addr(address_and_interface_args)
767            }
768            PendingRequestKind::DisableInterface(interface_id) => {
769                // NB: The interface must exist, because we were able to
770                // successfully disabled it (hence the pending request).
771                // The Netstack will send a 'changed' event to reflect
772                // the disable, before sending a `removed` event.
773                let fnet_interfaces_ext::PropertiesAndState { properties, state: _ } =
774                    self.interface_properties.get(&interface_id.get()).unwrap_or_else(|| {
775                        panic!("interface {interface_id} with pending disable should exist")
776                    });
777                // Note here we check "is the interface offline" which
778                // is a combination of, "is the underlying link state
779                // down" and "is the interface admin disabled". This
780                // means we cannot know with certainty whether the link
781                // is enabled or disabled. we take our best guess here.
782                // TODO(https://issues.fuchsia.dev/290372180): Make this
783                // check exact once link status is available via a FIDL
784                // API.
785                !properties.online
786            }
787        };
788
789        if done {
790            log_debug!("completed pending request; req = {pending_request:?}");
791
792            let PendingRequest { kind, client, completer } = pending_request;
793
794            respond_to_completer(client, completer, Ok(()), kind);
795            None
796        } else {
797            // Put the pending request back so that it can be handled later.
798            log_debug!("pending request not done yet; req = {pending_request:?}");
799            Some(pending_request)
800        }
801    }
802
803    /// Returns an admistrative control for the interface.
804    ///
805    /// Returns `None` if the interface is not known by the `EventLoop`.
806    fn get_interface_control(
807        &mut self,
808        interface_id: NonZeroU64,
809    ) -> Option<&fnet_interfaces_ext::admin::Control> {
810        let interface = self.interface_properties.get_mut(&interface_id.get())?;
811
812        Some(interface.state.control(&self.interfaces_proxy, interface_id))
813    }
814
815    // Get the associated `PropertiesAndState` for the given `LinkSpecifier`
816    fn get_link(
817        &self,
818        specifier: LinkSpecifier,
819    ) -> Option<
820        &fnet_interfaces_ext::PropertiesAndState<
821            InterfaceState,
822            fnet_interfaces_ext::DefaultInterest,
823        >,
824    > {
825        match specifier {
826            LinkSpecifier::Index(id) => self.interface_properties.get(&id.get().into()),
827            LinkSpecifier::Name(name) => self.interface_properties.values().find(
828                |fnet_interfaces_ext::PropertiesAndState { properties, state: _ }| {
829                    properties.name == name
830                },
831            ),
832        }
833    }
834
835    /// Handles a "RTM_GETLINK" request.
836    ///
837    /// The resulting "RTM_NEWLINK" messages will be sent directly to the
838    /// provided 'client'.
839    fn handle_get_link_request(
840        &self,
841        args: GetLinkArgs,
842        sequence_number: u32,
843        client: &mut InternalClient<NetlinkRoute, S>,
844    ) -> Result<(), RequestError> {
845        let (is_dump, interfaces_iter) = match args {
846            GetLinkArgs::Dump => {
847                let ifaces = self.interface_properties.values();
848                (true, Either::Left(ifaces))
849            }
850            GetLinkArgs::Get(specifier) => {
851                let iface = self.get_link(specifier).ok_or(RequestError::UnrecognizedInterface)?;
852                (false, Either::Right(std::iter::once(iface)))
853            }
854        };
855
856        interfaces_iter
857            .filter_map(
858                |fnet_interfaces_ext::PropertiesAndState {
859                     properties,
860                     state:
861                         InterfaceState {
862                             addresses: _,
863                             link_address,
864                             control: _,
865                             accept_ra_rt_table: _,
866                         },
867                 }| {
868                    NetlinkLinkMessage::optionally_from(&properties, &link_address)
869                },
870            )
871            .for_each(|message| {
872                client.send_unicast(message.into_rtnl_new_link(sequence_number, is_dump))
873            });
874        Ok(())
875    }
876
877    /// Handles a "RTM_SETLINK" request.
878    async fn handle_set_link_request(
879        &mut self,
880        args: SetLinkArgs,
881    ) -> Result<Option<PendingRequestKind>, RequestError> {
882        let SetLinkArgs { link, enable } = args;
883        let id = self.get_link(link).ok_or(RequestError::UnrecognizedInterface)?.properties.id;
884
885        // NB: Only check if their is a change after verifying the provided
886        // interface is valid. This is for conformance with Linux which will
887        // return ENODEV for invalid devices, even if no-change was requested.
888        let Some(enable) = enable else { return Ok(None) };
889
890        let control = self.get_interface_control(id).ok_or(RequestError::UnrecognizedInterface)?;
891
892        if enable {
893            let _did_enable = control
894                .enable()
895                .await
896                .map_err(|e| {
897                    log_warn!("error enabling interface {id}: {e:?}");
898                    map_existing_interface_terminal_error(e, id)
899                })?
900                .map_err(|e: fnet_interfaces_admin::ControlEnableError| {
901                    // `ControlEnableError` is currently an empty flexible enum.
902                    // It's not possible to know what went wrong.
903                    log_error!("failed to enable interface {id} for unknown reason: {e:?}");
904                    RequestError::Unknown
905                })?;
906            // TODO(https://issues.fuchsia.dev/290372180): Synchronize this
907            // request with observed changes from the watcher, once link status
908            // is available via a FIDL API.
909            Ok(None)
910        } else {
911            let did_disable = control
912                .disable()
913                .await
914                .map_err(|e| {
915                    log_warn!("error disabling interface {id}: {e:?}");
916                    map_existing_interface_terminal_error(e, id)
917                })?
918                .map_err(|e: fnet_interfaces_admin::ControlDisableError| {
919                    // `ControlDisableError` is currently an empty flexible enum.
920                    // It's not possible to know what went wrong,
921                    log_error!("failed to disable interface {id} for unknown reason: {e:?}");
922                    RequestError::Unknown
923                })?;
924            Ok(did_disable.then_some(PendingRequestKind::DisableInterface(id)))
925        }
926    }
927
928    /// Handles a new address request.
929    ///
930    /// Returns the address and interface ID if the address was successfully
931    /// added so that the caller can make sure their local state (from the
932    /// interfaces watcher) has sent an event holding the added address.
933    async fn handle_new_address_request(
934        &mut self,
935        NewAddressArgs {
936            address_and_interface_id:
937                address_and_interface_id @ AddressAndInterfaceArgs { address, interface_id },
938            add_subnet_route,
939            replace,
940        }: NewAddressArgs,
941    ) -> Result<Option<AddressAndInterfaceArgs>, RequestError> {
942        let control = self
943            .get_interface_control(interface_id.into())
944            .ok_or(RequestError::UnrecognizedInterface)?;
945
946        let (asp, asp_server_end) =
947            fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
948        control
949            .add_address(
950                &address.into_ext(),
951                &fnet_interfaces_admin::AddressParameters {
952                    // TODO(https://fxbug.dev/42074223): Update how we add subnet
953                    // routes for addresses.
954                    add_subnet_route: Some(add_subnet_route),
955                    ..fnet_interfaces_admin::AddressParameters::default()
956                },
957                asp_server_end,
958            )
959            .map_err(|e| {
960                log_warn!("error adding {address} to interface ({interface_id}): {e:?}");
961                map_existing_interface_terminal_error(e, interface_id.into())
962            })?;
963
964        // Detach the ASP so that the address's lifetime isn't bound to the
965        // client end of the ASP.
966        //
967        // We do this first because `assignment_state_stream` takes ownership
968        // of the ASP proxy.
969        asp.detach().unwrap_or_else(|e| {
970            // Likely failed because the address addition failed or it was
971            // immediately removed. Don't fail just yet because we need to check
972            // the assignment state & terminal error below.
973            log_warn!(
974                "error detaching ASP for {} on interface ({}): {:?}",
975                address,
976                interface_id,
977                e
978            )
979        });
980
981        match wait_for_address_added_event(&mut asp.take_event_stream()).await {
982            Ok(()) => {
983                log_debug!("{} added on interface ({})", address, interface_id);
984                Ok(Some(address_and_interface_id))
985            }
986            // Note: In Linux (`inet_rtm_newaddr` / `inet6_rtm_newaddr`),
987            // `RTM_NEWADDR` always adds the address if it is absent (ignoring
988            // whether `NLM_F_CREATE` or `NLM_F_REPLACE` is set), and only
989            // checks `NLM_F_REPLACE` (and `!NLM_F_EXCL`) when the address is
990            // already assigned.
991            Err(AddressStateProviderError::AddressRemoved(
992                AddressRemovalReason::AlreadyAssigned,
993            )) if replace => {
994                // TODO(https://fxbug.dev/573643839): Retain `AddressStateProvider`
995                // and call `UpdateAddressProperties` to update address lifetimes
996                // when replacing an existing address.
997                log_debug!(
998                    "ignoring AlreadyAssigned for {} on interface ({}) with replace flag",
999                    address,
1000                    interface_id,
1001                );
1002                Ok(None)
1003            }
1004            Err(e) => {
1005                log_warn!(
1006                    "error waiting for state update for {} on interface ({}): {:?}",
1007                    address,
1008                    interface_id,
1009                    e,
1010                );
1011
1012                Err(match e {
1013                    AddressStateProviderError::AddressRemoved(reason) => match reason {
1014                        AddressRemovalReason::Invalid | AddressRemovalReason::InvalidProperties => {
1015                            RequestError::InvalidRequest
1016                        }
1017                        AddressRemovalReason::AlreadyAssigned => RequestError::AlreadyExists,
1018                        reason @ (AddressRemovalReason::DadFailed
1019                        | AddressRemovalReason::Forfeited
1020                        | AddressRemovalReason::InterfaceRemoved
1021                        | AddressRemovalReason::UserRemoved) => {
1022                            // These errors are only returned when the address
1023                            // is removed after it has been added. We have not
1024                            // yet observed the initial state so these removal
1025                            // reasons are unexpected.
1026                            unreachable!(
1027                                "expected netstack to send initial state before removing {} on interface ({}) with reason {:?}",
1028                                address, interface_id, reason,
1029                            )
1030                        }
1031                    },
1032                    AddressStateProviderError::Fidl(e) => {
1033                        if !e.is_closed() {
1034                            log_error!(
1035                                "unexpected ASP error when adding {} on interface ({}): {:?}",
1036                                address,
1037                                interface_id,
1038                                e,
1039                            )
1040                        }
1041
1042                        RequestError::UnrecognizedInterface
1043                    }
1044                    AddressStateProviderError::ChannelClosed => {
1045                        // If the channel is closed, assume the interface was
1046                        // removed.
1047                        RequestError::UnrecognizedInterface
1048                    }
1049                })
1050            }
1051        }
1052    }
1053
1054    /// Handles a delete address request.
1055    ///
1056    /// Returns the address and interface ID if the address was successfully
1057    /// removed so that the caller can make sure their local state (from the
1058    /// interfaces watcher) has sent an event without the removed address.
1059    async fn handle_del_address_request(
1060        &mut self,
1061        DelAddressArgs {
1062            address_and_interface_id:
1063                address_and_interface_id @ AddressAndInterfaceArgs { address, interface_id },
1064        }: DelAddressArgs,
1065    ) -> Result<AddressAndInterfaceArgs, RequestError> {
1066        let control = self
1067            .get_interface_control(interface_id.into())
1068            .ok_or(RequestError::UnrecognizedInterface)?;
1069
1070        match control.remove_address(&address.into_ext()).await.map_err(|e| {
1071            log_warn!("error removing {address} from interface ({interface_id}): {e:?}");
1072            map_existing_interface_terminal_error(e, interface_id.into())
1073        })? {
1074            Ok(did_remove) => {
1075                if did_remove {
1076                    Ok(address_and_interface_id)
1077                } else {
1078                    Err(RequestError::AddressNotFound)
1079                }
1080            }
1081            Err(e) => {
1082                // `e` is a flexible FIDL enum so we cannot exhaustively match.
1083                let e: fnet_interfaces_admin::ControlRemoveAddressError = e;
1084                match e {
1085                    fnet_interfaces_admin::ControlRemoveAddressErrorUnknown!() => {
1086                        log_error!(
1087                            "unrecognized address removal error {:?} for address {} on interface ({})",
1088                            e,
1089                            address,
1090                            interface_id,
1091                        );
1092
1093                        // Assume the error was because the request was invalid.
1094                        Err(RequestError::InvalidRequest)
1095                    }
1096                }
1097            }
1098        }
1099    }
1100
1101    /// Handles a [`Request`].
1102    ///
1103    /// Returns a [`PendingRequest`] if state was updated and the caller needs
1104    /// to make sure the update has been propagated to the local state (the
1105    /// interfaces watcher has sent an event for our update).
1106    pub(crate) async fn handle_request(
1107        &mut self,
1108        Request { args, sequence_number, mut client, completer }: Request<S>,
1109    ) -> Option<PendingRequest<S>> {
1110        log_debug!("handling request {args:?} from {client}");
1111
1112        let result = match args.clone() {
1113            RequestArgs::Link(LinkRequestArgs::Get(args)) => {
1114                self.handle_get_link_request(args, sequence_number, &mut client)
1115            }
1116            RequestArgs::Link(LinkRequestArgs::Set(args)) => {
1117                match self.handle_set_link_request(args).await {
1118                    Ok(Some(kind)) => return Some(PendingRequest { kind, client, completer }),
1119                    Ok(None) => Ok(()),
1120                    Err(e) => Err(e),
1121                }
1122            }
1123            RequestArgs::Address(args) => match args {
1124                AddressRequestArgs::Get(args) => match args {
1125                    GetAddressArgs::Dump { ip_version_filter } => {
1126                        self.interface_properties
1127                            .values()
1128                            .map(|iface| iface.state.addresses.values())
1129                            .flatten()
1130                            .filter(|NetlinkAddressMessage(message)| {
1131                                ip_version_filter.map_or(true, |ip_version| {
1132                                    ip_version.eq(&match message.header.family() {
1133                                        AddressFamily::Inet => IpVersion::V4,
1134                                        AddressFamily::Inet6 => IpVersion::V6,
1135                                        family => unreachable!(
1136                                            "unexpected address family ({:?}); addr = {:?}",
1137                                            family, message,
1138                                        ),
1139                                    })
1140                                })
1141                            })
1142                            .for_each(|message| {
1143                                client.send_unicast(message.to_rtnl_new_addr(sequence_number, true))
1144                            });
1145                        Ok(())
1146                    }
1147                },
1148                AddressRequestArgs::New(args) => {
1149                    match self.handle_new_address_request(args).await {
1150                        Ok(None) => Ok(()),
1151                        Ok(Some(address_and_interface_id)) => {
1152                            return Some(PendingRequest {
1153                                kind: PendingRequestKind::AddAddress(address_and_interface_id),
1154                                client,
1155                                completer,
1156                            });
1157                        }
1158                        Err(e) => Err(e),
1159                    }
1160                }
1161                AddressRequestArgs::Del(args) => {
1162                    match self.handle_del_address_request(args).await {
1163                        Ok(address_and_interface_id) => {
1164                            return Some(PendingRequest {
1165                                kind: PendingRequestKind::DelAddress(address_and_interface_id),
1166                                client,
1167                                completer,
1168                            });
1169                        }
1170                        Err(e) => Err(e),
1171                    }
1172                }
1173            },
1174        };
1175
1176        log_debug!("handled request {args:?} from {client} with result = {result:?}");
1177        respond_to_completer(client, completer, result, args);
1178        None
1179    }
1180}
1181
1182fn update_addresses<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
1183    existing_addresses: &mut BTreeMap<fnet::IpAddress, NetlinkAddressMessage>,
1184    updated_addresses: BTreeMap<fnet::IpAddress, NetlinkAddressMessage>,
1185    route_clients: &ClientTable<NetlinkRoute, S>,
1186) {
1187    enum UpdateKind {
1188        New,
1189        Del,
1190    }
1191
1192    let send_update = |addr: &NetlinkAddressMessage, kind| {
1193        let NetlinkAddressMessage(inner) = addr;
1194        let group = match inner.header.family() {
1195            AddressFamily::Inet => rtnetlink_groups_RTNLGRP_IPV4_IFADDR,
1196            AddressFamily::Inet6 => rtnetlink_groups_RTNLGRP_IPV6_IFADDR,
1197            family => {
1198                unreachable!("unrecognized interface address family ({family:?}); addr = {addr:?}")
1199            }
1200        };
1201
1202        let message = match kind {
1203            UpdateKind::New => addr.to_rtnl_new_addr(UNSPECIFIED_SEQUENCE_NUMBER, false),
1204            UpdateKind::Del => addr.to_rtnl_del_addr(UNSPECIFIED_SEQUENCE_NUMBER),
1205        };
1206
1207        route_clients.send_message_to_group(message, ModernGroup(group));
1208    };
1209
1210    // Send a message to interested listeners only if the address is newly added
1211    // or its message has changed.
1212    for (key, message) in updated_addresses.iter() {
1213        if existing_addresses.get(key) != Some(message) {
1214            send_update(message, UpdateKind::New)
1215        }
1216    }
1217
1218    existing_addresses.retain(|addr, message| {
1219        // If the address exists in the latest update, keep it. If it was
1220        // updated, we will update this map with the updated values below.
1221        if updated_addresses.contains_key(addr) {
1222            return true;
1223        }
1224
1225        // The address is not present in the interfaces latest update so it
1226        // has been deleted.
1227        send_update(message, UpdateKind::Del);
1228
1229        false
1230    });
1231
1232    // Update our set of existing addresses with the latest set known to be
1233    // assigned to the interface.
1234    existing_addresses.extend(updated_addresses);
1235}
1236
1237/// A wrapper type for the netlink_packet_route `LinkMessage` to enable conversions
1238/// from [`fnet_interfaces_ext::Properties`]. The addresses component of this
1239/// struct will be handled separately.
1240#[derive(Clone, Debug, Eq, PartialEq)]
1241pub(crate) struct NetlinkLinkMessage(LinkMessage);
1242
1243impl NetlinkLinkMessage {
1244    fn optionally_from(
1245        properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1246        link_address: &Option<Vec<u8>>,
1247    ) -> Option<Self> {
1248        match interface_properties_to_link_message(properties, link_address) {
1249            Ok(o) => Some(o),
1250            Err(NetlinkLinkMessageConversionError::InvalidInterfaceId(id)) => {
1251                log_warn!("Invalid interface id: {:?}", id);
1252                None
1253            }
1254        }
1255    }
1256
1257    pub(crate) fn into_rtnl_new_link(
1258        self,
1259        sequence_number: u32,
1260        is_dump: bool,
1261    ) -> NetlinkMessage<RouteNetlinkMessage> {
1262        let Self(message) = self;
1263        let mut msg: NetlinkMessage<RouteNetlinkMessage> =
1264            RouteNetlinkMessage::NewLink(message).into();
1265        msg.header.sequence_number = sequence_number;
1266        if is_dump {
1267            msg.header.flags |= NLM_F_MULTIPART;
1268        }
1269        msg.finalize();
1270        msg
1271    }
1272
1273    fn into_rtnl_del_link(self, sequence_number: u32) -> NetlinkMessage<RouteNetlinkMessage> {
1274        let Self(message) = self;
1275        let mut msg: NetlinkMessage<RouteNetlinkMessage> =
1276            RouteNetlinkMessage::DelLink(message).into();
1277        msg.header.sequence_number = sequence_number;
1278        msg.finalize();
1279        msg
1280    }
1281}
1282
1283// NetlinkLinkMessage conversion related errors.
1284#[derive(Debug, PartialEq)]
1285pub(crate) enum NetlinkLinkMessageConversionError {
1286    // Interface id could not be downcasted to fit into the expected u32.
1287    InvalidInterfaceId(u64),
1288}
1289
1290fn port_class_to_link_type(port_class: fnet_interfaces_ext::PortClass) -> u16 {
1291    match port_class {
1292        fnet_interfaces_ext::PortClass::Loopback => ARPHRD_LOOPBACK,
1293        fnet_interfaces_ext::PortClass::Blackhole => ARPHRD_VOID,
1294        fnet_interfaces_ext::PortClass::Ethernet
1295        | fnet_interfaces_ext::PortClass::Bridge
1296        | fnet_interfaces_ext::PortClass::WlanClient
1297        | fnet_interfaces_ext::PortClass::WlanAp => ARPHRD_ETHER,
1298        fnet_interfaces_ext::PortClass::Ppp => ARPHRD_PPP,
1299        // NB: Virtual devices on fuchsia are overloaded. This may be a
1300        // tun/tap/no-op interface. Return `ARPHRD_VOID` since we have
1301        // insufficient information to precisely classify the link_type.
1302        fnet_interfaces_ext::PortClass::Virtual => ARPHRD_VOID,
1303        fnet_interfaces_ext::PortClass::Lowpan => ARPHRD_6LOWPAN,
1304    }
1305    .try_into()
1306    .expect("potential values will fit into the u16 range")
1307}
1308
1309// Netstack only reports 'online' when the 'admin status' is 'enabled' and the 'link
1310// state' is UP. IFF_RUNNING represents only `link state` UP, so it is likely that
1311// there will be cases where a flag should be set to IFF_RUNNING but we can not make
1312// the determination with the information provided.
1313//
1314// Per https://www.kernel.org/doc/html/latest/networking/operstates.html#querying-from-userspace,
1315//
1316//   Administrative state is the result of "ip link set dev <dev> up or down" and
1317//   reflects whether the administrator wants to use the device for traffic. [...]
1318//   Operational state shows the ability of an interface to transmit this user data.
1319//
1320//   Both admin and operational state can be queried via the netlink operation
1321//   RTM_GETLINK. It is also possible to subscribe to RTNLGRP_LINK to be
1322//   notified of updates while the interface is admin up. This is important for
1323//   setting from userspace.
1324//
1325//   These values contain interface state:
1326//
1327//   ifinfomsg::if_flags & IFF_UP:
1328//       Interface is admin up
1329//
1330//   ifinfomsg::if_flags & IFF_RUNNING:
1331//       Interface is in RFC2863 operational state UP or UNKNOWN. This is for
1332//       backward compatibility, routing daemons, dhcp clients can use this flag
1333//       to determine whether they should use the interface.
1334//
1335//   ifinfomsg::if_flags & IFF_LOWER_UP:
1336//       Driver has signaled netif_carrier_on()
1337//
1338//   ...
1339const ONLINE_IF_FLAGS: u32 =
1340    net_device_flags_IFF_UP | net_device_flags_IFF_RUNNING | net_device_flags_IFF_LOWER_UP;
1341
1342// Implement conversions from `Properties` to `NetlinkLinkMessage`
1343// which is fallible iff, the interface has an id greater than u32.
1344fn interface_properties_to_link_message(
1345    fnet_interfaces_ext::Properties {
1346        id,
1347        name,
1348        port_class,
1349        online,
1350        addresses: _,
1351        has_default_ipv4_route: _,
1352        has_default_ipv6_route: _,
1353        port_identity_koid: _,
1354    }: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1355    link_address: &Option<Vec<u8>>,
1356) -> Result<NetlinkLinkMessage, NetlinkLinkMessageConversionError> {
1357    let online = *online;
1358    let mut link_header = LinkHeader::default();
1359
1360    link_header.interface_family = AddressFamily::Unspec;
1361
1362    // We expect interface ids to safely fit in the range of u32 values.
1363    let id: u32 = match id.get().try_into() {
1364        Err(std::num::TryFromIntError { .. }) => {
1365            return Err(NetlinkLinkMessageConversionError::InvalidInterfaceId(id.clone().into()));
1366        }
1367        Ok(id) => id,
1368    };
1369    link_header.index = id;
1370
1371    let link_layer_type = port_class_to_link_type(*port_class);
1372    link_header.link_layer_type = LinkLayerType::from(link_layer_type);
1373
1374    let mut flags = 0;
1375    if online {
1376        flags |= ONLINE_IF_FLAGS;
1377    };
1378    if link_header.link_layer_type == LinkLayerType::Loopback {
1379        flags |= net_device_flags_IFF_LOOPBACK;
1380    };
1381    if *port_class == fnet_interfaces_ext::PortClass::WlanClient {
1382        // Upstream consumers expect WLAN interfaces to be "administratively up" even when
1383        // they're disconnected. Since we don't currently distinguish between device-layer
1384        // (adminitstrative) enablement and IP-layer enablement, we'll unconditionally
1385        // mark all WLAN interfaces as IFF_UP (administratively up).
1386        // TODO(b/290372180): Determine the actual device enablement status.
1387        flags |= net_device_flags_IFF_UP;
1388    }
1389
1390    // SAFETY: This and the following .unwrap() are safe as LinkFlags
1391    // can hold any valid u32.
1392    link_header.flags = LinkFlags::from_bits(flags).unwrap();
1393
1394    // As per netlink_package_route and rtnetlink documentation, this should be set to
1395    // `0xffff_ffff` and reserved for future use.
1396    link_header.change_mask = LinkFlags::from_bits(u32::MAX).unwrap();
1397
1398    // The NLA order follows the list that attributes are listed on the
1399    // rtnetlink man page.
1400    // The following fields are used in the options in the NLA, but they do
1401    // not have any corresponding values in `fnet_interfaces_ext::Properties`.
1402    //
1403    // IFLA_BROADCAST
1404    // IFLA_MTU
1405    // IFLA_LINK
1406    // IFLA_QDISC
1407    // IFLA_STATS
1408    //
1409    // There are other NLAs observed via the netlink_packet_route crate, and do
1410    // not have corresponding values in `fnet_interfaces_ext::Properties`.
1411    // This list is documented within issuetracker.google.com/283137644.
1412    let nlas = [
1413        LinkAttribute::IfName(name.clone()),
1414        LinkAttribute::Link(link_layer_type.into()),
1415        // Netstack only exposes enough state to determine between `Up` and `Down`
1416        // operating state.
1417        LinkAttribute::OperState(if online { State::Up } else { State::Down }),
1418    ]
1419    .into_iter()
1420    // If the interface has a link-address, include it in the NLAs.
1421    .chain(link_address.clone().map(LinkAttribute::Address))
1422    .collect();
1423
1424    let mut link_message = LinkMessage::default();
1425    link_message.header = link_header;
1426    link_message.attributes = nlas;
1427
1428    return Ok(NetlinkLinkMessage(link_message));
1429}
1430
1431/// A wrapper type for the netlink_packet_route `AddressMessage` to enable conversions
1432/// from [`fnet_interfaces_ext::Properties`] and implement hashing.
1433#[derive(Clone, Debug, Eq, PartialEq)]
1434pub(crate) struct NetlinkAddressMessage(AddressMessage);
1435
1436impl NetlinkAddressMessage {
1437    pub(crate) fn to_rtnl_new_addr(
1438        &self,
1439        sequence_number: u32,
1440        is_dump: bool,
1441    ) -> NetlinkMessage<RouteNetlinkMessage> {
1442        let Self(message) = self;
1443        let mut message: NetlinkMessage<RouteNetlinkMessage> =
1444            RouteNetlinkMessage::NewAddress(message.clone()).into();
1445        message.header.sequence_number = sequence_number;
1446        if is_dump {
1447            message.header.flags |= NLM_F_MULTIPART;
1448        }
1449        message.finalize();
1450        message
1451    }
1452
1453    pub(crate) fn to_rtnl_del_addr(
1454        &self,
1455        sequence_number: u32,
1456    ) -> NetlinkMessage<RouteNetlinkMessage> {
1457        let Self(message) = self;
1458        let mut message: NetlinkMessage<RouteNetlinkMessage> =
1459            RouteNetlinkMessage::DelAddress(message.clone()).into();
1460        message.header.sequence_number = sequence_number;
1461        message.finalize();
1462        message
1463    }
1464}
1465
1466// NetlinkAddressMessage conversion related errors.
1467#[derive(Debug, PartialEq)]
1468enum NetlinkAddressMessageConversionError {
1469    // Interface id could not be downcasted to fit into the expected u32.
1470    InvalidInterfaceId(u64),
1471}
1472
1473fn addresses_optionally_from_interface_properties(
1474    properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1475) -> Option<BTreeMap<fnet::IpAddress, NetlinkAddressMessage>> {
1476    match interface_properties_to_address_messages(properties) {
1477        Ok(o) => Some(o),
1478        Err(NetlinkAddressMessageConversionError::InvalidInterfaceId(id)) => {
1479            log_warn!("Invalid interface id: {:?}", id);
1480            None
1481        }
1482    }
1483}
1484
1485// Implement conversions from `Properties` to `Vec<NetlinkAddressMessage>`
1486// which is fallible iff, the interface has an id greater than u32.
1487fn interface_properties_to_address_messages(
1488    fnet_interfaces_ext::Properties {
1489        id,
1490        name,
1491        addresses,
1492        port_class: _,
1493        online: _,
1494        has_default_ipv4_route: _,
1495        has_default_ipv6_route: _,
1496        port_identity_koid: _,
1497    }: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
1498) -> Result<BTreeMap<fnet::IpAddress, NetlinkAddressMessage>, NetlinkAddressMessageConversionError>
1499{
1500    // We expect interface ids to safely fit in the range of the u32 values.
1501    let id: u32 = match id.get().try_into() {
1502        Err(std::num::TryFromIntError { .. }) => {
1503            return Err(NetlinkAddressMessageConversionError::InvalidInterfaceId(
1504                id.clone().into(),
1505            ));
1506        }
1507        Ok(id) => id,
1508    };
1509
1510    let address_messages = addresses
1511        .iter()
1512        .map(
1513            |fnet_interfaces_ext::Address {
1514                 addr: fnet::Subnet { addr, prefix_len },
1515                 valid_until: _,
1516                 preferred_lifetime_info: _,
1517                 assignment_state,
1518             }| {
1519                let mut addr_header = AddressHeader::default();
1520
1521                let (family, addr_bytes) = match addr {
1522                    fnet::IpAddress::Ipv4(ip_addr) => {
1523                        (AddressFamily::Inet, IpAddr::V4(ip_addr.addr.into()))
1524                    }
1525                    fnet::IpAddress::Ipv6(ip_addr) => {
1526                        (AddressFamily::Inet6, IpAddr::V6(ip_addr.addr.into()))
1527                    }
1528                };
1529
1530                // The possible constants below are in the range of u8-accepted values, so they can
1531                // be safely casted to a u8.
1532                addr_header.family = family.into();
1533                addr_header.prefix_len = *prefix_len;
1534
1535                // TODO(https://issues.fuchsia.dev/284980862): Determine proper
1536                // mapping from Netstack properties to address flags.
1537                let flags = AddressHeaderFlags::Permanent
1538                    | match assignment_state {
1539                        fnet_interfaces::AddressAssignmentState::Assigned => {
1540                            AddressHeaderFlags::empty()
1541                        }
1542                        fnet_interfaces::AddressAssignmentState::Tentative
1543                        | fnet_interfaces::AddressAssignmentState::Unavailable => {
1544                            // There is no equivalent `IFA_F_` flag for
1545                            // `Unavailable` so we treat it as tentative to
1546                            // signal that the address is installed but not
1547                            // considered assigned.
1548                            AddressHeaderFlags::Tentative
1549                        }
1550                    };
1551                addr_header.flags = flags.bits();
1552                addr_header.index = id.into();
1553
1554                // The NLA order follows the list that attributes are listed on the
1555                // rtnetlink man page.
1556                // The following fields are used in the options in the NLA, but they do
1557                // not have any corresponding values in `fnet_interfaces_ext::Properties` or
1558                // `fnet_interfaces_ext::Address`.
1559                //
1560                // IFA_LOCAL
1561                // IFA_BROADCAST
1562                // IFA_ANYCAST
1563                // IFA_CACHEINFO
1564                //
1565                // IFA_MULTICAST is documented via the netlink_packet_route crate but is not
1566                // present on the rtnetlink page.
1567                let nlas = vec![
1568                    AddressAttribute::Address(addr_bytes),
1569                    AddressAttribute::Label(name.clone()),
1570                    // SAFETY: This unwrap is safe because AddressFlags overlaps with
1571                    // AddressHeaderFlags.
1572                    AddressAttribute::Flags(AddressFlags::from_bits(flags.bits().into()).unwrap()),
1573                ];
1574
1575                let mut addr_message = AddressMessage::default();
1576                addr_message.header = addr_header;
1577                addr_message.attributes = nlas;
1578                (addr.clone(), NetlinkAddressMessage(addr_message))
1579            },
1580        )
1581        .collect();
1582
1583    Ok(address_messages)
1584}
1585
1586#[cfg(test)]
1587pub(crate) mod testutil {
1588    use super::*;
1589
1590    use fuchsia_sync::Mutex;
1591    use std::sync::Arc;
1592
1593    use futures::TryStreamExt as _;
1594    use futures::channel::mpsc;
1595    use futures::future::Future;
1596    use futures::stream::Stream;
1597    use net_declare::{fidl_subnet, net_addr_subnet};
1598
1599    use crate::client::AsyncWorkItem;
1600    use crate::messaging::testutil::FakeSender;
1601    use crate::route_eventloop::{EventLoopComponent, IncludedWorkers, Optional, Required};
1602
1603    pub(crate) const LO_INTERFACE_ID: u64 = 1;
1604    pub(crate) const LO_NAME: &str = "lo";
1605    pub(crate) const ETH_INTERFACE_ID: u64 = 2;
1606    pub(crate) const ETH_NAME: &str = "eth";
1607    pub(crate) const WLAN_INTERFACE_ID: u64 = 3;
1608    pub(crate) const WLAN_NAME: &str = "wlan";
1609    pub(crate) const PPP_INTERFACE_ID: u64 = 4;
1610    pub(crate) const PPP_NAME: &str = "ppp";
1611
1612    pub(crate) const BRIDGE: fnet_interfaces_ext::PortClass =
1613        fnet_interfaces_ext::PortClass::Bridge;
1614    pub(crate) const ETHERNET: fnet_interfaces_ext::PortClass =
1615        fnet_interfaces_ext::PortClass::Ethernet;
1616    pub(crate) const WLAN_CLIENT: fnet_interfaces_ext::PortClass =
1617        fnet_interfaces_ext::PortClass::WlanClient;
1618    pub(crate) const WLAN_AP: fnet_interfaces_ext::PortClass =
1619        fnet_interfaces_ext::PortClass::WlanAp;
1620    pub(crate) const PPP: fnet_interfaces_ext::PortClass = fnet_interfaces_ext::PortClass::Ppp;
1621    pub(crate) const LOOPBACK: fnet_interfaces_ext::PortClass =
1622        fnet_interfaces_ext::PortClass::Loopback;
1623    pub(crate) const TEST_V4_ADDR: fnet::Subnet = fidl_subnet!("192.0.2.1/24");
1624    pub(crate) const TEST_V6_ADDR: fnet::Subnet = fidl_subnet!("2001:db8::1/32");
1625
1626    // AddrSubnetEither does not have any const methods so we need a method.
1627    pub(crate) fn test_addr_subnet_v4() -> AddrSubnetEither {
1628        net_addr_subnet!("192.0.2.1/24")
1629    }
1630
1631    // AddrSubnetEither does not have any const methods so we need a method.
1632    pub(crate) fn test_addr_subnet_v6() -> AddrSubnetEither {
1633        net_addr_subnet!("2001:db8::1/32")
1634    }
1635
1636    // AddrSubnetEither does not have any const methods so we need a method.
1637    pub(crate) fn add_test_addr_subnet_v4() -> AddrSubnetEither {
1638        net_addr_subnet!("192.0.2.2/24")
1639    }
1640
1641    // AddrSubnetEither does not have any const methods so we need a method.
1642    pub(crate) fn add_test_addr_subnet_v6() -> AddrSubnetEither {
1643        net_addr_subnet!("2001:db8::2/32")
1644    }
1645
1646    #[derive(Debug, PartialEq, Eq)]
1647    pub(crate) enum HandledLinkKind {
1648        New,
1649        Del,
1650    }
1651
1652    #[derive(Debug, PartialEq, Eq)]
1653    pub(crate) struct HandledLink {
1654        pub name: String,
1655        pub kind: HandledLinkKind,
1656    }
1657
1658    pub(crate) struct FakeInterfacesHandlerSink(Arc<Mutex<Vec<HandledLink>>>);
1659
1660    impl FakeInterfacesHandlerSink {
1661        pub(crate) fn take_handled(&mut self) -> Vec<HandledLink> {
1662            let Self(rc) = self;
1663            core::mem::take(&mut *rc.lock())
1664        }
1665    }
1666
1667    pub(crate) struct FakeInterfacesHandler(Arc<Mutex<Vec<HandledLink>>>);
1668
1669    impl FakeInterfacesHandler {
1670        pub(crate) fn new() -> (FakeInterfacesHandler, FakeInterfacesHandlerSink) {
1671            let inner = Arc::default();
1672            (FakeInterfacesHandler(Arc::clone(&inner)), FakeInterfacesHandlerSink(inner))
1673        }
1674    }
1675
1676    impl InterfacesHandler for FakeInterfacesHandler {
1677        fn handle_new_link(&mut self, name: &str, _interface_id: NonZeroU64) {
1678            let Self(rc) = self;
1679            rc.lock().push(HandledLink { name: name.to_string(), kind: HandledLinkKind::New })
1680        }
1681
1682        fn handle_deleted_link(&mut self, name: &str) {
1683            let Self(rc) = self;
1684            rc.lock().push(HandledLink { name: name.to_string(), kind: HandledLinkKind::Del })
1685        }
1686    }
1687
1688    enum OnlyInterfaces {}
1689    impl crate::route_eventloop::EventLoopSpec for OnlyInterfaces {
1690        type InterfacesProxy = Required;
1691        type InterfacesStateProxy = Required;
1692        type InterfacesHandler = Required;
1693        type RouteClients = Required;
1694
1695        type V4RoutesState = Optional;
1696        type V6RoutesState = Optional;
1697        type V4RoutesSetProvider = Optional;
1698        type V6RoutesSetProvider = Optional;
1699        type V4RouteTableProvider = Optional;
1700        type V6RouteTableProvider = Optional;
1701
1702        type InterfacesWorker = Required;
1703        type RoutesV4Worker = Optional;
1704        type RoutesV6Worker = Optional;
1705        type RuleV4Worker = Optional;
1706        type RuleV6Worker = Optional;
1707        type NduseroptWorker = Optional;
1708        type NeighborWorker = Optional;
1709    }
1710
1711    pub(crate) struct Setup<E, W> {
1712        pub event_loop_fut: E,
1713        pub watcher_stream: W,
1714        pub request_sink:
1715            mpsc::Sender<crate::route_eventloop::UnifiedRequest<FakeSender<RouteNetlinkMessage>>>,
1716        pub interfaces_request_stream: fnet_root::InterfacesRequestStream,
1717        pub interfaces_handler_sink: FakeInterfacesHandlerSink,
1718        pub _async_work_sink: mpsc::UnboundedSender<AsyncWorkItem<NetlinkRoute>>,
1719    }
1720
1721    pub(crate) fn setup_with_route_clients(
1722        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
1723    ) -> Setup<impl Future<Output = !>, impl Stream<Item = fnet_interfaces::WatcherRequest>> {
1724        let (request_sink, request_stream) = mpsc::channel(1);
1725        let (interfaces_handler, interfaces_handler_sink) = FakeInterfacesHandler::new();
1726        let (interfaces_proxy, interfaces) =
1727            fidl::endpoints::create_proxy::<fnet_root::InterfacesMarker>();
1728        let (interfaces_state_proxy, interfaces_state) =
1729            fidl::endpoints::create_proxy::<fnet_interfaces::StateMarker>();
1730        let (async_work_sink, async_work_receiver) = mpsc::unbounded();
1731        let event_loop_inputs = crate::route_eventloop::EventLoopInputs::<_, _, OnlyInterfaces> {
1732            route_clients: EventLoopComponent::Present(route_clients),
1733            interfaces_handler: EventLoopComponent::Present(interfaces_handler),
1734            interfaces_proxy: EventLoopComponent::Present(interfaces_proxy),
1735            interfaces_state_proxy: EventLoopComponent::Present(interfaces_state_proxy),
1736            async_work_receiver,
1737
1738            v4_routes_state: EventLoopComponent::Absent(Optional),
1739            v6_routes_state: EventLoopComponent::Absent(Optional),
1740            v4_main_route_table: EventLoopComponent::Absent(Optional),
1741            v6_main_route_table: EventLoopComponent::Absent(Optional),
1742            v4_route_table_provider: EventLoopComponent::Absent(Optional),
1743            v6_route_table_provider: EventLoopComponent::Absent(Optional),
1744            v4_rule_table: EventLoopComponent::Absent(Optional),
1745            v6_rule_table: EventLoopComponent::Absent(Optional),
1746            neighbors_view: EventLoopComponent::Absent(Optional),
1747            neighbors_controller: EventLoopComponent::Absent(Optional),
1748            ndp_option_watcher_provider: EventLoopComponent::Absent(Optional),
1749
1750            unified_request_stream: request_stream,
1751        };
1752
1753        let interfaces_request_stream = interfaces.into_stream();
1754        let if_stream = interfaces_state.into_stream();
1755        let watcher_stream = if_stream
1756            .and_then(|req| match req {
1757                fnet_interfaces::StateRequest::GetWatcher {
1758                    options: _,
1759                    watcher,
1760                    control_handle: _,
1761                } => futures::future::ready(Ok(watcher.into_stream())),
1762            })
1763            .try_flatten()
1764            .map(|res| res.expect("watcher stream error"));
1765
1766        Setup {
1767            event_loop_fut: async move {
1768                let event_loop = event_loop_inputs
1769                    .initialize(IncludedWorkers {
1770                        interfaces: EventLoopComponent::Present(()),
1771                        routes_v4: EventLoopComponent::Absent(Optional),
1772                        routes_v6: EventLoopComponent::Absent(Optional),
1773                        rules_v4: EventLoopComponent::Absent(Optional),
1774                        rules_v6: EventLoopComponent::Absent(Optional),
1775                        nduseropt: EventLoopComponent::Absent(Optional),
1776                        neighbors: EventLoopComponent::Absent(Optional),
1777                    })
1778                    .await;
1779                event_loop.run().await
1780            },
1781            watcher_stream,
1782            request_sink,
1783            interfaces_request_stream,
1784            interfaces_handler_sink,
1785            _async_work_sink: async_work_sink,
1786        }
1787    }
1788
1789    pub(crate) async fn respond_to_watcher<S: Stream<Item = fnet_interfaces::WatcherRequest>>(
1790        stream: S,
1791        updates: impl IntoIterator<Item = fnet_interfaces::Event>,
1792    ) {
1793        stream
1794            .zip(futures::stream::iter(updates.into_iter()))
1795            .for_each(|(req, update)| async move {
1796                match req {
1797                    fnet_interfaces::WatcherRequest::Watch { responder } => {
1798                        responder.send(&update).expect("send watch response")
1799                    }
1800                }
1801            })
1802            .await
1803    }
1804
1805    pub(crate) fn create_netlink_link_message(
1806        id: u64,
1807        link_type: u16,
1808        flags: u32,
1809        nlas: Vec<LinkAttribute>,
1810    ) -> NetlinkLinkMessage {
1811        let mut link_header = LinkHeader::default();
1812        link_header.index = id.try_into().expect("should fit into u32");
1813        link_header.link_layer_type = LinkLayerType::from(link_type);
1814        link_header.flags = LinkFlags::from_bits(flags).unwrap();
1815        link_header.change_mask = LinkFlags::from_bits(u32::MAX).unwrap();
1816
1817        let mut link_message = LinkMessage::default();
1818        link_message.header = link_header;
1819        link_message.attributes = nlas;
1820
1821        NetlinkLinkMessage(link_message)
1822    }
1823
1824    pub(crate) fn create_nlas(
1825        name: String,
1826        link_type: u16,
1827        online: bool,
1828        mac: &Option<fnet::MacAddress>,
1829    ) -> Vec<LinkAttribute> {
1830        [
1831            LinkAttribute::IfName(name),
1832            LinkAttribute::Link(link_type.into()),
1833            LinkAttribute::OperState(if online { State::Up } else { State::Down }),
1834        ]
1835        .into_iter()
1836        .chain(mac.map(|fnet::MacAddress { octets }| LinkAttribute::Address(octets.to_vec())))
1837        .collect()
1838    }
1839
1840    pub(crate) fn create_address_message(
1841        interface_id: u32,
1842        subnet: fnet::Subnet,
1843        interface_name: String,
1844        flags: u32,
1845    ) -> NetlinkAddressMessage {
1846        let mut addr_header = AddressHeader::default();
1847        let (family, addr) = match subnet.addr {
1848            fnet::IpAddress::Ipv4(ip_addr) => {
1849                (AddressFamily::Inet, IpAddr::V4(ip_addr.addr.into()))
1850            }
1851            fnet::IpAddress::Ipv6(ip_addr) => {
1852                (AddressFamily::Inet6, IpAddr::V6(ip_addr.addr.into()))
1853            }
1854        };
1855        addr_header.family = family.into();
1856        addr_header.prefix_len = subnet.prefix_len;
1857        addr_header.flags = AddressHeaderFlags::from_bits(flags as u8).unwrap().bits();
1858        addr_header.index = interface_id.into();
1859
1860        let nlas = vec![
1861            AddressAttribute::Address(addr),
1862            AddressAttribute::Label(interface_name),
1863            AddressAttribute::Flags(AddressFlags::from_bits(flags).unwrap()),
1864        ];
1865
1866        let mut addr_message = AddressMessage::default();
1867        addr_message.header = addr_header;
1868        addr_message.attributes = nlas;
1869        NetlinkAddressMessage(addr_message)
1870    }
1871
1872    pub(crate) fn test_addr_with_assignment_state(
1873        addr: fnet::Subnet,
1874        assignment_state: fnet_interfaces::AddressAssignmentState,
1875    ) -> fnet_interfaces::Address {
1876        fnet_interfaces_ext::Address::<fnet_interfaces_ext::DefaultInterest> {
1877            addr,
1878            valid_until: fnet_interfaces_ext::NoInterest,
1879            preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
1880            assignment_state,
1881        }
1882        .into()
1883    }
1884
1885    pub(crate) fn test_addr(addr: fnet::Subnet) -> fnet_interfaces::Address {
1886        test_addr_with_assignment_state(addr, fnet_interfaces::AddressAssignmentState::Assigned)
1887    }
1888}
1889
1890#[cfg(test)]
1891mod tests {
1892    use super::testutil::*;
1893    use super::*;
1894
1895    use std::pin::{Pin, pin};
1896
1897    use fidl::endpoints::{ControlHandle as _, RequestStream as _, Responder as _};
1898    use fidl_fuchsia_net as fnet;
1899    use fnet_interfaces::AddressAssignmentState;
1900    use fuchsia_async::{self as fasync};
1901
1902    use assert_matches::assert_matches;
1903    use futures::FutureExt as _;
1904    use futures::sink::SinkExt as _;
1905    use futures::stream::Stream;
1906    use linux_uapi::{IFA_F_PERMANENT, IFA_F_TENTATIVE, rtnetlink_groups_RTNLGRP_IPV4_ROUTE};
1907    use pretty_assertions::assert_eq;
1908    use test_case::test_case;
1909
1910    use crate::messaging::testutil::SentMessage;
1911
1912    const TEST_SEQUENCE_NUMBER: u32 = 1234;
1913
1914    fn create_interface(
1915        id: u64,
1916        name: String,
1917        port_class: fnet_interfaces_ext::PortClass,
1918        online: bool,
1919        addresses: Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::DefaultInterest>>,
1920    ) -> fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest> {
1921        fnet_interfaces_ext::Properties {
1922            id: NonZeroU64::new(id).unwrap(),
1923            name,
1924            port_class,
1925            online,
1926            addresses,
1927            has_default_ipv4_route: false,
1928            has_default_ipv6_route: false,
1929            port_identity_koid: None,
1930        }
1931    }
1932
1933    fn create_interface_with_addresses(
1934        id: u64,
1935        name: String,
1936        port_class: fnet_interfaces_ext::PortClass,
1937        online: bool,
1938    ) -> fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest> {
1939        let addresses = vec![
1940            fnet_interfaces_ext::Address {
1941                addr: TEST_V4_ADDR,
1942                valid_until: fnet_interfaces_ext::NoInterest,
1943                assignment_state: AddressAssignmentState::Assigned,
1944                preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
1945            },
1946            fnet_interfaces_ext::Address {
1947                addr: TEST_V6_ADDR,
1948                valid_until: fnet_interfaces_ext::NoInterest,
1949                assignment_state: AddressAssignmentState::Assigned,
1950                preferred_lifetime_info: fnet_interfaces_ext::NoInterest,
1951            },
1952        ];
1953        create_interface(id, name, port_class, online, addresses)
1954    }
1955
1956    fn create_default_address_messages(
1957        interface_id: u64,
1958        interface_name: String,
1959        flags: u32,
1960    ) -> BTreeMap<fnet::IpAddress, NetlinkAddressMessage> {
1961        let interface_id = interface_id.try_into().expect("should fit into u32");
1962        BTreeMap::from_iter([
1963            (
1964                TEST_V4_ADDR.addr,
1965                create_address_message(interface_id, TEST_V4_ADDR, interface_name.clone(), flags),
1966            ),
1967            (
1968                TEST_V6_ADDR.addr,
1969                create_address_message(interface_id, TEST_V6_ADDR, interface_name, flags),
1970            ),
1971        ])
1972    }
1973
1974    #[test_case(ETHERNET, false, 0, ARPHRD_ETHER)]
1975    #[test_case(ETHERNET, true, ONLINE_IF_FLAGS, ARPHRD_ETHER)]
1976    #[test_case(WLAN_CLIENT, false, net_device_flags_IFF_UP, ARPHRD_ETHER)]
1977    #[test_case(WLAN_CLIENT, true, ONLINE_IF_FLAGS, ARPHRD_ETHER)]
1978    #[test_case(WLAN_AP, false, 0, ARPHRD_ETHER)]
1979    #[test_case(WLAN_AP, true, ONLINE_IF_FLAGS, ARPHRD_ETHER)]
1980    #[test_case(PPP, false, 0, ARPHRD_PPP)]
1981    #[test_case(PPP, true, ONLINE_IF_FLAGS, ARPHRD_PPP)]
1982    #[test_case(LOOPBACK, false, net_device_flags_IFF_LOOPBACK, ARPHRD_LOOPBACK)]
1983    #[test_case(LOOPBACK, true, ONLINE_IF_FLAGS | net_device_flags_IFF_LOOPBACK, ARPHRD_LOOPBACK)]
1984    #[test_case(BRIDGE, false, 0, ARPHRD_ETHER)]
1985    #[test_case(BRIDGE, true, ONLINE_IF_FLAGS, ARPHRD_ETHER)]
1986    fn test_interface_conversion(
1987        port_class: fnet_interfaces_ext::PortClass,
1988        online: bool,
1989        flags: u32,
1990        expected_link_type: u32,
1991    ) {
1992        // This conversion is safe as the link type is actually a u16,
1993        // but our bindings generator declared it as a u32.
1994        let expected_link_type = expected_link_type as u16;
1995        let interface_name = LO_NAME.to_string();
1996        let interface =
1997            create_interface(LO_INTERFACE_ID, interface_name.clone(), port_class, online, vec![]);
1998        let actual: NetlinkLinkMessage =
1999            interface_properties_to_link_message(&interface, &LO_MAC.map(|a| a.octets.to_vec()))
2000                .unwrap();
2001
2002        let nlas = create_nlas(interface_name, expected_link_type, online, &LO_MAC);
2003        let expected =
2004            create_netlink_link_message(LO_INTERFACE_ID, expected_link_type, flags, nlas);
2005        pretty_assertions::assert_eq!(actual, expected);
2006    }
2007
2008    #[fuchsia::test(logging = false)]
2009    fn test_oversized_interface_id_link_address_conversion() {
2010        let invalid_interface_id = (u32::MAX as u64) + 1;
2011        let interface =
2012            create_interface(invalid_interface_id, "test".into(), ETHERNET, true, vec![]);
2013
2014        let actual_link_message = interface_properties_to_link_message(&interface, &None);
2015        assert_eq!(
2016            actual_link_message,
2017            Err(NetlinkLinkMessageConversionError::InvalidInterfaceId(invalid_interface_id))
2018        );
2019
2020        assert_eq!(
2021            interface_properties_to_address_messages(&interface),
2022            Err(NetlinkAddressMessageConversionError::InvalidInterfaceId(invalid_interface_id))
2023        );
2024    }
2025
2026    #[fuchsia::test(logging = false)]
2027    fn test_interface_to_address_conversion() {
2028        let interface_name: String = "test".into();
2029        let interface_id = 1;
2030
2031        let interface =
2032            create_interface_with_addresses(interface_id, interface_name.clone(), ETHERNET, true);
2033        let actual = interface_properties_to_address_messages(&interface).unwrap();
2034
2035        let expected =
2036            create_default_address_messages(interface_id, interface_name, IFA_F_PERMANENT);
2037        assert_eq!(actual, expected);
2038    }
2039
2040    #[test]
2041    fn test_into_rtnl_new_link_is_serializable() {
2042        let link = create_netlink_link_message(0, 0, 0, vec![]);
2043        let new_link_message = link.into_rtnl_new_link(UNSPECIFIED_SEQUENCE_NUMBER, false);
2044        let mut buf = vec![0; new_link_message.buffer_len()];
2045        // Serialize will panic if `new_route_message` is malformed.
2046        new_link_message.serialize(&mut buf);
2047    }
2048
2049    #[test]
2050    fn test_into_rtnl_del_link_is_serializable() {
2051        let link = create_netlink_link_message(0, 0, 0, vec![]);
2052        let del_link_message = link.into_rtnl_del_link(UNSPECIFIED_SEQUENCE_NUMBER);
2053        let mut buf = vec![0; del_link_message.buffer_len()];
2054        // Serialize will panic if `del_route_message` is malformed.
2055        del_link_message.serialize(&mut buf);
2056    }
2057
2058    #[fuchsia::test(logging = false)]
2059    async fn test_deliver_updates() {
2060        let (mut link_sink, link_client, _async_work_drain_task) =
2061            crate::client::testutil::new_fake_client::<NetlinkRoute>(
2062                crate::client::testutil::CLIENT_ID_1,
2063                [ModernGroup(rtnetlink_groups_RTNLGRP_LINK)],
2064            );
2065        let (mut addr4_sink, addr4_client, _async_work_drain_task) =
2066            crate::client::testutil::new_fake_client::<NetlinkRoute>(
2067                crate::client::testutil::CLIENT_ID_2,
2068                [ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_IFADDR)],
2069            );
2070        let (mut addr6_sink, addr6_client, _async_work_drain_task) =
2071            crate::client::testutil::new_fake_client::<NetlinkRoute>(
2072                crate::client::testutil::CLIENT_ID_3,
2073                [ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_IFADDR)],
2074            );
2075        let (mut other_sink, other_client, _async_work_drain_task) =
2076            crate::client::testutil::new_fake_client::<NetlinkRoute>(
2077                crate::client::testutil::CLIENT_ID_4,
2078                [ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE)],
2079            );
2080        let (mut all_sink, all_client, _async_work_drain_task) =
2081            crate::client::testutil::new_fake_client::<NetlinkRoute>(
2082                crate::client::testutil::CLIENT_ID_5,
2083                [
2084                    ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
2085                    ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_IFADDR),
2086                    ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_IFADDR),
2087                ],
2088            );
2089        let Setup {
2090            event_loop_fut,
2091            mut watcher_stream,
2092            request_sink: _,
2093            interfaces_request_stream,
2094            mut interfaces_handler_sink,
2095            _async_work_sink: _,
2096        } = setup_with_route_clients({
2097            let route_clients = ClientTable::default();
2098            route_clients.add_client(link_client);
2099            route_clients.add_client(addr4_client);
2100            route_clients.add_client(addr6_client);
2101            route_clients.add_client(other_client);
2102            route_clients.add_client(all_client);
2103            route_clients
2104        });
2105        let event_loop_fut = event_loop_fut.fuse();
2106        let mut event_loop_fut = pin!(event_loop_fut);
2107        let root_interfaces_fut =
2108            handle_only_get_mac_root_requests_fut(interfaces_request_stream).fuse();
2109        let mut root_interfaces_fut = pin!(root_interfaces_fut);
2110
2111        // Existing events should never trigger messages to be sent.
2112        let watcher_stream_fut = respond_to_watcher(
2113            watcher_stream.by_ref(),
2114            [
2115                fnet_interfaces::Event::Existing(fnet_interfaces::Properties {
2116                    id: Some(LO_INTERFACE_ID),
2117                    name: Some(LO_NAME.to_string()),
2118                    port_class: Some(LOOPBACK.into()),
2119                    online: Some(false),
2120                    addresses: Some(vec![test_addr_with_assignment_state(
2121                        TEST_V4_ADDR,
2122                        fnet_interfaces::AddressAssignmentState::Assigned,
2123                    )]),
2124                    has_default_ipv4_route: Some(false),
2125                    has_default_ipv6_route: Some(false),
2126                    ..Default::default()
2127                }),
2128                fnet_interfaces::Event::Existing(fnet_interfaces::Properties {
2129                    id: Some(ETH_INTERFACE_ID),
2130                    name: Some(ETH_NAME.to_string()),
2131                    port_class: Some(ETHERNET.into()),
2132                    online: Some(false),
2133                    addresses: Some(vec![
2134                        test_addr_with_assignment_state(
2135                            TEST_V6_ADDR,
2136                            fnet_interfaces::AddressAssignmentState::Unavailable,
2137                        ),
2138                        test_addr_with_assignment_state(
2139                            TEST_V4_ADDR,
2140                            fnet_interfaces::AddressAssignmentState::Unavailable,
2141                        ),
2142                    ]),
2143                    has_default_ipv4_route: Some(false),
2144                    has_default_ipv6_route: Some(false),
2145                    ..Default::default()
2146                }),
2147                fnet_interfaces::Event::Existing(fnet_interfaces::Properties {
2148                    id: Some(PPP_INTERFACE_ID),
2149                    name: Some(PPP_NAME.to_string()),
2150                    port_class: Some(PPP.into()),
2151                    online: Some(false),
2152                    addresses: Some(vec![
2153                        test_addr_with_assignment_state(
2154                            TEST_V4_ADDR,
2155                            fnet_interfaces::AddressAssignmentState::Assigned,
2156                        ),
2157                        test_addr_with_assignment_state(
2158                            TEST_V6_ADDR,
2159                            fnet_interfaces::AddressAssignmentState::Assigned,
2160                        ),
2161                    ]),
2162                    has_default_ipv4_route: Some(false),
2163                    has_default_ipv6_route: Some(false),
2164                    ..Default::default()
2165                }),
2166                fnet_interfaces::Event::Idle(fnet_interfaces::Empty),
2167            ],
2168        );
2169        futures::select! {
2170            () = watcher_stream_fut.fuse() => {},
2171            () = root_interfaces_fut => {
2172                unreachable!("root interfaces request stream should never end")
2173            }
2174            err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2175        }
2176        assert_eq!(&link_sink.take_messages()[..], &[]);
2177        assert_eq!(&addr4_sink.take_messages()[..], &[]);
2178        assert_eq!(&addr6_sink.take_messages()[..], &[]);
2179        assert_eq!(&other_sink.take_messages()[..], &[]);
2180        assert_eq!(&all_sink.take_messages()[..], &[]);
2181
2182        let watcher_stream_fut = respond_to_watcher(
2183            watcher_stream.by_ref(),
2184            [
2185                fnet_interfaces::Event::Added(fnet_interfaces::Properties {
2186                    id: Some(WLAN_INTERFACE_ID),
2187                    name: Some(WLAN_NAME.to_string()),
2188                    port_class: Some(WLAN_CLIENT.into()),
2189                    online: Some(false),
2190                    addresses: Some(vec![
2191                        test_addr_with_assignment_state(
2192                            TEST_V4_ADDR,
2193                            fnet_interfaces::AddressAssignmentState::Tentative,
2194                        ),
2195                        test_addr_with_assignment_state(
2196                            TEST_V6_ADDR,
2197                            fnet_interfaces::AddressAssignmentState::Tentative,
2198                        ),
2199                    ]),
2200                    has_default_ipv4_route: Some(false),
2201                    has_default_ipv6_route: Some(false),
2202                    ..Default::default()
2203                }),
2204                fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
2205                    id: Some(LO_INTERFACE_ID),
2206                    online: Some(true),
2207                    addresses: Some(vec![
2208                        test_addr_with_assignment_state(
2209                            TEST_V4_ADDR,
2210                            fnet_interfaces::AddressAssignmentState::Assigned,
2211                        ),
2212                        test_addr_with_assignment_state(
2213                            TEST_V6_ADDR,
2214                            fnet_interfaces::AddressAssignmentState::Assigned,
2215                        ),
2216                    ]),
2217                    ..Default::default()
2218                }),
2219                fnet_interfaces::Event::Removed(ETH_INTERFACE_ID),
2220                fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
2221                    id: Some(PPP_INTERFACE_ID),
2222                    addresses: Some(Vec::new()),
2223                    ..Default::default()
2224                }),
2225                fnet_interfaces::Event::Changed(fnet_interfaces::Properties {
2226                    id: Some(WLAN_INTERFACE_ID),
2227                    has_default_ipv6_route: Some(true),
2228                    ..Default::default()
2229                }),
2230            ],
2231        );
2232
2233        futures::select! {
2234            () = watcher_stream_fut.fuse() => {},
2235            () = root_interfaces_fut => {
2236                unreachable!("root interfaces request stream should never end")
2237            }
2238            err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2239        }
2240
2241        // Poll the event loop to ensure it's had the opportunity to process the
2242        // events from the watcher. The event loop can never finish, so we
2243        // must see `None`.
2244        assert_matches!(event_loop_fut.now_or_never(), None);
2245
2246        assert_eq!(
2247            interfaces_handler_sink.take_handled(),
2248            [
2249                HandledLink { name: LO_NAME.to_string(), kind: HandledLinkKind::New },
2250                HandledLink { name: ETH_NAME.to_string(), kind: HandledLinkKind::New },
2251                HandledLink { name: PPP_NAME.to_string(), kind: HandledLinkKind::New },
2252                HandledLink { name: WLAN_NAME.to_string(), kind: HandledLinkKind::New },
2253                HandledLink { name: ETH_NAME.to_string(), kind: HandledLinkKind::Del },
2254            ],
2255        );
2256        // Conversion to u16 is safe because 1 < 65535
2257        let arphrd_ether_u16: u16 = ARPHRD_ETHER as u16;
2258        // Conversion to u16 is safe because 772 < 65535
2259        let arphrd_loopback_u16: u16 = ARPHRD_LOOPBACK as u16;
2260        let wlan_link = SentMessage::multicast(
2261            create_netlink_link_message(
2262                WLAN_INTERFACE_ID,
2263                arphrd_ether_u16,
2264                net_device_flags_IFF_UP, // For now, WLAN interfaces are always "IFF_UP"
2265                create_nlas(WLAN_NAME.to_string(), arphrd_ether_u16, false, &WLAN_MAC),
2266            )
2267            .into_rtnl_new_link(UNSPECIFIED_SEQUENCE_NUMBER, false),
2268            ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
2269        );
2270        let lo_link = SentMessage::multicast(
2271            create_netlink_link_message(
2272                LO_INTERFACE_ID,
2273                arphrd_loopback_u16,
2274                ONLINE_IF_FLAGS | net_device_flags_IFF_LOOPBACK,
2275                create_nlas(LO_NAME.to_string(), arphrd_loopback_u16, true, &LO_MAC),
2276            )
2277            .into_rtnl_new_link(UNSPECIFIED_SEQUENCE_NUMBER, false),
2278            ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
2279        );
2280        let eth_link = SentMessage::multicast(
2281            create_netlink_link_message(
2282                ETH_INTERFACE_ID,
2283                arphrd_ether_u16,
2284                0,
2285                create_nlas(ETH_NAME.to_string(), arphrd_ether_u16, false, &ETH_MAC),
2286            )
2287            .into_rtnl_del_link(UNSPECIFIED_SEQUENCE_NUMBER),
2288            ModernGroup(rtnetlink_groups_RTNLGRP_LINK),
2289        );
2290        assert_eq!(
2291            &link_sink.take_messages()[..],
2292            &[wlan_link.clone(), lo_link.clone(), eth_link.clone(),],
2293        );
2294
2295        let wlan_v4_addr = SentMessage::multicast(
2296            create_address_message(
2297                WLAN_INTERFACE_ID.try_into().unwrap(),
2298                TEST_V4_ADDR,
2299                WLAN_NAME.to_string(),
2300                IFA_F_PERMANENT | IFA_F_TENTATIVE,
2301            )
2302            .to_rtnl_new_addr(UNSPECIFIED_SEQUENCE_NUMBER, false),
2303            ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_IFADDR),
2304        );
2305        let eth_v4_addr = SentMessage::multicast(
2306            create_address_message(
2307                ETH_INTERFACE_ID.try_into().unwrap(),
2308                TEST_V4_ADDR,
2309                ETH_NAME.to_string(),
2310                IFA_F_PERMANENT | IFA_F_TENTATIVE,
2311            )
2312            .to_rtnl_del_addr(UNSPECIFIED_SEQUENCE_NUMBER),
2313            ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_IFADDR),
2314        );
2315        let ppp_v4_addr = SentMessage::multicast(
2316            create_address_message(
2317                PPP_INTERFACE_ID.try_into().unwrap(),
2318                TEST_V4_ADDR,
2319                PPP_NAME.to_string(),
2320                IFA_F_PERMANENT,
2321            )
2322            .to_rtnl_del_addr(UNSPECIFIED_SEQUENCE_NUMBER),
2323            ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_IFADDR),
2324        );
2325        assert_eq!(
2326            &addr4_sink.take_messages()[..],
2327            &[wlan_v4_addr.clone(), eth_v4_addr.clone(), ppp_v4_addr.clone(),],
2328        );
2329
2330        let wlan_v6_addr = SentMessage::multicast(
2331            create_address_message(
2332                WLAN_INTERFACE_ID.try_into().unwrap(),
2333                TEST_V6_ADDR,
2334                WLAN_NAME.to_string(),
2335                IFA_F_PERMANENT | IFA_F_TENTATIVE,
2336            )
2337            .to_rtnl_new_addr(UNSPECIFIED_SEQUENCE_NUMBER, false),
2338            ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_IFADDR),
2339        );
2340        let lo_v6_addr = SentMessage::multicast(
2341            create_address_message(
2342                LO_INTERFACE_ID.try_into().unwrap(),
2343                TEST_V6_ADDR,
2344                LO_NAME.to_string(),
2345                IFA_F_PERMANENT,
2346            )
2347            .to_rtnl_new_addr(UNSPECIFIED_SEQUENCE_NUMBER, false),
2348            ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_IFADDR),
2349        );
2350        let eth_v6_addr = SentMessage::multicast(
2351            create_address_message(
2352                ETH_INTERFACE_ID.try_into().unwrap(),
2353                TEST_V6_ADDR,
2354                ETH_NAME.to_string(),
2355                IFA_F_PERMANENT | IFA_F_TENTATIVE,
2356            )
2357            .to_rtnl_del_addr(UNSPECIFIED_SEQUENCE_NUMBER),
2358            ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_IFADDR),
2359        );
2360        let ppp_v6_addr = SentMessage::multicast(
2361            create_address_message(
2362                PPP_INTERFACE_ID.try_into().unwrap(),
2363                TEST_V6_ADDR,
2364                PPP_NAME.to_string(),
2365                IFA_F_PERMANENT,
2366            )
2367            .to_rtnl_del_addr(UNSPECIFIED_SEQUENCE_NUMBER),
2368            ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_IFADDR),
2369        );
2370        assert_eq!(
2371            &addr6_sink.take_messages()[..],
2372            &[wlan_v6_addr.clone(), lo_v6_addr.clone(), eth_v6_addr.clone(), ppp_v6_addr.clone(),],
2373        );
2374
2375        assert_eq!(
2376            &all_sink.take_messages()[..],
2377            &[
2378                // New links always appear before their addresses.
2379                wlan_link,
2380                wlan_v4_addr,
2381                wlan_v6_addr,
2382                lo_link,
2383                lo_v6_addr,
2384                // Removed addresses always appear before removed interfaces.
2385                eth_v4_addr,
2386                eth_v6_addr,
2387                eth_link,
2388                ppp_v4_addr,
2389                ppp_v6_addr,
2390            ],
2391        );
2392        assert_eq!(&other_sink.take_messages()[..], &[]);
2393    }
2394
2395    const LO_MAC: Option<fnet::MacAddress> = None;
2396    const ETH_MAC: Option<fnet::MacAddress> = Some(fnet::MacAddress { octets: [1, 1, 1, 1, 1, 1] });
2397    const PPP_MAC: Option<fnet::MacAddress> = Some(fnet::MacAddress { octets: [2, 2, 2, 2, 2, 2] });
2398    const WLAN_MAC: Option<fnet::MacAddress> =
2399        Some(fnet::MacAddress { octets: [3, 3, 3, 3, 3, 3] });
2400
2401    fn handle_get_mac_root_request_or_panic(req: fnet_root::InterfacesRequest) {
2402        match req {
2403            fnet_root::InterfacesRequest::GetMac { id, responder } => {
2404                let link_address = match id {
2405                    LO_INTERFACE_ID => LO_MAC,
2406                    ETH_INTERFACE_ID => ETH_MAC,
2407                    PPP_INTERFACE_ID => PPP_MAC,
2408                    WLAN_INTERFACE_ID => WLAN_MAC,
2409                    id => panic!("unexpected interface ID {id}"),
2410                };
2411
2412                responder.send(Ok(link_address.as_ref())).unwrap()
2413            }
2414            req => panic!("unexpected request {:?}", req),
2415        }
2416    }
2417
2418    fn expect_only_get_mac_root_requests(
2419        interfaces_request_stream: fnet_root::InterfacesRequestStream,
2420    ) -> impl Stream<Item = fnet_interfaces::Event> {
2421        futures::stream::unfold(interfaces_request_stream, |interfaces_request_stream| async move {
2422            interfaces_request_stream
2423                .for_each(|req| async move { handle_get_mac_root_request_or_panic(req.unwrap()) })
2424                .await;
2425
2426            None
2427        })
2428    }
2429
2430    async fn handle_only_get_mac_root_requests_fut(
2431        interfaces_request_stream: fnet_root::InterfacesRequestStream,
2432    ) {
2433        expect_only_get_mac_root_requests(interfaces_request_stream)
2434            .for_each(|item| async move { panic!("unexpected item = {item:?}") })
2435            .await
2436    }
2437
2438    #[derive(Debug, PartialEq)]
2439    struct TestRequestResult {
2440        messages: Vec<SentMessage<RouteNetlinkMessage>>,
2441        waiter_results: Vec<Result<(), RequestError>>,
2442    }
2443
2444    /// Test helper to handle a request.
2445    ///
2446    /// `root_handler` returns a future that returns an iterator of
2447    /// `fuchsia.net.interfaces/Event`s to feed to the netlink eventloop's
2448    /// interfaces watcher after a root API request is handled.
2449    async fn test_request<
2450        St: Stream<Item = fnet_interfaces::Event>,
2451        F: FnOnce(fnet_root::InterfacesRequestStream) -> St,
2452    >(
2453        args: impl IntoIterator<Item = RequestArgs>,
2454        root_handler: F,
2455    ) -> TestRequestResult {
2456        test_request_with_initial_state(
2457            args,
2458            root_handler,
2459            InitialState { eth_interface_online: false },
2460        )
2461        .await
2462    }
2463
2464    #[derive(Clone, Copy, Debug)]
2465    struct InitialState {
2466        eth_interface_online: bool,
2467    }
2468
2469    /// Test helper to handle a request.
2470    ///
2471    /// `root_handler` returns a future that returns an iterator of
2472    /// `fuchsia.net.interfaces/Event`s to feed to the netlink eventloop's
2473    /// interfaces watcher after a root API request is handled.
2474    /// `initial_state` parametrizes the initial state of the interfaces prior to the request being
2475    /// handled.
2476    async fn test_request_with_initial_state<
2477        St: Stream<Item = fnet_interfaces::Event>,
2478        F: FnOnce(fnet_root::InterfacesRequestStream) -> St,
2479    >(
2480        args: impl IntoIterator<Item = RequestArgs>,
2481        root_handler: F,
2482        initial_state: InitialState,
2483    ) -> TestRequestResult {
2484        let scope = fasync::Scope::new();
2485        let result = {
2486            let InitialState { eth_interface_online } = initial_state;
2487
2488            let (mut expected_sink, expected_client, async_work_drain_task) =
2489                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2490                    crate::client::testutil::CLIENT_ID_1,
2491                    std::iter::empty(),
2492                );
2493            let _join_handle = scope.spawn(async_work_drain_task);
2494            let (mut other_sink, other_client, async_work_drain_task) =
2495                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2496                    crate::client::testutil::CLIENT_ID_2,
2497                    std::iter::empty(),
2498                );
2499            let _join_handle = scope.spawn(async_work_drain_task);
2500            let Setup {
2501                event_loop_fut,
2502                mut watcher_stream,
2503                request_sink,
2504                interfaces_request_stream,
2505                interfaces_handler_sink: _,
2506                _async_work_sink: _,
2507            } = setup_with_route_clients({
2508                let route_clients = ClientTable::default();
2509                route_clients.add_client(expected_client.clone());
2510                route_clients.add_client(other_client);
2511                route_clients
2512            });
2513            let event_loop_fut = event_loop_fut.fuse();
2514            let mut event_loop_fut = pin!(event_loop_fut);
2515
2516            let watcher_stream_fut = respond_to_watcher(
2517                watcher_stream.by_ref(),
2518                [
2519                    fnet_interfaces::Event::Existing(fnet_interfaces::Properties {
2520                        id: Some(LO_INTERFACE_ID),
2521                        name: Some(LO_NAME.to_string()),
2522                        port_class: Some(LOOPBACK.into()),
2523                        online: Some(true),
2524                        addresses: Some(vec![test_addr(TEST_V6_ADDR), test_addr(TEST_V4_ADDR)]),
2525                        has_default_ipv4_route: Some(false),
2526                        has_default_ipv6_route: Some(false),
2527                        ..Default::default()
2528                    }),
2529                    fnet_interfaces::Event::Existing(fnet_interfaces::Properties {
2530                        id: Some(ETH_INTERFACE_ID),
2531                        name: Some(ETH_NAME.to_string()),
2532                        port_class: Some(ETHERNET.into()),
2533                        online: Some(eth_interface_online),
2534                        addresses: Some(vec![test_addr(TEST_V4_ADDR), test_addr(TEST_V6_ADDR)]),
2535                        has_default_ipv4_route: Some(false),
2536                        has_default_ipv6_route: Some(false),
2537                        ..Default::default()
2538                    }),
2539                    fnet_interfaces::Event::Idle(fnet_interfaces::Empty),
2540                ],
2541            );
2542            futures::select_biased! {
2543                err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2544                () = watcher_stream_fut.fuse() => {},
2545            }
2546            assert_eq!(&expected_sink.take_messages()[..], &[]);
2547            assert_eq!(&other_sink.take_messages()[..], &[]);
2548
2549            let expected_client = &expected_client;
2550            let fut = futures::stream::iter(args).fold(
2551                (Vec::new(), request_sink),
2552                |(mut results, mut request_sink), args| async move {
2553                    let (completer, waiter) = oneshot::channel();
2554                    request_sink
2555                        .send(crate::route_eventloop::UnifiedRequest::InterfacesRequest(Request {
2556                            args,
2557                            sequence_number: TEST_SEQUENCE_NUMBER,
2558                            client: expected_client.clone(),
2559                            completer,
2560                        }))
2561                        .await
2562                        .unwrap();
2563                    results.push(waiter.await.unwrap());
2564                    (results, request_sink)
2565                },
2566            );
2567            // Handle root API requests then feed the returned
2568            // `fuchsia.net.interfaces/Event`s to the watcher.
2569            let watcher_fut = root_handler(interfaces_request_stream).map(Ok).forward(
2570                futures::sink::unfold(watcher_stream.by_ref(), |st, event| async {
2571                    respond_to_watcher(st.by_ref(), [event]).await;
2572                    Ok::<_, !>(st)
2573                }),
2574            );
2575            let waiter_results = futures::select_biased! {
2576                res = futures::future::join(watcher_fut, event_loop_fut) => {
2577                    unreachable!("eventloop/watcher should not return: {res:?}")
2578                },
2579                (results, _request_sink) = fut.fuse() => results
2580            };
2581            assert_eq!(&other_sink.take_messages()[..], &[]);
2582
2583            TestRequestResult { messages: expected_sink.take_messages(), waiter_results }
2584        };
2585        scope.join().await;
2586        result
2587    }
2588
2589    #[test_case(
2590        GetLinkArgs::Dump,
2591        &[LO_INTERFACE_ID, ETH_INTERFACE_ID],
2592        Ok(()); "dump")]
2593    #[test_case(
2594        GetLinkArgs::Get(LinkSpecifier::Index(
2595            NonZeroU32::new(LO_INTERFACE_ID.try_into().unwrap()).unwrap())),
2596        &[LO_INTERFACE_ID],
2597        Ok(()); "id")]
2598    #[test_case(
2599        GetLinkArgs::Get(LinkSpecifier::Index(
2600            NonZeroU32::new(WLAN_INTERFACE_ID.try_into().unwrap()).unwrap())),
2601        &[],
2602        Err(RequestError::UnrecognizedInterface); "id_not_found")]
2603    #[test_case(
2604        GetLinkArgs::Get(LinkSpecifier::Name(LO_NAME.to_string())),
2605        &[LO_INTERFACE_ID],
2606        Ok(()); "name")]
2607    #[test_case(
2608        GetLinkArgs::Get(LinkSpecifier::Name(WLAN_NAME.to_string())),
2609        &[],
2610        Err(RequestError::UnrecognizedInterface); "name_not_found")]
2611    #[fuchsia::test(logging = false)]
2612    async fn test_get_link(
2613        args: GetLinkArgs,
2614        expected_new_links: &[u64],
2615        expected_result: Result<(), RequestError>,
2616    ) {
2617        let is_dump = match args {
2618            GetLinkArgs::Dump => true,
2619            GetLinkArgs::Get(_) => false,
2620        };
2621        // Conversion to u16 is safe because 1 <= 65535
2622        let arphrd_ether_u16: u16 = ARPHRD_ETHER as u16;
2623        // Conversion to u16 is safe because 772 <= 65535
2624        let arphrd_loopback_u16: u16 = ARPHRD_LOOPBACK as u16;
2625        let expected_messages = expected_new_links
2626            .iter()
2627            .map(|link_id| {
2628                let msg = match *link_id {
2629                    LO_INTERFACE_ID => create_netlink_link_message(
2630                        LO_INTERFACE_ID,
2631                        arphrd_loopback_u16,
2632                        ONLINE_IF_FLAGS | net_device_flags_IFF_LOOPBACK,
2633                        create_nlas(LO_NAME.to_string(), arphrd_loopback_u16, true, &LO_MAC),
2634                    ),
2635                    ETH_INTERFACE_ID => create_netlink_link_message(
2636                        ETH_INTERFACE_ID,
2637                        arphrd_ether_u16,
2638                        0,
2639                        create_nlas(ETH_NAME.to_string(), arphrd_ether_u16, false, &ETH_MAC),
2640                    ),
2641                    _ => unreachable!("GetLink should only be tested with loopback and ethernet"),
2642                };
2643                SentMessage::unicast(msg.into_rtnl_new_link(TEST_SEQUENCE_NUMBER, is_dump))
2644            })
2645            .collect();
2646
2647        assert_eq!(
2648            test_request(
2649                [RequestArgs::Link(LinkRequestArgs::Get(args))],
2650                expect_only_get_mac_root_requests,
2651            )
2652            .await,
2653            TestRequestResult {
2654                messages: expected_messages,
2655                waiter_results: vec![expected_result],
2656            },
2657        )
2658    }
2659
2660    fn handle_get_admin_for_eth_or_panic(
2661        req: Result<fnet_root::InterfacesRequest, fidl::Error>,
2662    ) -> impl Future<Output = Option<fnet_interfaces_admin::ControlRequestStream>> {
2663        futures::future::ready(match req.unwrap() {
2664            fnet_root::InterfacesRequest::GetAdmin { id, control, control_handle: _ } => {
2665                pretty_assertions::assert_eq!(id, ETH_INTERFACE_ID);
2666                Some(control.into_stream())
2667            }
2668            req => {
2669                handle_get_mac_root_request_or_panic(req);
2670                None
2671            }
2672        })
2673    }
2674
2675    /// Returns a `FnOnce` suitable for use with [`test_request`].
2676    ///
2677    /// The closure serves a single `GetAdmin` request for the Ethernet
2678    /// interface, and handles all subsequent
2679    /// [`fnet_interfaces_admin::ControlRequest`] by calling the provided
2680    /// handler.
2681    // TODO(https://github.com/rust-lang/rust/issues/99697): Remove the
2682    // `Pin<Box<dyn ...>>` from the return type once Rust supports
2683    // `impl Fn() -> impl <SomeTrait>` style declarations.
2684    fn expect_get_admin_with_handler<
2685        I: IntoIterator<Item = fnet_interfaces::Event> + 'static,
2686        H: FnMut(fnet_interfaces_admin::ControlRequest) -> I + 'static,
2687    >(
2688        admin_handler: H,
2689    ) -> impl FnOnce(
2690        fnet_root::InterfacesRequestStream,
2691    ) -> Pin<Box<dyn Stream<Item = fnet_interfaces::Event>>> {
2692        move |interfaces_request_stream: fnet_root::InterfacesRequestStream| {
2693            Box::pin(
2694                interfaces_request_stream
2695                    .filter_map(|req| handle_get_admin_for_eth_or_panic(req))
2696                    .into_future()
2697                    // This module's implementation is expected to only acquire one
2698                    // admin control handle per interface, so drop the remaining
2699                    // stream of admin control request streams.
2700                    .map(|(admin_control_stream, _stream_of_admin_control_streams)| {
2701                        admin_control_stream.unwrap()
2702                    })
2703                    .flatten_stream()
2704                    // Handle each Control request with the provided handler.
2705                    // `scan` transfers ownership of `admin_handle`, which
2706                    // circumvents some borrow chcker issues we would encounter
2707                    // with `map`.
2708                    .scan(admin_handler, |admin_handler, req| {
2709                        futures::future::ready(Some(futures::stream::iter(admin_handler(
2710                            req.unwrap(),
2711                        ))))
2712                    })
2713                    .flatten(),
2714            )
2715        }
2716    }
2717
2718    #[test_case(
2719        InitialState { eth_interface_online: false },
2720        SetLinkArgs{
2721            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2722            enable: None,
2723        },
2724        Ok(true),
2725        Ok(()); "no_change")]
2726    #[test_case(
2727        InitialState { eth_interface_online: false },
2728        SetLinkArgs{
2729            link: LinkSpecifier::Name(WLAN_NAME.to_string()),
2730            enable: None,
2731        },
2732        Ok(true),
2733        Err(RequestError::UnrecognizedInterface); "no_change_name_not_found")]
2734    #[test_case(
2735        InitialState { eth_interface_online: false },
2736        SetLinkArgs {
2737            link: LinkSpecifier::Index(
2738                NonZeroU32::new(WLAN_INTERFACE_ID.try_into().unwrap()).unwrap()),
2739            enable: None,
2740        },
2741        Ok(true),
2742        Err(RequestError::UnrecognizedInterface); "no_change_id_not_found")]
2743    #[test_case(
2744        InitialState { eth_interface_online: false },
2745        SetLinkArgs{
2746            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2747            enable: Some(true),
2748        },
2749        Ok(false),
2750        Ok(()); "enable_no_op_succeeds")]
2751    #[test_case(
2752        InitialState { eth_interface_online: false },
2753        SetLinkArgs{
2754            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2755            enable: Some(true),
2756        },
2757        Ok(true),
2758        Ok(()); "enable_newly_succeeds")]
2759    #[test_case(
2760        InitialState { eth_interface_online: false },
2761        SetLinkArgs{
2762            link: LinkSpecifier::Name(WLAN_NAME.to_string()),
2763            enable: Some(true),
2764        },
2765        Ok(true),
2766        Err(RequestError::UnrecognizedInterface); "enable_not_found")]
2767    #[test_case(
2768        InitialState { eth_interface_online: false },
2769        SetLinkArgs{
2770            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2771            enable: Some(true),
2772        },
2773        Err(()),
2774        Err(RequestError::Unknown); "enable_fails")]
2775    #[test_case(
2776        InitialState { eth_interface_online: false },
2777        SetLinkArgs{
2778            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2779            enable: Some(false),
2780        },
2781        Ok(false),
2782        Ok(()); "disable_no_op_succeeds")]
2783    #[test_case(
2784        InitialState { eth_interface_online: true },
2785        SetLinkArgs{
2786            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2787            enable: Some(false),
2788        },
2789        Ok(true),
2790        Ok(()); "disable_newly_succeeds")]
2791    #[test_case(
2792        InitialState { eth_interface_online: false },
2793        SetLinkArgs{
2794            link: LinkSpecifier::Name(WLAN_NAME.to_string()),
2795            enable: Some(false),
2796        },
2797        Ok(true),
2798        Err(RequestError::UnrecognizedInterface); "disable_not_found")]
2799    #[test_case(
2800        InitialState { eth_interface_online: false },
2801        SetLinkArgs{
2802            link: LinkSpecifier::Name(ETH_NAME.to_string()),
2803            enable: Some(false),
2804        },
2805        Err(()),
2806        Err(RequestError::Unknown); "disable_fails")]
2807    #[fuchsia::test(logging = false)]
2808    async fn test_set_link(
2809        initial_state: InitialState,
2810        args: SetLinkArgs,
2811        control_response: Result<bool, ()>,
2812        expected_result: Result<(), RequestError>,
2813    ) {
2814        let SetLinkArgs { link: _, enable } = args.clone();
2815        let request = RequestArgs::Link(LinkRequestArgs::Set(args));
2816
2817        let control_response_clone = control_response.clone();
2818        let handle_enable =
2819            move |req: fnet_interfaces_admin::ControlRequest| -> Option<fnet_interfaces::Event> {
2820                let responder = match req {
2821                    fnet_interfaces_admin::ControlRequest::Enable { responder } => responder,
2822                    _ => panic!("unexpected ControlRequest received"),
2823                };
2824                match control_response {
2825                    Err(()) => {
2826                        responder
2827                            .send(Err(fnet_interfaces_admin::ControlEnableError::unknown()))
2828                            .expect("should send response");
2829                        None
2830                    }
2831                    Ok(newly_enabled) => {
2832                        responder.send(Ok(newly_enabled)).expect("should send response");
2833                        newly_enabled.then_some(fnet_interfaces::Event::Changed(
2834                            fnet_interfaces::Properties {
2835                                id: Some(ETH_INTERFACE_ID),
2836                                online: Some(true),
2837                                ..fnet_interfaces::Properties::default()
2838                            },
2839                        ))
2840                    }
2841                }
2842            };
2843        let handle_disable =
2844            move |req: fnet_interfaces_admin::ControlRequest| -> Option<fnet_interfaces::Event> {
2845                let responder = match req {
2846                    fnet_interfaces_admin::ControlRequest::Disable { responder } => responder,
2847                    _ => panic!("unexpected ControlRequest received"),
2848                };
2849                match control_response_clone {
2850                    Err(()) => {
2851                        responder
2852                            .send(Err(fnet_interfaces_admin::ControlDisableError::unknown()))
2853                            .expect("should send response");
2854                        None
2855                    }
2856                    Ok(newly_disabled) => {
2857                        responder.send(Ok(newly_disabled)).expect("should send response");
2858                        newly_disabled.then_some(fnet_interfaces::Event::Changed(
2859                            fnet_interfaces::Properties {
2860                                id: Some(ETH_INTERFACE_ID),
2861                                online: Some(false),
2862                                ..fnet_interfaces::Properties::default()
2863                            },
2864                        ))
2865                    }
2866                }
2867            };
2868
2869        let test_result = match enable {
2870            None => {
2871                test_request_with_initial_state(
2872                    [request],
2873                    expect_only_get_mac_root_requests,
2874                    initial_state,
2875                )
2876                .await
2877            }
2878            Some(true) => {
2879                test_request_with_initial_state(
2880                    [request],
2881                    expect_get_admin_with_handler(handle_enable),
2882                    initial_state,
2883                )
2884                .await
2885            }
2886            Some(false) => {
2887                test_request_with_initial_state(
2888                    [request],
2889                    expect_get_admin_with_handler(handle_disable),
2890                    initial_state,
2891                )
2892                .await
2893            }
2894        };
2895
2896        assert_eq!(
2897            test_result,
2898            TestRequestResult {
2899                // SetLink requests never result in messages. Acks/errors
2900                // are handled by the caller.
2901                messages: vec![],
2902                waiter_results: vec![expected_result],
2903            },
2904        )
2905    }
2906
2907    #[test_case(Some(IpVersion::V4); "v4")]
2908    #[test_case(Some(IpVersion::V6); "v6")]
2909    #[test_case(None; "all")]
2910    #[fuchsia::test(logging = false)]
2911    async fn test_get_addr(ip_version_filter: Option<IpVersion>) {
2912        pretty_assertions::assert_eq!(
2913            test_request(
2914                [RequestArgs::Address(AddressRequestArgs::Get(GetAddressArgs::Dump {
2915                    ip_version_filter
2916                }))],
2917                expect_only_get_mac_root_requests,
2918            )
2919            .await,
2920            TestRequestResult {
2921                messages: [(LO_INTERFACE_ID, LO_NAME), (ETH_INTERFACE_ID, ETH_NAME)]
2922                    .into_iter()
2923                    .map(|(id, name)| {
2924                        [TEST_V4_ADDR, TEST_V6_ADDR]
2925                            .into_iter()
2926                            .filter(|fnet::Subnet { addr, prefix_len: _ }| {
2927                                ip_version_filter.map_or(true, |ip_version| {
2928                                    ip_version.eq(&match addr {
2929                                        fnet::IpAddress::Ipv4(_) => IpVersion::V4,
2930                                        fnet::IpAddress::Ipv6(_) => IpVersion::V6,
2931                                    })
2932                                })
2933                            })
2934                            .map(move |addr| {
2935                                SentMessage::unicast(
2936                                    create_address_message(
2937                                        id.try_into().unwrap(),
2938                                        addr,
2939                                        name.to_string(),
2940                                        IFA_F_PERMANENT,
2941                                    )
2942                                    .to_rtnl_new_addr(TEST_SEQUENCE_NUMBER, true),
2943                                )
2944                            })
2945                    })
2946                    .flatten()
2947                    .collect(),
2948                waiter_results: vec![Ok(())],
2949            },
2950        );
2951    }
2952
2953    /// Tests RTM_NEWADDR and RTM_DEL_ADDR when the interface is removed,
2954    /// indicated by the closure of the admin Control's server-end.
2955    #[test_case(
2956        test_addr_subnet_v4(),
2957        None,
2958        true; "v4_no_terminal_new")]
2959    #[test_case(
2960        test_addr_subnet_v6(),
2961        None,
2962        true; "v6_no_terminal_new")]
2963    #[test_case(
2964        test_addr_subnet_v4(),
2965        Some(InterfaceRemovedReason::PortClosed),
2966        true; "v4_port_closed_terminal_new")]
2967    #[test_case(
2968        test_addr_subnet_v6(),
2969        Some(InterfaceRemovedReason::PortClosed),
2970        true; "v6_port_closed_terminal_new")]
2971    #[test_case(
2972        test_addr_subnet_v4(),
2973        Some(InterfaceRemovedReason::User),
2974        true; "v4_user_terminal_new")]
2975    #[test_case(
2976        test_addr_subnet_v6(),
2977        Some(InterfaceRemovedReason::User),
2978        true; "v6_user_terminal_new")]
2979    #[test_case(
2980        test_addr_subnet_v4(),
2981        None,
2982        false; "v4_no_terminal_del")]
2983    #[test_case(
2984        test_addr_subnet_v6(),
2985        None,
2986        false; "v6_no_terminal_del")]
2987    #[test_case(
2988        test_addr_subnet_v4(),
2989        Some(InterfaceRemovedReason::PortClosed),
2990        false; "v4_port_closed_terminal_del")]
2991    #[test_case(
2992        test_addr_subnet_v6(),
2993        Some(InterfaceRemovedReason::PortClosed),
2994        false; "v6_port_closed_terminal_del")]
2995    #[test_case(
2996        test_addr_subnet_v4(),
2997        Some(InterfaceRemovedReason::User),
2998        false; "v4_user_terminal_del")]
2999    #[test_case(
3000        test_addr_subnet_v6(),
3001        Some(InterfaceRemovedReason::User),
3002        false; "v6_user_terminal_del")]
3003    #[fuchsia::test(logging = false)]
3004    async fn test_new_del_addr_interface_removed(
3005        address: AddrSubnetEither,
3006        removal_reason: Option<InterfaceRemovedReason>,
3007        is_new: bool,
3008    ) {
3009        let interface_id = NonZeroU32::new(LO_INTERFACE_ID.try_into().unwrap()).unwrap();
3010        let address_and_interface_id = AddressAndInterfaceArgs { address, interface_id };
3011        pretty_assertions::assert_eq!(
3012            test_request(
3013                [if is_new {
3014                    RequestArgs::Address(AddressRequestArgs::New(NewAddressArgs {
3015                        address_and_interface_id,
3016                        add_subnet_route: false,
3017                        replace: false,
3018                    }))
3019                } else {
3020                    RequestArgs::Address(AddressRequestArgs::Del(DelAddressArgs {
3021                        address_and_interface_id,
3022                    }))
3023                }],
3024                |interfaces_request_stream| futures::stream::unfold(
3025                    interfaces_request_stream,
3026                    |interfaces_request_stream| async move {
3027                        interfaces_request_stream
3028                            .for_each(|req| {
3029                                futures::future::ready(match req.unwrap() {
3030                                    fnet_root::InterfacesRequest::GetAdmin {
3031                                        id,
3032                                        control,
3033                                        control_handle: _,
3034                                    } => {
3035                                        pretty_assertions::assert_eq!(id, LO_INTERFACE_ID);
3036                                        let control = control.into_stream();
3037                                        let control = control.control_handle();
3038                                        if let Some(reason) = removal_reason {
3039                                            control.send_on_interface_removed(reason).unwrap()
3040                                        }
3041                                        control.shutdown();
3042                                    }
3043                                    req => handle_get_mac_root_request_or_panic(req),
3044                                })
3045                            })
3046                            .await;
3047
3048                        unreachable!("interfaces request stream should not end")
3049                    },
3050                ),
3051            )
3052            .await,
3053            TestRequestResult {
3054                messages: Vec::new(),
3055                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
3056            },
3057        )
3058    }
3059
3060    enum AddressRequestKind {
3061        New { add_subnet_route: bool, replace: bool },
3062        Del,
3063    }
3064
3065    /// Test that a request for an interface the eventloop does not recognize
3066    /// fails with an unrecognized interface error.
3067    #[test_case(
3068        add_test_addr_subnet_v4(),
3069        AddressRequestKind::New { add_subnet_route: false, replace: false }; "v4_new")]
3070    #[test_case(
3071        add_test_addr_subnet_v6(),
3072        AddressRequestKind::New { add_subnet_route: false, replace: false }; "v6_new")]
3073    #[test_case(add_test_addr_subnet_v4(), AddressRequestKind::Del; "v4_del")]
3074    #[test_case(add_test_addr_subnet_v6(), AddressRequestKind::Del; "v6_del")]
3075    #[fuchsia::test(logging = false)]
3076    async fn test_unknown_interface_request(address: AddrSubnetEither, kind: AddressRequestKind) {
3077        let interface_id = NonZeroU32::new(WLAN_INTERFACE_ID.try_into().unwrap()).unwrap();
3078        let address_and_interface_id = AddressAndInterfaceArgs { address, interface_id };
3079        pretty_assertions::assert_eq!(
3080            test_request(
3081                [match kind {
3082                    AddressRequestKind::New { add_subnet_route, replace } => {
3083                        RequestArgs::Address(AddressRequestArgs::New(NewAddressArgs {
3084                            address_and_interface_id,
3085                            add_subnet_route,
3086                            replace,
3087                        }))
3088                    }
3089                    AddressRequestKind::Del => {
3090                        RequestArgs::Address(AddressRequestArgs::Del(DelAddressArgs {
3091                            address_and_interface_id,
3092                        }))
3093                    }
3094                }],
3095                expect_only_get_mac_root_requests,
3096            )
3097            .await,
3098            TestRequestResult {
3099                messages: Vec::new(),
3100                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
3101            },
3102        )
3103    }
3104
3105    struct TestInterfaceRequestCase<F> {
3106        address: AddrSubnetEither,
3107        kind: AddressRequestKind,
3108        control_request_handler: F,
3109    }
3110
3111    impl<F> TestInterfaceRequestCase<F> {
3112        fn into_request_args_and_handler(self, interface_id: NonZeroU32) -> (RequestArgs, F) {
3113            let Self { address, kind, control_request_handler } = self;
3114            let address_and_interface_id = AddressAndInterfaceArgs { address, interface_id };
3115            let args = match kind {
3116                AddressRequestKind::New { add_subnet_route, replace } => {
3117                    RequestArgs::Address(AddressRequestArgs::New(NewAddressArgs {
3118                        address_and_interface_id,
3119                        add_subnet_route,
3120                        replace,
3121                    }))
3122                }
3123                AddressRequestKind::Del => {
3124                    RequestArgs::Address(AddressRequestArgs::Del(DelAddressArgs {
3125                        address_and_interface_id,
3126                    }))
3127                }
3128            };
3129
3130            (args, control_request_handler)
3131        }
3132    }
3133
3134    /// A test helper that calls the (up to two) test cases' callback with a
3135    /// [`fnet_interfaces_admin::ControlRequest`] as they arrive.
3136    ///
3137    /// This implementation makes sure that the the control handle for the
3138    /// interface is only requested once.
3139    async fn test_maybe_two_interface_requests_on_single_control<
3140        St1: Stream<Item = fnet_interfaces::Event>,
3141        F1: FnMut(fnet_interfaces_admin::ControlRequest) -> St1,
3142        St2: Stream<Item = fnet_interfaces::Event>,
3143        F2: FnMut(fnet_interfaces_admin::ControlRequest) -> St2,
3144    >(
3145        case1: TestInterfaceRequestCase<F1>,
3146        case2: Option<TestInterfaceRequestCase<F2>>,
3147    ) -> TestRequestResult {
3148        let interface_id = NonZeroU32::new(ETH_INTERFACE_ID.try_into().unwrap()).unwrap();
3149        let (args1, mut control_request_handler1) =
3150            case1.into_request_args_and_handler(interface_id);
3151
3152        let (args2, control_request_handler2) = if let Some(case) = case2 {
3153            let (args, control_request_handler) = case.into_request_args_and_handler(interface_id);
3154            (Some(args), Some(control_request_handler))
3155        } else {
3156            (None, None)
3157        };
3158
3159        test_request([args1].into_iter().chain(args2), |interfaces_request_stream| {
3160            interfaces_request_stream
3161                .filter_map(|req| handle_get_admin_for_eth_or_panic(req))
3162                .into_future()
3163                // This method supports tests that want to make sure that the
3164                // admin control is only requested once so we drop the remaining
3165                // stream of admin control request streams.
3166                .map(|(admin_control_stream, _stream_of_admin_control_streams)| {
3167                    admin_control_stream.unwrap()
3168                })
3169                .flatten_stream()
3170                .into_future()
3171                .map(|(admin_control_req, admin_control_stream)| {
3172                    control_request_handler1(admin_control_req.unwrap().unwrap()).chain(
3173                        futures::stream::iter(control_request_handler2.map(
3174                            |mut control_request_handler2| {
3175                                admin_control_stream
3176                                    .into_future()
3177                                    .map(move |(admin_control_req, _admin_control_stream)| {
3178                                        control_request_handler2(
3179                                            admin_control_req.unwrap().unwrap(),
3180                                        )
3181                                    })
3182                                    .flatten_stream()
3183                            },
3184                        ))
3185                        .flatten(),
3186                    )
3187                })
3188                .flatten_stream()
3189        })
3190        .await
3191    }
3192
3193    /// A test helper that calls the callback with a
3194    /// [`fnet_interfaces_admin::ControlRequest`] as they arrive.
3195    async fn test_interface_request<
3196        St: Stream<Item = fnet_interfaces::Event>,
3197        F: FnMut(fnet_interfaces_admin::ControlRequest) -> St,
3198    >(
3199        case: TestInterfaceRequestCase<F>,
3200    ) -> TestRequestResult {
3201        test_maybe_two_interface_requests_on_single_control(
3202            case,
3203            None::<TestInterfaceRequestCase<fn(_) -> futures::stream::Pending<_>>>,
3204        )
3205        .await
3206    }
3207
3208    /// An RTM_NEWADDR test helper that calls the callback with a stream of ASP
3209    /// requests.
3210    async fn test_new_addr_asp_helper<St, F>(
3211        address: AddrSubnetEither,
3212        add_subnet_route: bool,
3213        replace: bool,
3214        asp_handler: F,
3215    ) -> TestRequestResult
3216    where
3217        St: Stream<Item = fnet_interfaces::Event>,
3218        F: Fn(fnet_interfaces_admin::AddressStateProviderRequestStream) -> St,
3219    {
3220        test_interface_request(TestInterfaceRequestCase {
3221            address,
3222            kind: AddressRequestKind::New { add_subnet_route, replace },
3223            control_request_handler: |req| match req {
3224                fnet_interfaces_admin::ControlRequest::AddAddress {
3225                    address: got_address,
3226                    parameters,
3227                    address_state_provider,
3228                    control_handle: _,
3229                } => {
3230                    pretty_assertions::assert_eq!(got_address, address.into_ext());
3231                    pretty_assertions::assert_eq!(
3232                        parameters,
3233                        fnet_interfaces_admin::AddressParameters {
3234                            add_subnet_route: Some(add_subnet_route),
3235                            ..fnet_interfaces_admin::AddressParameters::default()
3236                        },
3237                    );
3238                    asp_handler(address_state_provider.into_stream())
3239                }
3240                req => panic!("unexpected request {req:?}"),
3241            },
3242        })
3243        .await
3244    }
3245
3246    /// Tests RTM_NEWADDR when the ASP is dropped immediately (doesn't handle
3247    /// any request).
3248    #[test_case(test_addr_subnet_v4(); "v4")]
3249    #[test_case(test_addr_subnet_v6(); "v6")]
3250    #[fuchsia::test(logging = false)]
3251    async fn test_new_addr_drop_asp_immediately(address: AddrSubnetEither) {
3252        pretty_assertions::assert_eq!(
3253            test_new_addr_asp_helper(address, false, false, |_asp_request_stream| {
3254                futures::stream::empty()
3255            })
3256            .await,
3257            TestRequestResult {
3258                messages: Vec::new(),
3259                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
3260            },
3261        )
3262    }
3263
3264    /// RTM_NEWADDR test helper that exercises the ASP being closed with a
3265    /// terminal event.
3266    async fn test_new_addr_failed_helper(
3267        address: AddrSubnetEither,
3268        reason: AddressRemovalReason,
3269    ) -> TestRequestResult {
3270        test_new_addr_asp_helper(address, true, false, |asp_request_stream| {
3271            asp_request_stream.control_handle().send_on_address_removed(reason).unwrap();
3272            futures::stream::empty()
3273        })
3274        .await
3275    }
3276
3277    /// Tests RTM_NEWADDR when the ASP is closed with an unexpected terminal
3278    /// event.
3279    #[test_case(
3280        test_addr_subnet_v4(),
3281        AddressRemovalReason::DadFailed; "v4_dad_failed")]
3282    #[test_case(
3283        test_addr_subnet_v6(),
3284        AddressRemovalReason::DadFailed; "v6_dad_failed")]
3285    #[test_case(
3286        test_addr_subnet_v4(),
3287        AddressRemovalReason::InterfaceRemoved; "v4_interface_removed")]
3288    #[test_case(
3289        test_addr_subnet_v6(),
3290        AddressRemovalReason::InterfaceRemoved; "v6_interface_removed")]
3291    #[test_case(
3292        test_addr_subnet_v4(),
3293        AddressRemovalReason::UserRemoved; "v4_user_removed")]
3294    #[test_case(
3295        test_addr_subnet_v6(),
3296        AddressRemovalReason::UserRemoved; "v6_user_removed")]
3297    #[should_panic(expected = "expected netstack to send initial state before removing")]
3298    #[fuchsia::test(logging = false)]
3299    async fn test_new_addr_failed_unexpected_reason(
3300        address: AddrSubnetEither,
3301        reason: AddressRemovalReason,
3302    ) {
3303        let _: TestRequestResult = test_new_addr_failed_helper(address, reason).await;
3304    }
3305
3306    /// Tests RTM_NEWADDR when the ASP is gracefully closed with a terminal event.
3307    #[test_case(
3308        test_addr_subnet_v4(),
3309        AddressRemovalReason::Invalid,
3310        RequestError::InvalidRequest; "v4_invalid")]
3311    #[test_case(
3312        test_addr_subnet_v6(),
3313        AddressRemovalReason::Invalid,
3314        RequestError::InvalidRequest; "v6_invalid")]
3315    #[test_case(
3316        test_addr_subnet_v4(),
3317        AddressRemovalReason::AlreadyAssigned,
3318        RequestError::AlreadyExists; "v4_exists")]
3319    #[test_case(
3320        test_addr_subnet_v6(),
3321        AddressRemovalReason::AlreadyAssigned,
3322        RequestError::AlreadyExists; "v6_exists")]
3323    #[fuchsia::test(logging = false)]
3324    async fn test_new_addr_failed(
3325        address: AddrSubnetEither,
3326        reason: AddressRemovalReason,
3327        expected_error: RequestError,
3328    ) {
3329        pretty_assertions::assert_eq!(
3330            test_new_addr_failed_helper(address, reason).await,
3331            TestRequestResult { messages: Vec::new(), waiter_results: vec![Err(expected_error)] },
3332        )
3333    }
3334
3335    /// Tests RTM_NEWADDR when replace is true and address is already assigned.
3336    #[test_case(test_addr_subnet_v4(); "v4")]
3337    #[test_case(test_addr_subnet_v6(); "v6")]
3338    #[fuchsia::test(logging = false)]
3339    async fn test_new_addr_replace_already_assigned(address: AddrSubnetEither) {
3340        pretty_assertions::assert_eq!(
3341            test_new_addr_asp_helper(
3342                address,
3343                /* add_subnet_route */ true,
3344                /* replace */ true,
3345                |asp_request_stream| {
3346                    asp_request_stream
3347                        .control_handle()
3348                        .send_on_address_removed(AddressRemovalReason::AlreadyAssigned)
3349                        .unwrap();
3350                    futures::stream::empty()
3351                },
3352            )
3353            .await,
3354            TestRequestResult { messages: Vec::new(), waiter_results: vec![Ok(())] },
3355        )
3356    }
3357
3358    /// An RTM_NEWADDR test helper that calls the callback with a stream of ASP
3359    /// requests after the Detach request is handled.
3360    async fn test_new_addr_asp_detach_handled_helper<
3361        St: Stream<Item = fnet_interfaces::Event>,
3362        F: Fn(fnet_interfaces_admin::AddressStateProviderRequestStream) -> St,
3363    >(
3364        address: AddrSubnetEither,
3365        add_subnet_route: bool,
3366        asp_handler: F,
3367    ) -> TestRequestResult {
3368        test_new_addr_asp_helper(address, add_subnet_route, false, |asp_request_stream| {
3369            asp_request_stream
3370                .into_future()
3371                .map(|(asp_request, asp_request_stream)| {
3372                    let _: fnet_interfaces_admin::AddressStateProviderControlHandle = asp_request
3373                        .expect("eventloop uses ASP before dropping")
3374                        .expect("unexpected error while waiting for Detach request")
3375                        .into_detach()
3376                        .expect("eventloop makes detach request immediately");
3377
3378                    asp_handler(asp_request_stream)
3379                })
3380                .flatten_stream()
3381        })
3382        .await
3383    }
3384
3385    /// Test RTM_NEWADDR when the ASP is dropped immediately after handling the
3386    /// Detach request (no assignment state update or terminal event).
3387    #[test_case(test_addr_subnet_v4(); "v4")]
3388    #[test_case(test_addr_subnet_v6(); "v6")]
3389    #[fuchsia::test(logging = false)]
3390    async fn test_new_addr_drop_asp_after_detach(address: AddrSubnetEither) {
3391        pretty_assertions::assert_eq!(
3392            test_new_addr_asp_detach_handled_helper(address, false, |_asp_stream| {
3393                futures::stream::empty()
3394            })
3395            .await,
3396            TestRequestResult {
3397                messages: Vec::new(),
3398                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
3399            },
3400        )
3401    }
3402
3403    /// Test RTM_NEWADDR when the ASP yields an assignment state update.
3404    #[test_case(add_test_addr_subnet_v4(); "v4")]
3405    #[test_case(add_test_addr_subnet_v6(); "v6")]
3406    #[fuchsia::test(logging = false)]
3407    async fn test_new_addr_with_address_added_event(address: AddrSubnetEither) {
3408        pretty_assertions::assert_eq!(
3409            test_new_addr_asp_detach_handled_helper(address, true, |asp_request_stream| {
3410                asp_request_stream
3411                    .control_handle()
3412                    .send_on_address_added()
3413                    .expect("send address added");
3414
3415                // Send an update with the added address to complete the
3416                // request.
3417                futures::stream::iter([fnet_interfaces::Event::Changed(
3418                    fnet_interfaces::Properties {
3419                        id: Some(ETH_INTERFACE_ID.try_into().unwrap()),
3420                        addresses: Some(vec![test_addr(address.into_ext())]),
3421                        ..fnet_interfaces::Properties::default()
3422                    },
3423                )])
3424            })
3425            .await,
3426            TestRequestResult { messages: Vec::new(), waiter_results: vec![Ok(())] },
3427        )
3428    }
3429
3430    /// Test RTM_DELADDR when the interface is closed with an unexpected reaosn.
3431    #[test_case(
3432        test_addr_subnet_v4(),
3433        InterfaceRemovedReason::DuplicateName; "v4_duplicate_name")]
3434    #[test_case(
3435        test_addr_subnet_v6(),
3436        InterfaceRemovedReason::DuplicateName; "v6_duplicate_name")]
3437    #[test_case(
3438        test_addr_subnet_v4(),
3439        InterfaceRemovedReason::PortAlreadyBound; "v4_port_already_bound")]
3440    #[test_case(
3441        test_addr_subnet_v6(),
3442        InterfaceRemovedReason::PortAlreadyBound; "v6_port_already_bound")]
3443    #[test_case(
3444        test_addr_subnet_v4(),
3445        InterfaceRemovedReason::BadPort; "v4_bad_port")]
3446    #[test_case(
3447        test_addr_subnet_v6(),
3448        InterfaceRemovedReason::BadPort; "v6_bad_port")]
3449    #[should_panic(expected = "unexpected interface removed reason")]
3450    #[fuchsia::test(logging = false)]
3451    async fn test_del_addr_interface_closed_unexpected_reason(
3452        address: AddrSubnetEither,
3453        removal_reason: InterfaceRemovedReason,
3454    ) {
3455        let _: TestRequestResult = test_interface_request(TestInterfaceRequestCase {
3456            address,
3457            kind: AddressRequestKind::Del,
3458            control_request_handler: |req| match req {
3459                fnet_interfaces_admin::ControlRequest::RemoveAddress {
3460                    address: got_address,
3461                    responder,
3462                } => {
3463                    pretty_assertions::assert_eq!(got_address, address.into_ext());
3464                    let control_handle = responder.control_handle();
3465                    control_handle.send_on_interface_removed(removal_reason).unwrap();
3466                    control_handle.shutdown();
3467                    futures::stream::empty()
3468                }
3469                req => panic!("unexpected request {req:?}"),
3470            },
3471        })
3472        .await;
3473    }
3474
3475    fn del_addr_test_interface_case(
3476        address: AddrSubnetEither,
3477        response: Result<bool, fnet_interfaces_admin::ControlRemoveAddressError>,
3478        remaining_address: Option<AddrSubnetEither>,
3479    ) -> TestInterfaceRequestCase<
3480        impl FnMut(
3481            fnet_interfaces_admin::ControlRequest,
3482        ) -> futures::stream::Iter<core::array::IntoIter<fnet_interfaces::Event, 1>>,
3483    > {
3484        TestInterfaceRequestCase {
3485            address,
3486            kind: AddressRequestKind::Del,
3487            control_request_handler: move |req| {
3488                match req {
3489                    fnet_interfaces_admin::ControlRequest::RemoveAddress {
3490                        address: got_address,
3491                        responder,
3492                    } => {
3493                        pretty_assertions::assert_eq!(got_address, address.into_ext());
3494                        responder.send(response).unwrap();
3495
3496                        // Send an update without the deleted address to complete
3497                        // the request.
3498                        futures::stream::iter([fnet_interfaces::Event::Changed(
3499                            fnet_interfaces::Properties {
3500                                id: Some(ETH_INTERFACE_ID.try_into().unwrap()),
3501                                addresses: Some(remaining_address.map_or_else(Vec::new, |addr| {
3502                                    vec![test_addr(addr.into_ext())]
3503                                })),
3504                                ..fnet_interfaces::Properties::default()
3505                            },
3506                        )])
3507                    }
3508                    req => panic!("unexpected request {req:?}"),
3509                }
3510            },
3511        }
3512    }
3513
3514    /// Test RTM_DELADDR with all interesting responses to remove address.
3515    #[test_case(
3516        test_addr_subnet_v4(),
3517        Ok(true),
3518        Ok(()); "v4_did_remove")]
3519    #[test_case(
3520        test_addr_subnet_v6(),
3521        Ok(true),
3522        Ok(()); "v6_did_remove")]
3523    #[test_case(
3524        test_addr_subnet_v4(),
3525        Ok(false),
3526        Err(RequestError::AddressNotFound); "v4_did_not_remove")]
3527    #[test_case(
3528        test_addr_subnet_v6(),
3529        Ok(false),
3530        Err(RequestError::AddressNotFound); "v6_did_not_remove")]
3531    #[test_case(
3532        test_addr_subnet_v4(),
3533        Err(fnet_interfaces_admin::ControlRemoveAddressError::unknown()),
3534        Err(RequestError::InvalidRequest); "v4_unrecognized_error")]
3535    #[test_case(
3536        test_addr_subnet_v6(),
3537        Err(fnet_interfaces_admin::ControlRemoveAddressError::unknown()),
3538        Err(RequestError::InvalidRequest); "v6_unrecognized_error")]
3539    #[fuchsia::test(logging = false)]
3540    async fn test_del_addr(
3541        address: AddrSubnetEither,
3542        response: Result<bool, fnet_interfaces_admin::ControlRemoveAddressError>,
3543        waiter_result: Result<(), RequestError>,
3544    ) {
3545        pretty_assertions::assert_eq!(
3546            test_interface_request(del_addr_test_interface_case(address, response, None)).await,
3547            TestRequestResult { messages: Vec::new(), waiter_results: vec![waiter_result] },
3548        )
3549    }
3550
3551    /// Tests that multiple interface update requests result in only one
3552    /// admin handle being created for that interface.
3553    #[fuchsia::test(logging = false)]
3554    async fn test_single_get_admin_for_multiple_interface_requests() {
3555        let first_address = test_addr_subnet_v4();
3556        let second_address = test_addr_subnet_v6();
3557        pretty_assertions::assert_eq!(
3558            test_maybe_two_interface_requests_on_single_control(
3559                del_addr_test_interface_case(first_address, Ok(true), Some(second_address)),
3560                Some(del_addr_test_interface_case(second_address, Ok(true), None)),
3561            )
3562            .await,
3563            TestRequestResult { messages: Vec::new(), waiter_results: vec![Ok(()), Ok(())] },
3564        )
3565    }
3566}