Skip to main content

netlink/
routes.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_ROUTE information by receiving RTM_ROUTE
6//! Netlink messages and maintaining route table state from Netstack.
7
8use std::collections::{HashMap, HashSet};
9use std::fmt::Debug;
10use std::hash::{Hash, Hasher};
11use std::num::{NonZeroU32, NonZeroU64};
12
13use fidl::endpoints::ProtocolMarker;
14use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
15use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
16use fidl_fuchsia_net_resources as fnet_resources;
17use fidl_fuchsia_net_root as fnet_root;
18use fidl_fuchsia_net_routes as fnet_routes;
19use fidl_fuchsia_net_routes_admin::RouteSetError;
20use fidl_fuchsia_net_routes_ext as fnet_routes_ext;
21
22use derivative::Derivative;
23use futures::StreamExt as _;
24use futures::channel::oneshot;
25use linux_uapi::{
26    rt_class_t_RT_TABLE_COMPAT, rt_class_t_RT_TABLE_MAIN, rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
27    rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
28};
29use net_types::ip::{GenericOverIp, Ip, IpAddress, IpVersion, Subnet};
30use net_types::{SpecifiedAddr, SpecifiedAddress, Witness as _};
31use netlink_packet_core::{NLM_F_MULTIPART, NetlinkMessage};
32use netlink_packet_route::route::{
33    RouteAddress, RouteAttribute, RouteHeader, RouteMessage, RouteProtocol, RouteScope, RouteType,
34};
35use netlink_packet_route::{AddressFamily, RouteNetlinkMessage};
36use netlink_packet_utils::DecodeError;
37use netlink_packet_utils::nla::Nla;
38
39use crate::client::{ClientTable, InternalClient};
40use crate::logging::{log_debug, log_error, log_info, log_warn};
41use crate::messaging::Sender;
42use crate::multicast_groups::ModernGroup;
43use crate::netlink_packet::UNSPECIFIED_SEQUENCE_NUMBER;
44use crate::netlink_packet::errno::Errno;
45use crate::protocol_family::ProtocolFamily;
46use crate::protocol_family::route::NetlinkRoute;
47use crate::route_tables::{
48    FidlRouteMap, ManagedRouteTable, NetlinkRouteTableIndex, NonZeroNetlinkRouteTableIndex,
49    RouteRemoveResult, RouteTable, RouteTableMap, TableNeedsCleanup, UnmanagedTable,
50};
51use crate::util::respond_to_completer;
52
53const MAIN_ROUTE_TABLE: u32 = rt_class_t_RT_TABLE_MAIN;
54pub(crate) const MAIN_ROUTE_TABLE_INDEX: NetlinkRouteTableIndex =
55    NetlinkRouteTableIndex::new(MAIN_ROUTE_TABLE);
56
57/// Arguments for an RTM_GETROUTE [`Request`].
58#[derive(Copy, Clone, Debug, PartialEq, Eq)]
59pub(crate) enum GetRouteArgs {
60    Dump,
61}
62
63/// Arguments for an RTM_NEWROUTE unicast route.
64#[derive(Copy, Clone, Debug, PartialEq, Eq, GenericOverIp)]
65#[generic_over_ip(I, Ip)]
66pub(crate) struct UnicastNewRouteArgs<I: Ip> {
67    // The network and prefix of the route.
68    pub subnet: Subnet<I::Addr>,
69    // The forwarding action. Unicast routes are gateway/direct routes and must
70    // have a target.
71    pub target: fnet_routes_ext::RouteTarget<I>,
72    // The metric used to weigh the importance of the route. `None` if unset or
73    // zero in the netlink message.
74    pub priority: Option<NonZeroU32>,
75    // The routing table.
76    pub table: NetlinkRouteTableIndex,
77}
78
79/// Arguments for an RTM_NEWROUTE [`Request`].
80#[derive(Copy, Clone, Debug, PartialEq, Eq)]
81pub(crate) enum NewRouteArgs<I: Ip> {
82    /// Direct or gateway routes.
83    Unicast(UnicastNewRouteArgs<I>),
84}
85
86/// Arguments for an RTM_DELROUTE unicast route.
87/// Only the subnet and table field are required. All other fields are optional.
88#[derive(Copy, Clone, Debug, PartialEq, Eq, GenericOverIp)]
89#[generic_over_ip(I, Ip)]
90pub(crate) struct UnicastDelRouteArgs<I: Ip> {
91    // The network and prefix of the route.
92    pub(crate) subnet: Subnet<I::Addr>,
93    // The outbound interface to use when forwarding packets.
94    pub(crate) outbound_interface: Option<NonZeroU64>,
95    // The next-hop IP address of the route.
96    pub(crate) next_hop: Option<SpecifiedAddr<I::Addr>>,
97    // The metric used to weigh the importance of the route.
98    pub(crate) priority: Option<NonZeroU32>,
99    // The routing table.
100    pub(crate) table: NonZeroNetlinkRouteTableIndex,
101}
102
103/// Arguments for an RTM_DELROUTE [`Request`].
104#[derive(Copy, Clone, Debug, PartialEq, Eq)]
105pub(crate) enum DelRouteArgs<I: Ip> {
106    /// Direct or gateway routes.
107    Unicast(UnicastDelRouteArgs<I>),
108}
109
110/// [`Request`] arguments associated with routes.
111#[derive(Copy, Clone, Debug, PartialEq, Eq)]
112pub(crate) enum RouteRequestArgs<I: Ip> {
113    /// RTM_GETROUTE
114    Get(GetRouteArgs),
115    /// RTM_NEWROUTE
116    New(NewRouteArgs<I>),
117    /// RTM_DELROUTE
118    Del(DelRouteArgs<I>),
119}
120
121/// The argument(s) for a [`Request`].
122#[derive(Copy, Clone, Debug, PartialEq, Eq)]
123pub(crate) enum RequestArgs<I: Ip> {
124    Route(RouteRequestArgs<I>),
125}
126
127/// An error encountered while handling a [`Request`].
128#[derive(Copy, Clone, Debug, PartialEq, Eq)]
129pub(crate) enum RequestError {
130    /// The route already exists in the route set.
131    AlreadyExists,
132    /// Netstack failed to delete the route due to the route not being
133    /// installed by Netlink.
134    DeletionNotAllowed,
135    /// Invalid destination subnet or next-hop.
136    InvalidRequest,
137    /// No routes in the route set matched the route query.
138    NotFound,
139    /// Interface present in request that was not recognized by Netstack.
140    UnrecognizedInterface,
141    /// Unspecified error.
142    Unknown,
143}
144
145impl RequestError {
146    pub(crate) fn into_errno(self) -> Errno {
147        match self {
148            RequestError::AlreadyExists => Errno::EEXIST,
149            RequestError::InvalidRequest => Errno::EINVAL,
150            RequestError::NotFound => Errno::ESRCH,
151            RequestError::DeletionNotAllowed | RequestError::Unknown => Errno::ENOTSUP,
152            RequestError::UnrecognizedInterface => Errno::ENODEV,
153        }
154    }
155}
156
157fn map_route_set_error<I: Ip + fnet_routes_ext::FidlRouteIpExt>(
158    e: RouteSetError,
159    route: &I::Route,
160    interface_id: u64,
161) -> RequestError {
162    match e {
163        RouteSetError::Unauthenticated => {
164            // Authenticated with Netstack for this interface, but
165            // the route set claims the interface did
166            // not authenticate.
167            panic!(
168                "authenticated for interface {:?}, but received unauthentication error from route set for route ({:?})",
169                interface_id, route,
170            );
171        }
172        RouteSetError::InvalidDestinationSubnet => {
173            // Subnet had an incorrect prefix length or host bits were set.
174            log_debug!(
175                "invalid subnet observed from route ({:?}) from interface {:?}",
176                route,
177                interface_id,
178            );
179            return RequestError::InvalidRequest;
180        }
181        RouteSetError::InvalidNextHop => {
182            // Non-unicast next-hop found in request.
183            log_debug!(
184                "invalid next hop observed from route ({:?}) from interface {:?}",
185                route,
186                interface_id,
187            );
188            return RequestError::InvalidRequest;
189        }
190        err => {
191            // `RouteSetError` is a flexible FIDL enum so we cannot
192            // exhaustively match.
193            //
194            // We don't know what the error is but we know that the route
195            // set was unmodified as a result of the operation.
196            log_error!(
197                "unrecognized route set error {:?} with route ({:?}) from interface {:?}",
198                err,
199                route,
200                interface_id
201            );
202            return RequestError::Unknown;
203        }
204    }
205}
206
207/// A request associated with routes.
208#[derive(Derivative, GenericOverIp)]
209#[derivative(Debug(bound = ""))]
210#[generic_over_ip(I, Ip)]
211pub(crate) struct Request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>, I: Ip> {
212    /// The resource and operation-specific argument(s) for this request.
213    pub args: RequestArgs<I>,
214    /// The request's sequence number.
215    ///
216    /// This value will be copied verbatim into any message sent as a result of
217    /// this request.
218    pub sequence_number: u32,
219    /// The client that made the request.
220    pub client: InternalClient<NetlinkRoute, S>,
221    /// A completer that will have the result of the request sent over.
222    pub completer: oneshot::Sender<Result<(), RequestError>>,
223}
224
225/// Handles asynchronous work related to RTM_ROUTE messages.
226///
227/// Can respond to RTM_ROUTE message requests.
228#[derive(GenericOverIp)]
229#[generic_over_ip(I, Ip)]
230pub(crate) struct RoutesWorker<
231    I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
232> {
233    fidl_route_map: FidlRouteMap<I>,
234    /// Stashed routes that are learned from netstack but don't yet have a
235    /// mapping for the netlink table ID.
236    stashed_routes: HashMap<fnet_routes_ext::TableId, HashSet<fnet_routes_ext::InstalledRoute<I>>>,
237}
238
239#[cfg(test)]
240impl<I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt> Drop
241    for RoutesWorker<I>
242{
243    fn drop(&mut self) {
244        assert!(self.stashed_routes.is_empty(), "the stashed routes must be eventually reconciled");
245    }
246}
247
248fn get_table_u8_and_nla_from_key(
249    netlink_id: NetlinkRouteTableIndex,
250) -> (u8, Option<RouteAttribute>) {
251    let table_id = netlink_id.get();
252    // When the table's value is >255, the value should be specified
253    // by an NLA and the header value should be RT_TABLE_COMPAT.
254    match u8::try_from(table_id) {
255        Ok(t) => (t, None),
256        // RT_TABLE_COMPAT (252) can be downcasted without loss into u8.
257        Err(_) => (rt_class_t_RT_TABLE_COMPAT as u8, Some(RouteAttribute::Table(table_id))),
258    }
259}
260
261/// A subset of `RouteRequestArgs`, containing only `Request` types that can be pending.
262#[derive(Clone, Debug, PartialEq, Eq)]
263pub(crate) enum PendingRouteRequestArgs<I: Ip> {
264    /// RTM_NEWROUTE
265    New(NewRouteArgs<I>),
266    /// RTM_DELROUTE
267    Del((NetlinkRouteMessage, NonZeroNetlinkRouteTableIndex)),
268}
269
270#[derive(Derivative)]
271#[derivative(Debug(bound = ""))]
272pub(crate) struct PendingRouteRequest<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>, I: Ip>
273{
274    request_args: PendingRouteRequestArgs<I>,
275    client: InternalClient<NetlinkRoute, S>,
276    completer: oneshot::Sender<Result<(), RequestError>>,
277}
278
279impl<I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt>
280    RoutesWorker<I>
281{
282    /// Create the Netlink Routes Worker.
283    ///
284    /// # Panics
285    ///
286    /// Panics if an unexpected error is encountered on one of the FIDL
287    /// connections with the Netstack.
288    pub(crate) async fn create(
289        main_route_table: &<I::RouteTableMarker as ProtocolMarker>::Proxy,
290        routes_state_proxy: &<I::StateMarker as ProtocolMarker>::Proxy,
291        route_table_provider: <I::RouteTableProviderMarker as ProtocolMarker>::Proxy,
292    ) -> (
293        Self,
294        RouteTableMap<I>,
295        impl futures::Stream<Item = Result<fnet_routes_ext::Event<I>, fnet_routes_ext::WatchError>>
296        + Unpin
297        + 'static,
298    ) {
299        let mut route_event_stream = Box::pin(
300            fnet_routes_ext::event_stream_from_state(routes_state_proxy)
301                .expect("connecting to fuchsia.net.routes.State FIDL should succeed"),
302        );
303        let installed_routes = fnet_routes_ext::collect_routes_until_idle::<_, HashSet<_>>(
304            route_event_stream.by_ref(),
305        )
306        .await
307        .expect("determining already installed routes should succeed");
308
309        let mut fidl_route_map = FidlRouteMap::<I>::default();
310        for fnet_routes_ext::InstalledRoute { route, effective_properties, table_id } in
311            installed_routes
312        {
313            let _: Option<fnet_routes_ext::EffectiveRouteProperties> =
314                fidl_route_map.add(route, table_id, effective_properties);
315        }
316
317        let main_route_table_id = fnet_routes_ext::admin::get_table_id::<I>(main_route_table)
318            .await
319            .expect("getting main route table ID should succeed");
320        let unmanaged_route_set_proxy =
321            fnet_routes_ext::admin::new_route_set::<I>(main_route_table)
322                .expect("getting unmanaged route set should succeed");
323        let route_table_map = RouteTableMap::new(
324            main_route_table.clone(),
325            main_route_table_id,
326            unmanaged_route_set_proxy,
327            route_table_provider,
328        );
329        (
330            Self { fidl_route_map, stashed_routes: HashMap::new() },
331            route_table_map,
332            route_event_stream,
333        )
334    }
335
336    /// Handles events observed by the route watchers by adding/removing routes
337    /// from the underlying `NetlinkRouteMessage` set.
338    ///
339    /// # Panics
340    ///
341    /// Panics if an unexpected Route Watcher Event is published by the
342    /// Netstack.
343    pub(crate) fn handle_route_watcher_event<
344        S: Sender<<NetlinkRoute as ProtocolFamily>::Response>,
345    >(
346        &mut self,
347        route_table_map: &mut RouteTableMap<I>,
348        route_clients: &ClientTable<NetlinkRoute, S>,
349        event: fnet_routes_ext::Event<I>,
350    ) -> Option<TableNeedsCleanup> {
351        let res = handle_route_watcher_event::<I, S>(
352            route_table_map,
353            &mut self.fidl_route_map,
354            route_clients,
355            event,
356        );
357        match res {
358            RouteEventOutcome::Noop => None,
359            RouteEventOutcome::Cleanup(cleanup) => Some(cleanup),
360            RouteEventOutcome::UpdateStash(event) => {
361                match event {
362                    AddOrRemoveRoute::Added(r) => {
363                        assert!(
364                            self.stashed_routes.entry(r.table_id).or_default().insert(r),
365                            "route {:?} already stashed",
366                            r,
367                        )
368                    }
369                    AddOrRemoveRoute::Removed(r) => {
370                        match self.stashed_routes.get_mut(&r.table_id) {
371                            Some(table) => {
372                                assert!(table.remove(&r), "route {:?} not stashed", r,);
373                            }
374                            None => {
375                                log_info!(
376                                    "netlink route table for {:?} already removed",
377                                    r.table_id
378                                );
379                            }
380                        }
381                    }
382                }
383                None
384            }
385        }
386    }
387
388    /// Processes stashed routes for the given netlink table.
389    ///
390    /// Panics if the given netlink table id is non-existent.
391    pub(crate) fn process_stashed_routes<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
392        &mut self,
393        route_table_map: &mut RouteTableMap<I>,
394        route_clients: &ClientTable<NetlinkRoute, S>,
395        netlink_table_id: NetlinkRouteTableIndex,
396    ) {
397        let fidl_table_id = route_table_map
398            .get_mut(&netlink_table_id)
399            .expect("invalid netlink table id")
400            .fidl_table_id();
401
402        if let Some(stashed_routes) = self.stashed_routes.remove(&fidl_table_id) {
403            log_info!(
404                "processing {} stashed route events for table {}",
405                stashed_routes.len(),
406                fidl_table_id
407            );
408            for route in stashed_routes {
409                assert_eq!(
410                    handle_route_watcher_event::<I, S>(
411                        route_table_map,
412                        &mut self.fidl_route_map,
413                        route_clients,
414                        fnet_routes_ext::Event::Added(route),
415                    ),
416                    RouteEventOutcome::Noop,
417                    "adding routes should not have clean ups"
418                );
419            }
420        }
421    }
422
423    fn get_interface_control(
424        interfaces_proxy: &fnet_root::InterfacesProxy,
425        interface_id: u64,
426    ) -> fnet_interfaces_ext::admin::Control {
427        let (control, server_end) =
428            fidl::endpoints::create_proxy::<fnet_interfaces_admin::ControlMarker>();
429        interfaces_proxy.get_admin(interface_id, server_end).expect("send get admin request");
430        fnet_interfaces_ext::admin::Control::new(control)
431    }
432
433    async fn authenticate_for_interface(
434        interfaces_proxy: &fnet_root::InterfacesProxy,
435        route_set_proxy: &<I::RouteSetMarker as fidl::endpoints::ProtocolMarker>::Proxy,
436        interface_id: u64,
437    ) -> Result<(), RequestError> {
438        let control = Self::get_interface_control(interfaces_proxy, interface_id);
439
440        let grant = match control.get_authorization_for_interface().await {
441            Ok(grant) => grant,
442            Err(fnet_interfaces_ext::admin::TerminalError::Fidl(
443                fidl::Error::ClientChannelClosed { epitaph, protocol_name, .. },
444            )) => {
445                log_debug!(
446                    "{epitaph:?}: netstack dropped the {protocol_name} channel, \
447                     interface {interface_id} does not exist"
448                );
449                return Err(RequestError::UnrecognizedInterface);
450            }
451            Err(e) => panic!("unexpected error from interface authorization request: {e:?}"),
452        };
453        let proof = fnet_interfaces_ext::admin::proof_from_grant(&grant);
454
455        #[derive(GenericOverIp)]
456        #[generic_over_ip(I, Ip)]
457        struct AuthorizeInputs<'a, I: fnet_routes_ext::admin::FidlRouteAdminIpExt> {
458            route_set_proxy: &'a <I::RouteSetMarker as fidl::endpoints::ProtocolMarker>::Proxy,
459            proof: fnet_resources::ProofOfInterfaceAuthorization,
460        }
461
462        let authorize_fut = I::map_ip_in(
463            AuthorizeInputs::<'_, I> { route_set_proxy, proof },
464            |AuthorizeInputs { route_set_proxy, proof }| {
465                route_set_proxy.authenticate_for_interface(proof)
466            },
467            |AuthorizeInputs { route_set_proxy, proof }| {
468                route_set_proxy.authenticate_for_interface(proof)
469            },
470        );
471
472        authorize_fut.await.expect("sent authorization request").map_err(|e| {
473            log_warn!("error authenticating for interface ({interface_id}): {e:?}");
474            RequestError::UnrecognizedInterface
475        })?;
476
477        Ok(())
478    }
479
480    /// Handles a new route request.
481    ///
482    /// Returns the `RouteRequestArgs` if the route was successfully
483    /// added so that the caller can make sure their local state (from the
484    /// routes watcher) has sent an event holding the added route.
485    async fn handle_new_route_request(
486        &self,
487        route_tables: &mut RouteTableMap<I>,
488        interfaces_proxy: &fnet_root::InterfacesProxy,
489        args: NewRouteArgs<I>,
490    ) -> Result<NewRouteArgs<I>, RequestError> {
491        let (interface_id, table) = match args {
492            NewRouteArgs::Unicast(args) => (args.target.outbound_interface, args.table),
493        };
494        let route: fnet_routes_ext::Route<I> = args.into();
495
496        // Ideally we'd combine the two following operations with some form of
497        // Entry API in order to avoid the panic, but this is difficult to pull
498        // off with async.
499        route_tables.create_managed_route_table_if_not_present(table).await;
500
501        let table_id = route_tables.get(&table).expect("should be populated").fidl_table_id();
502
503        // Check if the new route conflicts with an existing route.
504        //
505        // Note that Linux and Fuchsia differ on what constitutes a conflicting route.
506        // Linux is stricter than Fuchsia and requires that all routes have a unique
507        // (destination subnet, metric, table) tuple. Here we replicate the check that Linux
508        // performs, so that Netlink can reject requests before handing them off to the
509        // more flexible Netstack routing APIs.
510        let new_route_conflicts_with_existing = self
511            .fidl_route_map
512            .iter_table(route_tables.get(&table).expect("should be populated").fidl_table_id())
513            .any(|(stored_route, stored_props)| {
514                routes_conflict::<I>(
515                    fnet_routes_ext::InstalledRoute {
516                        route: *stored_route,
517                        effective_properties: *stored_props,
518                        table_id,
519                    },
520                    route,
521                    table_id,
522                )
523            });
524
525        if new_route_conflicts_with_existing {
526            return Err(RequestError::AlreadyExists);
527        }
528
529        let route_set = match route_tables.get(&table).expect("should have just been populated") {
530            RouteTable::Managed(ManagedRouteTable { route_set_proxy, .. }) => route_set_proxy,
531            RouteTable::Unmanaged(UnmanagedTable { route_set_proxy, .. }) => route_set_proxy,
532        };
533
534        let route: I::Route =
535            route.try_into().expect("should not have constructed unknown route action");
536        let added_to_table: bool = Self::dispatch_route_proxy_fn(
537            &route,
538            interface_id,
539            &interfaces_proxy,
540            route_set,
541            fnet_routes_ext::admin::add_route::<I>,
542        )
543        .await?;
544
545        // When `add_route` has an `Ok(false)` response, this indicates that the
546        // route already exists, which should manifest as a hard error in Linux.
547        if !added_to_table {
548            return Err(RequestError::AlreadyExists);
549        };
550
551        Ok(args)
552    }
553
554    /// Handles a delete route request.
555    ///
556    /// Returns the `NetlinkRouteMessage` along with its corresponding table index if the route was
557    /// successfully removed so that the caller can make sure their local state (from the routes
558    /// watcher) has sent a removal event for the removed route.
559    async fn handle_del_route_request(
560        &self,
561        interfaces_proxy: &fnet_root::InterfacesProxy,
562        route_tables: &mut RouteTableMap<I>,
563        del_route_args: DelRouteArgs<I>,
564    ) -> Result<(NetlinkRouteMessage, NonZeroNetlinkRouteTableIndex), RequestError> {
565        let table = match del_route_args {
566            DelRouteArgs::Unicast(args) => args.table,
567        };
568
569        let route_to_delete = &self
570            .select_route_for_deletion(route_tables, del_route_args)
571            .ok_or(RequestError::NotFound)?;
572        let NetlinkRouteMessage(route) = route_to_delete;
573        let interface_id = route
574            .attributes
575            .iter()
576            .filter_map(|nla| match nla {
577                RouteAttribute::Oif(interface) => Some(*interface as u64),
578                _nla => None,
579            })
580            .next()
581            .expect("there should be exactly one Oif NLA present");
582
583        let route_set = match route_tables.get(&table.into()) {
584            None => return Err(RequestError::NotFound),
585            Some(lookup) => match lookup {
586                RouteTable::Managed(ManagedRouteTable { route_set_proxy, .. }) => route_set_proxy,
587                RouteTable::Unmanaged(UnmanagedTable { route_set_proxy, .. }) => route_set_proxy,
588            },
589        };
590
591        let route: fnet_routes_ext::Route<I> = route_to_delete.to_owned().into();
592
593        let route: I::Route = route.try_into().expect("route should be converted");
594        let removed: bool = Self::dispatch_route_proxy_fn(
595            &route,
596            interface_id,
597            &interfaces_proxy,
598            route_set,
599            fnet_routes_ext::admin::remove_route::<I>,
600        )
601        .await?;
602
603        if !removed {
604            log_error!(
605                "Route was not removed as a result of this call. Likely Linux wanted \
606                to remove a route from the global route set which is not supported  \
607                by this API, route: {:?}",
608                route_to_delete
609            );
610            return Err(RequestError::DeletionNotAllowed);
611        }
612
613        Ok((route_to_delete.to_owned(), table))
614    }
615
616    /// Select a route for deletion, based on the given deletion arguments.
617    ///
618    /// Note that Linux and Fuchsia differ on how to specify a route for deletion.
619    /// Linux is more flexible and allows you specify matchers as arguments, where
620    /// Fuchsia requires that you exactly specify the route. Here, Linux's matchers
621    /// are provided in `deletion_args`; Many of the matchers are optional, and an
622    /// existing route matches the arguments if all provided arguments are equal to
623    /// the values held by the route. If multiple routes match the arguments, the
624    /// route with the lowest metric is selected.
625    fn select_route_for_deletion(
626        &self,
627        route_tables: &RouteTableMap<I>,
628        deletion_args: DelRouteArgs<I>,
629    ) -> Option<NetlinkRouteMessage> {
630        select_route_for_deletion(&self.fidl_route_map, route_tables, deletion_args)
631    }
632
633    // Dispatch a function to the RouteSetProxy.
634    //
635    // Attempt to dispatch the function without authenticating first. If the call is
636    // unsuccessful due to an Unauthenticated error, try again after authenticating
637    // for the interface.
638    // Returns: whether the RouteSetProxy function made a change in the Netstack
639    // (an add or delete), or `RequestError` if unsuccessful.
640    async fn dispatch_route_proxy_fn<'a, Fut>(
641        route: &'a I::Route,
642        interface_id: u64,
643        interfaces_proxy: &'a fnet_root::InterfacesProxy,
644        route_set_proxy: &'a <I::RouteSetMarker as ProtocolMarker>::Proxy,
645        dispatch_fn: impl Fn(&'a <I::RouteSetMarker as ProtocolMarker>::Proxy, &'a I::Route) -> Fut,
646    ) -> Result<bool, RequestError>
647    where
648        Fut: futures::Future<Output = Result<Result<bool, RouteSetError>, fidl::Error>>,
649    {
650        match dispatch_fn(route_set_proxy, &route).await.expect("sent route proxy request") {
651            Ok(made_change) => return Ok(made_change),
652            Err(RouteSetError::Unauthenticated) => {}
653            Err(e) => {
654                log_warn!("error altering route on interface ({interface_id}): {e:?}");
655                return Err(map_route_set_error::<I>(e, route, interface_id));
656            }
657        };
658
659        // Authenticate for the interface if we received the `Unauthenticated`
660        // error from the function that was dispatched.
661        Self::authenticate_for_interface(interfaces_proxy, route_set_proxy, interface_id).await?;
662
663        // Dispatch the function once more after authenticating. All errors are
664        // treated as hard errors after the second dispatch attempt. Further
665        // attempts are not expected to yield differing results.
666        dispatch_fn(route_set_proxy, &route).await.expect("sent route proxy request").map_err(|e| {
667            log_warn!(
668                "error altering route after authenticating for \
669                    interface ({interface_id}): {e:?}"
670            );
671            map_route_set_error::<I>(e, route, interface_id)
672        })
673    }
674
675    /// Handles a [`Request`].
676    ///
677    /// Returns a [`PendingRouteRequest`] if a route was updated and the caller
678    /// needs to make sure the update has been propagated to the local state
679    /// (the routes watcher has sent an event for our update).
680    pub(crate) async fn handle_request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
681        &mut self,
682        route_tables: &mut RouteTableMap<I>,
683        interfaces_proxy: &fnet_root::InterfacesProxy,
684        Request { args, sequence_number, mut client, completer }: Request<S, I>,
685    ) -> Option<PendingRouteRequest<S, I>> {
686        log_debug!("handling request {args:?} from {client}");
687
688        #[derive(Derivative)]
689        #[derivative(Debug(bound = ""))]
690        enum RequestHandled<S, I>
691        where
692            S: Sender<<NetlinkRoute as ProtocolFamily>::Response>,
693            I: Ip,
694        {
695            Pending(PendingRouteRequest<S, I>),
696            Done(
697                Result<(), RequestError>,
698                InternalClient<NetlinkRoute, S>,
699                oneshot::Sender<Result<(), RequestError>>,
700            ),
701        }
702
703        let request_handled = match args {
704            RequestArgs::Route(args) => match args {
705                RouteRequestArgs::Get(args) => match args {
706                    GetRouteArgs::Dump => {
707                        self.fidl_route_map
708                            .iter()
709                            .flat_map(|(route, tables)| {
710                                tables.iter().map(move |(fidl_table_id, props)| {
711                                    fnet_routes_ext::InstalledRoute {
712                                        route: *route,
713                                        table_id: *fidl_table_id,
714                                        effective_properties: *props,
715                                    }
716                                })
717                            })
718                            .filter_map(|installed_route| {
719                                let table_index =
720                                    route_tables.get_netlink_id(&installed_route.table_id)?;
721                                NetlinkRouteMessage::optionally_from(installed_route, table_index)
722                            })
723                            .for_each(|message| {
724                                client.send_unicast(
725                                    message.into_rtnl_new_route(sequence_number, true),
726                                )
727                            });
728                        RequestHandled::Done(Ok(()), client, completer)
729                    }
730                },
731                RouteRequestArgs::New(args) => {
732                    match self.handle_new_route_request(route_tables, interfaces_proxy, args).await
733                    {
734                        Ok(args) => {
735                            // Route additions must be confirmed via observing routes-watcher events
736                            // that indicate the route has been installed.
737                            RequestHandled::Pending(PendingRouteRequest {
738                                request_args: PendingRouteRequestArgs::New(args),
739                                client,
740                                completer,
741                            })
742                        }
743                        Err(err) => RequestHandled::Done(Err(err), client, completer),
744                    }
745                }
746                RouteRequestArgs::Del(args) => {
747                    match self.handle_del_route_request(interfaces_proxy, route_tables, args).await
748                    {
749                        Ok(del_route) => {
750                            // Route deletions must be confirmed via a message from the Routes
751                            // watcher with the same Route struct - using the route
752                            // matched for deletion.
753                            RequestHandled::Pending(PendingRouteRequest {
754                                request_args: PendingRouteRequestArgs::Del(del_route),
755                                client,
756                                completer,
757                            })
758                        }
759                        Err(e) => RequestHandled::Done(Err(e), client, completer),
760                    }
761                }
762            },
763        };
764
765        match request_handled {
766            RequestHandled::Done(result, client, completer) => {
767                log_debug!("handled request {args:?} from {client} with result = {result:?}");
768
769                respond_to_completer(client, completer, result, args);
770                None
771            }
772            RequestHandled::Pending(pending) => Some(pending),
773        }
774    }
775
776    /// Checks whether a `PendingRequest` can be marked completed given the current state of the
777    /// worker. If so, notifies the request's completer and returns `None`. If not, returns
778    /// the `PendingRequest` as `Some`.
779    ///
780    /// TODO(https://fxbug.dev/488124265): Use synchronization primitives to
781    /// more robustly match requests to their corresponding watch events.
782    pub(crate) fn handle_pending_request<S: Sender<<NetlinkRoute as ProtocolFamily>::Response>>(
783        &self,
784        route_tables: &mut RouteTableMap<I>,
785        pending_route_request: PendingRouteRequest<S, I>,
786    ) -> Option<PendingRouteRequest<S, I>> {
787        let PendingRouteRequest { request_args, client: _, completer: _ } = &pending_route_request;
788
789        let done = match request_args {
790            PendingRouteRequestArgs::New(args) => {
791                let netlink_table_id = match args {
792                    NewRouteArgs::Unicast(args) => &args.table,
793                };
794
795                let own_fidl_table_id = route_tables
796                    .get(netlink_table_id)
797                    .expect("should recognize table referenced in pending new route request")
798                    .fidl_table_id();
799
800                self.fidl_route_map
801                    .route_is_installed_in_tables(&(*args).into(), [&own_fidl_table_id])
802            }
803            // For `Del` messages, we expect the exact `NetlinkRouteMessage` to match,
804            // which was received as part of the `select_route_for_deletion` flow.
805            PendingRouteRequestArgs::Del((route_msg, pending_table)) => {
806                let netlink_table_id: NetlinkRouteTableIndex = (*pending_table).into();
807                // It's okay for this to be `None`, as we may have garbage collected the
808                // corresponding entry in `route_tables` if this was the last route in that table.
809                let own_fidl_table_id: Option<fnet_routes_ext::TableId> =
810                    route_tables.get(&netlink_table_id).map(|table| table.fidl_table_id());
811
812                if let Some(own_fidl_table_id) = own_fidl_table_id {
813                    self.fidl_route_map.route_is_uninstalled_in_tables(
814                        &route_msg.clone().into(),
815                        [&own_fidl_table_id],
816                    )
817                } else {
818                    true
819                }
820            }
821        };
822
823        if done {
824            log_debug!("completed pending request; req = {pending_route_request:?}");
825            let PendingRouteRequest { request_args, client, completer } = pending_route_request;
826
827            respond_to_completer(client, completer, Ok(()), request_args);
828            None
829        } else {
830            // Put the pending request back so that it can be handled later.
831            log_debug!("pending request not done yet; req = {pending_route_request:?}");
832            Some(pending_route_request)
833        }
834    }
835
836    pub(crate) fn any_routes_reference_table(
837        &self,
838        TableNeedsCleanup(table_id, _table_index): TableNeedsCleanup,
839    ) -> bool {
840        self.fidl_route_map.table_is_present(table_id)
841    }
842}
843
844/// Returns `true` if the new route conflicts with an existing route.
845///
846/// Note that Linux and Fuchsia differ on what constitutes a conflicting route.
847/// Linux is stricter than Fuchsia and requires that all routes have a unique
848/// (destination subnet, metric, table) tuple. Here we replicate the check that Linux
849/// performs, so that Netlink can reject requests before handing them off to the
850/// more flexible Netstack routing APIs.
851fn routes_conflict<I: Ip>(
852    stored_route: fnet_routes_ext::InstalledRoute<I>,
853    incoming_route: fnet_routes_ext::Route<I>,
854    incoming_table: fnet_routes_ext::TableId,
855) -> bool {
856    let fnet_routes_ext::InstalledRoute {
857        route:
858            fnet_routes_ext::Route {
859                destination: stored_destination,
860                action: _,
861                properties: stored_properties,
862            },
863        effective_properties: _,
864        table_id: stored_table_id,
865    } = stored_route;
866
867    let destinations_match = stored_destination == incoming_route.destination;
868    let specified_metrics_match = stored_properties.specified_properties.metric
869        == incoming_route.properties.specified_properties.metric;
870    let tables_match = stored_table_id == incoming_table;
871
872    destinations_match && specified_metrics_match && tables_match
873}
874
875/// Like a [`fnet_routes_ext::Event`], but only the `Added` and `Removed` variants.
876#[derive(Debug, Clone, PartialEq, Eq, Hash)]
877enum AddOrRemoveRoute<I: Ip> {
878    Added(fnet_routes_ext::InstalledRoute<I>),
879    Removed(fnet_routes_ext::InstalledRoute<I>),
880}
881
882/// Outcome for handing a route watcher event.
883#[derive(Debug, Clone, PartialEq, Eq)]
884enum RouteEventOutcome<I: Ip> {
885    Noop,
886    Cleanup(TableNeedsCleanup),
887    UpdateStash(AddOrRemoveRoute<I>),
888}
889
890fn handle_route_watcher_event<
891    I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
892    S: Sender<<NetlinkRoute as ProtocolFamily>::Response>,
893>(
894    route_table_map: &mut RouteTableMap<I>,
895    fidl_route_map: &mut FidlRouteMap<I>,
896    route_clients: &ClientTable<NetlinkRoute, S>,
897    event: fnet_routes_ext::Event<I>,
898) -> RouteEventOutcome<I> {
899    let (table_id, event) = match event {
900        fnet_routes_ext::Event::Added(e) => (e.table_id, AddOrRemoveRoute::Added(e)),
901        fnet_routes_ext::Event::Removed(e) => (e.table_id, AddOrRemoveRoute::Removed(e)),
902        e @ fnet_routes_ext::Event::Existing(_)
903        | e @ fnet_routes_ext::Event::Idle
904        | e @ fnet_routes_ext::Event::Unknown => {
905            panic!("Netstack reported an unexpected route event: {e:?}");
906        }
907    };
908
909    let netlink_id = match route_table_map.get_netlink_id(&table_id) {
910        None => {
911            // This is a FIDL table ID that the netlink worker didn't know about, and is
912            // not the main table ID.
913            log_info!(
914                "Observed a route event via the routes watcher that is installed in a \
915                non-main FIDL table currently not managed by netlink: {event:?}."
916            );
917            return RouteEventOutcome::UpdateStash(event);
918        }
919        Some(table) => table,
920    };
921
922    let (message_for_clients, table_no_routes) = match event {
923        AddOrRemoveRoute::Added(added_installed_route) => {
924            let fnet_routes_ext::InstalledRoute { route, table_id, effective_properties } =
925                added_installed_route;
926
927            match fidl_route_map.add(route, table_id, effective_properties) {
928                None => (),
929                Some(_properties) => {
930                    panic!(
931                        "Netstack reported the addition of an existing route: \
932                        route={route:?}, table={table_id:?}"
933                    );
934                }
935            }
936
937            (
938                NetlinkRouteMessage::optionally_from(added_installed_route, netlink_id).map(
939                    |route_message| {
940                        route_message.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false)
941                    },
942                ),
943                RouteEventOutcome::Noop,
944            )
945        }
946        AddOrRemoveRoute::Removed(removed_installed_route) => {
947            let fnet_routes_ext::InstalledRoute { route, table_id, effective_properties: _ } =
948                removed_installed_route;
949
950            let need_clean_up_empty_table = match fidl_route_map.remove(route, table_id) {
951                RouteRemoveResult::DidNotExist => {
952                    panic!(
953                        "Netstack reported the removal of an unknown route: \
954                        route={route:?}, table={table_id:?}"
955                    );
956                }
957                RouteRemoveResult::RemovedButTableNotEmpty(_properties) => false,
958                RouteRemoveResult::RemovedAndTableNewlyEmpty(_properties) => true,
959            };
960
961            (
962                NetlinkRouteMessage::optionally_from(removed_installed_route, netlink_id)
963                    .map(|route_message| route_message.into_rtnl_del_route()),
964                match need_clean_up_empty_table {
965                    true => RouteEventOutcome::Cleanup(TableNeedsCleanup(table_id, netlink_id)),
966                    false => RouteEventOutcome::Noop,
967                },
968            )
969        }
970    };
971    if let Some(message_for_clients) = message_for_clients {
972        let route_group = match I::VERSION {
973            IpVersion::V4 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
974            IpVersion::V6 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
975        };
976        route_clients.send_message_to_group(message_for_clients, route_group);
977    }
978
979    table_no_routes
980}
981
982/// A wrapper type for the netlink_packet_route `RouteMessage` to enable conversions
983/// from [`fnet_routes_ext::InstalledRoute`] and implement hashing.
984#[derive(Clone, Debug, Eq, PartialEq)]
985pub(crate) struct NetlinkRouteMessage(pub(crate) RouteMessage);
986
987impl NetlinkRouteMessage {
988    /// Implement optional conversions from `InstalledRoute` and `table`
989    /// to `NetlinkRouteMessage`. `Ok` becomes `Some`, while `Err` is
990    /// logged and becomes `None`.
991    pub(crate) fn optionally_from<I: Ip>(
992        route: fnet_routes_ext::InstalledRoute<I>,
993        table: NetlinkRouteTableIndex,
994    ) -> Option<NetlinkRouteMessage> {
995        match NetlinkRouteMessage::try_from_installed_route::<I>(route, table) {
996            Ok(route) => Some(route),
997            Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding) => {
998                log_warn!("Unexpected non-forwarding route in routing table: {:?}", route);
999                None
1000            }
1001            Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(id)) => {
1002                log_warn!("Invalid interface id found in routing table route: {:?}", id);
1003                None
1004            }
1005            Err(NetlinkRouteMessageConversionError::FailedToDecode(err)) => {
1006                log_warn!("Unable to decode route address: {:?}", err);
1007                None
1008            }
1009        }
1010    }
1011
1012    /// Wrap the inner [`RouteMessage`] in an [`RtnlMessage::NewRoute`].
1013    pub(crate) fn into_rtnl_new_route(
1014        self,
1015        sequence_number: u32,
1016        is_dump: bool,
1017    ) -> NetlinkMessage<RouteNetlinkMessage> {
1018        let NetlinkRouteMessage(message) = self;
1019        let mut msg: NetlinkMessage<RouteNetlinkMessage> =
1020            RouteNetlinkMessage::NewRoute(message).into();
1021        msg.header.sequence_number = sequence_number;
1022        if is_dump {
1023            msg.header.flags |= NLM_F_MULTIPART;
1024        }
1025        msg.finalize();
1026        msg
1027    }
1028
1029    /// Wrap the inner [`RouteMessage`] in an [`RtnlMessage::DelRoute`].
1030    fn into_rtnl_del_route(self) -> NetlinkMessage<RouteNetlinkMessage> {
1031        let NetlinkRouteMessage(message) = self;
1032        let mut msg: NetlinkMessage<RouteNetlinkMessage> =
1033            RouteNetlinkMessage::DelRoute(message).into();
1034        msg.finalize();
1035        msg
1036    }
1037
1038    // TODO(https://fxbug.dev/336382905): Refactor this as a TryFrom
1039    // impl once tables are present in `InstalledRoute`.
1040    // Implement conversions from `InstalledRoute` to `NetlinkRouteMessage`
1041    // which is fallible iff, the route's action is not `Forward`.
1042    fn try_from_installed_route<I: Ip>(
1043        fnet_routes_ext::InstalledRoute {
1044            route: fnet_routes_ext::Route { destination, action, properties: _ },
1045            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
1046            // TODO(https://fxbug.dev/336382905): Use the table ID.
1047            table_id: _,
1048        }: fnet_routes_ext::InstalledRoute<I>,
1049        table: NetlinkRouteTableIndex,
1050    ) -> Result<Self, NetlinkRouteMessageConversionError> {
1051        let fnet_routes_ext::RouteTarget { outbound_interface, next_hop } = match action {
1052            fnet_routes_ext::RouteAction::Unknown => {
1053                return Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding);
1054            }
1055            fnet_routes_ext::RouteAction::Forward(target) => target,
1056        };
1057
1058        let mut route_header = RouteHeader::default();
1059        // Both possible constants are in the range of u8-accepted values, so they can be
1060        // safely casted to a u8.
1061        route_header.address_family = match I::VERSION {
1062            IpVersion::V4 => AddressFamily::Inet,
1063            IpVersion::V6 => AddressFamily::Inet6,
1064        }
1065        .try_into()
1066        .expect("should fit into u8");
1067        route_header.destination_prefix_length = destination.prefix();
1068
1069        let (table_u8, table_nla) = get_table_u8_and_nla_from_key(table);
1070        route_header.table = table_u8;
1071
1072        // The following fields are used in the header, but they do not have any
1073        // corresponding values in `InstalledRoute`. The fields explicitly
1074        // defined below  are expected to be needed at some point, but the
1075        // information is not currently provided by the watcher.
1076        //
1077        // length of source prefix
1078        // tos filter (type of service)
1079        route_header.protocol = RouteProtocol::Kernel;
1080        // Universe for routes with next_hop. Valid as long as route action
1081        // is forwarding.
1082        route_header.scope = RouteScope::Universe;
1083        route_header.kind = RouteType::Unicast;
1084
1085        // The NLA order follows the list that attributes are listed on the
1086        // rtnetlink man page.
1087        // The following fields are used in the options in the NLA, but they
1088        // do not have any corresponding values in `InstalledRoute`.
1089        //
1090        // RTA_SRC (route source address)
1091        // RTA_IIF (input interface index)
1092        // RTA_PREFSRC (preferred source address)
1093        // RTA_METRICS (route statistics)
1094        // RTA_MULTIPATH
1095        // RTA_FLOW
1096        // RTA_CACHEINFO
1097        // RTA_MARK
1098        // RTA_MFC_STATS
1099        // RTA_VIA
1100        // RTA_NEWDST
1101        // RTA_PREF
1102        // RTA_ENCAP_TYPE
1103        // RTA_ENCAP
1104        // RTA_EXPIRES (can set to 'forever' if it is required)
1105        let mut nlas = vec![];
1106
1107        // A prefix length of 0 indicates it is the default route. Specifying
1108        // destination NLA does not provide useful information.
1109        if route_header.destination_prefix_length > 0 {
1110            let destination_nla = RouteAttribute::Destination(RouteAddress::parse(
1111                route_header.address_family,
1112                destination.network().bytes(),
1113            )?);
1114            nlas.push(destination_nla);
1115        }
1116
1117        // We expect interface ids to safely fit in the range of u32 values.
1118        let outbound_id: u32 = match outbound_interface.try_into() {
1119            Err(std::num::TryFromIntError { .. }) => {
1120                return Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(
1121                    outbound_interface,
1122                ));
1123            }
1124            Ok(id) => id,
1125        };
1126        let oif_nla = RouteAttribute::Oif(outbound_id);
1127        nlas.push(oif_nla);
1128
1129        if let Some(next_hop) = next_hop {
1130            let bytes = RouteAddress::parse(route_header.address_family, next_hop.bytes())?;
1131            let gateway_nla = RouteAttribute::Gateway(bytes);
1132            nlas.push(gateway_nla);
1133        }
1134
1135        let priority_nla = RouteAttribute::Priority(metric);
1136        nlas.push(priority_nla);
1137
1138        // Only include the table NLA when `table` does not fit into the u8 range.
1139        if let Some(nla) = table_nla {
1140            nlas.push(nla);
1141        }
1142
1143        let mut route_message = RouteMessage::default();
1144        route_message.header = route_header;
1145        route_message.attributes = nlas;
1146        Ok(NetlinkRouteMessage(route_message))
1147    }
1148}
1149
1150impl Hash for NetlinkRouteMessage {
1151    fn hash<H: Hasher>(&self, state: &mut H) {
1152        let NetlinkRouteMessage(message) = self;
1153        message.header.hash(state);
1154
1155        let mut buffer = vec![];
1156        message.attributes.iter().for_each(|nla| {
1157            buffer.resize(nla.value_len(), 0u8);
1158            nla.emit_value(&mut buffer);
1159            buffer.hash(state);
1160        });
1161    }
1162}
1163
1164// NetlinkRouteMessage conversion related errors.
1165#[derive(Debug, PartialEq)]
1166pub(crate) enum NetlinkRouteMessageConversionError {
1167    // Route with non-forward action received from Netstack.
1168    RouteActionNotForwarding,
1169    // Interface id could not be downcasted to fit into the expected u32.
1170    InvalidInterfaceId(u64),
1171    // Failed to decode route address.
1172    FailedToDecode(DecodeErrorWrapper),
1173}
1174
1175#[derive(Debug)]
1176pub(crate) struct DecodeErrorWrapper(DecodeError);
1177
1178impl PartialEq for DecodeErrorWrapper {
1179    fn eq(&self, other: &Self) -> bool {
1180        // DecodeError contains anyhow::Error which unfortunately
1181        // can't be compared without a call to format!;
1182        return format!("{:?}", self.0) == format!("{:?}", other.0);
1183    }
1184}
1185
1186impl From<DecodeError> for NetlinkRouteMessageConversionError {
1187    fn from(err: DecodeError) -> Self {
1188        NetlinkRouteMessageConversionError::FailedToDecode(DecodeErrorWrapper(err))
1189    }
1190}
1191
1192/// The route priority for new IPv4 routes missing a `Priority` NLA or with a
1193/// zero priority.
1194pub const DEFAULT_IPV4_ROUTE_PRIORITY: u32 = 0;
1195/// The route priority for new IPv6 routes missing a `Priority` NLA or with a
1196/// zero priority.
1197pub const DEFAULT_IPV6_ROUTE_PRIORITY: u32 = 1024;
1198
1199fn netlink_priority_to_specified_metric(
1200    prio: Option<NonZeroU32>,
1201    v: IpVersion,
1202) -> fnet_routes::SpecifiedMetric {
1203    // We always use an explicit metric for routes coming from starnix
1204    // processes, unwrapping to the same defaults that Linux uses.
1205    fnet_routes::SpecifiedMetric::ExplicitMetric(match (prio, v) {
1206        (Some(prio), IpVersion::V4 | IpVersion::V6) => prio.get(),
1207        (None, IpVersion::V4) => DEFAULT_IPV4_ROUTE_PRIORITY,
1208        (None, IpVersion::V6) => DEFAULT_IPV6_ROUTE_PRIORITY,
1209    })
1210}
1211
1212impl<I: Ip> From<NewRouteArgs<I>> for fnet_routes_ext::Route<I> {
1213    fn from(new_route_args: NewRouteArgs<I>) -> Self {
1214        match new_route_args {
1215            NewRouteArgs::Unicast(args) => {
1216                let UnicastNewRouteArgs { subnet, target, priority, table: _ } = args;
1217                let metric = netlink_priority_to_specified_metric(priority, I::VERSION);
1218                fnet_routes_ext::Route {
1219                    destination: subnet,
1220                    action: fnet_routes_ext::RouteAction::Forward(target),
1221                    properties: fnet_routes_ext::RouteProperties {
1222                        specified_properties: fnet_routes_ext::SpecifiedRouteProperties { metric },
1223                    },
1224                }
1225            }
1226        }
1227    }
1228}
1229
1230// Implement conversions from [`NetlinkRouteMessage`] to
1231// [`fnet_routes_ext::Route<I>`]. This is infallible, as all
1232// [`NetlinkRouteMessage`]s in this module are created
1233// with the expected NLAs and proper formatting.
1234impl<I: Ip> From<NetlinkRouteMessage> for fnet_routes_ext::Route<I> {
1235    fn from(netlink_route_message: NetlinkRouteMessage) -> Self {
1236        let NetlinkRouteMessage(route_message) = netlink_route_message;
1237        let RouteNlaView { subnet, metric, interface_id, next_hop } =
1238            view_existing_route_nlas(&route_message);
1239        let subnet = match subnet {
1240            Some(subnet) => crate::netlink_packet::ip_addr_from_route::<I>(&subnet)
1241                .expect("should be valid addr"),
1242            None => I::UNSPECIFIED_ADDRESS,
1243        };
1244
1245        let subnet = Subnet::new(subnet, route_message.header.destination_prefix_length)
1246            .expect("should be valid subnet");
1247
1248        let next_hop = match next_hop {
1249            Some(next_hop) => crate::netlink_packet::ip_addr_from_route::<I>(&next_hop)
1250                .map(SpecifiedAddr::new)
1251                .expect("should be valid addr"),
1252            None => None,
1253        };
1254
1255        fnet_routes_ext::Route {
1256            destination: subnet,
1257            action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
1258                outbound_interface: *interface_id as u64,
1259                next_hop,
1260            }),
1261            properties: fnet_routes_ext::RouteProperties {
1262                specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
1263                    metric: fnet_routes::SpecifiedMetric::ExplicitMetric(*metric),
1264                },
1265            },
1266        }
1267    }
1268}
1269
1270/// A view into the NLA's held by a `NetlinkRouteMessage`.
1271struct RouteNlaView<'a> {
1272    subnet: Option<&'a RouteAddress>,
1273    metric: &'a u32,
1274    interface_id: &'a u32,
1275    next_hop: Option<&'a RouteAddress>,
1276}
1277
1278/// Extract and return a view of the Nlas from the given route.
1279///
1280/// # Panics
1281///
1282/// Panics if:
1283///   * The route is missing any of the following Nlas: `Oif`, `Priority`,
1284///     or `Destination` (only when the destination_prefix_len is non-zero).
1285///   * Any Nla besides `Oif`, `Priority`, `Gateway`, `Destination`, `Table`
1286///     is provided.
1287///   * Any Nla is provided multiple times.
1288/// Note that this fn is so opinionated about the provided NLAs because it is
1289/// intended to be used on existing routes, which are constructed by the module
1290/// meaning the exact set of NLAs is known.
1291fn view_existing_route_nlas(route: &RouteMessage) -> RouteNlaView<'_> {
1292    let mut subnet = None;
1293    let mut metric = None;
1294    let mut interface_id = None;
1295    let mut next_hop = None;
1296    let mut table = None;
1297    route.attributes.iter().for_each(|nla| match nla {
1298        RouteAttribute::Destination(dst) => {
1299            assert_eq!(subnet, None, "existing route has multiple `Destination` NLAs");
1300            subnet = Some(dst)
1301        }
1302        RouteAttribute::Priority(p) => {
1303            assert_eq!(metric, None, "existing route has multiple `Priority` NLAs");
1304            metric = Some(p)
1305        }
1306        RouteAttribute::Oif(interface) => {
1307            assert_eq!(interface_id, None, "existing route has multiple `Oif` NLAs");
1308            interface_id = Some(interface)
1309        }
1310        RouteAttribute::Gateway(gateway) => {
1311            assert_eq!(next_hop, None, "existing route has multiple `Gateway` NLAs");
1312            next_hop = Some(gateway)
1313        }
1314        RouteAttribute::Table(t) => {
1315            assert_eq!(table, None, "existing route has multiple `Table` NLAs");
1316            table = Some(t)
1317        }
1318        nla => panic!("existing route has unexpected NLA: {nla:?}"),
1319    });
1320    if subnet.is_none() {
1321        assert_eq!(
1322            route.header.destination_prefix_length, 0,
1323            "existing route without `Destination` NLA must be a default route"
1324        );
1325    }
1326
1327    RouteNlaView {
1328        subnet,
1329        metric: metric.expect("existing routes must have a `Priority` NLA"),
1330        interface_id: interface_id.expect("existing routes must have an `Oif` NLA"),
1331        next_hop,
1332    }
1333}
1334
1335/// Select a route for deletion, based on the given deletion arguments.
1336///
1337/// Note that Linux and Fuchsia differ on how to specify a route for deletion.
1338/// Linux is more flexible and allows you specify matchers as arguments, where
1339/// Fuchsia requires that you exactly specify the route. Here, Linux's matchers
1340/// are provided in `deletion_args`; Many of the matchers are optional, and an
1341/// existing route matches the arguments if all provided arguments are equal to
1342/// the values held by the route. If multiple routes match the arguments, the
1343/// route with the lowest metric is selected.
1344fn select_route_for_deletion<
1345    I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1346>(
1347    fidl_route_map: &FidlRouteMap<I>,
1348    route_tables: &RouteTableMap<I>,
1349    deletion_args: DelRouteArgs<I>,
1350) -> Option<NetlinkRouteMessage> {
1351    // Find the set of candidate routes, mapping them to tuples (route, metric).
1352    fidl_route_map
1353        .iter_messages(
1354            route_tables,
1355            match deletion_args {
1356                DelRouteArgs::Unicast(args) => args.table.into(),
1357            },
1358        )
1359        .filter_map(|route: NetlinkRouteMessage| {
1360            let NetlinkRouteMessage(existing_route) = &route;
1361            let UnicastDelRouteArgs { subnet, outbound_interface, next_hop, priority, table: _ } =
1362                match deletion_args {
1363                    DelRouteArgs::Unicast(args) => args,
1364                };
1365            if subnet.prefix() != existing_route.header.destination_prefix_length {
1366                return None;
1367            }
1368            let RouteNlaView {
1369                subnet: existing_subnet,
1370                metric: existing_metric,
1371                interface_id: existing_interface,
1372                next_hop: existing_next_hop,
1373            } = view_existing_route_nlas(existing_route);
1374            let subnet_matches = existing_subnet.map_or_else(
1375                || !subnet.network().is_specified(),
1376                |dst| {
1377                    crate::netlink_packet::ip_addr_from_route::<I>(&dst)
1378                        .is_ok_and(|dst: I::Addr| dst == subnet.network())
1379                },
1380            );
1381            let metric_matches = priority.map_or(true, |p| p.get() == *existing_metric);
1382            let interface_matches =
1383                outbound_interface.map_or(true, |i| i.get() == (*existing_interface) as u64);
1384            let next_hop_matches = next_hop.map_or(true, |n| {
1385                existing_next_hop.map_or(false, |e| {
1386                    crate::netlink_packet::ip_addr_from_route::<I>(&e)
1387                        .is_ok_and(|e: I::Addr| e == n.get())
1388                })
1389            });
1390
1391            let existing_metric = *existing_metric;
1392
1393            if subnet_matches && metric_matches && interface_matches && next_hop_matches {
1394                Some((route, existing_metric))
1395            } else {
1396                None
1397            }
1398        })
1399        // Select the route with the lowest metric
1400        .min_by(|(_route1, metric1), (_route2, metric2)| metric1.cmp(metric2))
1401        .map(|(route, _metric)| route)
1402}
1403
1404#[cfg(test)]
1405mod tests {
1406    use super::*;
1407
1408    use std::collections::{HashMap, VecDeque};
1409    use std::pin::pin;
1410    use std::sync::atomic::{AtomicU32, Ordering};
1411
1412    use fidl::endpoints::{ControlHandle, RequestStream, ServerEnd};
1413    use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
1414    use fidl_fuchsia_net_routes as fnet_routes;
1415    use fidl_fuchsia_net_routes_admin as fnet_routes_admin;
1416    use fidl_fuchsia_net_routes_ext::Responder as _;
1417    use fidl_fuchsia_net_routes_ext::admin::{RouteSetRequest, RouteTableRequest};
1418
1419    use assert_matches::assert_matches;
1420    use fuchsia_async as fasync;
1421    use futures::channel::mpsc;
1422    use futures::future::{Future, FutureExt as _};
1423    use futures::stream::TryStreamExt as _;
1424    use futures::{SinkExt as _, Stream};
1425    use ip_test_macro::ip_test;
1426    use linux_uapi::rtnetlink_groups_RTNLGRP_LINK;
1427    use net_declare::{net_ip_v4, net_ip_v6, net_subnet_v4, net_subnet_v6};
1428    use net_types::SpecifiedAddr;
1429    use net_types::ip::{GenericOverIp, IpInvariant, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
1430    use netlink_packet_core::NetlinkPayload;
1431    use test_case::test_case;
1432
1433    use crate::client::AsyncWorkItem;
1434    use crate::interfaces::testutil::FakeInterfacesHandler;
1435    use crate::messaging::testutil::{FakeSender, SentMessage};
1436    use crate::route_eventloop::{EventLoopComponent, Optional, Required};
1437
1438    const V4_SUB1: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.0/32");
1439    const V4_SUB2: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.1/32");
1440    const V4_SUB3: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.0/24");
1441    const V4_DFLT: Subnet<Ipv4Addr> = net_subnet_v4!("0.0.0.0/0");
1442    const V4_NEXTHOP1: Ipv4Addr = net_ip_v4!("192.0.2.1");
1443    const V4_NEXTHOP2: Ipv4Addr = net_ip_v4!("192.0.2.2");
1444
1445    const V6_SUB1: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::/128");
1446    const V6_SUB2: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::1/128");
1447    const V6_SUB3: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::/64");
1448    const V6_DFLT: Subnet<Ipv6Addr> = net_subnet_v6!("::/0");
1449    const V6_NEXTHOP1: Ipv6Addr = net_ip_v6!("2001:db8::1");
1450    const V6_NEXTHOP2: Ipv6Addr = net_ip_v6!("2001:db8::2");
1451
1452    const DEV1: u32 = 1;
1453    const DEV2: u32 = 2;
1454
1455    const METRIC1: u32 = 1;
1456    const METRIC2: u32 = 100;
1457    const METRIC3: u32 = 9999;
1458    const TEST_SEQUENCE_NUMBER: u32 = 1234;
1459    const MANAGED_ROUTE_TABLE_ID: u32 = 5678;
1460    const MANAGED_ROUTE_TABLE_INDEX: NetlinkRouteTableIndex =
1461        NetlinkRouteTableIndex::new(MANAGED_ROUTE_TABLE_ID);
1462    const MAIN_FIDL_TABLE_ID: fnet_routes_ext::TableId = fnet_routes_ext::TableId::new(0);
1463    const OTHER_FIDL_TABLE_ID: fnet_routes_ext::TableId = fnet_routes_ext::TableId::new(1);
1464
1465    fn create_installed_route<I: Ip>(
1466        subnet: Subnet<I::Addr>,
1467        next_hop: Option<I::Addr>,
1468        interface_id: u64,
1469        metric: u32,
1470        table_id: fnet_routes_ext::TableId,
1471    ) -> fnet_routes_ext::InstalledRoute<I> {
1472        fnet_routes_ext::InstalledRoute::<I> {
1473            route: fnet_routes_ext::Route {
1474                destination: subnet,
1475                action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget::<I> {
1476                    outbound_interface: interface_id,
1477                    next_hop: next_hop.map(|next_hop| SpecifiedAddr::new(next_hop)).flatten(),
1478                }),
1479                properties: fnet_routes_ext::RouteProperties {
1480                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
1481                        metric: fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1482                    },
1483                },
1484            },
1485            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
1486            table_id,
1487        }
1488    }
1489
1490    fn create_netlink_route_message<I: Ip>(
1491        destination_prefix_length: u8,
1492        table: NetlinkRouteTableIndex,
1493        nlas: Vec<RouteAttribute>,
1494    ) -> NetlinkRouteMessage {
1495        let mut route_header = RouteHeader::default();
1496        let address_family = match I::VERSION {
1497            IpVersion::V4 => AddressFamily::Inet,
1498            IpVersion::V6 => AddressFamily::Inet6,
1499        }
1500        .try_into()
1501        .expect("should fit into u8");
1502        route_header.address_family = address_family;
1503        route_header.destination_prefix_length = destination_prefix_length;
1504        route_header.kind = RouteType::Unicast;
1505        route_header.protocol = RouteProtocol::Kernel;
1506
1507        let (table_u8, _) = get_table_u8_and_nla_from_key(table);
1508        route_header.table = table_u8;
1509
1510        let mut route_message = RouteMessage::default();
1511        route_message.header = route_header;
1512        route_message.attributes = nlas;
1513
1514        NetlinkRouteMessage(route_message)
1515    }
1516
1517    fn create_nlas<I: Ip>(
1518        destination: Option<Subnet<I::Addr>>,
1519        next_hop: Option<I::Addr>,
1520        outgoing_interface_id: u32,
1521        metric: u32,
1522        table: Option<u32>,
1523    ) -> Vec<RouteAttribute> {
1524        let mut nlas = vec![];
1525
1526        let family = match I::VERSION {
1527            IpVersion::V4 => AddressFamily::Inet,
1528            IpVersion::V6 => AddressFamily::Inet6,
1529        };
1530
1531        if let Some(destination) = destination {
1532            let destination_nla = RouteAttribute::Destination(
1533                RouteAddress::parse(family, destination.network().bytes()).unwrap(),
1534            );
1535            nlas.push(destination_nla);
1536        }
1537
1538        let oif_nla = RouteAttribute::Oif(outgoing_interface_id);
1539        nlas.push(oif_nla);
1540
1541        if let Some(next_hop) = next_hop {
1542            let bytes = RouteAddress::parse(family, next_hop.bytes()).unwrap();
1543            let gateway_nla = RouteAttribute::Gateway(bytes);
1544            nlas.push(gateway_nla);
1545        }
1546
1547        let priority_nla = RouteAttribute::Priority(metric);
1548        nlas.push(priority_nla);
1549
1550        if let Some(t) = table {
1551            let table_nla = RouteAttribute::Table(t);
1552            nlas.push(table_nla);
1553        }
1554        nlas
1555    }
1556
1557    #[ip_test(I)]
1558    #[test_case(MAIN_ROUTE_TABLE_INDEX, MAIN_FIDL_TABLE_ID)]
1559    #[test_case(MANAGED_ROUTE_TABLE_INDEX, OTHER_FIDL_TABLE_ID)]
1560    #[fuchsia::test]
1561    async fn handles_route_watcher_event<
1562        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1563    >(
1564        netlink_id: NetlinkRouteTableIndex,
1565        fidl_id: fnet_routes_ext::TableId,
1566    ) {
1567        let scope = fasync::Scope::new();
1568        let (subnet, next_hop) =
1569            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1570        let installed_route1: fnet_routes_ext::InstalledRoute<I> =
1571            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, fidl_id);
1572        let installed_route2: fnet_routes_ext::InstalledRoute<I> =
1573            create_installed_route(subnet, Some(next_hop), DEV2.into(), METRIC2, fidl_id);
1574
1575        let add_event1 = fnet_routes_ext::Event::Added(installed_route1);
1576        let add_event2 = fnet_routes_ext::Event::Added(installed_route2);
1577        let remove_event = fnet_routes_ext::Event::Removed(installed_route1);
1578
1579        let expected_route_message1: NetlinkRouteMessage =
1580            NetlinkRouteMessage::try_from_installed_route(installed_route1, netlink_id).unwrap();
1581        let expected_route_message2: NetlinkRouteMessage =
1582            NetlinkRouteMessage::try_from_installed_route(installed_route2, netlink_id).unwrap();
1583
1584        // Set up two fake clients: one is a member of the route multicast group.
1585        let (right_group, wrong_group) = match I::VERSION {
1586            IpVersion::V4 => (
1587                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1588                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1589            ),
1590            IpVersion::V6 => (
1591                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1592                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1593            ),
1594        };
1595
1596        let (mut right_sink, right_client, async_work_drain_task) =
1597            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1598                crate::client::testutil::CLIENT_ID_1,
1599                [right_group],
1600            );
1601        let _join_handle = scope.spawn(async_work_drain_task);
1602        let (mut wrong_sink, wrong_client, async_work_drain_task) =
1603            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1604                crate::client::testutil::CLIENT_ID_2,
1605                [wrong_group],
1606            );
1607        let _join_handle = scope.spawn(async_work_drain_task);
1608        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1609        route_clients.add_client(right_client);
1610        route_clients.add_client(wrong_client);
1611
1612        let (route_set_proxy, _route_set_server_end) =
1613            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1614        let (route_table_proxy, _route_table_server_end) =
1615            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1616        let (unmanaged_route_set_proxy, _server_end) =
1617            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1618        let (route_table_provider, _server_end) =
1619            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1620
1621        let mut route_table = RouteTableMap::new(
1622            route_table_proxy.clone(),
1623            MAIN_FIDL_TABLE_ID,
1624            unmanaged_route_set_proxy,
1625            route_table_provider,
1626        );
1627        let mut fidl_route_map = FidlRouteMap::<I>::default();
1628
1629        match netlink_id {
1630            MAIN_ROUTE_TABLE_INDEX => {}
1631            MANAGED_ROUTE_TABLE_INDEX => {
1632                route_table.insert(
1633                    netlink_id,
1634                    RouteTable::Managed(ManagedRouteTable {
1635                        route_table_proxy,
1636                        route_set_proxy,
1637                        fidl_table_id: OTHER_FIDL_TABLE_ID,
1638                        rule_set_authenticated: false,
1639                    }),
1640                );
1641            }
1642            _ => panic!("unexpected netlink id: {netlink_id:?}"),
1643        }
1644
1645        assert_eq!(fidl_route_map.iter_messages(&route_table, netlink_id).count(), 0);
1646        assert_eq!(&right_sink.take_messages()[..], &[]);
1647        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1648
1649        assert_eq!(
1650            handle_route_watcher_event(
1651                &mut route_table,
1652                &mut fidl_route_map,
1653                &route_clients,
1654                add_event1,
1655            ),
1656            RouteEventOutcome::Noop,
1657        );
1658        assert_eq!(
1659            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1660            HashSet::from_iter([expected_route_message1.clone()])
1661        );
1662        assert_eq!(
1663            &right_sink.take_messages()[..],
1664            &[SentMessage::multicast(
1665                expected_route_message1
1666                    .clone()
1667                    .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
1668                right_group
1669            )]
1670        );
1671        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1672
1673        assert_eq!(
1674            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1675            HashSet::from_iter([expected_route_message1.clone()])
1676        );
1677        assert_eq!(&right_sink.take_messages()[..], &[]);
1678        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1679
1680        // Adding a different route should result in an addition.
1681        assert_eq!(
1682            handle_route_watcher_event(
1683                &mut route_table,
1684                &mut fidl_route_map,
1685                &route_clients,
1686                add_event2,
1687            ),
1688            RouteEventOutcome::Noop
1689        );
1690        assert_eq!(
1691            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1692            HashSet::from_iter([expected_route_message1.clone(), expected_route_message2.clone()])
1693        );
1694        assert_eq!(
1695            &right_sink.take_messages()[..],
1696            &[SentMessage::multicast(
1697                expected_route_message2
1698                    .clone()
1699                    .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
1700                right_group
1701            )]
1702        );
1703        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1704
1705        assert_eq!(
1706            handle_route_watcher_event(
1707                &mut route_table,
1708                &mut fidl_route_map,
1709                &route_clients,
1710                remove_event,
1711            ),
1712            RouteEventOutcome::Noop
1713        );
1714        assert_eq!(
1715            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1716            HashSet::from_iter([expected_route_message2.clone()])
1717        );
1718        assert_eq!(
1719            &right_sink.take_messages()[..],
1720            &[SentMessage::multicast(
1721                expected_route_message1.clone().into_rtnl_del_route(),
1722                right_group
1723            )]
1724        );
1725        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1726
1727        assert_eq!(
1728            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1729            HashSet::from_iter([expected_route_message2.clone()])
1730        );
1731        assert_eq!(&right_sink.take_messages()[..], &[]);
1732        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1733        drop(route_clients);
1734        scope.join().await;
1735    }
1736
1737    // Test handling of watcher events for routes in unmanaged tables.
1738    #[ip_test(I, test = false)]
1739    #[fuchsia::test]
1740    async fn handles_route_watcher_event_unmanaged_route_table<
1741        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1742    >() {
1743        let _scope = fasync::Scope::new();
1744        let (subnet, next_hop) =
1745            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1746        let installed_route: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1747            subnet,
1748            Some(next_hop),
1749            DEV1.into(),
1750            METRIC1,
1751            OTHER_FIDL_TABLE_ID,
1752        );
1753        let add_event = fnet_routes_ext::Event::Added(installed_route);
1754        let remove_event = fnet_routes_ext::Event::Removed(installed_route);
1755
1756        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1757        let (route_table_proxy, _route_table_server_end) =
1758            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1759        let (unmanaged_route_set_proxy, _server_end) =
1760            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1761        let (route_table_provider, _server_end) =
1762            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1763
1764        let mut route_table = RouteTableMap::new(
1765            route_table_proxy.clone(),
1766            MAIN_FIDL_TABLE_ID,
1767            unmanaged_route_set_proxy,
1768            route_table_provider,
1769        );
1770        let mut fidl_route_map = FidlRouteMap::<I>::default();
1771
1772        // Process Add message.
1773        assert_eq!(
1774            handle_route_watcher_event(
1775                &mut route_table,
1776                &mut fidl_route_map,
1777                &route_clients,
1778                add_event,
1779            ),
1780            RouteEventOutcome::UpdateStash(AddOrRemoveRoute::Added(installed_route))
1781        );
1782
1783        // Process Remove message.
1784        assert_eq!(
1785            handle_route_watcher_event(
1786                &mut route_table,
1787                &mut fidl_route_map,
1788                &route_clients,
1789                remove_event,
1790            ),
1791            RouteEventOutcome::UpdateStash(AddOrRemoveRoute::Removed(installed_route))
1792        );
1793    }
1794
1795    #[ip_test(I, test = false)]
1796    #[fuchsia::test]
1797    #[should_panic(expected = "Netstack reported an unexpected route event")]
1798    async fn handles_unknown_route_watcher_event<
1799        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1800    >() {
1801        let (route_table_proxy, _route_table_server_end) =
1802            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1803        let (unmanaged_route_set_proxy, _server_end) =
1804            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1805        let (route_table_provider, _server_end) =
1806            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1807        let mut route_table = RouteTableMap::new(
1808            route_table_proxy.clone(),
1809            MAIN_FIDL_TABLE_ID,
1810            unmanaged_route_set_proxy,
1811            route_table_provider,
1812        );
1813        let mut fidl_route_map = FidlRouteMap::<I>::default();
1814        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1815
1816        // Receiving an unknown event should result in a panic.
1817        let _ = handle_route_watcher_event(
1818            &mut route_table,
1819            &mut fidl_route_map,
1820            &route_clients,
1821            fnet_routes_ext::Event::Unknown,
1822        );
1823    }
1824
1825    #[ip_test(I, test = false)]
1826    #[fuchsia::test]
1827    #[should_panic(expected = "Netstack reported the addition of an existing route")]
1828    async fn handles_duplicate_route_watcher_event<
1829        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1830    >() {
1831        let (subnet, next_hop) =
1832            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1833        let table_id = MAIN_FIDL_TABLE_ID;
1834        let installed_route: fnet_routes_ext::InstalledRoute<I> =
1835            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
1836
1837        let (route_table_proxy, _route_table_server_end) =
1838            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1839        let (unmanaged_route_set_proxy, _server_end) =
1840            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1841        let (route_table_provider, _server_end) =
1842            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1843        let mut route_table = RouteTableMap::new(
1844            route_table_proxy.clone(),
1845            MAIN_FIDL_TABLE_ID,
1846            unmanaged_route_set_proxy,
1847            route_table_provider,
1848        );
1849        let mut fidl_route_map = FidlRouteMap::<I>::default();
1850        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1851
1852        // Receiving an add route event multiple times should result in a panic.
1853        for _ in 0..2 {
1854            assert_eq!(
1855                handle_route_watcher_event(
1856                    &mut route_table,
1857                    &mut fidl_route_map,
1858                    &route_clients,
1859                    fnet_routes_ext::Event::Added(installed_route),
1860                ),
1861                RouteEventOutcome::Noop
1862            );
1863        }
1864    }
1865
1866    #[ip_test(I, test = false)]
1867    #[fuchsia::test]
1868    #[should_panic(expected = "Netstack reported the removal of an unknown route")]
1869    async fn handles_remove_nonexisting_route_watcher_event<
1870        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1871    >() {
1872        let (subnet, next_hop) =
1873            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1874        let table_id = MAIN_FIDL_TABLE_ID;
1875        let installed_route: fnet_routes_ext::InstalledRoute<I> =
1876            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
1877
1878        let (route_table_proxy, _route_table_server_end) =
1879            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1880        let (unmanaged_route_set_proxy, _server_end) =
1881            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1882        let (route_table_provider, _server_end) =
1883            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1884        let mut route_table = RouteTableMap::new(
1885            route_table_proxy.clone(),
1886            MAIN_FIDL_TABLE_ID,
1887            unmanaged_route_set_proxy,
1888            route_table_provider,
1889        );
1890        let mut fidl_route_map = FidlRouteMap::<I>::default();
1891        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1892
1893        // Receiving a remove event for an unknown route should result in a panic.
1894        let _ = handle_route_watcher_event(
1895            &mut route_table,
1896            &mut fidl_route_map,
1897            &route_clients,
1898            fnet_routes_ext::Event::Removed(installed_route),
1899        );
1900    }
1901
1902    #[ip_test(I, test = false)]
1903    #[fuchsia::test]
1904    async fn handle_route_watcher_event_two_routesets<
1905        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1906    >() {
1907        let scope = fasync::Scope::new();
1908        let (subnet, next_hop) =
1909            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1910
1911        let installed_route1: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1912            subnet,
1913            Some(next_hop),
1914            DEV1.into(),
1915            METRIC1,
1916            OTHER_FIDL_TABLE_ID,
1917        );
1918        let installed_route2: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1919            subnet,
1920            Some(next_hop),
1921            DEV2.into(),
1922            METRIC2,
1923            MAIN_FIDL_TABLE_ID,
1924        );
1925
1926        let add_events1 = [
1927            fnet_routes_ext::Event::Added(fnet_routes_ext::InstalledRoute {
1928                table_id: MAIN_FIDL_TABLE_ID,
1929                ..installed_route1
1930            }),
1931            fnet_routes_ext::Event::Added(installed_route1),
1932        ];
1933        let add_event2 = fnet_routes_ext::Event::Added(installed_route2);
1934        let remove_event = fnet_routes_ext::Event::Removed(installed_route1);
1935
1936        // Due to the double-writing of routes into managed tables and into the main tables, we need
1937        // to account for notifications for both routes being added.
1938        let expected_route_message1_unmanaged =
1939            NetlinkRouteMessage::try_from_installed_route(installed_route1, MAIN_ROUTE_TABLE_INDEX)
1940                .unwrap();
1941        let expected_route_message1_managed = NetlinkRouteMessage::try_from_installed_route(
1942            installed_route1,
1943            MANAGED_ROUTE_TABLE_INDEX,
1944        )
1945        .unwrap();
1946        let expected_route_message2: NetlinkRouteMessage =
1947            NetlinkRouteMessage::try_from_installed_route(installed_route2, MAIN_ROUTE_TABLE_INDEX)
1948                .unwrap();
1949
1950        // Set up two fake clients: one is a member of the route multicast group.
1951        let (right_group, wrong_group) = match I::VERSION {
1952            IpVersion::V4 => (
1953                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1954                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1955            ),
1956            IpVersion::V6 => (
1957                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1958                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1959            ),
1960        };
1961        let (mut right_sink, right_client, async_work_drain_task) =
1962            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1963                crate::client::testutil::CLIENT_ID_1,
1964                [right_group],
1965            );
1966        let _join_handle = scope.spawn(async_work_drain_task);
1967        let (mut wrong_sink, wrong_client, async_work_drain_task) =
1968            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1969                crate::client::testutil::CLIENT_ID_2,
1970                [wrong_group],
1971            );
1972        let _join_handle = scope.spawn(async_work_drain_task);
1973        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1974        route_clients.add_client(right_client);
1975        route_clients.add_client(wrong_client);
1976
1977        let (main_route_table_proxy, _route_table_server_end) =
1978            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1979        let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
1980            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1981        let (route_table_proxy, _route_table_server_end) =
1982            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1983        let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1984        let (route_table_provider, _server_end) =
1985            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1986
1987        let mut route_table = RouteTableMap::new(
1988            main_route_table_proxy,
1989            MAIN_FIDL_TABLE_ID,
1990            unmanaged_route_set_proxy,
1991            route_table_provider,
1992        );
1993        route_table.insert(
1994            MANAGED_ROUTE_TABLE_INDEX,
1995            RouteTable::Managed(ManagedRouteTable {
1996                route_set_proxy,
1997                route_table_proxy,
1998                fidl_table_id: OTHER_FIDL_TABLE_ID,
1999                rule_set_authenticated: false,
2000            }),
2001        );
2002
2003        let mut fidl_route_map = FidlRouteMap::<I>::default();
2004
2005        // Send the first of the added-route events (corresponding to the route having been added
2006        // to the main FIDL table).
2007        assert_eq!(
2008            handle_route_watcher_event(
2009                &mut route_table,
2010                &mut fidl_route_map,
2011                &route_clients,
2012                add_events1[0],
2013            ),
2014            RouteEventOutcome::Noop
2015        );
2016
2017        // Shouldn't be counted yet, as we haven't seen the route added to its own table yet.
2018        assert_eq!(
2019            &fidl_route_map
2020                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2021                .collect::<HashSet<_>>(),
2022            &HashSet::new()
2023        );
2024
2025        // Now send the other event (corresponding to the route having been also added to the real
2026        // FIDL table).
2027        assert_eq!(
2028            handle_route_watcher_event(
2029                &mut route_table,
2030                &mut fidl_route_map,
2031                &route_clients,
2032                add_events1[1],
2033            ),
2034            RouteEventOutcome::Noop
2035        );
2036
2037        // Now the route should have been added.
2038        assert_eq!(
2039            &fidl_route_map
2040                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2041                .chain(fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX))
2042                .collect::<HashSet<_>>(),
2043            &HashSet::from_iter([
2044                expected_route_message1_unmanaged.clone(),
2045                expected_route_message1_managed.clone()
2046            ])
2047        );
2048        assert_eq!(
2049            &right_sink.take_messages()[..],
2050            &[expected_route_message1_unmanaged.clone(), expected_route_message1_managed.clone()]
2051                .map(|message| SentMessage::multicast(
2052                    message.clone().into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
2053                    right_group
2054                ))
2055        );
2056        assert_eq!(&wrong_sink.take_messages()[..], &[]);
2057
2058        // Ensure that an unmanaged Route can be observed and added to the
2059        // unmanaged route set (signified by no pending request).
2060        assert_eq!(
2061            handle_route_watcher_event(
2062                &mut route_table,
2063                &mut fidl_route_map,
2064                &route_clients,
2065                add_event2,
2066            ),
2067            RouteEventOutcome::Noop
2068        );
2069
2070        // Should also contain the route from before.
2071        assert_eq!(
2072            &fidl_route_map
2073                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2074                .chain(fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX))
2075                .collect::<HashSet<_>>(),
2076            &HashSet::from_iter([
2077                expected_route_message1_unmanaged.clone(),
2078                expected_route_message1_managed.clone(),
2079                expected_route_message2.clone()
2080            ])
2081        );
2082
2083        // However, netlink won't send any notifications about unmanaged routes.
2084        assert_eq!(
2085            &right_sink.take_messages()[..],
2086            &[SentMessage::multicast(
2087                expected_route_message2
2088                    .clone()
2089                    .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
2090                right_group
2091            )]
2092        );
2093        assert_eq!(&wrong_sink.take_messages()[..], &[]);
2094
2095        // Notify of the route being removed from the managed table.
2096        assert_eq!(
2097            handle_route_watcher_event(
2098                &mut route_table,
2099                &mut fidl_route_map,
2100                &route_clients,
2101                remove_event,
2102            ),
2103            RouteEventOutcome::Cleanup(TableNeedsCleanup(
2104                OTHER_FIDL_TABLE_ID,
2105                MANAGED_ROUTE_TABLE_INDEX
2106            ))
2107        );
2108        assert_eq!(
2109            &fidl_route_map
2110                .iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX)
2111                .collect::<HashSet<_>>(),
2112            &HashSet::from_iter([
2113                expected_route_message1_unmanaged.clone(),
2114                expected_route_message2.clone()
2115            ])
2116        );
2117        assert_eq!(
2118            fidl_route_map
2119                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2120                .collect::<HashSet<_>>(),
2121            HashSet::new()
2122        );
2123        assert_eq!(
2124            &right_sink.take_messages()[..],
2125            &[SentMessage::multicast(
2126                expected_route_message1_managed.clone().into_rtnl_del_route(),
2127                right_group
2128            )]
2129        );
2130        assert_eq!(&wrong_sink.take_messages()[..], &[]);
2131        drop(route_clients);
2132        scope.join().await;
2133    }
2134
2135    #[test_case(V4_SUB1, V4_NEXTHOP1)]
2136    #[test_case(V6_SUB1, V6_NEXTHOP1)]
2137    #[test_case(net_subnet_v4!("0.0.0.0/0"), net_ip_v4!("0.0.0.1"))]
2138    #[test_case(net_subnet_v6!("::/0"), net_ip_v6!("::1"))]
2139    fn test_netlink_route_message_try_from_installed_route<A: IpAddress>(
2140        subnet: Subnet<A>,
2141        next_hop: A,
2142    ) {
2143        netlink_route_message_conversion_helper::<A::Version>(subnet, next_hop);
2144    }
2145
2146    fn netlink_route_message_conversion_helper<I: Ip>(subnet: Subnet<I::Addr>, next_hop: I::Addr) {
2147        let installed_route = create_installed_route::<I>(
2148            subnet,
2149            Some(next_hop),
2150            DEV1.into(),
2151            METRIC1,
2152            MAIN_FIDL_TABLE_ID,
2153        );
2154        let prefix_length = subnet.prefix();
2155        let subnet = if prefix_length > 0 { Some(subnet) } else { None };
2156        let nlas = create_nlas::<I>(subnet, Some(next_hop), DEV1, METRIC1, None);
2157        let expected =
2158            create_netlink_route_message::<I>(prefix_length, MAIN_ROUTE_TABLE_INDEX, nlas);
2159
2160        let actual =
2161            NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX)
2162                .unwrap();
2163        assert_eq!(actual, expected);
2164    }
2165
2166    #[test_case(V4_SUB1)]
2167    #[test_case(V6_SUB1)]
2168    fn test_non_forward_route_conversion<A: IpAddress>(subnet: Subnet<A>) {
2169        let installed_route = fnet_routes_ext::InstalledRoute::<A::Version> {
2170            route: fnet_routes_ext::Route {
2171                destination: subnet,
2172                action: fnet_routes_ext::RouteAction::Unknown,
2173                properties: fnet_routes_ext::RouteProperties {
2174                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
2175                        metric: fnet_routes::SpecifiedMetric::ExplicitMetric(METRIC1),
2176                    },
2177                },
2178            },
2179            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric: METRIC1 },
2180            // TODO(https://fxbug.dev/336382905): The tests should use the ID.
2181            table_id: MAIN_FIDL_TABLE_ID,
2182        };
2183
2184        let actual: Result<NetlinkRouteMessage, NetlinkRouteMessageConversionError> =
2185            NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX);
2186        assert_eq!(actual, Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding));
2187    }
2188
2189    #[fuchsia::test]
2190    fn test_oversized_interface_id_route_conversion() {
2191        let invalid_interface_id = (u32::MAX as u64) + 1;
2192        let installed_route: fnet_routes_ext::InstalledRoute<Ipv4> = create_installed_route(
2193            V4_SUB1,
2194            Some(V4_NEXTHOP1),
2195            invalid_interface_id,
2196            Default::default(),
2197            MAIN_FIDL_TABLE_ID,
2198        );
2199
2200        let actual: Result<NetlinkRouteMessage, NetlinkRouteMessageConversionError> =
2201            NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX);
2202        assert_eq!(
2203            actual,
2204            Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(invalid_interface_id))
2205        );
2206    }
2207
2208    #[test]
2209    fn test_into_rtnl_new_route_is_serializable() {
2210        let route = create_netlink_route_message::<Ipv4>(0, MAIN_ROUTE_TABLE_INDEX, vec![]);
2211        let new_route_message = route.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false);
2212        let mut buf = vec![0; new_route_message.buffer_len()];
2213        // Serialize will panic if `new_route_message` is malformed.
2214        new_route_message.serialize(&mut buf);
2215    }
2216
2217    #[test]
2218    fn test_into_rtnl_del_route_is_serializable() {
2219        let route = create_netlink_route_message::<Ipv6>(0, MAIN_ROUTE_TABLE_INDEX, vec![]);
2220        let del_route_message = route.into_rtnl_del_route();
2221        let mut buf = vec![0; del_route_message.buffer_len()];
2222        // Serialize will panic if `del_route_message` is malformed.
2223        del_route_message.serialize(&mut buf);
2224    }
2225
2226    enum OnlyRoutes {}
2227    impl crate::route_eventloop::EventLoopSpec for OnlyRoutes {
2228        type InterfacesProxy = Required;
2229        type InterfacesHandler = Required;
2230        type RouteClients = Required;
2231
2232        // To avoid needing a different spec for V4 and V6 tests, just make both routes optional --
2233        // we're fine with panicking in tests anyway.
2234        type V4RoutesState = Optional;
2235        type V6RoutesState = Optional;
2236        type V4RoutesSetProvider = Optional;
2237        type V6RoutesSetProvider = Optional;
2238        type V4RouteTableProvider = Optional;
2239        type V6RouteTableProvider = Optional;
2240        type InterfacesStateProxy = Optional;
2241
2242        type InterfacesWorker = Optional;
2243        type RoutesV4Worker = Optional;
2244        type RoutesV6Worker = Optional;
2245        type RuleV4Worker = Optional;
2246        type RuleV6Worker = Optional;
2247        type NduseroptWorker = Optional;
2248        type NeighborWorker = Optional;
2249    }
2250
2251    struct Setup<W, R> {
2252        pub event_loop_inputs: crate::route_eventloop::EventLoopInputs<
2253            FakeInterfacesHandler,
2254            FakeSender<RouteNetlinkMessage>,
2255            OnlyRoutes,
2256        >,
2257        pub watcher_stream: W,
2258        pub route_sets: R,
2259        pub interfaces_request_stream: fnet_root::InterfacesRequestStream,
2260        pub request_sink:
2261            mpsc::Sender<crate::route_eventloop::UnifiedRequest<FakeSender<RouteNetlinkMessage>>>,
2262        pub async_work_sink: mpsc::UnboundedSender<AsyncWorkItem<NetlinkRoute>>,
2263    }
2264
2265    fn setup_with_route_clients_yielding_admin_server_ends<
2266        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2267    >(
2268        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2269    ) -> Setup<
2270        impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2271        (ServerEnd<I::RouteTableMarker>, ServerEnd<I::RouteTableProviderMarker>),
2272    > {
2273        let (interfaces_handler, _interfaces_handler_sink) = FakeInterfacesHandler::new();
2274        let (request_sink, request_stream) = mpsc::channel(1);
2275        let (interfaces_proxy, interfaces) =
2276            fidl::endpoints::create_proxy::<fnet_root::InterfacesMarker>();
2277        let (async_work_sink, async_work_receiver) = mpsc::unbounded();
2278
2279        #[derive(GenericOverIp)]
2280        #[generic_over_ip(I, Ip)]
2281        struct ServerEnds<
2282            I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2283        > {
2284            routes_state: ServerEnd<I::StateMarker>,
2285            routes_set_provider: ServerEnd<I::RouteTableMarker>,
2286            route_table_provider: ServerEnd<I::RouteTableProviderMarker>,
2287        }
2288
2289        let base_inputs = crate::route_eventloop::EventLoopInputs {
2290            interfaces_handler: EventLoopComponent::Present(interfaces_handler),
2291            route_clients: EventLoopComponent::Present(route_clients),
2292            interfaces_proxy: EventLoopComponent::Present(interfaces_proxy),
2293            async_work_receiver,
2294
2295            interfaces_state_proxy: EventLoopComponent::Absent(Optional),
2296            v4_routes_state: EventLoopComponent::Absent(Optional),
2297            v6_routes_state: EventLoopComponent::Absent(Optional),
2298            v4_main_route_table: EventLoopComponent::Absent(Optional),
2299            v6_main_route_table: EventLoopComponent::Absent(Optional),
2300            v4_route_table_provider: EventLoopComponent::Absent(Optional),
2301            v6_route_table_provider: EventLoopComponent::Absent(Optional),
2302            v4_rule_table: EventLoopComponent::Absent(Optional),
2303            v6_rule_table: EventLoopComponent::Absent(Optional),
2304            ndp_option_watcher_provider: EventLoopComponent::Absent(Optional),
2305            neighbors_view: EventLoopComponent::Absent(Optional),
2306            neighbors_controller: EventLoopComponent::Absent(Optional),
2307
2308            unified_request_stream: request_stream,
2309        };
2310
2311        let (IpInvariant(inputs), server_ends) = I::map_ip_out(
2312            base_inputs,
2313            |base_inputs| {
2314                let (v4_routes_state, routes_state) =
2315                    fidl::endpoints::create_proxy::<fnet_routes::StateV4Marker>();
2316                let (v4_main_route_table, routes_set_provider) =
2317                    fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV4Marker>();
2318                let (v4_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2319                    fnet_routes_admin::RouteTableProviderV4Marker,
2320                >();
2321                let inputs = crate::route_eventloop::EventLoopInputs {
2322                    v4_routes_state: EventLoopComponent::Present(v4_routes_state),
2323                    v4_main_route_table: EventLoopComponent::Present(v4_main_route_table),
2324                    v4_route_table_provider: EventLoopComponent::Present(v4_route_table_provider),
2325                    ..base_inputs
2326                };
2327                let server_ends =
2328                    ServerEnds::<Ipv4> { routes_state, routes_set_provider, route_table_provider };
2329                (IpInvariant(inputs), server_ends)
2330            },
2331            |base_inputs| {
2332                let (v6_routes_state, routes_state) =
2333                    fidl::endpoints::create_proxy::<fnet_routes::StateV6Marker>();
2334                let (v6_main_route_table, routes_set_provider) =
2335                    fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV6Marker>();
2336                let (v6_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2337                    fnet_routes_admin::RouteTableProviderV6Marker,
2338                >();
2339                let inputs = crate::route_eventloop::EventLoopInputs {
2340                    v6_routes_state: EventLoopComponent::Present(v6_routes_state),
2341                    v6_main_route_table: EventLoopComponent::Present(v6_main_route_table),
2342                    v6_route_table_provider: EventLoopComponent::Present(v6_route_table_provider),
2343                    ..base_inputs
2344                };
2345                let server_ends =
2346                    ServerEnds::<Ipv6> { routes_state, routes_set_provider, route_table_provider };
2347                (IpInvariant(inputs), server_ends)
2348            },
2349        );
2350
2351        let ServerEnds { routes_state, routes_set_provider, route_table_provider } = server_ends;
2352
2353        let state_stream = routes_state.into_stream().boxed_local();
2354
2355        let interfaces_request_stream = interfaces.into_stream();
2356
2357        #[derive(GenericOverIp)]
2358        #[generic_over_ip(I, Ip)]
2359        struct StateRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2360            request: <<I::StateMarker as ProtocolMarker>::RequestStream as futures::Stream>::Item,
2361        }
2362
2363        #[derive(GenericOverIp)]
2364        #[generic_over_ip(I, Ip)]
2365        struct WatcherRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2366            watcher: <I::WatcherMarker as ProtocolMarker>::RequestStream,
2367        }
2368
2369        let watcher_stream = state_stream
2370            .map(|request| {
2371                let wrapper = I::map_ip(
2372                    StateRequestWrapper { request },
2373                    |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2374                        fnet_routes::StateV4Request::GetWatcherV4 {
2375                            options: _,
2376                            watcher,
2377                            control_handle: _,
2378                        } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2379                        fnet_routes::StateV4Request::GetRuleWatcherV4 {
2380                            options: _,
2381                            watcher: _,
2382                            control_handle: _,
2383                        } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2384                    },
2385                    |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2386                        fnet_routes::StateV6Request::GetWatcherV6 {
2387                            options: _,
2388                            watcher,
2389                            control_handle: _,
2390                        } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2391                        fnet_routes::StateV6Request::GetRuleWatcherV6 {
2392                            options: _,
2393                            watcher: _,
2394                            control_handle: _,
2395                        } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2396                    },
2397                );
2398                wrapper
2399            })
2400            .map(|WatcherRequestWrapper { watcher }| watcher)
2401            // For testing, we only expect there to be a single connection to the watcher, so the
2402            // stream is condensed into a single `WatchRequest` stream.
2403            .flatten()
2404            .fuse();
2405
2406        Setup {
2407            event_loop_inputs: inputs,
2408            watcher_stream,
2409            route_sets: (routes_set_provider, route_table_provider),
2410            interfaces_request_stream,
2411            request_sink,
2412            async_work_sink,
2413        }
2414    }
2415
2416    fn setup_with_route_clients<
2417        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2418    >(
2419        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2420    ) -> Setup<
2421        impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2422        impl Stream<
2423            Item = (
2424                fnet_routes_ext::TableId,
2425                <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2426            ),
2427        >,
2428    > {
2429        let Setup {
2430            event_loop_inputs,
2431            watcher_stream,
2432            route_sets: (routes_set_provider, route_table_provider),
2433            interfaces_request_stream,
2434            request_sink,
2435            async_work_sink,
2436        } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
2437        let route_set_stream =
2438            fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2439                routes_set_provider,
2440                Some(MAIN_FIDL_TABLE_ID),
2441            )
2442            .map(|item| (MAIN_FIDL_TABLE_ID, item));
2443
2444        let route_table_provider_request_stream = route_table_provider.into_stream();
2445
2446        let table_id = AtomicU32::new(OTHER_FIDL_TABLE_ID.get());
2447
2448        let route_sets_from_route_table_provider =
2449            futures::TryStreamExt::map_ok(route_table_provider_request_stream, move |request| {
2450                match I::into_route_table_provider_request(request) {
2451                    fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
2452                        provider,
2453                        options: _,
2454                        control_handle: _,
2455                    } => {
2456                        let table_id =
2457                            fnet_routes_ext::TableId::new(table_id.fetch_add(1, Ordering::SeqCst));
2458                        fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2459                            provider,
2460                            Some(table_id),
2461                        )
2462                        .map(move |route_set_request| (table_id, route_set_request))
2463                    }
2464                    r => panic!("unexpected request {r:?}"),
2465                }
2466            })
2467            .map(|result| result.expect("should not get FIDL error"))
2468            .flatten_unordered(None)
2469            .fuse();
2470        let route_set_stream = futures::stream::select_all([
2471            route_set_stream.left_stream(),
2472            route_sets_from_route_table_provider.right_stream(),
2473        ])
2474        .fuse();
2475
2476        Setup {
2477            event_loop_inputs,
2478            watcher_stream,
2479            route_sets: route_set_stream,
2480            interfaces_request_stream,
2481            request_sink,
2482            async_work_sink,
2483        }
2484    }
2485
2486    async fn respond_to_watcher<
2487        I: fnet_routes_ext::FidlRouteIpExt,
2488        S: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2489    >(
2490        stream: S,
2491        updates: impl IntoIterator<Item = I::WatchEvent>,
2492    ) {
2493        #[derive(GenericOverIp)]
2494        #[generic_over_ip(I, Ip)]
2495        struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
2496            request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2497            update: I::WatchEvent,
2498        }
2499        stream
2500            .zip(futures::stream::iter(updates.into_iter()))
2501            .for_each(|(request, update)| async move {
2502                I::map_ip_in(
2503                    HandleInputs { request, update },
2504                    |HandleInputs { request, update }| match request
2505                        .expect("failed to receive `Watch` request")
2506                    {
2507                        fnet_routes::WatcherV4Request::Watch { responder } => {
2508                            responder.send(&[update]).expect("failed to respond to `Watch`")
2509                        }
2510                    },
2511                    |HandleInputs { request, update }| match request
2512                        .expect("failed to receive `Watch` request")
2513                    {
2514                        fnet_routes::WatcherV6Request::Watch { responder } => {
2515                            responder.send(&[update]).expect("failed to respond to `Watch`")
2516                        }
2517                    },
2518                );
2519            })
2520            .await;
2521    }
2522
2523    async fn run_event_loop<I: Ip>(
2524        inputs: crate::route_eventloop::EventLoopInputs<
2525            FakeInterfacesHandler,
2526            FakeSender<RouteNetlinkMessage>,
2527            OnlyRoutes,
2528        >,
2529    ) -> ! {
2530        let included_workers = match I::VERSION {
2531            IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
2532                routes_v4: EventLoopComponent::Present(()),
2533                routes_v6: EventLoopComponent::Absent(Optional),
2534                interfaces: EventLoopComponent::Absent(Optional),
2535                rules_v4: EventLoopComponent::Absent(Optional),
2536                rules_v6: EventLoopComponent::Absent(Optional),
2537                nduseropt: EventLoopComponent::Absent(Optional),
2538                neighbors: EventLoopComponent::Absent(Optional),
2539            },
2540            IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
2541                routes_v4: EventLoopComponent::Absent(Optional),
2542                routes_v6: EventLoopComponent::Present(()),
2543                interfaces: EventLoopComponent::Absent(Optional),
2544                rules_v4: EventLoopComponent::Absent(Optional),
2545                rules_v6: EventLoopComponent::Absent(Optional),
2546                nduseropt: EventLoopComponent::Absent(Optional),
2547                neighbors: EventLoopComponent::Absent(Optional),
2548            },
2549        };
2550
2551        let event_loop = inputs.initialize(included_workers).await;
2552        event_loop.run().await
2553    }
2554
2555    fn get_test_route_events_new_route_args<A: IpAddress>(
2556        subnet: Subnet<A>,
2557        next_hop1: A,
2558        next_hop2: A,
2559    ) -> [RequestArgs<A::Version>; 2]
2560    where
2561        A::Version: fnet_routes_ext::FidlRouteIpExt,
2562    {
2563        [
2564            RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2565                create_unicast_new_route_args(
2566                    subnet,
2567                    next_hop1,
2568                    DEV1.into(),
2569                    METRIC1,
2570                    MANAGED_ROUTE_TABLE_INDEX,
2571                ),
2572            ))),
2573            RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2574                create_unicast_new_route_args(
2575                    subnet,
2576                    next_hop2,
2577                    DEV2.into(),
2578                    METRIC2,
2579                    MANAGED_ROUTE_TABLE_INDEX,
2580                ),
2581            ))),
2582        ]
2583    }
2584
2585    fn create_unicast_new_route_args<A: IpAddress>(
2586        subnet: Subnet<A>,
2587        next_hop: A,
2588        interface_id: u64,
2589        priority: u32,
2590        table: NetlinkRouteTableIndex,
2591    ) -> UnicastNewRouteArgs<A::Version> {
2592        UnicastNewRouteArgs {
2593            subnet,
2594            target: fnet_routes_ext::RouteTarget {
2595                outbound_interface: interface_id,
2596                next_hop: SpecifiedAddr::new(next_hop),
2597            },
2598            priority: NonZeroU32::new(priority),
2599            table,
2600        }
2601    }
2602
2603    fn create_unicast_del_route_args<A: IpAddress>(
2604        subnet: Subnet<A>,
2605        next_hop: Option<A>,
2606        interface_id: Option<u64>,
2607        priority: Option<u32>,
2608        table: NetlinkRouteTableIndex,
2609    ) -> UnicastDelRouteArgs<A::Version> {
2610        UnicastDelRouteArgs {
2611            subnet,
2612            outbound_interface: interface_id.map(NonZeroU64::new).flatten(),
2613            next_hop: next_hop.map(SpecifiedAddr::new).flatten(),
2614            priority: priority.map(NonZeroU32::new).flatten(),
2615            table: NonZeroNetlinkRouteTableIndex::new(table).unwrap(),
2616        }
2617    }
2618
2619    #[derive(Debug, PartialEq)]
2620    struct TestRequestResult {
2621        messages: Vec<SentMessage<RouteNetlinkMessage>>,
2622        waiter_results: Vec<Result<(), RequestError>>,
2623    }
2624
2625    /// Test helper to handle an iterator of route requests
2626    /// using the same clients and event loop.
2627    ///
2628    /// `root_handler` returns a future that handles
2629    /// `fnet_root::InterfacesRequest`s.
2630    async fn test_requests<
2631        A: IpAddress,
2632        Fut: Future<Output = ()>,
2633        F: FnOnce(fnet_root::InterfacesRequestStream) -> Fut,
2634    >(
2635        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
2636        root_handler: F,
2637        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2638        subnet: Subnet<A>,
2639        next_hop1: A,
2640        next_hop2: A,
2641        num_sink_messages: usize,
2642    ) -> TestRequestResult
2643    where
2644        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2645    {
2646        let scope = fasync::Scope::new();
2647        let result = {
2648            let (mut route_sink, route_client, async_work_drain_task) =
2649                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2650                    crate::client::testutil::CLIENT_ID_1,
2651                    [ModernGroup(match A::Version::VERSION {
2652                        IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
2653                        IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
2654                    })],
2655                );
2656            let _join_handle = scope.spawn(async_work_drain_task);
2657            let (mut other_sink, other_client, async_work_drain_task) =
2658                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2659                    crate::client::testutil::CLIENT_ID_2,
2660                    [ModernGroup(rtnetlink_groups_RTNLGRP_LINK)],
2661                );
2662            let _join_handle = scope.spawn(async_work_drain_task);
2663            let Setup {
2664                event_loop_inputs,
2665                mut watcher_stream,
2666                route_sets: mut route_set_stream,
2667                interfaces_request_stream,
2668                request_sink,
2669                async_work_sink: _,
2670            } = setup_with_route_clients::<A::Version>({
2671                let route_clients = ClientTable::default();
2672                route_clients.add_client(route_client.clone());
2673                route_clients.add_client(other_client);
2674                route_clients
2675            });
2676
2677            let mut event_loop_fut = pin!(run_event_loop::<A::Version>(event_loop_inputs).fuse());
2678
2679            let watcher_stream_fut = respond_to_watcher::<A::Version, _>(
2680                watcher_stream.by_ref(),
2681                std::iter::once(fnet_routes_ext::Event::<A::Version>::Idle.try_into().unwrap()),
2682            );
2683            futures::select! {
2684                () = watcher_stream_fut.fuse() => {},
2685                err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2686            }
2687            assert_eq!(&route_sink.take_messages()[..], &[]);
2688            assert_eq!(&other_sink.take_messages()[..], &[]);
2689
2690            let route_client = &route_client;
2691            let fut = async {
2692                // Add some initial route state by sending through PendingRequests.
2693                let initial_new_routes =
2694                    get_test_route_events_new_route_args(subnet, next_hop1, next_hop2);
2695                let count_initial_new_routes = initial_new_routes.len();
2696
2697                let request_sink = futures::stream::iter(initial_new_routes)
2698                    .fold(request_sink, |mut request_sink, args| async move {
2699                        let (completer, waiter) = oneshot::channel();
2700                        request_sink
2701                            .send(
2702                                Request {
2703                                    args,
2704                                    sequence_number: TEST_SEQUENCE_NUMBER,
2705                                    client: route_client.clone(),
2706                                    completer,
2707                                }
2708                                .into(),
2709                            )
2710                            .await
2711                            .unwrap();
2712                        assert_matches!(waiter.await.unwrap(), Ok(()));
2713                        request_sink
2714                    })
2715                    .await;
2716
2717                // Ensure these messages to load the initial route state are
2718                // received prior to handling the next requests. The messages for
2719                // these requests are not needed by the callers, so drop them.
2720                for _ in 0..count_initial_new_routes {
2721                    let _ = route_sink.next_message().await;
2722                }
2723                assert_eq!(route_sink.next_message().now_or_never(), None);
2724
2725                let (results, _request_sink) = futures::stream::iter(args)
2726                    .fold(
2727                        (Vec::new(), request_sink),
2728                        |(mut results, mut request_sink), args| async move {
2729                            let (completer, waiter) = oneshot::channel();
2730                            request_sink
2731                                .send(
2732                                    Request {
2733                                        args,
2734                                        sequence_number: TEST_SEQUENCE_NUMBER,
2735                                        client: route_client.clone(),
2736                                        completer,
2737                                    }
2738                                    .into(),
2739                                )
2740                                .await
2741                                .unwrap();
2742                            results.push(waiter.await.unwrap());
2743                            (results, request_sink)
2744                        },
2745                    )
2746                    .await;
2747
2748                let messages = {
2749                    assert_eq!(&other_sink.take_messages()[..], &[]);
2750                    let mut messages = Vec::new();
2751                    while messages.len() < num_sink_messages {
2752                        messages.push(route_sink.next_message().await);
2753                    }
2754                    assert_eq!(route_sink.next_message().now_or_never(), None);
2755                    messages
2756                };
2757
2758                (messages, results)
2759            };
2760
2761            let route_set_fut = respond_to_route_set_modifications::<A::Version, _, _>(
2762                route_set_stream.by_ref(),
2763                watcher_stream.by_ref(),
2764                route_set_results,
2765            )
2766            .fuse();
2767
2768            let root_interfaces_fut = root_handler(interfaces_request_stream).fuse();
2769
2770            let (messages, results) = futures::select! {
2771                (messages, results) = fut.fuse() => (messages, results),
2772                res = futures::future::join3(
2773                        route_set_fut,
2774                        root_interfaces_fut,
2775                        event_loop_fut,
2776                    ) => {
2777                    unreachable!("eventloop/stream handlers should not return: {res:?}")
2778                }
2779            };
2780
2781            TestRequestResult { messages, waiter_results: results }
2782        };
2783        scope.join().await;
2784        result
2785    }
2786
2787    #[test_case(V4_SUB1, V4_NEXTHOP1, V4_NEXTHOP2; "v4_route_dump")]
2788    #[test_case(V6_SUB1, V6_NEXTHOP1, V6_NEXTHOP2; "v6_route_dump")]
2789    #[fuchsia::test]
2790    async fn test_get_route<A: IpAddress>(subnet: Subnet<A>, next_hop1: A, next_hop2: A)
2791    where
2792        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2793    {
2794        let expected_messages = vec![
2795            SentMessage::unicast(
2796                create_netlink_route_message::<A::Version>(
2797                    subnet.prefix(),
2798                    MANAGED_ROUTE_TABLE_INDEX,
2799                    create_nlas::<A::Version>(
2800                        Some(subnet),
2801                        Some(next_hop1),
2802                        DEV1,
2803                        METRIC1,
2804                        Some(MANAGED_ROUTE_TABLE_ID),
2805                    ),
2806                )
2807                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2808            ),
2809            SentMessage::unicast(
2810                create_netlink_route_message::<A::Version>(
2811                    subnet.prefix(),
2812                    MANAGED_ROUTE_TABLE_INDEX,
2813                    create_nlas::<A::Version>(
2814                        Some(subnet),
2815                        Some(next_hop2),
2816                        DEV2,
2817                        METRIC2,
2818                        Some(MANAGED_ROUTE_TABLE_ID),
2819                    ),
2820                )
2821                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2822            ),
2823        ];
2824
2825        pretty_assertions::assert_eq!(
2826            {
2827                let mut test_request_result = test_requests(
2828                    [RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump))],
2829                    |interfaces_request_stream| async {
2830                        interfaces_request_stream
2831                            .for_each(|req| async move {
2832                                panic!("unexpected InterfacesRequest: {req:?}")
2833                            })
2834                            .await;
2835                    },
2836                    HashMap::new(),
2837                    subnet,
2838                    next_hop1,
2839                    next_hop2,
2840                    expected_messages.len(),
2841                )
2842                .await;
2843                test_request_result.messages.sort_by_key(|message| {
2844                    assert_matches!(
2845                        &message.message.payload,
2846                        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m)) => {
2847                            // We expect there to be exactly one Oif NLA present
2848                            // for the given inputs.
2849                            m.attributes.clone().into_iter().filter_map(|nla|
2850                                match nla {
2851                                    RouteAttribute::Oif(interface_id) =>
2852                                        Some((m.header.address_family, interface_id)),
2853                                    RouteAttribute::Destination(_)
2854                                    | RouteAttribute::Gateway(_)
2855                                    | RouteAttribute::Priority(_)
2856                                    | RouteAttribute::Table(_) => None,
2857                                    _ => panic!("unexpected NLA {nla:?} present in payload"),
2858                                }
2859                            ).next()
2860                        }
2861                    )
2862                });
2863                test_request_result
2864            },
2865            TestRequestResult { messages: expected_messages, waiter_results: vec![Ok(())] },
2866        )
2867    }
2868
2869    #[derive(Debug, Clone, Copy)]
2870    enum RouteSetResult {
2871        AddResult(Result<bool, fnet_routes_admin::RouteSetError>),
2872        DelResult(Result<bool, fnet_routes_admin::RouteSetError>),
2873        AuthenticationResult(Result<(), fnet_routes_admin::AuthenticateForInterfaceError>),
2874    }
2875
2876    fn route_event_from_route<
2877        I: Ip + fnet_routes_ext::FidlRouteIpExt,
2878        F: FnOnce(fnet_routes_ext::InstalledRoute<I>) -> fnet_routes_ext::Event<I>,
2879    >(
2880        route: I::Route,
2881        table_id: fnet_routes_ext::TableId,
2882        event_fn: F,
2883    ) -> I::WatchEvent {
2884        let route: fnet_routes_ext::Route<I> = route.try_into().unwrap();
2885
2886        let metric = match route.properties.specified_properties.metric {
2887            fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => metric,
2888            fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => {
2889                panic!("metric should be explicit")
2890            }
2891        };
2892
2893        event_fn(fnet_routes_ext::InstalledRoute {
2894            route,
2895            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
2896            // TODO(https://fxbug.dev/336382905): The tests should use the ID.
2897            table_id,
2898        })
2899        .try_into()
2900        .unwrap()
2901    }
2902
2903    // Handle RouteSet API requests then feed the returned
2904    // `fuchsia.net.routes.ext/Event`s to the routes watcher.
2905    async fn respond_to_route_set_modifications<
2906        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2907        RS: Stream<
2908            Item = (
2909                fnet_routes_ext::TableId,
2910                <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2911            ),
2912        >,
2913        WS: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>
2914            + std::marker::Unpin,
2915    >(
2916        route_stream: RS,
2917        watcher_stream: WS,
2918        mut route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2919    ) {
2920        #[derive(GenericOverIp)]
2921        #[generic_over_ip(I, Ip)]
2922        struct RouteSetInputs<I: fnet_routes_ext::admin::FidlRouteAdminIpExt> {
2923            request: <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2924            route_set_result: RouteSetResult,
2925        }
2926        #[derive(GenericOverIp)]
2927        #[generic_over_ip(I, Ip)]
2928        struct RouteSetOutputs<I: fnet_routes_ext::FidlRouteIpExt> {
2929            event: Option<I::WatchEvent>,
2930        }
2931
2932        let mut route_stream = std::pin::pin!(route_stream);
2933        let mut watcher_stream = std::pin::pin!(watcher_stream);
2934
2935        {
2936            let queue = route_set_results.entry(OTHER_FIDL_TABLE_ID).or_default();
2937            queue.push_front(RouteSetResult::AddResult(Ok(true)));
2938            queue.push_front(RouteSetResult::AddResult(Ok(true)));
2939        }
2940
2941        while let Some((table_id, request)) = route_stream.next().await {
2942            let route_set_result = route_set_results
2943                .get_mut(&table_id)
2944                .unwrap_or_else(|| panic!("missing result for {table_id:?}"))
2945                .pop_front()
2946                .unwrap_or_else(|| panic!("missing result for {table_id:?}"));
2947            let RouteSetOutputs { event } = I::map_ip(
2948                RouteSetInputs { request, route_set_result },
2949                |RouteSetInputs { request, route_set_result }| {
2950                    let request = request.expect("failed to receive request");
2951                    crate::logging::log_debug!(
2952                        "responding on {table_id:?} to route set request {request:?} \
2953                        with result {route_set_result:?}"
2954                    );
2955                    match request {
2956                        fnet_routes_admin::RouteSetV4Request::AddRoute { route, responder } => {
2957                            let route_set_result = assert_matches!(
2958                                route_set_result,
2959                                RouteSetResult::AddResult(res) => res
2960                            );
2961
2962                            responder
2963                                .send(route_set_result)
2964                                .expect("failed to respond to `AddRoute`");
2965
2966                            RouteSetOutputs {
2967                                event: match route_set_result {
2968                                    Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2969                                        route,
2970                                        table_id,
2971                                        fnet_routes_ext::Event::<Ipv4>::Added,
2972                                    )),
2973                                    _ => None,
2974                                },
2975                            }
2976                        }
2977                        fnet_routes_admin::RouteSetV4Request::RemoveRoute { route, responder } => {
2978                            let route_set_result = assert_matches!(
2979                                route_set_result,
2980                                RouteSetResult::DelResult(res) => res
2981                            );
2982
2983                            responder
2984                                .send(route_set_result)
2985                                .expect("failed to respond to `RemoveRoute`");
2986
2987                            RouteSetOutputs {
2988                                event: match route_set_result {
2989                                    Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2990                                        route,
2991                                        table_id,
2992                                        fnet_routes_ext::Event::<Ipv4>::Removed,
2993                                    )),
2994                                    _ => None,
2995                                },
2996                            }
2997                        }
2998                        fnet_routes_admin::RouteSetV4Request::AuthenticateForInterface {
2999                            credential: _,
3000                            responder,
3001                        } => {
3002                            let route_set_result = assert_matches!(
3003                                route_set_result,
3004                                RouteSetResult::AuthenticationResult(res) => res
3005                            );
3006
3007                            responder
3008                                .send(route_set_result)
3009                                .expect("failed to respond to `AuthenticateForInterface`");
3010                            RouteSetOutputs { event: None }
3011                        }
3012                    }
3013                },
3014                |RouteSetInputs { request, route_set_result }| {
3015                    let request = request.expect("failed to receive request");
3016                    crate::logging::log_debug!(
3017                        "responding on {table_id:?} to route set request {request:?} \
3018                        with result {route_set_result:?}"
3019                    );
3020                    match request {
3021                        fnet_routes_admin::RouteSetV6Request::AddRoute { route, responder } => {
3022                            let route_set_result = assert_matches!(
3023                                route_set_result,
3024                                RouteSetResult::AddResult(res) => res
3025                            );
3026
3027                            responder
3028                                .send(route_set_result)
3029                                .expect("failed to respond to `AddRoute`");
3030
3031                            RouteSetOutputs {
3032                                event: match route_set_result {
3033                                    Ok(true) => Some(route_event_from_route::<Ipv6, _>(
3034                                        route,
3035                                        table_id,
3036                                        fnet_routes_ext::Event::<Ipv6>::Added,
3037                                    )),
3038                                    _ => None,
3039                                },
3040                            }
3041                        }
3042                        fnet_routes_admin::RouteSetV6Request::RemoveRoute { route, responder } => {
3043                            let route_set_result = assert_matches!(
3044                                route_set_result,
3045                                RouteSetResult::DelResult(res) => res
3046                            );
3047
3048                            responder
3049                                .send(route_set_result)
3050                                .expect("failed to respond to `RemoveRoute`");
3051
3052                            RouteSetOutputs {
3053                                event: match route_set_result {
3054                                    Ok(true) => Some(route_event_from_route::<Ipv6, _>(
3055                                        route,
3056                                        table_id,
3057                                        fnet_routes_ext::Event::<Ipv6>::Removed,
3058                                    )),
3059                                    _ => None,
3060                                },
3061                            }
3062                        }
3063                        fnet_routes_admin::RouteSetV6Request::AuthenticateForInterface {
3064                            credential: _,
3065                            responder,
3066                        } => {
3067                            let route_set_result = assert_matches!(
3068                                route_set_result,
3069                                RouteSetResult::AuthenticationResult(res) => res
3070                            );
3071
3072                            responder
3073                                .send(route_set_result)
3074                                .expect("failed to respond to `AuthenticateForInterface`");
3075                            RouteSetOutputs { event: None }
3076                        }
3077                    }
3078                },
3079            );
3080
3081            if let Some(update) = event {
3082                let request = watcher_stream.next().await.expect("watcher stream should not end");
3083
3084                #[derive(GenericOverIp)]
3085                #[generic_over_ip(I, Ip)]
3086                struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
3087                    request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
3088                    update: I::WatchEvent,
3089                }
3090
3091                I::map_ip_in(
3092                    HandleInputs { request, update },
3093                    |HandleInputs { request, update }| match request
3094                        .expect("failed to receive `Watch` request")
3095                    {
3096                        fnet_routes::WatcherV4Request::Watch { responder } => {
3097                            responder.send(&[update]).expect("failed to respond to `Watch`")
3098                        }
3099                    },
3100                    |HandleInputs { request, update }| match request
3101                        .expect("failed to receive `Watch` request")
3102                    {
3103                        fnet_routes::WatcherV6Request::Watch { responder } => {
3104                            responder.send(&[update]).expect("failed to respond to `Watch`")
3105                        }
3106                    },
3107                );
3108            }
3109        }
3110
3111        if route_set_results.values().any(|value| !value.is_empty()) {
3112            panic!("unused route_set_results entries: {route_set_results:?}");
3113        }
3114    }
3115
3116    /// A test helper to exercise multiple route requests.
3117    ///
3118    /// A test helper that calls the provided callback with a
3119    /// [`fnet_interfaces_admin::ControlRequest`] as they arrive.
3120    async fn test_route_requests<
3121        A: IpAddress,
3122        Fut: Future<Output = ()>,
3123        F: FnMut(fnet_interfaces_admin::ControlRequest) -> Fut,
3124    >(
3125        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3126        mut control_request_handler: F,
3127        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3128        subnet: Subnet<A>,
3129        next_hop1: A,
3130        next_hop2: A,
3131        num_sink_messages: usize,
3132    ) -> TestRequestResult
3133    where
3134        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3135    {
3136        test_requests(
3137            args,
3138            |interfaces_request_stream| async move {
3139                interfaces_request_stream
3140                    .filter_map(|req| {
3141                        futures::future::ready(match req.unwrap() {
3142                            fnet_root::InterfacesRequest::GetAdmin {
3143                                id,
3144                                control,
3145                                control_handle: _,
3146                            } => {
3147                                pretty_assertions::assert_eq!(id, DEV1 as u64);
3148                                Some(control.into_stream())
3149                            }
3150                            req => unreachable!("unexpected interfaces request: {req:?}"),
3151                        })
3152                    })
3153                    .flatten()
3154                    .next()
3155                    .then(|req| control_request_handler(req.unwrap().unwrap()))
3156                    .await
3157            },
3158            route_set_results,
3159            subnet,
3160            next_hop1,
3161            next_hop2,
3162            num_sink_messages,
3163        )
3164        .await
3165    }
3166
3167    // A test helper that calls `test_route_requests()` with the provided
3168    // inputs and expected values.
3169    async fn test_route_requests_helper<A: IpAddress>(
3170        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3171        expected_messages: Vec<SentMessage<RouteNetlinkMessage>>,
3172        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3173        waiter_results: Vec<Result<(), RequestError>>,
3174        subnet: Subnet<A>,
3175    ) where
3176        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3177    {
3178        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3179            (),
3180            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3181            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3182        );
3183
3184        pretty_assertions::assert_eq!(
3185            {
3186                let mut test_request_result = test_route_requests(
3187                    args,
3188                    |req| async {
3189                        match req {
3190                            fnet_interfaces_admin::ControlRequest::GetAuthorizationForInterface {
3191                                responder,
3192                            } => {
3193                                let token = fidl::Event::create();
3194                                let grant = fnet_resources::GrantForInterfaceAuthorization {
3195                                    interface_id: DEV1 as u64,
3196                                    token,
3197                                };
3198                                responder.send(grant).unwrap();
3199                            }
3200                            req => panic!("unexpected request {req:?}"),
3201                        }
3202                    },
3203                    route_set_results,
3204                    subnet,
3205                    next_hop1,
3206                    next_hop2,
3207                    expected_messages.len(),
3208                )
3209                .await;
3210                test_request_result.messages.sort_by_key(|message| {
3211                    // The sequence number sorts multicast messages prior to
3212                    // unicast messages.
3213                    let sequence_number = message.message.header.sequence_number;
3214                    assert_matches!(
3215                        &message.message.payload,
3216                        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m))
3217                        | NetlinkPayload::InnerMessage(RouteNetlinkMessage::DelRoute(m)) => {
3218                            // We expect there to be exactly one Priority NLA present
3219                            // for the given inputs.
3220                            m.attributes.clone().into_iter().filter_map(|nla|
3221                                match nla {
3222                                    RouteAttribute::Priority(priority) =>
3223                                        Some((sequence_number, priority)),
3224                                    RouteAttribute::Destination(_)
3225                                    | RouteAttribute::Gateway(_)
3226                                    | RouteAttribute::Oif(_)
3227                                    | RouteAttribute::Table(_) => None,
3228                                    _ => panic!("unexpected NLA {nla:?} present in payload"),
3229                                }
3230                            ).next()
3231                        }
3232                    )
3233                });
3234                test_request_result
3235            },
3236            TestRequestResult { messages: expected_messages, waiter_results },
3237        )
3238    }
3239
3240    enum RouteRequestKind {
3241        New,
3242        Del,
3243    }
3244
3245    fn route_set_for_table_id(
3246        results: Vec<RouteSetResult>,
3247        table_id: fnet_routes_ext::TableId,
3248    ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3249        HashMap::from_iter([(table_id, results.into())])
3250    }
3251
3252    fn route_set_for_first_new_table(
3253        results: Vec<RouteSetResult>,
3254    ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3255        route_set_for_table_id(results, OTHER_FIDL_TABLE_ID)
3256    }
3257
3258    // Tests RTM_NEWROUTE with all interesting responses to add a route.
3259    #[test_case(
3260        RouteRequestKind::New,
3261        vec![
3262            RouteSetResult::AddResult(Ok(true))
3263        ],
3264        Ok(()),
3265        V4_SUB1,
3266        Some(METRIC3),
3267        DEV1;
3268        "v4_new_success")]
3269    #[test_case(
3270        RouteRequestKind::New,
3271        vec![
3272            RouteSetResult::AddResult(Ok(true))
3273        ],
3274        Ok(()),
3275        V6_SUB1,
3276        Some(METRIC3),
3277        DEV1;
3278        "v6_new_success")]
3279    #[test_case(
3280        RouteRequestKind::New,
3281        vec![
3282            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3283            RouteSetResult::AuthenticationResult(Err(
3284                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3285            )),
3286        ],
3287        Err(RequestError::UnrecognizedInterface),
3288        V4_SUB1,
3289        Some(METRIC3),
3290        DEV1;
3291        "v4_new_failed_auth")]
3292    #[test_case(
3293        RouteRequestKind::New,
3294        vec![
3295            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3296            RouteSetResult::AuthenticationResult(Err(
3297                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3298            )),
3299        ],
3300        Err(RequestError::UnrecognizedInterface),
3301        V6_SUB1,
3302        Some(METRIC3),
3303        DEV1;
3304        "v6_new_failed_auth")]
3305    #[test_case(
3306        RouteRequestKind::New,
3307        vec![
3308            RouteSetResult::AddResult(Ok(false))
3309        ],
3310        Err(RequestError::AlreadyExists),
3311        V4_SUB1,
3312        Some(METRIC3),
3313        DEV1;
3314        "v4_new_failed_netstack_reports_exists")]
3315    #[test_case(
3316        RouteRequestKind::New,
3317        vec![
3318            RouteSetResult::AddResult(Ok(false))
3319        ],
3320        Err(RequestError::AlreadyExists),
3321        V6_SUB1,
3322        Some(METRIC3),
3323        DEV1;
3324        "v6_new_failed_netstack_reports_exists")]
3325    #[test_case(
3326        RouteRequestKind::New,
3327        vec![],
3328        Err(RequestError::AlreadyExists),
3329        V4_SUB1,
3330        Some(METRIC1),
3331        DEV1;
3332        "v4_new_failed_netlink_reports_exists")]
3333    #[test_case(
3334        RouteRequestKind::New,
3335        vec![],
3336        Err(RequestError::AlreadyExists),
3337        V4_SUB1,
3338        Some(METRIC1),
3339        DEV2;
3340        "v4_new_failed_netlink_reports_exists_different_interface")]
3341    #[test_case(
3342        RouteRequestKind::New,
3343        vec![],
3344        Err(RequestError::AlreadyExists),
3345        V6_SUB1,
3346        Some(METRIC1),
3347        DEV1;
3348        "v6_new_failed_netlink_reports_exists")]
3349    #[test_case(
3350        RouteRequestKind::New,
3351        vec![],
3352        Err(RequestError::AlreadyExists),
3353        V6_SUB1,
3354        Some(METRIC1),
3355        DEV2;
3356        "v6_new_failed_netlink_reports_exists_different_interface")]
3357    #[test_case(
3358        RouteRequestKind::New,
3359        vec![
3360            RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3361        ],
3362        Err(RequestError::InvalidRequest),
3363        V4_SUB1,
3364        Some(METRIC3),
3365        DEV1;
3366        "v4_new_invalid_dest")]
3367    #[test_case(
3368        RouteRequestKind::New,
3369        vec![
3370            RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3371        ],
3372        Err(RequestError::InvalidRequest),
3373        V6_SUB1,
3374        Some(METRIC3),
3375        DEV1;
3376        "v6_new_invalid_dest")]
3377    #[test_case(
3378        RouteRequestKind::New,
3379        vec![
3380            RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3381        ],
3382        Err(RequestError::InvalidRequest),
3383        V4_SUB1,
3384        Some(METRIC3),
3385        DEV1;
3386        "v4_new_invalid_hop")]
3387    #[test_case(
3388        RouteRequestKind::New,
3389        vec![
3390            RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3391        ],
3392        Err(RequestError::InvalidRequest),
3393        V6_SUB1,
3394        Some(METRIC3),
3395        DEV1;
3396        "v6_new_invalid_hop")]
3397    // Tests RTM_DELROUTE with all interesting responses to remove a route.
3398    #[test_case(
3399        RouteRequestKind::Del,
3400        vec![
3401            RouteSetResult::DelResult(Ok(true))
3402        ],
3403        Ok(()),
3404        V4_SUB1,
3405        None,
3406        DEV1;
3407        "v4_del_success_only_subnet")]
3408    #[test_case(
3409        RouteRequestKind::Del,
3410        vec![
3411            RouteSetResult::DelResult(Ok(true))
3412        ],
3413        Ok(()),
3414        V4_SUB1,
3415        Some(METRIC1),
3416        DEV1;
3417        "v4_del_success_only_subnet_metric")]
3418    #[test_case(
3419        RouteRequestKind::Del,
3420        vec![
3421            RouteSetResult::DelResult(Ok(true))
3422        ],
3423        Ok(()),
3424        V6_SUB1,
3425        None,
3426        DEV1;
3427        "v6_del_success_only_subnet")]
3428    #[test_case(
3429        RouteRequestKind::Del,
3430        vec![
3431            RouteSetResult::DelResult(Ok(true))
3432        ],
3433        Ok(()),
3434        V6_SUB1,
3435        Some(METRIC1),
3436        DEV1;
3437        "v6_del_success_only_subnet_metric")]
3438    #[test_case(
3439        RouteRequestKind::Del,
3440        vec![
3441            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3442            RouteSetResult::AuthenticationResult(Err(
3443                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3444            )),
3445        ],
3446        Err(RequestError::UnrecognizedInterface),
3447        V4_SUB1,
3448        None,
3449        DEV1;
3450        "v4_del_failed_auth")]
3451    #[test_case(
3452        RouteRequestKind::Del,
3453        vec![
3454            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3455            RouteSetResult::AuthenticationResult(Err(
3456                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3457            )),
3458        ],
3459        Err(RequestError::UnrecognizedInterface),
3460        V6_SUB1,
3461        None,
3462        DEV1;
3463        "v6_del_failed_auth")]
3464    #[test_case(
3465        RouteRequestKind::Del,
3466        vec![
3467            RouteSetResult::DelResult(Ok(false))
3468        ],
3469        Err(RequestError::DeletionNotAllowed),
3470        V4_SUB1,
3471        None,
3472        DEV1;
3473        "v4_del_failed_attempt_to_delete_route_from_global_set")]
3474    #[test_case(
3475        RouteRequestKind::Del,
3476        vec![
3477            RouteSetResult::DelResult(Ok(false))
3478        ],
3479        Err(RequestError::DeletionNotAllowed),
3480        V6_SUB1,
3481        None,
3482        DEV1;
3483        "v6_del_failed_attempt_to_delete_route_from_global_set")]
3484    // This deliberately only includes one case where a route is
3485    // not selected for deletion, `test_select_route_for_deletion`
3486    // covers these cases.
3487    // No route with `METRIC3` exists, so this extra selector causes the
3488    // `NotFound` result.
3489    #[test_case(
3490        RouteRequestKind::Del,
3491        vec![],
3492        Err(RequestError::NotFound),
3493        V4_SUB1,
3494        Some(METRIC3),
3495        DEV1;
3496        "v4_del_no_matching_route")]
3497    #[test_case(
3498        RouteRequestKind::Del,
3499        vec![],
3500        Err(RequestError::NotFound),
3501        V6_SUB1,
3502        Some(METRIC3),
3503        DEV1;
3504        "v6_del_no_matching_route")]
3505    #[test_case(
3506        RouteRequestKind::Del,
3507        vec![
3508            RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3509        ],
3510        Err(RequestError::InvalidRequest),
3511        V4_SUB1,
3512        None,
3513        DEV1;
3514        "v4_del_invalid_dest")]
3515    #[test_case(
3516        RouteRequestKind::Del,
3517        vec![
3518            RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3519        ],
3520        Err(RequestError::InvalidRequest),
3521        V6_SUB1,
3522        None,
3523        DEV1;
3524        "v6_del_invalid_dest")]
3525    #[test_case(
3526        RouteRequestKind::Del,
3527        vec![
3528            RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3529        ],
3530        Err(RequestError::InvalidRequest),
3531        V4_SUB1,
3532        None,
3533        DEV1;
3534        "v4_del_invalid_hop")]
3535    #[test_case(
3536        RouteRequestKind::Del,
3537        vec![
3538            RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3539        ],
3540        Err(RequestError::InvalidRequest),
3541        V6_SUB1,
3542        None,
3543        DEV1;
3544        "v6_del_invalid_hop")]
3545    #[fuchsia::test]
3546    async fn test_new_del_route<A: IpAddress>(
3547        kind: RouteRequestKind,
3548        route_set_results: Vec<RouteSetResult>,
3549        waiter_result: Result<(), RequestError>,
3550        subnet: Subnet<A>,
3551        metric: Option<u32>,
3552        interface_id: u32,
3553    ) where
3554        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3555    {
3556        let route_group = match A::Version::VERSION {
3557            IpVersion::V4 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3558            IpVersion::V6 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3559        };
3560
3561        let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3562
3563        // There are two pre-set routes in `test_route_requests`.
3564        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3565        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3566        let route_req_args = match kind {
3567            RouteRequestKind::New => {
3568                // Add a route that is not already present.
3569                RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3570                    subnet,
3571                    next_hop,
3572                    interface_id.into(),
3573                    metric.expect("add cases should be Some"),
3574                    MANAGED_ROUTE_TABLE_INDEX,
3575                )))
3576            }
3577            RouteRequestKind::Del => {
3578                // Remove an existing route.
3579                RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3580                    subnet,
3581                    None,
3582                    None,
3583                    metric,
3584                    MANAGED_ROUTE_TABLE_INDEX,
3585                )))
3586            }
3587        };
3588
3589        // When the waiter result is Ok(()), then we know that the add or delete
3590        // was successful and we got a message.
3591        let messages = match waiter_result {
3592            Ok(()) => {
3593                let build_message = |table| {
3594                    let route_message = create_netlink_route_message::<A::Version>(
3595                        subnet.prefix(),
3596                        table,
3597                        create_nlas::<A::Version>(
3598                            Some(subnet),
3599                            Some(next_hop),
3600                            DEV1,
3601                            match kind {
3602                                RouteRequestKind::New => metric.expect("add cases should be some"),
3603                                // When a route is found for deletion, we expect that route to have
3604                                // a metric value of `METRIC1`. Even though there are two different
3605                                // routes with `subnet`, deletion prefers to select the route with
3606                                // the lowest metric.
3607                                RouteRequestKind::Del => METRIC1,
3608                            },
3609                            (table != MAIN_ROUTE_TABLE_INDEX).then_some(table.get()),
3610                        ),
3611                    );
3612                    let netlink_message = match kind {
3613                        RouteRequestKind::New => {
3614                            route_message.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false)
3615                        }
3616                        RouteRequestKind::Del => route_message.into_rtnl_del_route(),
3617                    };
3618                    SentMessage::multicast(netlink_message, route_group)
3619                };
3620
3621                let route_message_in_managed_table = build_message(MANAGED_ROUTE_TABLE_INDEX);
3622
3623                vec![route_message_in_managed_table]
3624            }
3625            Err(_) => Vec::new(),
3626        };
3627
3628        test_route_requests_helper(
3629            [RequestArgs::Route(route_req_args)],
3630            messages,
3631            route_set_for_first_new_table(route_set_results),
3632            vec![waiter_result],
3633            subnet,
3634        )
3635        .await;
3636    }
3637
3638    // Tests RTM_NEWROUTE and RTM_DELROUTE when two unauthentication events are received - once
3639    // prior to making an attempt to authenticate and once after attempting to authenticate.
3640    #[test_case(
3641        RouteRequestKind::New,
3642        vec![
3643            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3644            RouteSetResult::AuthenticationResult(Ok(())),
3645            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3646        ],
3647        Err(RequestError::InvalidRequest),
3648        V4_SUB1;
3649        "v4_new_unauthenticated")]
3650    #[test_case(
3651        RouteRequestKind::New,
3652        vec![
3653            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3654            RouteSetResult::AuthenticationResult(Ok(())),
3655            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3656        ],
3657        Err(RequestError::InvalidRequest),
3658        V6_SUB1;
3659        "v6_new_unauthenticated")]
3660    #[test_case(
3661        RouteRequestKind::Del,
3662        vec![
3663            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3664            RouteSetResult::AuthenticationResult(Ok(())),
3665            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3666        ],
3667        Err(RequestError::InvalidRequest),
3668        V4_SUB1;
3669        "v4_del_unauthenticated")]
3670    #[test_case(
3671        RouteRequestKind::Del,
3672        vec![
3673            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3674            RouteSetResult::AuthenticationResult(Ok(())),
3675            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3676        ],
3677        Err(RequestError::InvalidRequest),
3678        V6_SUB1;
3679        "v6_del_unauthenticated")]
3680    #[should_panic(expected = "received unauthentication error from route set for route")]
3681    #[fuchsia::test]
3682    async fn test_new_del_route_failed<A: IpAddress>(
3683        kind: RouteRequestKind,
3684        route_set_results: Vec<RouteSetResult>,
3685        waiter_result: Result<(), RequestError>,
3686        subnet: Subnet<A>,
3687    ) where
3688        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3689    {
3690        let route_req_args = match kind {
3691            RouteRequestKind::New => {
3692                let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3693                // Add a route that is not already present.
3694                RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3695                    subnet,
3696                    next_hop,
3697                    DEV1.into(),
3698                    METRIC3,
3699                    MANAGED_ROUTE_TABLE_INDEX,
3700                )))
3701            }
3702            RouteRequestKind::Del => {
3703                // Remove an existing route.
3704                RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3705                    subnet,
3706                    None,
3707                    None,
3708                    None,
3709                    MANAGED_ROUTE_TABLE_INDEX,
3710                )))
3711            }
3712        };
3713        test_route_requests_helper(
3714            [RequestArgs::Route(route_req_args)],
3715            Vec::new(),
3716            route_set_for_first_new_table(route_set_results),
3717            vec![waiter_result],
3718            subnet,
3719        )
3720        .await;
3721    }
3722
3723    #[test_case(
3724        Err(RequestError::NotFound),
3725        V4_SUB1; "v4_del")]
3726    #[test_case(
3727        Err(RequestError::NotFound),
3728        V6_SUB1; "v6_del")]
3729    #[fuchsia::test]
3730    async fn test_del_route_nonexistent_table<A: IpAddress>(
3731        waiter_result: Result<(), RequestError>,
3732        subnet: Subnet<A>,
3733    ) where
3734        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3735    {
3736        // Remove a route from a table that doesn't exist yet.
3737        let route_req_args =
3738            RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3739                subnet,
3740                None,
3741                None,
3742                None,
3743                NetlinkRouteTableIndex::new(1234),
3744            )));
3745        test_route_requests_helper(
3746            [RequestArgs::Route(route_req_args)],
3747            Vec::new(),
3748            HashMap::new(),
3749            vec![waiter_result],
3750            subnet,
3751        )
3752        .await;
3753    }
3754
3755    /// A test to exercise a `RTM_NEWROUTE` followed by a `RTM_GETROUTE`
3756    /// route request, ensuring that the new route is included in the
3757    /// dump request.
3758    #[test_case(
3759        V4_SUB1,
3760        ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3761        MANAGED_ROUTE_TABLE_INDEX;
3762        "v4_new_same_table_dump")]
3763    #[test_case(
3764        V6_SUB1,
3765        ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3766        MANAGED_ROUTE_TABLE_INDEX;
3767        "v6_new_same_table_dump")]
3768    #[test_case(
3769        V4_SUB1,
3770        ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3771        NetlinkRouteTableIndex::new(1234);
3772        "v4_new_different_table_dump")]
3773    #[test_case(
3774        V6_SUB1,
3775        ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3776        NetlinkRouteTableIndex::new(1234);
3777        "v6_new_different_table_dump")]
3778    #[fuchsia::test]
3779    async fn test_new_then_get_dump_request<A: IpAddress>(
3780        subnet: Subnet<A>,
3781        group: ModernGroup,
3782        table: NetlinkRouteTableIndex,
3783    ) where
3784        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3785    {
3786        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3787            (),
3788            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3789            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3790        );
3791
3792        // There are two pre-set routes in `test_route_requests`.
3793        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3794        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3795        // To add a new route that does not get rejected by the handler due to it
3796        // already existing, we use a route that has METRIC3.
3797        let unicast_route_args =
3798            create_unicast_new_route_args(subnet, next_hop1, DEV1.into(), METRIC3, table);
3799
3800        // We expect to see 1 multicast message, representing the route that was added to
3801        // a managed table.
3802        // Then, three unicast messages, representing the two routes that existed already in the
3803        // route set, and the one new route that was added.
3804        let messages = vec![
3805            SentMessage::multicast(
3806                create_netlink_route_message::<A::Version>(
3807                    subnet.prefix(),
3808                    table,
3809                    create_nlas::<A::Version>(
3810                        Some(subnet),
3811                        Some(next_hop1),
3812                        DEV1,
3813                        METRIC3,
3814                        Some(table.get()),
3815                    ),
3816                )
3817                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3818                group,
3819            ),
3820            SentMessage::unicast(
3821                create_netlink_route_message::<A::Version>(
3822                    subnet.prefix(),
3823                    MANAGED_ROUTE_TABLE_INDEX,
3824                    create_nlas::<A::Version>(
3825                        Some(subnet),
3826                        Some(next_hop1),
3827                        DEV1,
3828                        METRIC1,
3829                        Some(MANAGED_ROUTE_TABLE_ID),
3830                    ),
3831                )
3832                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3833            ),
3834            SentMessage::unicast(
3835                create_netlink_route_message::<A::Version>(
3836                    subnet.prefix(),
3837                    MANAGED_ROUTE_TABLE_INDEX,
3838                    create_nlas::<A::Version>(
3839                        Some(subnet),
3840                        Some(next_hop2),
3841                        DEV2,
3842                        METRIC2,
3843                        Some(MANAGED_ROUTE_TABLE_ID),
3844                    ),
3845                )
3846                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3847            ),
3848            SentMessage::unicast(
3849                create_netlink_route_message::<A::Version>(
3850                    subnet.prefix(),
3851                    table,
3852                    create_nlas::<A::Version>(
3853                        Some(subnet),
3854                        Some(next_hop1),
3855                        DEV1,
3856                        METRIC3,
3857                        Some(table.get()),
3858                    ),
3859                )
3860                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3861            ),
3862        ];
3863
3864        test_route_requests_helper(
3865            [
3866                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
3867                    unicast_route_args,
3868                ))),
3869                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
3870            ],
3871            messages,
3872            route_set_for_table_id(
3873                vec![RouteSetResult::AddResult(Ok(true))],
3874                if table == MANAGED_ROUTE_TABLE_INDEX {
3875                    OTHER_FIDL_TABLE_ID
3876                } else {
3877                    fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1)
3878                },
3879            ),
3880            vec![Ok(()), Ok(())],
3881            subnet,
3882        )
3883        .await;
3884    }
3885
3886    /// TODO(https://fxbug.dev/336382905): Once otherwise equivalent
3887    /// routes can be inserted into different tables, update the
3888    /// assertions to recognize the route as being added successfully.
3889    ///
3890    /// A test to exercise a `RTM_NEWROUTE` with a route that already
3891    /// exists, but in a different routing table, followed by a `RTM_GETROUTE`
3892    /// route request, ensuring that the new route does not initiate a
3893    /// multicast message and is not included in the dump request.
3894    #[test_case(V4_SUB1; "v4_new_dump")]
3895    #[test_case(V6_SUB1; "v6_new_dump")]
3896    #[fuchsia::test]
3897    async fn test_new_route_different_table_then_get_dump_request<A: IpAddress>(subnet: Subnet<A>)
3898    where
3899        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3900    {
3901        let (next_hop1, next_hop2, IpInvariant(group)): (A, A, IpInvariant<ModernGroup>) =
3902            A::Version::map_ip(
3903                (),
3904                |()| {
3905                    (
3906                        V4_NEXTHOP1,
3907                        V4_NEXTHOP2,
3908                        IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE)),
3909                    )
3910                },
3911                |()| {
3912                    (
3913                        V6_NEXTHOP1,
3914                        V6_NEXTHOP2,
3915                        IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE)),
3916                    )
3917                },
3918            );
3919
3920        const ALTERNATIVE_ROUTE_TABLE: NetlinkRouteTableIndex = NetlinkRouteTableIndex::new(1337);
3921
3922        // There are two pre-set routes in `test_route_requests`.
3923        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3924        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3925        // Attempt to install the same first route, but with a different table.
3926        // Table id isn't important, as long as it is different
3927        // than MANAGED_ROUTE_TABLE.
3928        let unicast_route_args = create_unicast_new_route_args(
3929            subnet,
3930            next_hop1,
3931            DEV1.into(),
3932            METRIC1,
3933            ALTERNATIVE_ROUTE_TABLE,
3934        );
3935
3936        // We expect to see one multicast message for having added the new route, then three unicast
3937        // messages, for dumping the two routes that existed already in the route set, plus the new
3938        // one we added.
3939        let messages = vec![
3940            SentMessage::multicast(
3941                create_netlink_route_message::<A::Version>(
3942                    subnet.prefix(),
3943                    ALTERNATIVE_ROUTE_TABLE,
3944                    create_nlas::<A::Version>(
3945                        Some(subnet),
3946                        Some(next_hop1),
3947                        DEV1,
3948                        METRIC1,
3949                        Some(ALTERNATIVE_ROUTE_TABLE.get()),
3950                    ),
3951                )
3952                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3953                group,
3954            ),
3955            SentMessage::unicast(
3956                create_netlink_route_message::<A::Version>(
3957                    subnet.prefix(),
3958                    MANAGED_ROUTE_TABLE_INDEX,
3959                    create_nlas::<A::Version>(
3960                        Some(subnet),
3961                        Some(next_hop1),
3962                        DEV1,
3963                        METRIC1,
3964                        Some(MANAGED_ROUTE_TABLE_ID),
3965                    ),
3966                )
3967                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3968            ),
3969            SentMessage::unicast(
3970                create_netlink_route_message::<A::Version>(
3971                    subnet.prefix(),
3972                    ALTERNATIVE_ROUTE_TABLE,
3973                    create_nlas::<A::Version>(
3974                        Some(subnet),
3975                        Some(next_hop1),
3976                        DEV1,
3977                        METRIC1,
3978                        Some(ALTERNATIVE_ROUTE_TABLE.get()),
3979                    ),
3980                )
3981                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3982            ),
3983            SentMessage::unicast(
3984                create_netlink_route_message::<A::Version>(
3985                    subnet.prefix(),
3986                    MANAGED_ROUTE_TABLE_INDEX,
3987                    create_nlas::<A::Version>(
3988                        Some(subnet),
3989                        Some(next_hop2),
3990                        DEV2,
3991                        METRIC2,
3992                        Some(MANAGED_ROUTE_TABLE_ID),
3993                    ),
3994                )
3995                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3996            ),
3997        ];
3998
3999        test_route_requests_helper(
4000            [
4001                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
4002                    unicast_route_args,
4003                ))),
4004                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4005            ],
4006            messages,
4007            HashMap::from_iter([
4008                // The added route already existed in the main table.
4009                (MAIN_FIDL_TABLE_ID, vec![RouteSetResult::AddResult(Ok(false))].into()),
4010                // But it is new to the other table.
4011                (
4012                    fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1),
4013                    vec![RouteSetResult::AddResult(Ok(true))].into(),
4014                ),
4015            ]),
4016            vec![Ok(()), Ok(())],
4017            subnet,
4018        )
4019        .await;
4020    }
4021
4022    /// A test to exercise a `RTM_NEWROUTE` followed by a `RTM_DELROUTE` for the same route, then a
4023    /// `RTM_GETROUTE` request, ensuring that the route added created a multicast message, but does
4024    /// not appear in the dump.
4025    #[test_case(V4_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE); "v4_new_del_dump")]
4026    #[test_case(V6_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE); "v6_new_del_dump")]
4027    #[fuchsia::test]
4028    async fn test_new_then_del_then_get_dump_request<A: IpAddress>(
4029        subnet: Subnet<A>,
4030        group: ModernGroup,
4031    ) where
4032        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4033    {
4034        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4035            (),
4036            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4037            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4038        );
4039
4040        // There are two pre-set routes in `test_route_requests`.
4041        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
4042        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
4043        // To add a new route that does not get rejected by the handler due to it
4044        // already existing, we use a route that has METRIC3.
4045        let new_route_args = create_unicast_new_route_args(
4046            subnet,
4047            next_hop1,
4048            DEV1.into(),
4049            METRIC3,
4050            MANAGED_ROUTE_TABLE_INDEX,
4051        );
4052
4053        // The subnet and metric are enough to uniquely identify the above route.
4054        let del_route_args = create_unicast_del_route_args(
4055            subnet,
4056            None,
4057            None,
4058            Some(METRIC3),
4059            MANAGED_ROUTE_TABLE_INDEX,
4060        );
4061
4062        // We expect to see 2 multicast messages, the first representing the route that was
4063        // added and the other representing the same route being removed. Then, two unicast
4064        // messages, representing the two routes that existed already in the route set.
4065        let messages = vec![
4066            SentMessage::multicast(
4067                create_netlink_route_message::<A::Version>(
4068                    subnet.prefix(),
4069                    MANAGED_ROUTE_TABLE_INDEX,
4070                    create_nlas::<A::Version>(
4071                        Some(subnet),
4072                        Some(next_hop1),
4073                        DEV1,
4074                        METRIC3,
4075                        Some(MANAGED_ROUTE_TABLE_ID),
4076                    ),
4077                )
4078                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
4079                group,
4080            ),
4081            SentMessage::multicast(
4082                create_netlink_route_message::<A::Version>(
4083                    subnet.prefix(),
4084                    MANAGED_ROUTE_TABLE_INDEX,
4085                    create_nlas::<A::Version>(
4086                        Some(subnet),
4087                        Some(next_hop1),
4088                        DEV1,
4089                        METRIC3,
4090                        Some(MANAGED_ROUTE_TABLE_ID),
4091                    ),
4092                )
4093                .into_rtnl_del_route(),
4094                group,
4095            ),
4096            SentMessage::unicast(
4097                create_netlink_route_message::<A::Version>(
4098                    subnet.prefix(),
4099                    MANAGED_ROUTE_TABLE_INDEX,
4100                    create_nlas::<A::Version>(
4101                        Some(subnet),
4102                        Some(next_hop1),
4103                        DEV1,
4104                        METRIC1,
4105                        Some(MANAGED_ROUTE_TABLE_ID),
4106                    ),
4107                )
4108                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4109            ),
4110            SentMessage::unicast(
4111                create_netlink_route_message::<A::Version>(
4112                    subnet.prefix(),
4113                    MANAGED_ROUTE_TABLE_INDEX,
4114                    create_nlas::<A::Version>(
4115                        Some(subnet),
4116                        Some(next_hop2),
4117                        DEV2,
4118                        METRIC2,
4119                        Some(MANAGED_ROUTE_TABLE_ID),
4120                    ),
4121                )
4122                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4123            ),
4124        ];
4125
4126        test_route_requests_helper(
4127            [
4128                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(new_route_args))),
4129                RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(del_route_args))),
4130                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4131            ],
4132            messages,
4133            route_set_for_first_new_table(vec![
4134                RouteSetResult::AddResult(Ok(true)),
4135                RouteSetResult::DelResult(Ok(true)),
4136            ]),
4137            vec![Ok(()), Ok(()), Ok(())],
4138            subnet,
4139        )
4140        .await;
4141    }
4142
4143    /// Tests RTM_NEWROUTE and RTM_DELROUTE when the interface is removed,
4144    /// indicated by the closure of the admin Control's server-end.
4145    /// The specific cause of the interface removal is unimportant
4146    /// for this test.
4147    #[test_case(RouteRequestKind::New, V4_SUB1; "v4_new_if_removed")]
4148    #[test_case(RouteRequestKind::New, V6_SUB1; "v6_new_if_removed")]
4149    #[test_case(RouteRequestKind::Del, V4_SUB1; "v4_del_if_removed")]
4150    #[test_case(RouteRequestKind::Del, V6_SUB1; "v6_del_if_removed")]
4151    #[fuchsia::test]
4152    async fn test_new_del_route_interface_removed<A: IpAddress>(
4153        kind: RouteRequestKind,
4154        subnet: Subnet<A>,
4155    ) where
4156        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4157    {
4158        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4159            (),
4160            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4161            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4162        );
4163
4164        // There are two pre-set routes in `test_route_requests`.
4165        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
4166        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
4167        let (route_req_args, route_set_result) = match kind {
4168            RouteRequestKind::New => {
4169                // Add a route that is not already present.
4170                let args =
4171                    RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
4172                        subnet,
4173                        next_hop1,
4174                        DEV1.into(),
4175                        METRIC3,
4176                        MANAGED_ROUTE_TABLE_INDEX,
4177                    )));
4178                let res = RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated));
4179                (args, res)
4180            }
4181            RouteRequestKind::Del => {
4182                // Remove an existing route.
4183                let args =
4184                    RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
4185                        subnet,
4186                        None,
4187                        None,
4188                        None,
4189                        MANAGED_ROUTE_TABLE_INDEX,
4190                    )));
4191                let res = RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated));
4192                (args, res)
4193            }
4194        };
4195
4196        // No routes will be added or removed successfully, so there are no expected messages.
4197        let expected_messages = Vec::new();
4198
4199        pretty_assertions::assert_eq!(
4200            test_requests(
4201                [RequestArgs::Route(route_req_args)],
4202                |interfaces_request_stream| async move {
4203                    interfaces_request_stream
4204                        .for_each(|req| {
4205                            futures::future::ready(match req.unwrap() {
4206                                fnet_root::InterfacesRequest::GetAdmin {
4207                                    id,
4208                                    control,
4209                                    control_handle: _,
4210                                } => {
4211                                    pretty_assertions::assert_eq!(id, DEV1 as u64);
4212                                    let control = control.into_stream();
4213                                    let control = control.control_handle();
4214                                    control.shutdown();
4215                                }
4216                                req => unreachable!("unexpected interfaces request: {req:?}"),
4217                            })
4218                        })
4219                        .await
4220                },
4221                route_set_for_first_new_table(vec![route_set_result]),
4222                subnet,
4223                next_hop1,
4224                next_hop2,
4225                expected_messages.len(),
4226            )
4227            .await,
4228            TestRequestResult {
4229                messages: expected_messages,
4230                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
4231            },
4232        )
4233    }
4234
4235    // A flattened view of Route, convenient for holding testdata.
4236    #[derive(Clone)]
4237    struct Route<I: Ip> {
4238        subnet: Subnet<I::Addr>,
4239        device: u32,
4240        nexthop: Option<I::Addr>,
4241        metric: Option<NonZeroU32>,
4242    }
4243
4244    impl<I: Ip> Route<I> {
4245        fn to_route(self) -> fnet_routes_ext::Route<I> {
4246            let Self { subnet, device, nexthop, metric } = self;
4247            fnet_routes_ext::Route {
4248                destination: subnet,
4249                action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
4250                    outbound_interface: device.into(),
4251                    next_hop: nexthop
4252                        .map(|a| SpecifiedAddr::new(a).expect("nexthop should be specified")),
4253                }),
4254                properties: fnet_routes_ext::RouteProperties {
4255                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
4256                        metric: netlink_priority_to_specified_metric(metric, I::VERSION),
4257                    },
4258                },
4259            }
4260        }
4261
4262        fn to_installed_route(
4263            self,
4264            table_id: fnet_routes_ext::TableId,
4265        ) -> fnet_routes_ext::InstalledRoute<I> {
4266            const DEFAULT_INTERFACE_METRIC: u32 = 100000;
4267            let effective_metric = match self.metric {
4268                None => DEFAULT_INTERFACE_METRIC,
4269                Some(metric) => metric.into(),
4270            };
4271            let route = self.to_route();
4272            fnet_routes_ext::InstalledRoute {
4273                route,
4274                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
4275                    metric: effective_metric,
4276                },
4277                table_id,
4278            }
4279        }
4280    }
4281
4282    const ROUTE_METRIC1: NonZeroU32 = NonZeroU32::new(METRIC1).unwrap();
4283    const ROUTE_METRIC2: NonZeroU32 = NonZeroU32::new(METRIC2).unwrap();
4284    const ROUTE_METRIC3: NonZeroU32 = NonZeroU32::new(METRIC3).unwrap();
4285
4286    #[test_case(
4287        Route::<Ipv4>{
4288            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4289        },
4290        Route::<Ipv4>{
4291            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4292        },
4293        true; "all_fields_the_same_v4_should_match")]
4294    #[test_case(
4295        Route::<Ipv6>{
4296            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4297        },
4298        Route::<Ipv6>{
4299            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4300        },
4301        true; "all_fields_the_same_v6_should_match")]
4302    #[test_case(
4303        Route::<Ipv4>{
4304            subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4305        },
4306        Route::<Ipv4>{
4307            subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4308        },
4309        true; "default_route_v4_should_match")]
4310    #[test_case(
4311        Route::<Ipv6>{
4312            subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4313        },
4314        Route::<Ipv6>{
4315            subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4316        },
4317        true; "default_route_v6_should_match")]
4318    #[test_case(
4319        Route::<Ipv4>{
4320            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4321        },
4322        Route::<Ipv4>{
4323            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4324        },
4325        true; "different_device_v4_should_match")]
4326    #[test_case(
4327        Route::<Ipv6>{
4328            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4329        },
4330        Route::<Ipv6>{
4331            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4332        },
4333        true; "different_device_v6_should_match")]
4334    #[test_case(
4335        Route::<Ipv4>{
4336            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4337        },
4338        Route::<Ipv4>{
4339            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4340        },
4341        true; "different_nexthop_v4_should_match")]
4342    #[test_case(
4343        Route::<Ipv6>{
4344            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4345        },
4346        Route::<Ipv6>{
4347            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4348        },
4349        true; "different_nexthop_v6_should_match")]
4350    #[test_case(
4351        Route::<Ipv4>{
4352            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4353        },
4354        Route::<Ipv4>{
4355            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4356        },
4357        true; "different_device_and_nexthop_v4_should_match")]
4358    #[test_case(
4359        Route::<Ipv6>{
4360            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4361        },
4362        Route::<Ipv6>{
4363            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4364        },
4365        true; "different_device_and_nexthop_v6_should_match")]
4366    #[test_case(
4367        Route::<Ipv4>{
4368            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4369        },
4370        Route::<Ipv4>{
4371            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4372        },
4373        true; "nexthop_newly_unset_v4_should_match")]
4374    #[test_case(
4375        Route::<Ipv6>{
4376            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4377        },
4378        Route::<Ipv6>{
4379            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4380        },
4381        true; "nexthop_newly_unset_v6_should_match")]
4382    #[test_case(
4383        Route::<Ipv4>{
4384            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4385        },
4386        Route::<Ipv4>{
4387            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4388        },
4389        true; "nexthop_previously_unset_v4_should_match")]
4390    #[test_case(
4391        Route::<Ipv6>{
4392            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4393        },
4394        Route::<Ipv6>{
4395            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4396        },
4397        true; "nexthop_previously_unset_v6_should_match")]
4398    #[test_case(
4399        Route::<Ipv4>{
4400            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4401        },
4402        Route::<Ipv4>{
4403            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4404        },
4405        false; "different_metric_v4_should_not_match")]
4406    #[test_case(
4407        Route::<Ipv4>{
4408            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4409        },
4410        Route::<Ipv4>{
4411            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4412        },
4413        false; "default_and_non_default_v4_should_not_match")]
4414    #[test_case(
4415        Route::<Ipv4>{
4416            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4417        },
4418        Route::<Ipv4>{
4419            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4420        },
4421        true; "default_and_default_v4_should_match")]
4422    #[test_case(
4423        Route::<Ipv6>{
4424            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4425        },
4426        Route::<Ipv6>{
4427            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4428        },
4429        false; "different_metric_v6_should_not_match")]
4430    #[test_case(
4431        Route::<Ipv4>{
4432            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4433        },
4434        Route::<Ipv4>{
4435            subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4436        },
4437        false; "different_subnet_v4_should_not_match")]
4438    #[test_case(
4439        Route::<Ipv6>{
4440            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4441        },
4442        Route::<Ipv6>{
4443            subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4444        },
4445        false; "different_subnet_v6_should_not_match")]
4446    #[test_case(
4447        Route::<Ipv4>{
4448            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4449        },
4450        Route::<Ipv4>{
4451            subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4452        },
4453        false; "different_subnet_prefixlen_v4_should_not_match")]
4454    #[test_case(
4455        Route::<Ipv6>{
4456            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4457        },
4458        Route::<Ipv6>{
4459            subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4460        },
4461        false; "different_subnet_prefixlen_v6_should_not_match")]
4462    #[test_case(
4463        Route::<Ipv6>{
4464            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4465        },
4466        Route::<Ipv6>{
4467            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4468        },
4469        false; "default_and_non_default_v6_should_not_match")]
4470    #[test_case(
4471        Route::<Ipv6>{
4472            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4473        },
4474        Route::<Ipv6>{
4475            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4476        },
4477        true; "default_and_default_v6_should_match")]
4478    fn test_new_route_matcher<I: Ip>(
4479        route1: Route<I>,
4480        route2: Route<I>,
4481        expected_to_conflict: bool,
4482    ) {
4483        let route1 = route1.to_installed_route(MAIN_FIDL_TABLE_ID);
4484        let route2 = route2.to_installed_route(MAIN_FIDL_TABLE_ID);
4485
4486        let got_conflict = routes_conflict::<I>(route1, route2.route, route2.table_id);
4487        assert_eq!(got_conflict, expected_to_conflict);
4488
4489        let got_conflict = routes_conflict::<I>(route2, route1.route, route1.table_id);
4490        assert_eq!(got_conflict, expected_to_conflict);
4491    }
4492
4493    // Calls `select_route_for_deletion` with the given args & existing_routes.
4494    //
4495    // Asserts that the return route matches the route in `existing_routes` at
4496    // `expected_index`.
4497    fn test_select_route_for_deletion_helper<
4498        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4499    >(
4500        args: UnicastDelRouteArgs<I>,
4501        existing_routes: &[Route<I>],
4502        // The index into `existing_routes` of the route that should be selected.
4503        expected_index: Option<usize>,
4504    ) {
4505        let mut fidl_route_map = FidlRouteMap::<I>::default();
4506
4507        // We create a bunch of proxies that go unused in this test. In order for this to succeed
4508        // we must have an executor.
4509        let _executor = fuchsia_async::TestExecutor::new();
4510
4511        let (main_route_table_proxy, _server_end) =
4512            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4513        let (own_route_table_proxy, _server_end) =
4514            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4515        let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4516        let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
4517            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4518        let (route_table_provider, _server_end) =
4519            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
4520
4521        let mut route_table_map = RouteTableMap::<I>::new(
4522            main_route_table_proxy,
4523            MAIN_FIDL_TABLE_ID,
4524            unmanaged_route_set_proxy,
4525            route_table_provider,
4526        );
4527
4528        route_table_map.insert(
4529            MANAGED_ROUTE_TABLE_INDEX,
4530            RouteTable::Managed(ManagedRouteTable {
4531                route_table_proxy: own_route_table_proxy,
4532                route_set_proxy,
4533                fidl_table_id: OTHER_FIDL_TABLE_ID,
4534                rule_set_authenticated: false,
4535            }),
4536        );
4537
4538        for Route { subnet, device, nexthop, metric } in existing_routes {
4539            let fnet_routes_ext::InstalledRoute { route, effective_properties, table_id } =
4540                create_installed_route::<I>(
4541                    *subnet,
4542                    *nexthop,
4543                    (*device).into(),
4544                    metric.map_or(0, NonZeroU32::get),
4545                    OTHER_FIDL_TABLE_ID,
4546                );
4547            assert_matches!(fidl_route_map.add(route, table_id, effective_properties), None);
4548        }
4549
4550        let existing_routes = existing_routes
4551            .iter()
4552            .map(|Route { subnet, device, nexthop, metric }| {
4553                // Don't populate the Destination NLA if this is the default route.
4554                let destination = (subnet.prefix() != 0).then_some(*subnet);
4555                create_netlink_route_message::<I>(
4556                    subnet.prefix(),
4557                    MANAGED_ROUTE_TABLE_INDEX,
4558                    create_nlas::<I>(
4559                        destination,
4560                        nexthop.to_owned(),
4561                        *device,
4562                        metric.map_or(0, NonZeroU32::get),
4563                        Some(MANAGED_ROUTE_TABLE_ID),
4564                    ),
4565                )
4566            })
4567            .collect::<Vec<_>>();
4568        let expected_route = expected_index.map(|index| {
4569            existing_routes
4570                .get(index)
4571                .expect("index should be within the bounds of `existing_routes`")
4572                .clone()
4573        });
4574
4575        assert_eq!(
4576            select_route_for_deletion(
4577                &fidl_route_map,
4578                &route_table_map,
4579                DelRouteArgs::Unicast(args).try_into().unwrap(),
4580            ),
4581            expected_route
4582        )
4583    }
4584
4585    #[test_case(
4586        UnicastDelRouteArgs::<Ipv4> {
4587            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4588            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4589                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4590            ),
4591        },
4592        Route::<Ipv4>{
4593            subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4594        },
4595        false; "subnet_does_not_match_v4")]
4596    #[test_case(
4597        UnicastDelRouteArgs::<Ipv4> {
4598            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4599            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4600                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4601            ),
4602        },
4603        Route::<Ipv4>{
4604            subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4605        },
4606        false; "subnet_prefix_len_does_not_match_v4")]
4607    #[test_case(
4608        UnicastDelRouteArgs::<Ipv4> {
4609            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4610            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4611                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4612            ),
4613        },
4614        Route::<Ipv4>{
4615            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4616        },
4617        true; "subnet_matches_v4")]
4618    #[test_case(
4619        UnicastDelRouteArgs::<Ipv4> {
4620            subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4621            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4622                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4623            ),
4624        },
4625        Route::<Ipv4>{
4626            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4627        },
4628        false; "interface_does_not_match_v4")]
4629    #[test_case(
4630        UnicastDelRouteArgs::<Ipv4> {
4631            subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4632            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4633                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4634            ),
4635        },
4636        Route::<Ipv4>{
4637            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4638        },
4639        true; "interface_matches_v4")]
4640    #[test_case(
4641        UnicastDelRouteArgs::<Ipv4> {
4642            subnet: V4_SUB1, outbound_interface: None,
4643            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4644            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4645                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4646            ),
4647        },
4648        Route::<Ipv4>{
4649            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4650        },
4651        false; "nexthop_absent_v4")]
4652    #[test_case(
4653        UnicastDelRouteArgs::<Ipv4> {
4654            subnet: V4_SUB1, outbound_interface: None,
4655            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4656            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4657                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4658            ),
4659        },
4660        Route::<Ipv4>{
4661            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4662        },
4663        false; "nexthop_does_not_match_v4")]
4664    #[test_case(
4665        UnicastDelRouteArgs::<Ipv4> {
4666            subnet: V4_SUB1, outbound_interface: None,
4667            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4668            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4669                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4670            ),
4671        },
4672        Route::<Ipv4>{
4673            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4674        },
4675        true; "nexthop_matches_v4")]
4676    #[test_case(
4677        UnicastDelRouteArgs::<Ipv4> {
4678            subnet: V4_SUB1, outbound_interface: None,
4679            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4680            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4681                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4682            ),
4683        },
4684        Route::<Ipv4>{
4685            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4686        },
4687        false; "metric_does_not_match_v4")]
4688    #[test_case(
4689        UnicastDelRouteArgs::<Ipv4> {
4690            subnet: V4_SUB1, outbound_interface: None,
4691            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4692            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4693                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4694            ),
4695        },
4696        Route::<Ipv4>{
4697            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4698        },
4699        true; "metric_matches_v4")]
4700    #[test_case(
4701        UnicastDelRouteArgs::<Ipv6> {
4702            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4703            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4704                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4705            ),
4706        },
4707        Route::<Ipv6>{
4708            subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4709        },
4710        false; "subnet_does_not_match_v6")]
4711    #[test_case(
4712        UnicastDelRouteArgs::<Ipv6> {
4713            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4714            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4715                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4716            ),
4717        },
4718        Route::<Ipv6>{
4719            subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4720        },
4721        false; "subnet_prefix_len_does_not_match_v6")]
4722    #[test_case(
4723        UnicastDelRouteArgs::<Ipv6> {
4724            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4725            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4726                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4727            ),
4728        },
4729        Route::<Ipv6>{
4730            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4731        },
4732        true; "subnet_matches_v6")]
4733    #[test_case(
4734        UnicastDelRouteArgs::<Ipv6> {
4735            subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4736            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4737                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4738            ),
4739        },
4740        Route::<Ipv6>{
4741            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4742        },
4743        false; "interface_does_not_match_v6")]
4744    #[test_case(
4745        UnicastDelRouteArgs::<Ipv6> {
4746            subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4747            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4748                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4749            ),
4750        },
4751        Route::<Ipv6>{
4752            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4753        },
4754        true; "interface_matches_v6")]
4755    #[test_case(
4756        UnicastDelRouteArgs::<Ipv6> {
4757            subnet: V6_SUB1, outbound_interface: None,
4758            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4759            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4760                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4761            ),
4762        },
4763        Route::<Ipv6>{
4764            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4765        },
4766        false; "nexthop_absent_v6")]
4767    #[test_case(
4768        UnicastDelRouteArgs::<Ipv6> {
4769            subnet: V6_SUB1, outbound_interface: None,
4770            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4771            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4772                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4773            ),
4774        },
4775        Route::<Ipv6>{
4776            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4777        },
4778        false; "nexthop_does_not_match_v6")]
4779    #[test_case(
4780        UnicastDelRouteArgs::<Ipv6> {
4781            subnet: V6_SUB1, outbound_interface: None,
4782            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4783            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4784                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4785            ),
4786        },
4787        Route::<Ipv6>{
4788            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4789        },
4790        true; "nexthop_matches_v6")]
4791    #[test_case(
4792        UnicastDelRouteArgs::<Ipv6> {
4793            subnet: V6_SUB1, outbound_interface: None,
4794            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4795            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4796                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4797            ),
4798        },
4799        Route::<Ipv6>{
4800            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4801        },
4802        false; "metric_does_not_match_v6")]
4803    #[test_case(
4804        UnicastDelRouteArgs::<Ipv6> {
4805            subnet: V6_SUB1, outbound_interface: None,
4806            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4807            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4808                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4809            ),
4810        },
4811        Route::<Ipv6>{
4812            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4813        },
4814        true; "metric_matches_v6")]
4815    fn test_select_route_for_deletion<
4816        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4817    >(
4818        args: UnicastDelRouteArgs<I>,
4819        existing_route: Route<I>,
4820        expect_match: bool,
4821    ) {
4822        test_select_route_for_deletion_helper(args, &[existing_route], expect_match.then_some(0))
4823    }
4824
4825    #[test_case(
4826        UnicastDelRouteArgs::<Ipv4> {
4827            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4828            table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4829        },
4830        &[
4831        Route::<Ipv4>{
4832            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4833        },
4834        Route::<Ipv4>{
4835            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4836        },
4837        Route::<Ipv4>{
4838            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4839        },
4840        ],
4841        Some(1); "multiple_matches_prefers_lowest_metric_v4")]
4842    #[test_case(
4843        UnicastDelRouteArgs::<Ipv6> {
4844            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4845            table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4846        },
4847        &[
4848        Route::<Ipv6>{
4849            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4850        },
4851        Route::<Ipv6>{
4852            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4853        },
4854        Route::<Ipv6>{
4855            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4856        },
4857        ],
4858        Some(1); "multiple_matches_prefers_lowest_metric_v6")]
4859    fn test_select_route_for_deletion_multiple_matches<
4860        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4861    >(
4862        args: UnicastDelRouteArgs<I>,
4863        existing_routes: &[Route<I>],
4864        expected_index: Option<usize>,
4865    ) {
4866        test_select_route_for_deletion_helper(args, existing_routes, expected_index);
4867    }
4868
4869    #[ip_test(I, test = false)]
4870    #[fuchsia::test]
4871    async fn garbage_collects_empty_table<
4872        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4873    >() {
4874        let (_route_sink, route_client, async_work_drain_task) =
4875            crate::client::testutil::new_fake_client::<NetlinkRoute>(
4876                crate::client::testutil::CLIENT_ID_1,
4877                [ModernGroup(match I::VERSION {
4878                    IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
4879                    IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
4880                })],
4881            );
4882        let join_handle = fasync::Task::spawn(async_work_drain_task);
4883        {
4884            // Move `route_client` into the scope so it gets dropped.
4885            let route_client = route_client;
4886            let route_clients = ClientTable::default();
4887            route_clients.add_client(route_client.clone());
4888
4889            let Setup {
4890                event_loop_inputs,
4891                watcher_stream,
4892                route_sets: (main_route_table_server_end, route_table_provider_server_end),
4893                interfaces_request_stream: _,
4894                mut request_sink,
4895                async_work_sink: _,
4896            } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
4897
4898            let mut main_route_table_fut = pin!(
4899                fnet_routes_ext::testutil::admin::serve_noop_route_sets_with_table_id::<I>(
4900                    main_route_table_server_end,
4901                    MAIN_FIDL_TABLE_ID
4902                )
4903                .fuse()
4904            );
4905
4906            let mut watcher_stream = pin!(watcher_stream.fuse());
4907            let mut route_table_provider_stream = route_table_provider_server_end.into_stream();
4908
4909            let mut event_loop = {
4910                let included_workers = match I::VERSION {
4911                    IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
4912                        routes_v4: EventLoopComponent::Present(()),
4913                        routes_v6: EventLoopComponent::Absent(Optional),
4914                        interfaces: EventLoopComponent::Absent(Optional),
4915                        rules_v4: EventLoopComponent::Absent(Optional),
4916                        rules_v6: EventLoopComponent::Absent(Optional),
4917                        nduseropt: EventLoopComponent::Absent(Optional),
4918                        neighbors: EventLoopComponent::Absent(Optional),
4919                    },
4920                    IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
4921                        routes_v4: EventLoopComponent::Absent(Optional),
4922                        routes_v6: EventLoopComponent::Present(()),
4923                        interfaces: EventLoopComponent::Absent(Optional),
4924                        rules_v4: EventLoopComponent::Absent(Optional),
4925                        rules_v6: EventLoopComponent::Absent(Optional),
4926                        nduseropt: EventLoopComponent::Absent(Optional),
4927                        neighbors: EventLoopComponent::Absent(Optional),
4928                    },
4929                };
4930
4931                let event_loop_fut = event_loop_inputs.initialize(included_workers).fuse();
4932                let watcher_fut = async {
4933                    let watch_req =
4934                        watcher_stream.by_ref().next().await.expect("should not have ended");
4935                    // Start with no routes.
4936                    fnet_routes_ext::testutil::handle_watch::<I>(
4937                        watch_req,
4938                        vec![fnet_routes_ext::Event::<I>::Idle.try_into().unwrap()],
4939                    )
4940                }
4941                .fuse();
4942
4943                futures::select! {
4944                    () = main_route_table_fut => unreachable!(),
4945                    (event_loop, ()) = futures::future::join(
4946                        event_loop_fut, watcher_fut
4947                    ) => event_loop,
4948                }
4949            };
4950
4951            let (completer, mut initial_add_request_waiter) = oneshot::channel();
4952
4953            let new_route_args = NewRouteArgs::Unicast(I::map_ip_out(
4954                (),
4955                |()| {
4956                    create_unicast_new_route_args(
4957                        V4_SUB1,
4958                        V4_NEXTHOP1,
4959                        DEV1.into(),
4960                        METRIC1,
4961                        MANAGED_ROUTE_TABLE_INDEX,
4962                    )
4963                },
4964                |()| {
4965                    create_unicast_new_route_args(
4966                        V6_SUB1,
4967                        V6_NEXTHOP1,
4968                        DEV1.into(),
4969                        METRIC1,
4970                        MANAGED_ROUTE_TABLE_INDEX,
4971                    )
4972                },
4973            ));
4974            let expected_route = fnet_routes_ext::Route::<I>::from(new_route_args);
4975
4976            // Request that a route is installed in a new table.
4977            request_sink
4978                .try_send(
4979                    Request {
4980                        args: RequestArgs::Route(RouteRequestArgs::New(new_route_args)),
4981                        sequence_number: TEST_SEQUENCE_NUMBER,
4982                        client: route_client.clone(),
4983                        completer,
4984                    }
4985                    .into(),
4986                )
4987                .expect("should succeed");
4988
4989            // Run the event loop and observe the new table get created and the
4990            // route set requests go out.
4991            let (mut route_table_stream, mut route_set_stream) = {
4992                let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
4993                let route_table_fut = async {
4994                    let server_end = match I::into_route_table_provider_request(
4995                        route_table_provider_stream
4996                            .try_next()
4997                            .await
4998                            .expect("should not have ended")
4999                            .expect("fidl error"),
5000                    ) {
5001                        fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
5002                            provider,
5003                            options: _,
5004                            control_handle: _,
5005                        } => provider,
5006                        r => panic!("unexpected request {r:?}"),
5007                    };
5008                    let mut route_table_stream = server_end.into_stream().boxed().fuse();
5009
5010                    let request = I::into_route_table_request_result(
5011                        route_table_stream.by_ref().next().await.expect("should not have ended"),
5012                    )
5013                    .expect("should not get error");
5014
5015                    let responder = match request {
5016                        RouteTableRequest::GetTableId { responder } => responder,
5017                        _ => panic!("should be GetTableId"),
5018                    };
5019                    responder.send(OTHER_FIDL_TABLE_ID.get()).expect("should succeed");
5020
5021                    let request = I::into_route_table_request_result(
5022                        route_table_stream.by_ref().next().await.expect("should not have ended"),
5023                    )
5024                    .expect("should not get error");
5025
5026                    let server_end = match request {
5027                        RouteTableRequest::NewRouteSet { route_set, control_handle: _ } => {
5028                            route_set
5029                        }
5030                        _ => panic!("should be NewRouteSet"),
5031                    };
5032                    let mut route_set_stream = server_end.into_stream().boxed().fuse();
5033
5034                    let request = I::into_route_set_request_result(
5035                        route_set_stream.by_ref().next().await.expect("should not have ended"),
5036                    )
5037                    .expect("should not get error");
5038
5039                    let (route, responder) = match request {
5040                        RouteSetRequest::AddRoute { route, responder } => (route, responder),
5041                        _ => panic!("should be AddRoute"),
5042                    };
5043                    let route = route.expect("should successfully convert FIDl");
5044                    assert_eq!(route, expected_route);
5045
5046                    responder.send(Ok(true)).expect("sending response should succeed");
5047                    (route_table_stream, route_set_stream)
5048                }
5049                .fuse();
5050                futures::select! {
5051                    () = main_route_table_fut => unreachable!(),
5052                    ((), streams) = futures::future::join(event_loop_fut, route_table_fut) => {
5053                        streams
5054                    }
5055                }
5056            };
5057
5058            {
5059                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5060                // The new route table should be present in the map.
5061                let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5062                    Some(RouteTable::Managed(table)) => table,
5063                    _ => panic!("table should be present"),
5064                };
5065                assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5066
5067                // But the new route won't be tracked because we haven't
5068                // confirmed it via the watcher yet.
5069                assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5070                    &expected_route,
5071                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5072                ));
5073            }
5074
5075            // The request won't be complete until we've confirmed addition via the watcher.
5076            assert_matches!(initial_add_request_waiter.try_recv(), Ok(None));
5077
5078            // Run the event loop while yielding the new route via the watcher.
5079            {
5080                let event_loop_fut = async {
5081                    // Handling two events, so run two steps.
5082                    event_loop.run_one_step_in_tests().await;
5083                    event_loop.run_one_step_in_tests().await;
5084                }
5085                .fuse();
5086                let watcher_fut = async {
5087                    let watch_req =
5088                        watcher_stream.by_ref().next().await.expect("should not have ended");
5089                    // Show that the route was added for both the main and the owned FIDL table.
5090                    fnet_routes_ext::testutil::handle_watch::<I>(
5091                        watch_req,
5092                        vec![
5093                            fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5094                                route: expected_route,
5095                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5096                                    metric: METRIC1,
5097                                },
5098                                table_id: MAIN_FIDL_TABLE_ID,
5099                            })
5100                            .try_into()
5101                            .unwrap(),
5102                            fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5103                                route: expected_route,
5104                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5105                                    metric: METRIC1,
5106                                },
5107                                table_id: OTHER_FIDL_TABLE_ID,
5108                            })
5109                            .try_into()
5110                            .unwrap(),
5111                        ],
5112                    );
5113                };
5114                let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5115            }
5116
5117            {
5118                let (routes_worker, _route_table_map) = event_loop.route_table_state::<I>();
5119
5120                // The route should be noted as stored in both tables now.
5121                assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5122                    &expected_route,
5123                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5124                ));
5125            }
5126
5127            assert_matches!(initial_add_request_waiter.try_recv(), Ok(Some(Ok(()))));
5128
5129            let (completer, mut del_request_waiter) = oneshot::channel();
5130
5131            let del_route_args = I::map_ip_out(
5132                (),
5133                |()| {
5134                    create_unicast_del_route_args(
5135                        V4_SUB1,
5136                        Some(V4_NEXTHOP1),
5137                        Some(DEV1.into()),
5138                        Some(METRIC1),
5139                        MANAGED_ROUTE_TABLE_INDEX,
5140                    )
5141                },
5142                |()| {
5143                    create_unicast_del_route_args(
5144                        V6_SUB1,
5145                        Some(V6_NEXTHOP1),
5146                        Some(DEV1.into()),
5147                        Some(METRIC1),
5148                        MANAGED_ROUTE_TABLE_INDEX,
5149                    )
5150                },
5151            );
5152
5153            // Request the route's removal.
5154            request_sink
5155                .try_send(
5156                    Request {
5157                        args: RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(
5158                            del_route_args,
5159                        ))),
5160                        sequence_number: TEST_SEQUENCE_NUMBER,
5161                        client: route_client.clone(),
5162                        completer,
5163                    }
5164                    .into(),
5165                )
5166                .expect("should succeed");
5167
5168            // Observe and handle the removal requests.
5169            {
5170                let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
5171                let route_set_fut = async {
5172                    let request = I::into_route_set_request_result(
5173                        route_set_stream.next().await.expect("should not have ended"),
5174                    )
5175                    .expect("should not get error");
5176                    let (route, responder) = match request {
5177                        RouteSetRequest::RemoveRoute { route, responder } => (route, responder),
5178                        _ => panic!("should be DelRoute"),
5179                    };
5180                    let route = route.expect("should successfully convert FIDl");
5181                    assert_eq!(route, expected_route);
5182
5183                    responder.send(Ok(true)).expect("sending response should succeed");
5184                }
5185                .fuse();
5186
5187                futures::select! {
5188                    () = main_route_table_fut => unreachable!(),
5189                    ((), ()) = futures::future::join(event_loop_fut, route_set_fut) => (),
5190                }
5191            }
5192
5193            // We still haven't confirmed the removal via the watcher.
5194            {
5195                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5196                // The route table should still be present in the map.
5197                let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5198                    Some(RouteTable::Managed(table)) => table,
5199                    _ => panic!("table should be present"),
5200                };
5201                assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5202
5203                assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5204                    &expected_route,
5205                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5206                ));
5207            }
5208            assert_matches!(del_request_waiter.try_recv(), Ok(None));
5209
5210            // Run the event loop while yielding the deleted route via the watcher.
5211            {
5212                let event_loop_fut = async {
5213                    // Handling two events, so run two steps.
5214                    event_loop.run_one_step_in_tests().await;
5215                    event_loop.run_one_step_in_tests().await;
5216                }
5217                .fuse();
5218                let watcher_fut = async {
5219                    let watch_req =
5220                        watcher_stream.by_ref().next().await.expect("should not have ended");
5221                    // Show that the route was removed for both the main and the owned FIDL table.
5222                    fnet_routes_ext::testutil::handle_watch::<I>(
5223                        watch_req,
5224                        vec![
5225                            fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5226                                route: expected_route,
5227                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5228                                    metric: 0,
5229                                },
5230                                table_id: MAIN_FIDL_TABLE_ID,
5231                            })
5232                            .try_into()
5233                            .unwrap(),
5234                            fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5235                                route: expected_route,
5236                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5237                                    metric: 0,
5238                                },
5239                                table_id: OTHER_FIDL_TABLE_ID,
5240                            })
5241                            .try_into()
5242                            .unwrap(),
5243                        ],
5244                    );
5245                };
5246                let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5247            }
5248
5249            {
5250                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5251
5252                // The route should be noted as being removed from both tables now.
5253                assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5254                    &expected_route,
5255                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5256                ));
5257
5258                // And the table should now be cleaned up from the map.
5259                assert_matches!(route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX), None);
5260            }
5261            assert_matches!(del_request_waiter.try_recv(), Ok(Some(Ok(()))));
5262
5263            // Because the table was dropped from the map, the route table
5264            // request stream should close.
5265            let route_table_request = route_table_stream.next().await;
5266            assert!(route_table_request.is_none());
5267        };
5268        join_handle.await;
5269    }
5270
5271    #[ip_test(I, test = false)]
5272    #[fuchsia::test]
5273    async fn process_stashed_routes<
5274        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
5275    >() {
5276        let (subnet, next_hop) =
5277            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
5278        let table_id = OTHER_FIDL_TABLE_ID;
5279        let installed_route =
5280            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
5281        let to_be_removed_route =
5282            create_installed_route(subnet, Some(next_hop), DEV2.into(), METRIC2, table_id);
5283
5284        let (route_table_proxy, _route_table_server_end) =
5285            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
5286        let (unmanaged_route_set_proxy, _server_end) =
5287            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5288        let (route_table_provider, _server_end) =
5289            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
5290        let mut route_table = RouteTableMap::new(
5291            route_table_proxy.clone(),
5292            MAIN_FIDL_TABLE_ID,
5293            unmanaged_route_set_proxy,
5294            route_table_provider,
5295        );
5296        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
5297
5298        let mut worker = RoutesWorker {
5299            fidl_route_map: FidlRouteMap::<I>::default(),
5300            stashed_routes: HashMap::new(),
5301        };
5302
5303        // 1. Add routes, but the table is unknown to netlink.
5304        assert_eq!(
5305            worker.handle_route_watcher_event(
5306                &mut route_table,
5307                &route_clients,
5308                fnet_routes_ext::Event::Added(installed_route.clone()),
5309            ),
5310            None
5311        );
5312        assert_eq!(
5313            worker.handle_route_watcher_event(
5314                &mut route_table,
5315                &route_clients,
5316                fnet_routes_ext::Event::Added(to_be_removed_route.clone()),
5317            ),
5318            None
5319        );
5320
5321        // Verify they are stashed.
5322        assert!(worker.stashed_routes.contains_key(&table_id));
5323        assert!(worker.stashed_routes[&table_id].contains(&installed_route));
5324        assert!(worker.stashed_routes[&table_id].contains(&to_be_removed_route));
5325        assert_eq!(
5326            // The routes should NOT be in the FIDL map yet.
5327            worker.fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX).count(),
5328            0
5329        );
5330
5331        assert_eq!(
5332            worker.handle_route_watcher_event(
5333                &mut route_table,
5334                &route_clients,
5335                fnet_routes_ext::Event::Removed(to_be_removed_route.clone()),
5336            ),
5337            None
5338        );
5339        assert!(!worker.stashed_routes[&table_id].contains(&to_be_removed_route));
5340
5341        // 2. Add the table mapping.
5342        let netlink_table_id = NetlinkRouteTableIndex::new(123);
5343        let (route_set_proxy, _) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5344        let table = RouteTable::Unmanaged(UnmanagedTable {
5345            route_table_proxy: route_table_proxy.clone(),
5346            route_set_proxy,
5347            fidl_table_id: table_id,
5348            rule_set_authenticated: false,
5349        });
5350        route_table.insert(netlink_table_id, table);
5351
5352        // 3. Process stashed routes.
5353        worker.process_stashed_routes(&mut route_table, &route_clients, netlink_table_id);
5354
5355        // Verify only one route is unstashed and added to fidl_route_map.
5356        assert!(!worker.stashed_routes.contains_key(&table_id));
5357        // Verify it is in the FIDL map.
5358        // We need to iterate messages for the new table.
5359        assert_eq!(worker.fidl_route_map.iter_messages(&route_table, netlink_table_id).count(), 1);
5360    }
5361
5362    #[ip_test(I, test = false)]
5363    #[fuchsia::test]
5364    async fn remove_non_empty_table<
5365        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
5366    >() {
5367        let (subnet, next_hop) =
5368            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
5369        let table_id = OTHER_FIDL_TABLE_ID;
5370        let installed_route =
5371            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
5372
5373        let (route_table_proxy, _route_table_server_end) =
5374            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
5375        let (unmanaged_route_set_proxy, _server_end) =
5376            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5377        let (route_table_provider, _server_end) =
5378            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
5379        let mut route_table = RouteTableMap::new(
5380            route_table_proxy.clone(),
5381            MAIN_FIDL_TABLE_ID,
5382            unmanaged_route_set_proxy,
5383            route_table_provider,
5384        );
5385        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
5386
5387        let mut worker = RoutesWorker {
5388            fidl_route_map: FidlRouteMap::<I>::default(),
5389            stashed_routes: HashMap::new(),
5390        };
5391
5392        let netlink_table_id = NetlinkRouteTableIndex::new(123);
5393        let (route_set_proxy, _) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5394
5395        // Create a non-empty netlink table.
5396        let table = RouteTable::Unmanaged(UnmanagedTable {
5397            route_table_proxy: route_table_proxy.clone(),
5398            route_set_proxy,
5399            fidl_table_id: table_id,
5400            rule_set_authenticated: false,
5401        });
5402
5403        route_table.insert(netlink_table_id, table);
5404        assert_eq!(
5405            worker.handle_route_watcher_event(
5406                &mut route_table,
5407                &route_clients,
5408                fnet_routes_ext::Event::Added(installed_route.clone()),
5409            ),
5410            None
5411        );
5412
5413        // Remove the table.
5414        assert_matches!(route_table.remove_table_by_fidl_id(table_id), Some(Ok(_)));
5415        // Asynchronously, netstack will generate a remove event for the route
5416        // in that table.
5417        assert_eq!(
5418            worker.handle_route_watcher_event(
5419                &mut route_table,
5420                &route_clients,
5421                fnet_routes_ext::Event::Removed(installed_route.clone()),
5422            ),
5423            None
5424        );
5425    }
5426}