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