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::convert::Infallible as Never;
1410    use std::pin::pin;
1411    use std::sync::atomic::{AtomicU32, Ordering};
1412
1413    use fidl::endpoints::{ControlHandle, RequestStream, ServerEnd};
1414    use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
1415    use fidl_fuchsia_net_routes as fnet_routes;
1416    use fidl_fuchsia_net_routes_admin as fnet_routes_admin;
1417    use fidl_fuchsia_net_routes_ext::Responder as _;
1418    use fidl_fuchsia_net_routes_ext::admin::{RouteSetRequest, RouteTableRequest};
1419
1420    use assert_matches::assert_matches;
1421    use fuchsia_async as fasync;
1422    use futures::channel::mpsc;
1423    use futures::future::{Future, FutureExt as _};
1424    use futures::stream::TryStreamExt as _;
1425    use futures::{SinkExt as _, Stream};
1426    use ip_test_macro::ip_test;
1427    use linux_uapi::rtnetlink_groups_RTNLGRP_LINK;
1428    use net_declare::{net_ip_v4, net_ip_v6, net_subnet_v4, net_subnet_v6};
1429    use net_types::SpecifiedAddr;
1430    use net_types::ip::{GenericOverIp, IpInvariant, IpVersion, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
1431    use netlink_packet_core::NetlinkPayload;
1432    use test_case::test_case;
1433
1434    use crate::client::AsyncWorkItem;
1435    use crate::interfaces::testutil::FakeInterfacesHandler;
1436    use crate::messaging::testutil::{FakeSender, SentMessage};
1437    use crate::route_eventloop::{EventLoopComponent, Optional, Required};
1438
1439    const V4_SUB1: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.0/32");
1440    const V4_SUB2: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.1/32");
1441    const V4_SUB3: Subnet<Ipv4Addr> = net_subnet_v4!("192.0.2.0/24");
1442    const V4_DFLT: Subnet<Ipv4Addr> = net_subnet_v4!("0.0.0.0/0");
1443    const V4_NEXTHOP1: Ipv4Addr = net_ip_v4!("192.0.2.1");
1444    const V4_NEXTHOP2: Ipv4Addr = net_ip_v4!("192.0.2.2");
1445
1446    const V6_SUB1: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::/128");
1447    const V6_SUB2: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::1/128");
1448    const V6_SUB3: Subnet<Ipv6Addr> = net_subnet_v6!("2001:db8::/64");
1449    const V6_DFLT: Subnet<Ipv6Addr> = net_subnet_v6!("::/0");
1450    const V6_NEXTHOP1: Ipv6Addr = net_ip_v6!("2001:db8::1");
1451    const V6_NEXTHOP2: Ipv6Addr = net_ip_v6!("2001:db8::2");
1452
1453    const DEV1: u32 = 1;
1454    const DEV2: u32 = 2;
1455
1456    const METRIC1: u32 = 1;
1457    const METRIC2: u32 = 100;
1458    const METRIC3: u32 = 9999;
1459    const TEST_SEQUENCE_NUMBER: u32 = 1234;
1460    const MANAGED_ROUTE_TABLE_ID: u32 = 5678;
1461    const MANAGED_ROUTE_TABLE_INDEX: NetlinkRouteTableIndex =
1462        NetlinkRouteTableIndex::new(MANAGED_ROUTE_TABLE_ID);
1463    const MAIN_FIDL_TABLE_ID: fnet_routes_ext::TableId = fnet_routes_ext::TableId::new(0);
1464    const OTHER_FIDL_TABLE_ID: fnet_routes_ext::TableId = fnet_routes_ext::TableId::new(1);
1465
1466    fn create_installed_route<I: Ip>(
1467        subnet: Subnet<I::Addr>,
1468        next_hop: Option<I::Addr>,
1469        interface_id: u64,
1470        metric: u32,
1471        table_id: fnet_routes_ext::TableId,
1472    ) -> fnet_routes_ext::InstalledRoute<I> {
1473        fnet_routes_ext::InstalledRoute::<I> {
1474            route: fnet_routes_ext::Route {
1475                destination: subnet,
1476                action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget::<I> {
1477                    outbound_interface: interface_id,
1478                    next_hop: next_hop.map(|next_hop| SpecifiedAddr::new(next_hop)).flatten(),
1479                }),
1480                properties: fnet_routes_ext::RouteProperties {
1481                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
1482                        metric: fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1483                    },
1484                },
1485            },
1486            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
1487            table_id,
1488        }
1489    }
1490
1491    fn create_netlink_route_message<I: Ip>(
1492        destination_prefix_length: u8,
1493        table: NetlinkRouteTableIndex,
1494        nlas: Vec<RouteAttribute>,
1495    ) -> NetlinkRouteMessage {
1496        let mut route_header = RouteHeader::default();
1497        let address_family = match I::VERSION {
1498            IpVersion::V4 => AddressFamily::Inet,
1499            IpVersion::V6 => AddressFamily::Inet6,
1500        }
1501        .try_into()
1502        .expect("should fit into u8");
1503        route_header.address_family = address_family;
1504        route_header.destination_prefix_length = destination_prefix_length;
1505        route_header.kind = RouteType::Unicast;
1506        route_header.protocol = RouteProtocol::Kernel;
1507
1508        let (table_u8, _) = get_table_u8_and_nla_from_key(table);
1509        route_header.table = table_u8;
1510
1511        let mut route_message = RouteMessage::default();
1512        route_message.header = route_header;
1513        route_message.attributes = nlas;
1514
1515        NetlinkRouteMessage(route_message)
1516    }
1517
1518    fn create_nlas<I: Ip>(
1519        destination: Option<Subnet<I::Addr>>,
1520        next_hop: Option<I::Addr>,
1521        outgoing_interface_id: u32,
1522        metric: u32,
1523        table: Option<u32>,
1524    ) -> Vec<RouteAttribute> {
1525        let mut nlas = vec![];
1526
1527        let family = match I::VERSION {
1528            IpVersion::V4 => AddressFamily::Inet,
1529            IpVersion::V6 => AddressFamily::Inet6,
1530        };
1531
1532        if let Some(destination) = destination {
1533            let destination_nla = RouteAttribute::Destination(
1534                RouteAddress::parse(family, destination.network().bytes()).unwrap(),
1535            );
1536            nlas.push(destination_nla);
1537        }
1538
1539        let oif_nla = RouteAttribute::Oif(outgoing_interface_id);
1540        nlas.push(oif_nla);
1541
1542        if let Some(next_hop) = next_hop {
1543            let bytes = RouteAddress::parse(family, next_hop.bytes()).unwrap();
1544            let gateway_nla = RouteAttribute::Gateway(bytes);
1545            nlas.push(gateway_nla);
1546        }
1547
1548        let priority_nla = RouteAttribute::Priority(metric);
1549        nlas.push(priority_nla);
1550
1551        if let Some(t) = table {
1552            let table_nla = RouteAttribute::Table(t);
1553            nlas.push(table_nla);
1554        }
1555        nlas
1556    }
1557
1558    #[ip_test(I)]
1559    #[test_case(MAIN_ROUTE_TABLE_INDEX, MAIN_FIDL_TABLE_ID)]
1560    #[test_case(MANAGED_ROUTE_TABLE_INDEX, OTHER_FIDL_TABLE_ID)]
1561    #[fuchsia::test]
1562    async fn handles_route_watcher_event<
1563        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1564    >(
1565        netlink_id: NetlinkRouteTableIndex,
1566        fidl_id: fnet_routes_ext::TableId,
1567    ) {
1568        let scope = fasync::Scope::new();
1569        let (subnet, next_hop) =
1570            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1571        let installed_route1: fnet_routes_ext::InstalledRoute<I> =
1572            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, fidl_id);
1573        let installed_route2: fnet_routes_ext::InstalledRoute<I> =
1574            create_installed_route(subnet, Some(next_hop), DEV2.into(), METRIC2, fidl_id);
1575
1576        let add_event1 = fnet_routes_ext::Event::Added(installed_route1);
1577        let add_event2 = fnet_routes_ext::Event::Added(installed_route2);
1578        let remove_event = fnet_routes_ext::Event::Removed(installed_route1);
1579
1580        let expected_route_message1: NetlinkRouteMessage =
1581            NetlinkRouteMessage::try_from_installed_route(installed_route1, netlink_id).unwrap();
1582        let expected_route_message2: NetlinkRouteMessage =
1583            NetlinkRouteMessage::try_from_installed_route(installed_route2, netlink_id).unwrap();
1584
1585        // Set up two fake clients: one is a member of the route multicast group.
1586        let (right_group, wrong_group) = match I::VERSION {
1587            IpVersion::V4 => (
1588                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1589                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1590            ),
1591            IpVersion::V6 => (
1592                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1593                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1594            ),
1595        };
1596
1597        let (mut right_sink, right_client, async_work_drain_task) =
1598            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1599                crate::client::testutil::CLIENT_ID_1,
1600                [right_group],
1601            );
1602        let _join_handle = scope.spawn(async_work_drain_task);
1603        let (mut wrong_sink, wrong_client, async_work_drain_task) =
1604            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1605                crate::client::testutil::CLIENT_ID_2,
1606                [wrong_group],
1607            );
1608        let _join_handle = scope.spawn(async_work_drain_task);
1609        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1610        route_clients.add_client(right_client);
1611        route_clients.add_client(wrong_client);
1612
1613        let (route_set_proxy, _route_set_server_end) =
1614            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1615        let (route_table_proxy, _route_table_server_end) =
1616            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1617        let (unmanaged_route_set_proxy, _server_end) =
1618            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1619        let (route_table_provider, _server_end) =
1620            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1621
1622        let mut route_table = RouteTableMap::new(
1623            route_table_proxy.clone(),
1624            MAIN_FIDL_TABLE_ID,
1625            unmanaged_route_set_proxy,
1626            route_table_provider,
1627        );
1628        let mut fidl_route_map = FidlRouteMap::<I>::default();
1629
1630        match netlink_id {
1631            MAIN_ROUTE_TABLE_INDEX => {}
1632            MANAGED_ROUTE_TABLE_INDEX => {
1633                route_table.insert(
1634                    netlink_id,
1635                    RouteTable::Managed(ManagedRouteTable {
1636                        route_table_proxy,
1637                        route_set_proxy,
1638                        fidl_table_id: OTHER_FIDL_TABLE_ID,
1639                        rule_set_authenticated: false,
1640                    }),
1641                );
1642            }
1643            _ => panic!("unexpected netlink id: {netlink_id:?}"),
1644        }
1645
1646        assert_eq!(fidl_route_map.iter_messages(&route_table, netlink_id).count(), 0);
1647        assert_eq!(&right_sink.take_messages()[..], &[]);
1648        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1649
1650        assert_eq!(
1651            handle_route_watcher_event(
1652                &mut route_table,
1653                &mut fidl_route_map,
1654                &route_clients,
1655                add_event1,
1656            ),
1657            RouteEventOutcome::Noop,
1658        );
1659        assert_eq!(
1660            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1661            HashSet::from_iter([expected_route_message1.clone()])
1662        );
1663        assert_eq!(
1664            &right_sink.take_messages()[..],
1665            &[SentMessage::multicast(
1666                expected_route_message1
1667                    .clone()
1668                    .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
1669                right_group
1670            )]
1671        );
1672        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1673
1674        assert_eq!(
1675            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1676            HashSet::from_iter([expected_route_message1.clone()])
1677        );
1678        assert_eq!(&right_sink.take_messages()[..], &[]);
1679        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1680
1681        // Adding a different route should result in an addition.
1682        assert_eq!(
1683            handle_route_watcher_event(
1684                &mut route_table,
1685                &mut fidl_route_map,
1686                &route_clients,
1687                add_event2,
1688            ),
1689            RouteEventOutcome::Noop
1690        );
1691        assert_eq!(
1692            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1693            HashSet::from_iter([expected_route_message1.clone(), expected_route_message2.clone()])
1694        );
1695        assert_eq!(
1696            &right_sink.take_messages()[..],
1697            &[SentMessage::multicast(
1698                expected_route_message2
1699                    .clone()
1700                    .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
1701                right_group
1702            )]
1703        );
1704        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1705
1706        assert_eq!(
1707            handle_route_watcher_event(
1708                &mut route_table,
1709                &mut fidl_route_map,
1710                &route_clients,
1711                remove_event,
1712            ),
1713            RouteEventOutcome::Noop
1714        );
1715        assert_eq!(
1716            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1717            HashSet::from_iter([expected_route_message2.clone()])
1718        );
1719        assert_eq!(
1720            &right_sink.take_messages()[..],
1721            &[SentMessage::multicast(
1722                expected_route_message1.clone().into_rtnl_del_route(),
1723                right_group
1724            )]
1725        );
1726        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1727
1728        assert_eq!(
1729            fidl_route_map.iter_messages(&route_table, netlink_id).collect::<HashSet<_>>(),
1730            HashSet::from_iter([expected_route_message2.clone()])
1731        );
1732        assert_eq!(&right_sink.take_messages()[..], &[]);
1733        assert_eq!(&wrong_sink.take_messages()[..], &[]);
1734        drop(route_clients);
1735        scope.join().await;
1736    }
1737
1738    // Test handling of watcher events for routes in unmanaged tables.
1739    #[ip_test(I, test = false)]
1740    #[fuchsia::test]
1741    async fn handles_route_watcher_event_unmanaged_route_table<
1742        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1743    >() {
1744        let _scope = fasync::Scope::new();
1745        let (subnet, next_hop) =
1746            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1747        let installed_route: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1748            subnet,
1749            Some(next_hop),
1750            DEV1.into(),
1751            METRIC1,
1752            OTHER_FIDL_TABLE_ID,
1753        );
1754        let add_event = fnet_routes_ext::Event::Added(installed_route);
1755        let remove_event = fnet_routes_ext::Event::Removed(installed_route);
1756
1757        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1758        let (route_table_proxy, _route_table_server_end) =
1759            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1760        let (unmanaged_route_set_proxy, _server_end) =
1761            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1762        let (route_table_provider, _server_end) =
1763            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1764
1765        let mut route_table = RouteTableMap::new(
1766            route_table_proxy.clone(),
1767            MAIN_FIDL_TABLE_ID,
1768            unmanaged_route_set_proxy,
1769            route_table_provider,
1770        );
1771        let mut fidl_route_map = FidlRouteMap::<I>::default();
1772
1773        // Process Add message.
1774        assert_eq!(
1775            handle_route_watcher_event(
1776                &mut route_table,
1777                &mut fidl_route_map,
1778                &route_clients,
1779                add_event,
1780            ),
1781            RouteEventOutcome::UpdateStash(AddOrRemoveRoute::Added(installed_route))
1782        );
1783
1784        // Process Remove message.
1785        assert_eq!(
1786            handle_route_watcher_event(
1787                &mut route_table,
1788                &mut fidl_route_map,
1789                &route_clients,
1790                remove_event,
1791            ),
1792            RouteEventOutcome::UpdateStash(AddOrRemoveRoute::Removed(installed_route))
1793        );
1794    }
1795
1796    #[ip_test(I, test = false)]
1797    #[fuchsia::test]
1798    #[should_panic(expected = "Netstack reported an unexpected route event")]
1799    async fn handles_unknown_route_watcher_event<
1800        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1801    >() {
1802        let (route_table_proxy, _route_table_server_end) =
1803            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1804        let (unmanaged_route_set_proxy, _server_end) =
1805            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1806        let (route_table_provider, _server_end) =
1807            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1808        let mut route_table = RouteTableMap::new(
1809            route_table_proxy.clone(),
1810            MAIN_FIDL_TABLE_ID,
1811            unmanaged_route_set_proxy,
1812            route_table_provider,
1813        );
1814        let mut fidl_route_map = FidlRouteMap::<I>::default();
1815        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1816
1817        // Receiving an unknown event should result in a panic.
1818        let _ = handle_route_watcher_event(
1819            &mut route_table,
1820            &mut fidl_route_map,
1821            &route_clients,
1822            fnet_routes_ext::Event::Unknown,
1823        );
1824    }
1825
1826    #[ip_test(I, test = false)]
1827    #[fuchsia::test]
1828    #[should_panic(expected = "Netstack reported the addition of an existing route")]
1829    async fn handles_duplicate_route_watcher_event<
1830        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1831    >() {
1832        let (subnet, next_hop) =
1833            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1834        let table_id = MAIN_FIDL_TABLE_ID;
1835        let installed_route: fnet_routes_ext::InstalledRoute<I> =
1836            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
1837
1838        let (route_table_proxy, _route_table_server_end) =
1839            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1840        let (unmanaged_route_set_proxy, _server_end) =
1841            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1842        let (route_table_provider, _server_end) =
1843            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1844        let mut route_table = RouteTableMap::new(
1845            route_table_proxy.clone(),
1846            MAIN_FIDL_TABLE_ID,
1847            unmanaged_route_set_proxy,
1848            route_table_provider,
1849        );
1850        let mut fidl_route_map = FidlRouteMap::<I>::default();
1851        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1852
1853        // Receiving an add route event multiple times should result in a panic.
1854        for _ in 0..2 {
1855            assert_eq!(
1856                handle_route_watcher_event(
1857                    &mut route_table,
1858                    &mut fidl_route_map,
1859                    &route_clients,
1860                    fnet_routes_ext::Event::Added(installed_route),
1861                ),
1862                RouteEventOutcome::Noop
1863            );
1864        }
1865    }
1866
1867    #[ip_test(I, test = false)]
1868    #[fuchsia::test]
1869    #[should_panic(expected = "Netstack reported the removal of an unknown route")]
1870    async fn handles_remove_nonexisting_route_watcher_event<
1871        I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1872    >() {
1873        let (subnet, next_hop) =
1874            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1875        let table_id = MAIN_FIDL_TABLE_ID;
1876        let installed_route: fnet_routes_ext::InstalledRoute<I> =
1877            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
1878
1879        let (route_table_proxy, _route_table_server_end) =
1880            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1881        let (unmanaged_route_set_proxy, _server_end) =
1882            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1883        let (route_table_provider, _server_end) =
1884            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1885        let mut route_table = RouteTableMap::new(
1886            route_table_proxy.clone(),
1887            MAIN_FIDL_TABLE_ID,
1888            unmanaged_route_set_proxy,
1889            route_table_provider,
1890        );
1891        let mut fidl_route_map = FidlRouteMap::<I>::default();
1892        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1893
1894        // Receiving a remove event for an unknown route should result in a panic.
1895        let _ = handle_route_watcher_event(
1896            &mut route_table,
1897            &mut fidl_route_map,
1898            &route_clients,
1899            fnet_routes_ext::Event::Removed(installed_route),
1900        );
1901    }
1902
1903    #[ip_test(I, test = false)]
1904    #[fuchsia::test]
1905    async fn handle_route_watcher_event_two_routesets<
1906        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1907    >() {
1908        let scope = fasync::Scope::new();
1909        let (subnet, next_hop) =
1910            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
1911
1912        let installed_route1: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1913            subnet,
1914            Some(next_hop),
1915            DEV1.into(),
1916            METRIC1,
1917            OTHER_FIDL_TABLE_ID,
1918        );
1919        let installed_route2: fnet_routes_ext::InstalledRoute<I> = create_installed_route(
1920            subnet,
1921            Some(next_hop),
1922            DEV2.into(),
1923            METRIC2,
1924            MAIN_FIDL_TABLE_ID,
1925        );
1926
1927        let add_events1 = [
1928            fnet_routes_ext::Event::Added(fnet_routes_ext::InstalledRoute {
1929                table_id: MAIN_FIDL_TABLE_ID,
1930                ..installed_route1
1931            }),
1932            fnet_routes_ext::Event::Added(installed_route1),
1933        ];
1934        let add_event2 = fnet_routes_ext::Event::Added(installed_route2);
1935        let remove_event = fnet_routes_ext::Event::Removed(installed_route1);
1936
1937        // Due to the double-writing of routes into managed tables and into the main tables, we need
1938        // to account for notifications for both routes being added.
1939        let expected_route_message1_unmanaged =
1940            NetlinkRouteMessage::try_from_installed_route(installed_route1, MAIN_ROUTE_TABLE_INDEX)
1941                .unwrap();
1942        let expected_route_message1_managed = NetlinkRouteMessage::try_from_installed_route(
1943            installed_route1,
1944            MANAGED_ROUTE_TABLE_INDEX,
1945        )
1946        .unwrap();
1947        let expected_route_message2: NetlinkRouteMessage =
1948            NetlinkRouteMessage::try_from_installed_route(installed_route2, MAIN_ROUTE_TABLE_INDEX)
1949                .unwrap();
1950
1951        // Set up two fake clients: one is a member of the route multicast group.
1952        let (right_group, wrong_group) = match I::VERSION {
1953            IpVersion::V4 => (
1954                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1955                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1956            ),
1957            IpVersion::V6 => (
1958                ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
1959                ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
1960            ),
1961        };
1962        let (mut right_sink, right_client, async_work_drain_task) =
1963            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1964                crate::client::testutil::CLIENT_ID_1,
1965                [right_group],
1966            );
1967        let _join_handle = scope.spawn(async_work_drain_task);
1968        let (mut wrong_sink, wrong_client, async_work_drain_task) =
1969            crate::client::testutil::new_fake_client::<NetlinkRoute>(
1970                crate::client::testutil::CLIENT_ID_2,
1971                [wrong_group],
1972            );
1973        let _join_handle = scope.spawn(async_work_drain_task);
1974        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
1975        route_clients.add_client(right_client);
1976        route_clients.add_client(wrong_client);
1977
1978        let (main_route_table_proxy, _route_table_server_end) =
1979            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1980        let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
1981            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1982        let (route_table_proxy, _route_table_server_end) =
1983            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
1984        let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
1985        let (route_table_provider, _server_end) =
1986            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
1987
1988        let mut route_table = RouteTableMap::new(
1989            main_route_table_proxy,
1990            MAIN_FIDL_TABLE_ID,
1991            unmanaged_route_set_proxy,
1992            route_table_provider,
1993        );
1994        route_table.insert(
1995            MANAGED_ROUTE_TABLE_INDEX,
1996            RouteTable::Managed(ManagedRouteTable {
1997                route_set_proxy,
1998                route_table_proxy,
1999                fidl_table_id: OTHER_FIDL_TABLE_ID,
2000                rule_set_authenticated: false,
2001            }),
2002        );
2003
2004        let mut fidl_route_map = FidlRouteMap::<I>::default();
2005
2006        // Send the first of the added-route events (corresponding to the route having been added
2007        // to the main FIDL table).
2008        assert_eq!(
2009            handle_route_watcher_event(
2010                &mut route_table,
2011                &mut fidl_route_map,
2012                &route_clients,
2013                add_events1[0],
2014            ),
2015            RouteEventOutcome::Noop
2016        );
2017
2018        // Shouldn't be counted yet, as we haven't seen the route added to its own table yet.
2019        assert_eq!(
2020            &fidl_route_map
2021                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2022                .collect::<HashSet<_>>(),
2023            &HashSet::new()
2024        );
2025
2026        // Now send the other event (corresponding to the route having been also added to the real
2027        // FIDL table).
2028        assert_eq!(
2029            handle_route_watcher_event(
2030                &mut route_table,
2031                &mut fidl_route_map,
2032                &route_clients,
2033                add_events1[1],
2034            ),
2035            RouteEventOutcome::Noop
2036        );
2037
2038        // Now the route should have been added.
2039        assert_eq!(
2040            &fidl_route_map
2041                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2042                .chain(fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX))
2043                .collect::<HashSet<_>>(),
2044            &HashSet::from_iter([
2045                expected_route_message1_unmanaged.clone(),
2046                expected_route_message1_managed.clone()
2047            ])
2048        );
2049        assert_eq!(
2050            &right_sink.take_messages()[..],
2051            &[expected_route_message1_unmanaged.clone(), expected_route_message1_managed.clone()]
2052                .map(|message| SentMessage::multicast(
2053                    message.clone().into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
2054                    right_group
2055                ))
2056        );
2057        assert_eq!(&wrong_sink.take_messages()[..], &[]);
2058
2059        // Ensure that an unmanaged Route can be observed and added to the
2060        // unmanaged route set (signified by no pending request).
2061        assert_eq!(
2062            handle_route_watcher_event(
2063                &mut route_table,
2064                &mut fidl_route_map,
2065                &route_clients,
2066                add_event2,
2067            ),
2068            RouteEventOutcome::Noop
2069        );
2070
2071        // Should also contain the route from before.
2072        assert_eq!(
2073            &fidl_route_map
2074                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2075                .chain(fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX))
2076                .collect::<HashSet<_>>(),
2077            &HashSet::from_iter([
2078                expected_route_message1_unmanaged.clone(),
2079                expected_route_message1_managed.clone(),
2080                expected_route_message2.clone()
2081            ])
2082        );
2083
2084        // However, netlink won't send any notifications about unmanaged routes.
2085        assert_eq!(
2086            &right_sink.take_messages()[..],
2087            &[SentMessage::multicast(
2088                expected_route_message2
2089                    .clone()
2090                    .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
2091                right_group
2092            )]
2093        );
2094        assert_eq!(&wrong_sink.take_messages()[..], &[]);
2095
2096        // Notify of the route being removed from the managed table.
2097        assert_eq!(
2098            handle_route_watcher_event(
2099                &mut route_table,
2100                &mut fidl_route_map,
2101                &route_clients,
2102                remove_event,
2103            ),
2104            RouteEventOutcome::Cleanup(TableNeedsCleanup(
2105                OTHER_FIDL_TABLE_ID,
2106                MANAGED_ROUTE_TABLE_INDEX
2107            ))
2108        );
2109        assert_eq!(
2110            &fidl_route_map
2111                .iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX)
2112                .collect::<HashSet<_>>(),
2113            &HashSet::from_iter([
2114                expected_route_message1_unmanaged.clone(),
2115                expected_route_message2.clone()
2116            ])
2117        );
2118        assert_eq!(
2119            fidl_route_map
2120                .iter_messages(&route_table, MANAGED_ROUTE_TABLE_INDEX)
2121                .collect::<HashSet<_>>(),
2122            HashSet::new()
2123        );
2124        assert_eq!(
2125            &right_sink.take_messages()[..],
2126            &[SentMessage::multicast(
2127                expected_route_message1_managed.clone().into_rtnl_del_route(),
2128                right_group
2129            )]
2130        );
2131        assert_eq!(&wrong_sink.take_messages()[..], &[]);
2132        drop(route_clients);
2133        scope.join().await;
2134    }
2135
2136    #[test_case(V4_SUB1, V4_NEXTHOP1)]
2137    #[test_case(V6_SUB1, V6_NEXTHOP1)]
2138    #[test_case(net_subnet_v4!("0.0.0.0/0"), net_ip_v4!("0.0.0.1"))]
2139    #[test_case(net_subnet_v6!("::/0"), net_ip_v6!("::1"))]
2140    fn test_netlink_route_message_try_from_installed_route<A: IpAddress>(
2141        subnet: Subnet<A>,
2142        next_hop: A,
2143    ) {
2144        netlink_route_message_conversion_helper::<A::Version>(subnet, next_hop);
2145    }
2146
2147    fn netlink_route_message_conversion_helper<I: Ip>(subnet: Subnet<I::Addr>, next_hop: I::Addr) {
2148        let installed_route = create_installed_route::<I>(
2149            subnet,
2150            Some(next_hop),
2151            DEV1.into(),
2152            METRIC1,
2153            MAIN_FIDL_TABLE_ID,
2154        );
2155        let prefix_length = subnet.prefix();
2156        let subnet = if prefix_length > 0 { Some(subnet) } else { None };
2157        let nlas = create_nlas::<I>(subnet, Some(next_hop), DEV1, METRIC1, None);
2158        let expected =
2159            create_netlink_route_message::<I>(prefix_length, MAIN_ROUTE_TABLE_INDEX, nlas);
2160
2161        let actual =
2162            NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX)
2163                .unwrap();
2164        assert_eq!(actual, expected);
2165    }
2166
2167    #[test_case(V4_SUB1)]
2168    #[test_case(V6_SUB1)]
2169    fn test_non_forward_route_conversion<A: IpAddress>(subnet: Subnet<A>) {
2170        let installed_route = fnet_routes_ext::InstalledRoute::<A::Version> {
2171            route: fnet_routes_ext::Route {
2172                destination: subnet,
2173                action: fnet_routes_ext::RouteAction::Unknown,
2174                properties: fnet_routes_ext::RouteProperties {
2175                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
2176                        metric: fnet_routes::SpecifiedMetric::ExplicitMetric(METRIC1),
2177                    },
2178                },
2179            },
2180            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric: METRIC1 },
2181            // TODO(https://fxbug.dev/336382905): The tests should use the ID.
2182            table_id: MAIN_FIDL_TABLE_ID,
2183        };
2184
2185        let actual: Result<NetlinkRouteMessage, NetlinkRouteMessageConversionError> =
2186            NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX);
2187        assert_eq!(actual, Err(NetlinkRouteMessageConversionError::RouteActionNotForwarding));
2188    }
2189
2190    #[fuchsia::test]
2191    fn test_oversized_interface_id_route_conversion() {
2192        let invalid_interface_id = (u32::MAX as u64) + 1;
2193        let installed_route: fnet_routes_ext::InstalledRoute<Ipv4> = create_installed_route(
2194            V4_SUB1,
2195            Some(V4_NEXTHOP1),
2196            invalid_interface_id,
2197            Default::default(),
2198            MAIN_FIDL_TABLE_ID,
2199        );
2200
2201        let actual: Result<NetlinkRouteMessage, NetlinkRouteMessageConversionError> =
2202            NetlinkRouteMessage::try_from_installed_route(installed_route, MAIN_ROUTE_TABLE_INDEX);
2203        assert_eq!(
2204            actual,
2205            Err(NetlinkRouteMessageConversionError::InvalidInterfaceId(invalid_interface_id))
2206        );
2207    }
2208
2209    #[test]
2210    fn test_into_rtnl_new_route_is_serializable() {
2211        let route = create_netlink_route_message::<Ipv4>(0, MAIN_ROUTE_TABLE_INDEX, vec![]);
2212        let new_route_message = route.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false);
2213        let mut buf = vec![0; new_route_message.buffer_len()];
2214        // Serialize will panic if `new_route_message` is malformed.
2215        new_route_message.serialize(&mut buf);
2216    }
2217
2218    #[test]
2219    fn test_into_rtnl_del_route_is_serializable() {
2220        let route = create_netlink_route_message::<Ipv6>(0, MAIN_ROUTE_TABLE_INDEX, vec![]);
2221        let del_route_message = route.into_rtnl_del_route();
2222        let mut buf = vec![0; del_route_message.buffer_len()];
2223        // Serialize will panic if `del_route_message` is malformed.
2224        del_route_message.serialize(&mut buf);
2225    }
2226
2227    enum OnlyRoutes {}
2228    impl crate::route_eventloop::EventLoopSpec for OnlyRoutes {
2229        type InterfacesProxy = Required;
2230        type InterfacesHandler = Required;
2231        type RouteClients = Required;
2232
2233        // To avoid needing a different spec for V4 and V6 tests, just make both routes optional --
2234        // we're fine with panicking in tests anyway.
2235        type V4RoutesState = Optional;
2236        type V6RoutesState = Optional;
2237        type V4RoutesSetProvider = Optional;
2238        type V6RoutesSetProvider = Optional;
2239        type V4RouteTableProvider = Optional;
2240        type V6RouteTableProvider = Optional;
2241        type InterfacesStateProxy = Optional;
2242
2243        type InterfacesWorker = Optional;
2244        type RoutesV4Worker = Optional;
2245        type RoutesV6Worker = Optional;
2246        type RuleV4Worker = Optional;
2247        type RuleV6Worker = Optional;
2248        type NduseroptWorker = Optional;
2249        type NeighborWorker = Optional;
2250    }
2251
2252    struct Setup<W, R> {
2253        pub event_loop_inputs: crate::route_eventloop::EventLoopInputs<
2254            FakeInterfacesHandler,
2255            FakeSender<RouteNetlinkMessage>,
2256            OnlyRoutes,
2257        >,
2258        pub watcher_stream: W,
2259        pub route_sets: R,
2260        pub interfaces_request_stream: fnet_root::InterfacesRequestStream,
2261        pub request_sink:
2262            mpsc::Sender<crate::route_eventloop::UnifiedRequest<FakeSender<RouteNetlinkMessage>>>,
2263        pub async_work_sink: mpsc::UnboundedSender<AsyncWorkItem<NetlinkRoute>>,
2264    }
2265
2266    fn setup_with_route_clients_yielding_admin_server_ends<
2267        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2268    >(
2269        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2270    ) -> Setup<
2271        impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2272        (ServerEnd<I::RouteTableMarker>, ServerEnd<I::RouteTableProviderMarker>),
2273    > {
2274        let (interfaces_handler, _interfaces_handler_sink) = FakeInterfacesHandler::new();
2275        let (request_sink, request_stream) = mpsc::channel(1);
2276        let (interfaces_proxy, interfaces) =
2277            fidl::endpoints::create_proxy::<fnet_root::InterfacesMarker>();
2278        let (async_work_sink, async_work_receiver) = mpsc::unbounded();
2279
2280        #[derive(GenericOverIp)]
2281        #[generic_over_ip(I, Ip)]
2282        struct ServerEnds<
2283            I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2284        > {
2285            routes_state: ServerEnd<I::StateMarker>,
2286            routes_set_provider: ServerEnd<I::RouteTableMarker>,
2287            route_table_provider: ServerEnd<I::RouteTableProviderMarker>,
2288        }
2289
2290        let base_inputs = crate::route_eventloop::EventLoopInputs {
2291            interfaces_handler: EventLoopComponent::Present(interfaces_handler),
2292            route_clients: EventLoopComponent::Present(route_clients),
2293            interfaces_proxy: EventLoopComponent::Present(interfaces_proxy),
2294            async_work_receiver,
2295
2296            interfaces_state_proxy: EventLoopComponent::Absent(Optional),
2297            v4_routes_state: EventLoopComponent::Absent(Optional),
2298            v6_routes_state: EventLoopComponent::Absent(Optional),
2299            v4_main_route_table: EventLoopComponent::Absent(Optional),
2300            v6_main_route_table: EventLoopComponent::Absent(Optional),
2301            v4_route_table_provider: EventLoopComponent::Absent(Optional),
2302            v6_route_table_provider: EventLoopComponent::Absent(Optional),
2303            v4_rule_table: EventLoopComponent::Absent(Optional),
2304            v6_rule_table: EventLoopComponent::Absent(Optional),
2305            ndp_option_watcher_provider: EventLoopComponent::Absent(Optional),
2306            neighbors_view: EventLoopComponent::Absent(Optional),
2307            neighbors_controller: EventLoopComponent::Absent(Optional),
2308
2309            unified_request_stream: request_stream,
2310        };
2311
2312        let (IpInvariant(inputs), server_ends) = I::map_ip_out(
2313            base_inputs,
2314            |base_inputs| {
2315                let (v4_routes_state, routes_state) =
2316                    fidl::endpoints::create_proxy::<fnet_routes::StateV4Marker>();
2317                let (v4_main_route_table, routes_set_provider) =
2318                    fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV4Marker>();
2319                let (v4_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2320                    fnet_routes_admin::RouteTableProviderV4Marker,
2321                >();
2322                let inputs = crate::route_eventloop::EventLoopInputs {
2323                    v4_routes_state: EventLoopComponent::Present(v4_routes_state),
2324                    v4_main_route_table: EventLoopComponent::Present(v4_main_route_table),
2325                    v4_route_table_provider: EventLoopComponent::Present(v4_route_table_provider),
2326                    ..base_inputs
2327                };
2328                let server_ends =
2329                    ServerEnds::<Ipv4> { routes_state, routes_set_provider, route_table_provider };
2330                (IpInvariant(inputs), server_ends)
2331            },
2332            |base_inputs| {
2333                let (v6_routes_state, routes_state) =
2334                    fidl::endpoints::create_proxy::<fnet_routes::StateV6Marker>();
2335                let (v6_main_route_table, routes_set_provider) =
2336                    fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV6Marker>();
2337                let (v6_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2338                    fnet_routes_admin::RouteTableProviderV6Marker,
2339                >();
2340                let inputs = crate::route_eventloop::EventLoopInputs {
2341                    v6_routes_state: EventLoopComponent::Present(v6_routes_state),
2342                    v6_main_route_table: EventLoopComponent::Present(v6_main_route_table),
2343                    v6_route_table_provider: EventLoopComponent::Present(v6_route_table_provider),
2344                    ..base_inputs
2345                };
2346                let server_ends =
2347                    ServerEnds::<Ipv6> { routes_state, routes_set_provider, route_table_provider };
2348                (IpInvariant(inputs), server_ends)
2349            },
2350        );
2351
2352        let ServerEnds { routes_state, routes_set_provider, route_table_provider } = server_ends;
2353
2354        let state_stream = routes_state.into_stream().boxed_local();
2355
2356        let interfaces_request_stream = interfaces.into_stream();
2357
2358        #[derive(GenericOverIp)]
2359        #[generic_over_ip(I, Ip)]
2360        struct StateRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2361            request: <<I::StateMarker as ProtocolMarker>::RequestStream as futures::Stream>::Item,
2362        }
2363
2364        #[derive(GenericOverIp)]
2365        #[generic_over_ip(I, Ip)]
2366        struct WatcherRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2367            watcher: <I::WatcherMarker as ProtocolMarker>::RequestStream,
2368        }
2369
2370        let watcher_stream = state_stream
2371            .map(|request| {
2372                let wrapper = I::map_ip(
2373                    StateRequestWrapper { request },
2374                    |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2375                        fnet_routes::StateV4Request::GetWatcherV4 {
2376                            options: _,
2377                            watcher,
2378                            control_handle: _,
2379                        } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2380                        fnet_routes::StateV4Request::GetRuleWatcherV4 {
2381                            options: _,
2382                            watcher: _,
2383                            control_handle: _,
2384                        } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2385                    },
2386                    |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2387                        fnet_routes::StateV6Request::GetWatcherV6 {
2388                            options: _,
2389                            watcher,
2390                            control_handle: _,
2391                        } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2392                        fnet_routes::StateV6Request::GetRuleWatcherV6 {
2393                            options: _,
2394                            watcher: _,
2395                            control_handle: _,
2396                        } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2397                    },
2398                );
2399                wrapper
2400            })
2401            .map(|WatcherRequestWrapper { watcher }| watcher)
2402            // For testing, we only expect there to be a single connection to the watcher, so the
2403            // stream is condensed into a single `WatchRequest` stream.
2404            .flatten()
2405            .fuse();
2406
2407        Setup {
2408            event_loop_inputs: inputs,
2409            watcher_stream,
2410            route_sets: (routes_set_provider, route_table_provider),
2411            interfaces_request_stream,
2412            request_sink,
2413            async_work_sink,
2414        }
2415    }
2416
2417    fn setup_with_route_clients<
2418        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2419    >(
2420        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2421    ) -> Setup<
2422        impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2423        impl Stream<
2424            Item = (
2425                fnet_routes_ext::TableId,
2426                <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2427            ),
2428        >,
2429    > {
2430        let Setup {
2431            event_loop_inputs,
2432            watcher_stream,
2433            route_sets: (routes_set_provider, route_table_provider),
2434            interfaces_request_stream,
2435            request_sink,
2436            async_work_sink,
2437        } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
2438        let route_set_stream =
2439            fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2440                routes_set_provider,
2441                Some(MAIN_FIDL_TABLE_ID),
2442            )
2443            .map(|item| (MAIN_FIDL_TABLE_ID, item));
2444
2445        let route_table_provider_request_stream = route_table_provider.into_stream();
2446
2447        let table_id = AtomicU32::new(OTHER_FIDL_TABLE_ID.get());
2448
2449        let route_sets_from_route_table_provider =
2450            futures::TryStreamExt::map_ok(route_table_provider_request_stream, move |request| {
2451                match I::into_route_table_provider_request(request) {
2452                    fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
2453                        provider,
2454                        options: _,
2455                        control_handle: _,
2456                    } => {
2457                        let table_id =
2458                            fnet_routes_ext::TableId::new(table_id.fetch_add(1, Ordering::SeqCst));
2459                        fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2460                            provider,
2461                            Some(table_id),
2462                        )
2463                        .map(move |route_set_request| (table_id, route_set_request))
2464                    }
2465                    r => panic!("unexpected request {r:?}"),
2466                }
2467            })
2468            .map(|result| result.expect("should not get FIDL error"))
2469            .flatten_unordered(None)
2470            .fuse();
2471        let route_set_stream = futures::stream::select_all([
2472            route_set_stream.left_stream(),
2473            route_sets_from_route_table_provider.right_stream(),
2474        ])
2475        .fuse();
2476
2477        Setup {
2478            event_loop_inputs,
2479            watcher_stream,
2480            route_sets: route_set_stream,
2481            interfaces_request_stream,
2482            request_sink,
2483            async_work_sink,
2484        }
2485    }
2486
2487    async fn respond_to_watcher<
2488        I: fnet_routes_ext::FidlRouteIpExt,
2489        S: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2490    >(
2491        stream: S,
2492        updates: impl IntoIterator<Item = I::WatchEvent>,
2493    ) {
2494        #[derive(GenericOverIp)]
2495        #[generic_over_ip(I, Ip)]
2496        struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
2497            request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2498            update: I::WatchEvent,
2499        }
2500        stream
2501            .zip(futures::stream::iter(updates.into_iter()))
2502            .for_each(|(request, update)| async move {
2503                I::map_ip_in(
2504                    HandleInputs { request, update },
2505                    |HandleInputs { request, update }| match request
2506                        .expect("failed to receive `Watch` request")
2507                    {
2508                        fnet_routes::WatcherV4Request::Watch { responder } => {
2509                            responder.send(&[update]).expect("failed to respond to `Watch`")
2510                        }
2511                    },
2512                    |HandleInputs { request, update }| match request
2513                        .expect("failed to receive `Watch` request")
2514                    {
2515                        fnet_routes::WatcherV6Request::Watch { responder } => {
2516                            responder.send(&[update]).expect("failed to respond to `Watch`")
2517                        }
2518                    },
2519                );
2520            })
2521            .await;
2522    }
2523
2524    async fn run_event_loop<I: Ip>(
2525        inputs: crate::route_eventloop::EventLoopInputs<
2526            FakeInterfacesHandler,
2527            FakeSender<RouteNetlinkMessage>,
2528            OnlyRoutes,
2529        >,
2530    ) -> Never {
2531        let included_workers = match I::VERSION {
2532            IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
2533                routes_v4: EventLoopComponent::Present(()),
2534                routes_v6: EventLoopComponent::Absent(Optional),
2535                interfaces: EventLoopComponent::Absent(Optional),
2536                rules_v4: EventLoopComponent::Absent(Optional),
2537                rules_v6: EventLoopComponent::Absent(Optional),
2538                nduseropt: EventLoopComponent::Absent(Optional),
2539                neighbors: EventLoopComponent::Absent(Optional),
2540            },
2541            IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
2542                routes_v4: EventLoopComponent::Absent(Optional),
2543                routes_v6: EventLoopComponent::Present(()),
2544                interfaces: EventLoopComponent::Absent(Optional),
2545                rules_v4: EventLoopComponent::Absent(Optional),
2546                rules_v6: EventLoopComponent::Absent(Optional),
2547                nduseropt: EventLoopComponent::Absent(Optional),
2548                neighbors: EventLoopComponent::Absent(Optional),
2549            },
2550        };
2551
2552        let event_loop = inputs.initialize(included_workers).await;
2553        event_loop.run().await
2554    }
2555
2556    fn get_test_route_events_new_route_args<A: IpAddress>(
2557        subnet: Subnet<A>,
2558        next_hop1: A,
2559        next_hop2: A,
2560    ) -> [RequestArgs<A::Version>; 2]
2561    where
2562        A::Version: fnet_routes_ext::FidlRouteIpExt,
2563    {
2564        [
2565            RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2566                create_unicast_new_route_args(
2567                    subnet,
2568                    next_hop1,
2569                    DEV1.into(),
2570                    METRIC1,
2571                    MANAGED_ROUTE_TABLE_INDEX,
2572                ),
2573            ))),
2574            RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2575                create_unicast_new_route_args(
2576                    subnet,
2577                    next_hop2,
2578                    DEV2.into(),
2579                    METRIC2,
2580                    MANAGED_ROUTE_TABLE_INDEX,
2581                ),
2582            ))),
2583        ]
2584    }
2585
2586    fn create_unicast_new_route_args<A: IpAddress>(
2587        subnet: Subnet<A>,
2588        next_hop: A,
2589        interface_id: u64,
2590        priority: u32,
2591        table: NetlinkRouteTableIndex,
2592    ) -> UnicastNewRouteArgs<A::Version> {
2593        UnicastNewRouteArgs {
2594            subnet,
2595            target: fnet_routes_ext::RouteTarget {
2596                outbound_interface: interface_id,
2597                next_hop: SpecifiedAddr::new(next_hop),
2598            },
2599            priority: NonZeroU32::new(priority),
2600            table,
2601        }
2602    }
2603
2604    fn create_unicast_del_route_args<A: IpAddress>(
2605        subnet: Subnet<A>,
2606        next_hop: Option<A>,
2607        interface_id: Option<u64>,
2608        priority: Option<u32>,
2609        table: NetlinkRouteTableIndex,
2610    ) -> UnicastDelRouteArgs<A::Version> {
2611        UnicastDelRouteArgs {
2612            subnet,
2613            outbound_interface: interface_id.map(NonZeroU64::new).flatten(),
2614            next_hop: next_hop.map(SpecifiedAddr::new).flatten(),
2615            priority: priority.map(NonZeroU32::new).flatten(),
2616            table: NonZeroNetlinkRouteTableIndex::new(table).unwrap(),
2617        }
2618    }
2619
2620    #[derive(Debug, PartialEq)]
2621    struct TestRequestResult {
2622        messages: Vec<SentMessage<RouteNetlinkMessage>>,
2623        waiter_results: Vec<Result<(), RequestError>>,
2624    }
2625
2626    /// Test helper to handle an iterator of route requests
2627    /// using the same clients and event loop.
2628    ///
2629    /// `root_handler` returns a future that handles
2630    /// `fnet_root::InterfacesRequest`s.
2631    async fn test_requests<
2632        A: IpAddress,
2633        Fut: Future<Output = ()>,
2634        F: FnOnce(fnet_root::InterfacesRequestStream) -> Fut,
2635    >(
2636        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
2637        root_handler: F,
2638        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2639        subnet: Subnet<A>,
2640        next_hop1: A,
2641        next_hop2: A,
2642        num_sink_messages: usize,
2643    ) -> TestRequestResult
2644    where
2645        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2646    {
2647        let scope = fasync::Scope::new();
2648        let result = {
2649            let (mut route_sink, route_client, async_work_drain_task) =
2650                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2651                    crate::client::testutil::CLIENT_ID_1,
2652                    [ModernGroup(match A::Version::VERSION {
2653                        IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
2654                        IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
2655                    })],
2656                );
2657            let _join_handle = scope.spawn(async_work_drain_task);
2658            let (mut other_sink, other_client, async_work_drain_task) =
2659                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2660                    crate::client::testutil::CLIENT_ID_2,
2661                    [ModernGroup(rtnetlink_groups_RTNLGRP_LINK)],
2662                );
2663            let _join_handle = scope.spawn(async_work_drain_task);
2664            let Setup {
2665                event_loop_inputs,
2666                mut watcher_stream,
2667                route_sets: mut route_set_stream,
2668                interfaces_request_stream,
2669                request_sink,
2670                async_work_sink: _,
2671            } = setup_with_route_clients::<A::Version>({
2672                let route_clients = ClientTable::default();
2673                route_clients.add_client(route_client.clone());
2674                route_clients.add_client(other_client);
2675                route_clients
2676            });
2677
2678            let mut event_loop_fut = pin!(run_event_loop::<A::Version>(event_loop_inputs).fuse());
2679
2680            let watcher_stream_fut = respond_to_watcher::<A::Version, _>(
2681                watcher_stream.by_ref(),
2682                std::iter::once(fnet_routes_ext::Event::<A::Version>::Idle.try_into().unwrap()),
2683            );
2684            futures::select! {
2685                () = watcher_stream_fut.fuse() => {},
2686                err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2687            }
2688            assert_eq!(&route_sink.take_messages()[..], &[]);
2689            assert_eq!(&other_sink.take_messages()[..], &[]);
2690
2691            let route_client = &route_client;
2692            let fut = async {
2693                // Add some initial route state by sending through PendingRequests.
2694                let initial_new_routes =
2695                    get_test_route_events_new_route_args(subnet, next_hop1, next_hop2);
2696                let count_initial_new_routes = initial_new_routes.len();
2697
2698                let request_sink = futures::stream::iter(initial_new_routes)
2699                    .fold(request_sink, |mut request_sink, args| async move {
2700                        let (completer, waiter) = oneshot::channel();
2701                        request_sink
2702                            .send(
2703                                Request {
2704                                    args,
2705                                    sequence_number: TEST_SEQUENCE_NUMBER,
2706                                    client: route_client.clone(),
2707                                    completer,
2708                                }
2709                                .into(),
2710                            )
2711                            .await
2712                            .unwrap();
2713                        assert_matches!(waiter.await.unwrap(), Ok(()));
2714                        request_sink
2715                    })
2716                    .await;
2717
2718                // Ensure these messages to load the initial route state are
2719                // received prior to handling the next requests. The messages for
2720                // these requests are not needed by the callers, so drop them.
2721                for _ in 0..count_initial_new_routes {
2722                    let _ = route_sink.next_message().await;
2723                }
2724                assert_eq!(route_sink.next_message().now_or_never(), None);
2725
2726                let (results, _request_sink) = futures::stream::iter(args)
2727                    .fold(
2728                        (Vec::new(), request_sink),
2729                        |(mut results, mut request_sink), args| async move {
2730                            let (completer, waiter) = oneshot::channel();
2731                            request_sink
2732                                .send(
2733                                    Request {
2734                                        args,
2735                                        sequence_number: TEST_SEQUENCE_NUMBER,
2736                                        client: route_client.clone(),
2737                                        completer,
2738                                    }
2739                                    .into(),
2740                                )
2741                                .await
2742                                .unwrap();
2743                            results.push(waiter.await.unwrap());
2744                            (results, request_sink)
2745                        },
2746                    )
2747                    .await;
2748
2749                let messages = {
2750                    assert_eq!(&other_sink.take_messages()[..], &[]);
2751                    let mut messages = Vec::new();
2752                    while messages.len() < num_sink_messages {
2753                        messages.push(route_sink.next_message().await);
2754                    }
2755                    assert_eq!(route_sink.next_message().now_or_never(), None);
2756                    messages
2757                };
2758
2759                (messages, results)
2760            };
2761
2762            let route_set_fut = respond_to_route_set_modifications::<A::Version, _, _>(
2763                route_set_stream.by_ref(),
2764                watcher_stream.by_ref(),
2765                route_set_results,
2766            )
2767            .fuse();
2768
2769            let root_interfaces_fut = root_handler(interfaces_request_stream).fuse();
2770
2771            let (messages, results) = futures::select! {
2772                (messages, results) = fut.fuse() => (messages, results),
2773                res = futures::future::join3(
2774                        route_set_fut,
2775                        root_interfaces_fut,
2776                        event_loop_fut,
2777                    ) => {
2778                    unreachable!("eventloop/stream handlers should not return: {res:?}")
2779                }
2780            };
2781
2782            TestRequestResult { messages, waiter_results: results }
2783        };
2784        scope.join().await;
2785        result
2786    }
2787
2788    #[test_case(V4_SUB1, V4_NEXTHOP1, V4_NEXTHOP2; "v4_route_dump")]
2789    #[test_case(V6_SUB1, V6_NEXTHOP1, V6_NEXTHOP2; "v6_route_dump")]
2790    #[fuchsia::test]
2791    async fn test_get_route<A: IpAddress>(subnet: Subnet<A>, next_hop1: A, next_hop2: A)
2792    where
2793        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2794    {
2795        let expected_messages = vec![
2796            SentMessage::unicast(
2797                create_netlink_route_message::<A::Version>(
2798                    subnet.prefix(),
2799                    MANAGED_ROUTE_TABLE_INDEX,
2800                    create_nlas::<A::Version>(
2801                        Some(subnet),
2802                        Some(next_hop1),
2803                        DEV1,
2804                        METRIC1,
2805                        Some(MANAGED_ROUTE_TABLE_ID),
2806                    ),
2807                )
2808                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2809            ),
2810            SentMessage::unicast(
2811                create_netlink_route_message::<A::Version>(
2812                    subnet.prefix(),
2813                    MANAGED_ROUTE_TABLE_INDEX,
2814                    create_nlas::<A::Version>(
2815                        Some(subnet),
2816                        Some(next_hop2),
2817                        DEV2,
2818                        METRIC2,
2819                        Some(MANAGED_ROUTE_TABLE_ID),
2820                    ),
2821                )
2822                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2823            ),
2824        ];
2825
2826        pretty_assertions::assert_eq!(
2827            {
2828                let mut test_request_result = test_requests(
2829                    [RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump))],
2830                    |interfaces_request_stream| async {
2831                        interfaces_request_stream
2832                            .for_each(|req| async move {
2833                                panic!("unexpected InterfacesRequest: {req:?}")
2834                            })
2835                            .await;
2836                    },
2837                    HashMap::new(),
2838                    subnet,
2839                    next_hop1,
2840                    next_hop2,
2841                    expected_messages.len(),
2842                )
2843                .await;
2844                test_request_result.messages.sort_by_key(|message| {
2845                    assert_matches!(
2846                        &message.message.payload,
2847                        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m)) => {
2848                            // We expect there to be exactly one Oif NLA present
2849                            // for the given inputs.
2850                            m.attributes.clone().into_iter().filter_map(|nla|
2851                                match nla {
2852                                    RouteAttribute::Oif(interface_id) =>
2853                                        Some((m.header.address_family, interface_id)),
2854                                    RouteAttribute::Destination(_)
2855                                    | RouteAttribute::Gateway(_)
2856                                    | RouteAttribute::Priority(_)
2857                                    | RouteAttribute::Table(_) => None,
2858                                    _ => panic!("unexpected NLA {nla:?} present in payload"),
2859                                }
2860                            ).next()
2861                        }
2862                    )
2863                });
2864                test_request_result
2865            },
2866            TestRequestResult { messages: expected_messages, waiter_results: vec![Ok(())] },
2867        )
2868    }
2869
2870    #[derive(Debug, Clone, Copy)]
2871    enum RouteSetResult {
2872        AddResult(Result<bool, fnet_routes_admin::RouteSetError>),
2873        DelResult(Result<bool, fnet_routes_admin::RouteSetError>),
2874        AuthenticationResult(Result<(), fnet_routes_admin::AuthenticateForInterfaceError>),
2875    }
2876
2877    fn route_event_from_route<
2878        I: Ip + fnet_routes_ext::FidlRouteIpExt,
2879        F: FnOnce(fnet_routes_ext::InstalledRoute<I>) -> fnet_routes_ext::Event<I>,
2880    >(
2881        route: I::Route,
2882        table_id: fnet_routes_ext::TableId,
2883        event_fn: F,
2884    ) -> I::WatchEvent {
2885        let route: fnet_routes_ext::Route<I> = route.try_into().unwrap();
2886
2887        let metric = match route.properties.specified_properties.metric {
2888            fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => metric,
2889            fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => {
2890                panic!("metric should be explicit")
2891            }
2892        };
2893
2894        event_fn(fnet_routes_ext::InstalledRoute {
2895            route,
2896            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
2897            // TODO(https://fxbug.dev/336382905): The tests should use the ID.
2898            table_id,
2899        })
2900        .try_into()
2901        .unwrap()
2902    }
2903
2904    // Handle RouteSet API requests then feed the returned
2905    // `fuchsia.net.routes.ext/Event`s to the routes watcher.
2906    async fn respond_to_route_set_modifications<
2907        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2908        RS: Stream<
2909            Item = (
2910                fnet_routes_ext::TableId,
2911                <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2912            ),
2913        >,
2914        WS: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>
2915            + std::marker::Unpin,
2916    >(
2917        route_stream: RS,
2918        watcher_stream: WS,
2919        mut route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2920    ) {
2921        #[derive(GenericOverIp)]
2922        #[generic_over_ip(I, Ip)]
2923        struct RouteSetInputs<I: fnet_routes_ext::admin::FidlRouteAdminIpExt> {
2924            request: <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2925            route_set_result: RouteSetResult,
2926        }
2927        #[derive(GenericOverIp)]
2928        #[generic_over_ip(I, Ip)]
2929        struct RouteSetOutputs<I: fnet_routes_ext::FidlRouteIpExt> {
2930            event: Option<I::WatchEvent>,
2931        }
2932
2933        let mut route_stream = std::pin::pin!(route_stream);
2934        let mut watcher_stream = std::pin::pin!(watcher_stream);
2935
2936        {
2937            let queue = route_set_results.entry(OTHER_FIDL_TABLE_ID).or_default();
2938            queue.push_front(RouteSetResult::AddResult(Ok(true)));
2939            queue.push_front(RouteSetResult::AddResult(Ok(true)));
2940        }
2941
2942        while let Some((table_id, request)) = route_stream.next().await {
2943            let route_set_result = route_set_results
2944                .get_mut(&table_id)
2945                .unwrap_or_else(|| panic!("missing result for {table_id:?}"))
2946                .pop_front()
2947                .unwrap_or_else(|| panic!("missing result for {table_id:?}"));
2948            let RouteSetOutputs { event } = I::map_ip(
2949                RouteSetInputs { request, route_set_result },
2950                |RouteSetInputs { request, route_set_result }| {
2951                    let request = request.expect("failed to receive request");
2952                    crate::logging::log_debug!(
2953                        "responding on {table_id:?} to route set request {request:?} \
2954                        with result {route_set_result:?}"
2955                    );
2956                    match request {
2957                        fnet_routes_admin::RouteSetV4Request::AddRoute { route, responder } => {
2958                            let route_set_result = assert_matches!(
2959                                route_set_result,
2960                                RouteSetResult::AddResult(res) => res
2961                            );
2962
2963                            responder
2964                                .send(route_set_result)
2965                                .expect("failed to respond to `AddRoute`");
2966
2967                            RouteSetOutputs {
2968                                event: match route_set_result {
2969                                    Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2970                                        route,
2971                                        table_id,
2972                                        fnet_routes_ext::Event::<Ipv4>::Added,
2973                                    )),
2974                                    _ => None,
2975                                },
2976                            }
2977                        }
2978                        fnet_routes_admin::RouteSetV4Request::RemoveRoute { route, responder } => {
2979                            let route_set_result = assert_matches!(
2980                                route_set_result,
2981                                RouteSetResult::DelResult(res) => res
2982                            );
2983
2984                            responder
2985                                .send(route_set_result)
2986                                .expect("failed to respond to `RemoveRoute`");
2987
2988                            RouteSetOutputs {
2989                                event: match route_set_result {
2990                                    Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2991                                        route,
2992                                        table_id,
2993                                        fnet_routes_ext::Event::<Ipv4>::Removed,
2994                                    )),
2995                                    _ => None,
2996                                },
2997                            }
2998                        }
2999                        fnet_routes_admin::RouteSetV4Request::AuthenticateForInterface {
3000                            credential: _,
3001                            responder,
3002                        } => {
3003                            let route_set_result = assert_matches!(
3004                                route_set_result,
3005                                RouteSetResult::AuthenticationResult(res) => res
3006                            );
3007
3008                            responder
3009                                .send(route_set_result)
3010                                .expect("failed to respond to `AuthenticateForInterface`");
3011                            RouteSetOutputs { event: None }
3012                        }
3013                    }
3014                },
3015                |RouteSetInputs { request, route_set_result }| {
3016                    let request = request.expect("failed to receive request");
3017                    crate::logging::log_debug!(
3018                        "responding on {table_id:?} to route set request {request:?} \
3019                        with result {route_set_result:?}"
3020                    );
3021                    match request {
3022                        fnet_routes_admin::RouteSetV6Request::AddRoute { route, responder } => {
3023                            let route_set_result = assert_matches!(
3024                                route_set_result,
3025                                RouteSetResult::AddResult(res) => res
3026                            );
3027
3028                            responder
3029                                .send(route_set_result)
3030                                .expect("failed to respond to `AddRoute`");
3031
3032                            RouteSetOutputs {
3033                                event: match route_set_result {
3034                                    Ok(true) => Some(route_event_from_route::<Ipv6, _>(
3035                                        route,
3036                                        table_id,
3037                                        fnet_routes_ext::Event::<Ipv6>::Added,
3038                                    )),
3039                                    _ => None,
3040                                },
3041                            }
3042                        }
3043                        fnet_routes_admin::RouteSetV6Request::RemoveRoute { route, responder } => {
3044                            let route_set_result = assert_matches!(
3045                                route_set_result,
3046                                RouteSetResult::DelResult(res) => res
3047                            );
3048
3049                            responder
3050                                .send(route_set_result)
3051                                .expect("failed to respond to `RemoveRoute`");
3052
3053                            RouteSetOutputs {
3054                                event: match route_set_result {
3055                                    Ok(true) => Some(route_event_from_route::<Ipv6, _>(
3056                                        route,
3057                                        table_id,
3058                                        fnet_routes_ext::Event::<Ipv6>::Removed,
3059                                    )),
3060                                    _ => None,
3061                                },
3062                            }
3063                        }
3064                        fnet_routes_admin::RouteSetV6Request::AuthenticateForInterface {
3065                            credential: _,
3066                            responder,
3067                        } => {
3068                            let route_set_result = assert_matches!(
3069                                route_set_result,
3070                                RouteSetResult::AuthenticationResult(res) => res
3071                            );
3072
3073                            responder
3074                                .send(route_set_result)
3075                                .expect("failed to respond to `AuthenticateForInterface`");
3076                            RouteSetOutputs { event: None }
3077                        }
3078                    }
3079                },
3080            );
3081
3082            if let Some(update) = event {
3083                let request = watcher_stream.next().await.expect("watcher stream should not end");
3084
3085                #[derive(GenericOverIp)]
3086                #[generic_over_ip(I, Ip)]
3087                struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
3088                    request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
3089                    update: I::WatchEvent,
3090                }
3091
3092                I::map_ip_in(
3093                    HandleInputs { request, update },
3094                    |HandleInputs { request, update }| match request
3095                        .expect("failed to receive `Watch` request")
3096                    {
3097                        fnet_routes::WatcherV4Request::Watch { responder } => {
3098                            responder.send(&[update]).expect("failed to respond to `Watch`")
3099                        }
3100                    },
3101                    |HandleInputs { request, update }| match request
3102                        .expect("failed to receive `Watch` request")
3103                    {
3104                        fnet_routes::WatcherV6Request::Watch { responder } => {
3105                            responder.send(&[update]).expect("failed to respond to `Watch`")
3106                        }
3107                    },
3108                );
3109            }
3110        }
3111
3112        if route_set_results.values().any(|value| !value.is_empty()) {
3113            panic!("unused route_set_results entries: {route_set_results:?}");
3114        }
3115    }
3116
3117    /// A test helper to exercise multiple route requests.
3118    ///
3119    /// A test helper that calls the provided callback with a
3120    /// [`fnet_interfaces_admin::ControlRequest`] as they arrive.
3121    async fn test_route_requests<
3122        A: IpAddress,
3123        Fut: Future<Output = ()>,
3124        F: FnMut(fnet_interfaces_admin::ControlRequest) -> Fut,
3125    >(
3126        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3127        mut control_request_handler: F,
3128        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3129        subnet: Subnet<A>,
3130        next_hop1: A,
3131        next_hop2: A,
3132        num_sink_messages: usize,
3133    ) -> TestRequestResult
3134    where
3135        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3136    {
3137        test_requests(
3138            args,
3139            |interfaces_request_stream| async move {
3140                interfaces_request_stream
3141                    .filter_map(|req| {
3142                        futures::future::ready(match req.unwrap() {
3143                            fnet_root::InterfacesRequest::GetAdmin {
3144                                id,
3145                                control,
3146                                control_handle: _,
3147                            } => {
3148                                pretty_assertions::assert_eq!(id, DEV1 as u64);
3149                                Some(control.into_stream())
3150                            }
3151                            req => unreachable!("unexpected interfaces request: {req:?}"),
3152                        })
3153                    })
3154                    .flatten()
3155                    .next()
3156                    .then(|req| control_request_handler(req.unwrap().unwrap()))
3157                    .await
3158            },
3159            route_set_results,
3160            subnet,
3161            next_hop1,
3162            next_hop2,
3163            num_sink_messages,
3164        )
3165        .await
3166    }
3167
3168    // A test helper that calls `test_route_requests()` with the provided
3169    // inputs and expected values.
3170    async fn test_route_requests_helper<A: IpAddress>(
3171        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3172        expected_messages: Vec<SentMessage<RouteNetlinkMessage>>,
3173        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3174        waiter_results: Vec<Result<(), RequestError>>,
3175        subnet: Subnet<A>,
3176    ) where
3177        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3178    {
3179        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3180            (),
3181            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3182            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3183        );
3184
3185        pretty_assertions::assert_eq!(
3186            {
3187                let mut test_request_result = test_route_requests(
3188                    args,
3189                    |req| async {
3190                        match req {
3191                            fnet_interfaces_admin::ControlRequest::GetAuthorizationForInterface {
3192                                responder,
3193                            } => {
3194                                let token = fidl::Event::create();
3195                                let grant = fnet_resources::GrantForInterfaceAuthorization {
3196                                    interface_id: DEV1 as u64,
3197                                    token,
3198                                };
3199                                responder.send(grant).unwrap();
3200                            }
3201                            req => panic!("unexpected request {req:?}"),
3202                        }
3203                    },
3204                    route_set_results,
3205                    subnet,
3206                    next_hop1,
3207                    next_hop2,
3208                    expected_messages.len(),
3209                )
3210                .await;
3211                test_request_result.messages.sort_by_key(|message| {
3212                    // The sequence number sorts multicast messages prior to
3213                    // unicast messages.
3214                    let sequence_number = message.message.header.sequence_number;
3215                    assert_matches!(
3216                        &message.message.payload,
3217                        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m))
3218                        | NetlinkPayload::InnerMessage(RouteNetlinkMessage::DelRoute(m)) => {
3219                            // We expect there to be exactly one Priority NLA present
3220                            // for the given inputs.
3221                            m.attributes.clone().into_iter().filter_map(|nla|
3222                                match nla {
3223                                    RouteAttribute::Priority(priority) =>
3224                                        Some((sequence_number, priority)),
3225                                    RouteAttribute::Destination(_)
3226                                    | RouteAttribute::Gateway(_)
3227                                    | RouteAttribute::Oif(_)
3228                                    | RouteAttribute::Table(_) => None,
3229                                    _ => panic!("unexpected NLA {nla:?} present in payload"),
3230                                }
3231                            ).next()
3232                        }
3233                    )
3234                });
3235                test_request_result
3236            },
3237            TestRequestResult { messages: expected_messages, waiter_results },
3238        )
3239    }
3240
3241    enum RouteRequestKind {
3242        New,
3243        Del,
3244    }
3245
3246    fn route_set_for_table_id(
3247        results: Vec<RouteSetResult>,
3248        table_id: fnet_routes_ext::TableId,
3249    ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3250        HashMap::from_iter([(table_id, results.into())])
3251    }
3252
3253    fn route_set_for_first_new_table(
3254        results: Vec<RouteSetResult>,
3255    ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3256        route_set_for_table_id(results, OTHER_FIDL_TABLE_ID)
3257    }
3258
3259    // Tests RTM_NEWROUTE with all interesting responses to add a route.
3260    #[test_case(
3261        RouteRequestKind::New,
3262        vec![
3263            RouteSetResult::AddResult(Ok(true))
3264        ],
3265        Ok(()),
3266        V4_SUB1,
3267        Some(METRIC3),
3268        DEV1;
3269        "v4_new_success")]
3270    #[test_case(
3271        RouteRequestKind::New,
3272        vec![
3273            RouteSetResult::AddResult(Ok(true))
3274        ],
3275        Ok(()),
3276        V6_SUB1,
3277        Some(METRIC3),
3278        DEV1;
3279        "v6_new_success")]
3280    #[test_case(
3281        RouteRequestKind::New,
3282        vec![
3283            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3284            RouteSetResult::AuthenticationResult(Err(
3285                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3286            )),
3287        ],
3288        Err(RequestError::UnrecognizedInterface),
3289        V4_SUB1,
3290        Some(METRIC3),
3291        DEV1;
3292        "v4_new_failed_auth")]
3293    #[test_case(
3294        RouteRequestKind::New,
3295        vec![
3296            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3297            RouteSetResult::AuthenticationResult(Err(
3298                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3299            )),
3300        ],
3301        Err(RequestError::UnrecognizedInterface),
3302        V6_SUB1,
3303        Some(METRIC3),
3304        DEV1;
3305        "v6_new_failed_auth")]
3306    #[test_case(
3307        RouteRequestKind::New,
3308        vec![
3309            RouteSetResult::AddResult(Ok(false))
3310        ],
3311        Err(RequestError::AlreadyExists),
3312        V4_SUB1,
3313        Some(METRIC3),
3314        DEV1;
3315        "v4_new_failed_netstack_reports_exists")]
3316    #[test_case(
3317        RouteRequestKind::New,
3318        vec![
3319            RouteSetResult::AddResult(Ok(false))
3320        ],
3321        Err(RequestError::AlreadyExists),
3322        V6_SUB1,
3323        Some(METRIC3),
3324        DEV1;
3325        "v6_new_failed_netstack_reports_exists")]
3326    #[test_case(
3327        RouteRequestKind::New,
3328        vec![],
3329        Err(RequestError::AlreadyExists),
3330        V4_SUB1,
3331        Some(METRIC1),
3332        DEV1;
3333        "v4_new_failed_netlink_reports_exists")]
3334    #[test_case(
3335        RouteRequestKind::New,
3336        vec![],
3337        Err(RequestError::AlreadyExists),
3338        V4_SUB1,
3339        Some(METRIC1),
3340        DEV2;
3341        "v4_new_failed_netlink_reports_exists_different_interface")]
3342    #[test_case(
3343        RouteRequestKind::New,
3344        vec![],
3345        Err(RequestError::AlreadyExists),
3346        V6_SUB1,
3347        Some(METRIC1),
3348        DEV1;
3349        "v6_new_failed_netlink_reports_exists")]
3350    #[test_case(
3351        RouteRequestKind::New,
3352        vec![],
3353        Err(RequestError::AlreadyExists),
3354        V6_SUB1,
3355        Some(METRIC1),
3356        DEV2;
3357        "v6_new_failed_netlink_reports_exists_different_interface")]
3358    #[test_case(
3359        RouteRequestKind::New,
3360        vec![
3361            RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3362        ],
3363        Err(RequestError::InvalidRequest),
3364        V4_SUB1,
3365        Some(METRIC3),
3366        DEV1;
3367        "v4_new_invalid_dest")]
3368    #[test_case(
3369        RouteRequestKind::New,
3370        vec![
3371            RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3372        ],
3373        Err(RequestError::InvalidRequest),
3374        V6_SUB1,
3375        Some(METRIC3),
3376        DEV1;
3377        "v6_new_invalid_dest")]
3378    #[test_case(
3379        RouteRequestKind::New,
3380        vec![
3381            RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3382        ],
3383        Err(RequestError::InvalidRequest),
3384        V4_SUB1,
3385        Some(METRIC3),
3386        DEV1;
3387        "v4_new_invalid_hop")]
3388    #[test_case(
3389        RouteRequestKind::New,
3390        vec![
3391            RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3392        ],
3393        Err(RequestError::InvalidRequest),
3394        V6_SUB1,
3395        Some(METRIC3),
3396        DEV1;
3397        "v6_new_invalid_hop")]
3398    // Tests RTM_DELROUTE with all interesting responses to remove a route.
3399    #[test_case(
3400        RouteRequestKind::Del,
3401        vec![
3402            RouteSetResult::DelResult(Ok(true))
3403        ],
3404        Ok(()),
3405        V4_SUB1,
3406        None,
3407        DEV1;
3408        "v4_del_success_only_subnet")]
3409    #[test_case(
3410        RouteRequestKind::Del,
3411        vec![
3412            RouteSetResult::DelResult(Ok(true))
3413        ],
3414        Ok(()),
3415        V4_SUB1,
3416        Some(METRIC1),
3417        DEV1;
3418        "v4_del_success_only_subnet_metric")]
3419    #[test_case(
3420        RouteRequestKind::Del,
3421        vec![
3422            RouteSetResult::DelResult(Ok(true))
3423        ],
3424        Ok(()),
3425        V6_SUB1,
3426        None,
3427        DEV1;
3428        "v6_del_success_only_subnet")]
3429    #[test_case(
3430        RouteRequestKind::Del,
3431        vec![
3432            RouteSetResult::DelResult(Ok(true))
3433        ],
3434        Ok(()),
3435        V6_SUB1,
3436        Some(METRIC1),
3437        DEV1;
3438        "v6_del_success_only_subnet_metric")]
3439    #[test_case(
3440        RouteRequestKind::Del,
3441        vec![
3442            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3443            RouteSetResult::AuthenticationResult(Err(
3444                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3445            )),
3446        ],
3447        Err(RequestError::UnrecognizedInterface),
3448        V4_SUB1,
3449        None,
3450        DEV1;
3451        "v4_del_failed_auth")]
3452    #[test_case(
3453        RouteRequestKind::Del,
3454        vec![
3455            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3456            RouteSetResult::AuthenticationResult(Err(
3457                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3458            )),
3459        ],
3460        Err(RequestError::UnrecognizedInterface),
3461        V6_SUB1,
3462        None,
3463        DEV1;
3464        "v6_del_failed_auth")]
3465    #[test_case(
3466        RouteRequestKind::Del,
3467        vec![
3468            RouteSetResult::DelResult(Ok(false))
3469        ],
3470        Err(RequestError::DeletionNotAllowed),
3471        V4_SUB1,
3472        None,
3473        DEV1;
3474        "v4_del_failed_attempt_to_delete_route_from_global_set")]
3475    #[test_case(
3476        RouteRequestKind::Del,
3477        vec![
3478            RouteSetResult::DelResult(Ok(false))
3479        ],
3480        Err(RequestError::DeletionNotAllowed),
3481        V6_SUB1,
3482        None,
3483        DEV1;
3484        "v6_del_failed_attempt_to_delete_route_from_global_set")]
3485    // This deliberately only includes one case where a route is
3486    // not selected for deletion, `test_select_route_for_deletion`
3487    // covers these cases.
3488    // No route with `METRIC3` exists, so this extra selector causes the
3489    // `NotFound` result.
3490    #[test_case(
3491        RouteRequestKind::Del,
3492        vec![],
3493        Err(RequestError::NotFound),
3494        V4_SUB1,
3495        Some(METRIC3),
3496        DEV1;
3497        "v4_del_no_matching_route")]
3498    #[test_case(
3499        RouteRequestKind::Del,
3500        vec![],
3501        Err(RequestError::NotFound),
3502        V6_SUB1,
3503        Some(METRIC3),
3504        DEV1;
3505        "v6_del_no_matching_route")]
3506    #[test_case(
3507        RouteRequestKind::Del,
3508        vec![
3509            RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3510        ],
3511        Err(RequestError::InvalidRequest),
3512        V4_SUB1,
3513        None,
3514        DEV1;
3515        "v4_del_invalid_dest")]
3516    #[test_case(
3517        RouteRequestKind::Del,
3518        vec![
3519            RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3520        ],
3521        Err(RequestError::InvalidRequest),
3522        V6_SUB1,
3523        None,
3524        DEV1;
3525        "v6_del_invalid_dest")]
3526    #[test_case(
3527        RouteRequestKind::Del,
3528        vec![
3529            RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3530        ],
3531        Err(RequestError::InvalidRequest),
3532        V4_SUB1,
3533        None,
3534        DEV1;
3535        "v4_del_invalid_hop")]
3536    #[test_case(
3537        RouteRequestKind::Del,
3538        vec![
3539            RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3540        ],
3541        Err(RequestError::InvalidRequest),
3542        V6_SUB1,
3543        None,
3544        DEV1;
3545        "v6_del_invalid_hop")]
3546    #[fuchsia::test]
3547    async fn test_new_del_route<A: IpAddress>(
3548        kind: RouteRequestKind,
3549        route_set_results: Vec<RouteSetResult>,
3550        waiter_result: Result<(), RequestError>,
3551        subnet: Subnet<A>,
3552        metric: Option<u32>,
3553        interface_id: u32,
3554    ) where
3555        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3556    {
3557        let route_group = match A::Version::VERSION {
3558            IpVersion::V4 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3559            IpVersion::V6 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3560        };
3561
3562        let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3563
3564        // There are two pre-set routes in `test_route_requests`.
3565        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3566        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3567        let route_req_args = match kind {
3568            RouteRequestKind::New => {
3569                // Add a route that is not already present.
3570                RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3571                    subnet,
3572                    next_hop,
3573                    interface_id.into(),
3574                    metric.expect("add cases should be Some"),
3575                    MANAGED_ROUTE_TABLE_INDEX,
3576                )))
3577            }
3578            RouteRequestKind::Del => {
3579                // Remove an existing route.
3580                RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3581                    subnet,
3582                    None,
3583                    None,
3584                    metric,
3585                    MANAGED_ROUTE_TABLE_INDEX,
3586                )))
3587            }
3588        };
3589
3590        // When the waiter result is Ok(()), then we know that the add or delete
3591        // was successful and we got a message.
3592        let messages = match waiter_result {
3593            Ok(()) => {
3594                let build_message = |table| {
3595                    let route_message = create_netlink_route_message::<A::Version>(
3596                        subnet.prefix(),
3597                        table,
3598                        create_nlas::<A::Version>(
3599                            Some(subnet),
3600                            Some(next_hop),
3601                            DEV1,
3602                            match kind {
3603                                RouteRequestKind::New => metric.expect("add cases should be some"),
3604                                // When a route is found for deletion, we expect that route to have
3605                                // a metric value of `METRIC1`. Even though there are two different
3606                                // routes with `subnet`, deletion prefers to select the route with
3607                                // the lowest metric.
3608                                RouteRequestKind::Del => METRIC1,
3609                            },
3610                            (table != MAIN_ROUTE_TABLE_INDEX).then_some(table.get()),
3611                        ),
3612                    );
3613                    let netlink_message = match kind {
3614                        RouteRequestKind::New => {
3615                            route_message.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false)
3616                        }
3617                        RouteRequestKind::Del => route_message.into_rtnl_del_route(),
3618                    };
3619                    SentMessage::multicast(netlink_message, route_group)
3620                };
3621
3622                let route_message_in_managed_table = build_message(MANAGED_ROUTE_TABLE_INDEX);
3623
3624                vec![route_message_in_managed_table]
3625            }
3626            Err(_) => Vec::new(),
3627        };
3628
3629        test_route_requests_helper(
3630            [RequestArgs::Route(route_req_args)],
3631            messages,
3632            route_set_for_first_new_table(route_set_results),
3633            vec![waiter_result],
3634            subnet,
3635        )
3636        .await;
3637    }
3638
3639    // Tests RTM_NEWROUTE and RTM_DELROUTE when two unauthentication events are received - once
3640    // prior to making an attempt to authenticate and once after attempting to authenticate.
3641    #[test_case(
3642        RouteRequestKind::New,
3643        vec![
3644            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3645            RouteSetResult::AuthenticationResult(Ok(())),
3646            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3647        ],
3648        Err(RequestError::InvalidRequest),
3649        V4_SUB1;
3650        "v4_new_unauthenticated")]
3651    #[test_case(
3652        RouteRequestKind::New,
3653        vec![
3654            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3655            RouteSetResult::AuthenticationResult(Ok(())),
3656            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3657        ],
3658        Err(RequestError::InvalidRequest),
3659        V6_SUB1;
3660        "v6_new_unauthenticated")]
3661    #[test_case(
3662        RouteRequestKind::Del,
3663        vec![
3664            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3665            RouteSetResult::AuthenticationResult(Ok(())),
3666            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3667        ],
3668        Err(RequestError::InvalidRequest),
3669        V4_SUB1;
3670        "v4_del_unauthenticated")]
3671    #[test_case(
3672        RouteRequestKind::Del,
3673        vec![
3674            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3675            RouteSetResult::AuthenticationResult(Ok(())),
3676            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3677        ],
3678        Err(RequestError::InvalidRequest),
3679        V6_SUB1;
3680        "v6_del_unauthenticated")]
3681    #[should_panic(expected = "received unauthentication error from route set for route")]
3682    #[fuchsia::test]
3683    async fn test_new_del_route_failed<A: IpAddress>(
3684        kind: RouteRequestKind,
3685        route_set_results: Vec<RouteSetResult>,
3686        waiter_result: Result<(), RequestError>,
3687        subnet: Subnet<A>,
3688    ) where
3689        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3690    {
3691        let route_req_args = match kind {
3692            RouteRequestKind::New => {
3693                let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3694                // Add a route that is not already present.
3695                RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3696                    subnet,
3697                    next_hop,
3698                    DEV1.into(),
3699                    METRIC3,
3700                    MANAGED_ROUTE_TABLE_INDEX,
3701                )))
3702            }
3703            RouteRequestKind::Del => {
3704                // Remove an existing route.
3705                RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3706                    subnet,
3707                    None,
3708                    None,
3709                    None,
3710                    MANAGED_ROUTE_TABLE_INDEX,
3711                )))
3712            }
3713        };
3714        test_route_requests_helper(
3715            [RequestArgs::Route(route_req_args)],
3716            Vec::new(),
3717            route_set_for_first_new_table(route_set_results),
3718            vec![waiter_result],
3719            subnet,
3720        )
3721        .await;
3722    }
3723
3724    #[test_case(
3725        Err(RequestError::NotFound),
3726        V4_SUB1; "v4_del")]
3727    #[test_case(
3728        Err(RequestError::NotFound),
3729        V6_SUB1; "v6_del")]
3730    #[fuchsia::test]
3731    async fn test_del_route_nonexistent_table<A: IpAddress>(
3732        waiter_result: Result<(), RequestError>,
3733        subnet: Subnet<A>,
3734    ) where
3735        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3736    {
3737        // Remove a route from a table that doesn't exist yet.
3738        let route_req_args =
3739            RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3740                subnet,
3741                None,
3742                None,
3743                None,
3744                NetlinkRouteTableIndex::new(1234),
3745            )));
3746        test_route_requests_helper(
3747            [RequestArgs::Route(route_req_args)],
3748            Vec::new(),
3749            HashMap::new(),
3750            vec![waiter_result],
3751            subnet,
3752        )
3753        .await;
3754    }
3755
3756    /// A test to exercise a `RTM_NEWROUTE` followed by a `RTM_GETROUTE`
3757    /// route request, ensuring that the new route is included in the
3758    /// dump request.
3759    #[test_case(
3760        V4_SUB1,
3761        ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3762        MANAGED_ROUTE_TABLE_INDEX;
3763        "v4_new_same_table_dump")]
3764    #[test_case(
3765        V6_SUB1,
3766        ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3767        MANAGED_ROUTE_TABLE_INDEX;
3768        "v6_new_same_table_dump")]
3769    #[test_case(
3770        V4_SUB1,
3771        ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3772        NetlinkRouteTableIndex::new(1234);
3773        "v4_new_different_table_dump")]
3774    #[test_case(
3775        V6_SUB1,
3776        ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3777        NetlinkRouteTableIndex::new(1234);
3778        "v6_new_different_table_dump")]
3779    #[fuchsia::test]
3780    async fn test_new_then_get_dump_request<A: IpAddress>(
3781        subnet: Subnet<A>,
3782        group: ModernGroup,
3783        table: NetlinkRouteTableIndex,
3784    ) where
3785        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3786    {
3787        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3788            (),
3789            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3790            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3791        );
3792
3793        // There are two pre-set routes in `test_route_requests`.
3794        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3795        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3796        // To add a new route that does not get rejected by the handler due to it
3797        // already existing, we use a route that has METRIC3.
3798        let unicast_route_args =
3799            create_unicast_new_route_args(subnet, next_hop1, DEV1.into(), METRIC3, table);
3800
3801        // We expect to see 1 multicast message, representing the route that was added to
3802        // a managed table.
3803        // Then, three unicast messages, representing the two routes that existed already in the
3804        // route set, and the one new route that was added.
3805        let messages = vec![
3806            SentMessage::multicast(
3807                create_netlink_route_message::<A::Version>(
3808                    subnet.prefix(),
3809                    table,
3810                    create_nlas::<A::Version>(
3811                        Some(subnet),
3812                        Some(next_hop1),
3813                        DEV1,
3814                        METRIC3,
3815                        Some(table.get()),
3816                    ),
3817                )
3818                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3819                group,
3820            ),
3821            SentMessage::unicast(
3822                create_netlink_route_message::<A::Version>(
3823                    subnet.prefix(),
3824                    MANAGED_ROUTE_TABLE_INDEX,
3825                    create_nlas::<A::Version>(
3826                        Some(subnet),
3827                        Some(next_hop1),
3828                        DEV1,
3829                        METRIC1,
3830                        Some(MANAGED_ROUTE_TABLE_ID),
3831                    ),
3832                )
3833                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3834            ),
3835            SentMessage::unicast(
3836                create_netlink_route_message::<A::Version>(
3837                    subnet.prefix(),
3838                    MANAGED_ROUTE_TABLE_INDEX,
3839                    create_nlas::<A::Version>(
3840                        Some(subnet),
3841                        Some(next_hop2),
3842                        DEV2,
3843                        METRIC2,
3844                        Some(MANAGED_ROUTE_TABLE_ID),
3845                    ),
3846                )
3847                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3848            ),
3849            SentMessage::unicast(
3850                create_netlink_route_message::<A::Version>(
3851                    subnet.prefix(),
3852                    table,
3853                    create_nlas::<A::Version>(
3854                        Some(subnet),
3855                        Some(next_hop1),
3856                        DEV1,
3857                        METRIC3,
3858                        Some(table.get()),
3859                    ),
3860                )
3861                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3862            ),
3863        ];
3864
3865        test_route_requests_helper(
3866            [
3867                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
3868                    unicast_route_args,
3869                ))),
3870                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
3871            ],
3872            messages,
3873            route_set_for_table_id(
3874                vec![RouteSetResult::AddResult(Ok(true))],
3875                if table == MANAGED_ROUTE_TABLE_INDEX {
3876                    OTHER_FIDL_TABLE_ID
3877                } else {
3878                    fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1)
3879                },
3880            ),
3881            vec![Ok(()), Ok(())],
3882            subnet,
3883        )
3884        .await;
3885    }
3886
3887    /// TODO(https://fxbug.dev/336382905): Once otherwise equivalent
3888    /// routes can be inserted into different tables, update the
3889    /// assertions to recognize the route as being added successfully.
3890    ///
3891    /// A test to exercise a `RTM_NEWROUTE` with a route that already
3892    /// exists, but in a different routing table, followed by a `RTM_GETROUTE`
3893    /// route request, ensuring that the new route does not initiate a
3894    /// multicast message and is not included in the dump request.
3895    #[test_case(V4_SUB1; "v4_new_dump")]
3896    #[test_case(V6_SUB1; "v6_new_dump")]
3897    #[fuchsia::test]
3898    async fn test_new_route_different_table_then_get_dump_request<A: IpAddress>(subnet: Subnet<A>)
3899    where
3900        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3901    {
3902        let (next_hop1, next_hop2, IpInvariant(group)): (A, A, IpInvariant<ModernGroup>) =
3903            A::Version::map_ip(
3904                (),
3905                |()| {
3906                    (
3907                        V4_NEXTHOP1,
3908                        V4_NEXTHOP2,
3909                        IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE)),
3910                    )
3911                },
3912                |()| {
3913                    (
3914                        V6_NEXTHOP1,
3915                        V6_NEXTHOP2,
3916                        IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE)),
3917                    )
3918                },
3919            );
3920
3921        const ALTERNATIVE_ROUTE_TABLE: NetlinkRouteTableIndex = NetlinkRouteTableIndex::new(1337);
3922
3923        // There are two pre-set routes in `test_route_requests`.
3924        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3925        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3926        // Attempt to install the same first route, but with a different table.
3927        // Table id isn't important, as long as it is different
3928        // than MANAGED_ROUTE_TABLE.
3929        let unicast_route_args = create_unicast_new_route_args(
3930            subnet,
3931            next_hop1,
3932            DEV1.into(),
3933            METRIC1,
3934            ALTERNATIVE_ROUTE_TABLE,
3935        );
3936
3937        // We expect to see one multicast message for having added the new route, then three unicast
3938        // messages, for dumping the two routes that existed already in the route set, plus the new
3939        // one we added.
3940        let messages = vec![
3941            SentMessage::multicast(
3942                create_netlink_route_message::<A::Version>(
3943                    subnet.prefix(),
3944                    ALTERNATIVE_ROUTE_TABLE,
3945                    create_nlas::<A::Version>(
3946                        Some(subnet),
3947                        Some(next_hop1),
3948                        DEV1,
3949                        METRIC1,
3950                        Some(ALTERNATIVE_ROUTE_TABLE.get()),
3951                    ),
3952                )
3953                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3954                group,
3955            ),
3956            SentMessage::unicast(
3957                create_netlink_route_message::<A::Version>(
3958                    subnet.prefix(),
3959                    MANAGED_ROUTE_TABLE_INDEX,
3960                    create_nlas::<A::Version>(
3961                        Some(subnet),
3962                        Some(next_hop1),
3963                        DEV1,
3964                        METRIC1,
3965                        Some(MANAGED_ROUTE_TABLE_ID),
3966                    ),
3967                )
3968                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3969            ),
3970            SentMessage::unicast(
3971                create_netlink_route_message::<A::Version>(
3972                    subnet.prefix(),
3973                    ALTERNATIVE_ROUTE_TABLE,
3974                    create_nlas::<A::Version>(
3975                        Some(subnet),
3976                        Some(next_hop1),
3977                        DEV1,
3978                        METRIC1,
3979                        Some(ALTERNATIVE_ROUTE_TABLE.get()),
3980                    ),
3981                )
3982                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3983            ),
3984            SentMessage::unicast(
3985                create_netlink_route_message::<A::Version>(
3986                    subnet.prefix(),
3987                    MANAGED_ROUTE_TABLE_INDEX,
3988                    create_nlas::<A::Version>(
3989                        Some(subnet),
3990                        Some(next_hop2),
3991                        DEV2,
3992                        METRIC2,
3993                        Some(MANAGED_ROUTE_TABLE_ID),
3994                    ),
3995                )
3996                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3997            ),
3998        ];
3999
4000        test_route_requests_helper(
4001            [
4002                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
4003                    unicast_route_args,
4004                ))),
4005                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4006            ],
4007            messages,
4008            HashMap::from_iter([
4009                // The added route already existed in the main table.
4010                (MAIN_FIDL_TABLE_ID, vec![RouteSetResult::AddResult(Ok(false))].into()),
4011                // But it is new to the other table.
4012                (
4013                    fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1),
4014                    vec![RouteSetResult::AddResult(Ok(true))].into(),
4015                ),
4016            ]),
4017            vec![Ok(()), Ok(())],
4018            subnet,
4019        )
4020        .await;
4021    }
4022
4023    /// A test to exercise a `RTM_NEWROUTE` followed by a `RTM_DELROUTE` for the same route, then a
4024    /// `RTM_GETROUTE` request, ensuring that the route added created a multicast message, but does
4025    /// not appear in the dump.
4026    #[test_case(V4_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE); "v4_new_del_dump")]
4027    #[test_case(V6_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE); "v6_new_del_dump")]
4028    #[fuchsia::test]
4029    async fn test_new_then_del_then_get_dump_request<A: IpAddress>(
4030        subnet: Subnet<A>,
4031        group: ModernGroup,
4032    ) where
4033        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4034    {
4035        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4036            (),
4037            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4038            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4039        );
4040
4041        // There are two pre-set routes in `test_route_requests`.
4042        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
4043        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
4044        // To add a new route that does not get rejected by the handler due to it
4045        // already existing, we use a route that has METRIC3.
4046        let new_route_args = create_unicast_new_route_args(
4047            subnet,
4048            next_hop1,
4049            DEV1.into(),
4050            METRIC3,
4051            MANAGED_ROUTE_TABLE_INDEX,
4052        );
4053
4054        // The subnet and metric are enough to uniquely identify the above route.
4055        let del_route_args = create_unicast_del_route_args(
4056            subnet,
4057            None,
4058            None,
4059            Some(METRIC3),
4060            MANAGED_ROUTE_TABLE_INDEX,
4061        );
4062
4063        // We expect to see 2 multicast messages, the first representing the route that was
4064        // added and the other representing the same route being removed. Then, two unicast
4065        // messages, representing the two routes that existed already in the route set.
4066        let messages = vec![
4067            SentMessage::multicast(
4068                create_netlink_route_message::<A::Version>(
4069                    subnet.prefix(),
4070                    MANAGED_ROUTE_TABLE_INDEX,
4071                    create_nlas::<A::Version>(
4072                        Some(subnet),
4073                        Some(next_hop1),
4074                        DEV1,
4075                        METRIC3,
4076                        Some(MANAGED_ROUTE_TABLE_ID),
4077                    ),
4078                )
4079                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
4080                group,
4081            ),
4082            SentMessage::multicast(
4083                create_netlink_route_message::<A::Version>(
4084                    subnet.prefix(),
4085                    MANAGED_ROUTE_TABLE_INDEX,
4086                    create_nlas::<A::Version>(
4087                        Some(subnet),
4088                        Some(next_hop1),
4089                        DEV1,
4090                        METRIC3,
4091                        Some(MANAGED_ROUTE_TABLE_ID),
4092                    ),
4093                )
4094                .into_rtnl_del_route(),
4095                group,
4096            ),
4097            SentMessage::unicast(
4098                create_netlink_route_message::<A::Version>(
4099                    subnet.prefix(),
4100                    MANAGED_ROUTE_TABLE_INDEX,
4101                    create_nlas::<A::Version>(
4102                        Some(subnet),
4103                        Some(next_hop1),
4104                        DEV1,
4105                        METRIC1,
4106                        Some(MANAGED_ROUTE_TABLE_ID),
4107                    ),
4108                )
4109                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4110            ),
4111            SentMessage::unicast(
4112                create_netlink_route_message::<A::Version>(
4113                    subnet.prefix(),
4114                    MANAGED_ROUTE_TABLE_INDEX,
4115                    create_nlas::<A::Version>(
4116                        Some(subnet),
4117                        Some(next_hop2),
4118                        DEV2,
4119                        METRIC2,
4120                        Some(MANAGED_ROUTE_TABLE_ID),
4121                    ),
4122                )
4123                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4124            ),
4125        ];
4126
4127        test_route_requests_helper(
4128            [
4129                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(new_route_args))),
4130                RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(del_route_args))),
4131                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4132            ],
4133            messages,
4134            route_set_for_first_new_table(vec![
4135                RouteSetResult::AddResult(Ok(true)),
4136                RouteSetResult::DelResult(Ok(true)),
4137            ]),
4138            vec![Ok(()), Ok(()), Ok(())],
4139            subnet,
4140        )
4141        .await;
4142    }
4143
4144    /// Tests RTM_NEWROUTE and RTM_DELROUTE when the interface is removed,
4145    /// indicated by the closure of the admin Control's server-end.
4146    /// The specific cause of the interface removal is unimportant
4147    /// for this test.
4148    #[test_case(RouteRequestKind::New, V4_SUB1; "v4_new_if_removed")]
4149    #[test_case(RouteRequestKind::New, V6_SUB1; "v6_new_if_removed")]
4150    #[test_case(RouteRequestKind::Del, V4_SUB1; "v4_del_if_removed")]
4151    #[test_case(RouteRequestKind::Del, V6_SUB1; "v6_del_if_removed")]
4152    #[fuchsia::test]
4153    async fn test_new_del_route_interface_removed<A: IpAddress>(
4154        kind: RouteRequestKind,
4155        subnet: Subnet<A>,
4156    ) where
4157        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4158    {
4159        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4160            (),
4161            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4162            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4163        );
4164
4165        // There are two pre-set routes in `test_route_requests`.
4166        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
4167        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
4168        let (route_req_args, route_set_result) = match kind {
4169            RouteRequestKind::New => {
4170                // Add a route that is not already present.
4171                let args =
4172                    RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
4173                        subnet,
4174                        next_hop1,
4175                        DEV1.into(),
4176                        METRIC3,
4177                        MANAGED_ROUTE_TABLE_INDEX,
4178                    )));
4179                let res = RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated));
4180                (args, res)
4181            }
4182            RouteRequestKind::Del => {
4183                // Remove an existing route.
4184                let args =
4185                    RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
4186                        subnet,
4187                        None,
4188                        None,
4189                        None,
4190                        MANAGED_ROUTE_TABLE_INDEX,
4191                    )));
4192                let res = RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated));
4193                (args, res)
4194            }
4195        };
4196
4197        // No routes will be added or removed successfully, so there are no expected messages.
4198        let expected_messages = Vec::new();
4199
4200        pretty_assertions::assert_eq!(
4201            test_requests(
4202                [RequestArgs::Route(route_req_args)],
4203                |interfaces_request_stream| async move {
4204                    interfaces_request_stream
4205                        .for_each(|req| {
4206                            futures::future::ready(match req.unwrap() {
4207                                fnet_root::InterfacesRequest::GetAdmin {
4208                                    id,
4209                                    control,
4210                                    control_handle: _,
4211                                } => {
4212                                    pretty_assertions::assert_eq!(id, DEV1 as u64);
4213                                    let control = control.into_stream();
4214                                    let control = control.control_handle();
4215                                    control.shutdown();
4216                                }
4217                                req => unreachable!("unexpected interfaces request: {req:?}"),
4218                            })
4219                        })
4220                        .await
4221                },
4222                route_set_for_first_new_table(vec![route_set_result]),
4223                subnet,
4224                next_hop1,
4225                next_hop2,
4226                expected_messages.len(),
4227            )
4228            .await,
4229            TestRequestResult {
4230                messages: expected_messages,
4231                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
4232            },
4233        )
4234    }
4235
4236    // A flattened view of Route, convenient for holding testdata.
4237    #[derive(Clone)]
4238    struct Route<I: Ip> {
4239        subnet: Subnet<I::Addr>,
4240        device: u32,
4241        nexthop: Option<I::Addr>,
4242        metric: Option<NonZeroU32>,
4243    }
4244
4245    impl<I: Ip> Route<I> {
4246        fn to_route(self) -> fnet_routes_ext::Route<I> {
4247            let Self { subnet, device, nexthop, metric } = self;
4248            fnet_routes_ext::Route {
4249                destination: subnet,
4250                action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
4251                    outbound_interface: device.into(),
4252                    next_hop: nexthop
4253                        .map(|a| SpecifiedAddr::new(a).expect("nexthop should be specified")),
4254                }),
4255                properties: fnet_routes_ext::RouteProperties {
4256                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
4257                        metric: netlink_priority_to_specified_metric(metric, I::VERSION),
4258                    },
4259                },
4260            }
4261        }
4262
4263        fn to_installed_route(
4264            self,
4265            table_id: fnet_routes_ext::TableId,
4266        ) -> fnet_routes_ext::InstalledRoute<I> {
4267            const DEFAULT_INTERFACE_METRIC: u32 = 100000;
4268            let effective_metric = match self.metric {
4269                None => DEFAULT_INTERFACE_METRIC,
4270                Some(metric) => metric.into(),
4271            };
4272            let route = self.to_route();
4273            fnet_routes_ext::InstalledRoute {
4274                route,
4275                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
4276                    metric: effective_metric,
4277                },
4278                table_id,
4279            }
4280        }
4281    }
4282
4283    const ROUTE_METRIC1: NonZeroU32 = NonZeroU32::new(METRIC1).unwrap();
4284    const ROUTE_METRIC2: NonZeroU32 = NonZeroU32::new(METRIC2).unwrap();
4285    const ROUTE_METRIC3: NonZeroU32 = NonZeroU32::new(METRIC3).unwrap();
4286
4287    #[test_case(
4288        Route::<Ipv4>{
4289            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4290        },
4291        Route::<Ipv4>{
4292            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4293        },
4294        true; "all_fields_the_same_v4_should_match")]
4295    #[test_case(
4296        Route::<Ipv6>{
4297            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4298        },
4299        Route::<Ipv6>{
4300            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4301        },
4302        true; "all_fields_the_same_v6_should_match")]
4303    #[test_case(
4304        Route::<Ipv4>{
4305            subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4306        },
4307        Route::<Ipv4>{
4308            subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4309        },
4310        true; "default_route_v4_should_match")]
4311    #[test_case(
4312        Route::<Ipv6>{
4313            subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4314        },
4315        Route::<Ipv6>{
4316            subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4317        },
4318        true; "default_route_v6_should_match")]
4319    #[test_case(
4320        Route::<Ipv4>{
4321            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4322        },
4323        Route::<Ipv4>{
4324            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4325        },
4326        true; "different_device_v4_should_match")]
4327    #[test_case(
4328        Route::<Ipv6>{
4329            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4330        },
4331        Route::<Ipv6>{
4332            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4333        },
4334        true; "different_device_v6_should_match")]
4335    #[test_case(
4336        Route::<Ipv4>{
4337            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4338        },
4339        Route::<Ipv4>{
4340            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4341        },
4342        true; "different_nexthop_v4_should_match")]
4343    #[test_case(
4344        Route::<Ipv6>{
4345            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4346        },
4347        Route::<Ipv6>{
4348            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4349        },
4350        true; "different_nexthop_v6_should_match")]
4351    #[test_case(
4352        Route::<Ipv4>{
4353            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4354        },
4355        Route::<Ipv4>{
4356            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4357        },
4358        true; "different_device_and_nexthop_v4_should_match")]
4359    #[test_case(
4360        Route::<Ipv6>{
4361            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4362        },
4363        Route::<Ipv6>{
4364            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4365        },
4366        true; "different_device_and_nexthop_v6_should_match")]
4367    #[test_case(
4368        Route::<Ipv4>{
4369            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4370        },
4371        Route::<Ipv4>{
4372            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4373        },
4374        true; "nexthop_newly_unset_v4_should_match")]
4375    #[test_case(
4376        Route::<Ipv6>{
4377            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4378        },
4379        Route::<Ipv6>{
4380            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4381        },
4382        true; "nexthop_newly_unset_v6_should_match")]
4383    #[test_case(
4384        Route::<Ipv4>{
4385            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4386        },
4387        Route::<Ipv4>{
4388            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4389        },
4390        true; "nexthop_previously_unset_v4_should_match")]
4391    #[test_case(
4392        Route::<Ipv6>{
4393            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4394        },
4395        Route::<Ipv6>{
4396            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4397        },
4398        true; "nexthop_previously_unset_v6_should_match")]
4399    #[test_case(
4400        Route::<Ipv4>{
4401            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4402        },
4403        Route::<Ipv4>{
4404            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4405        },
4406        false; "different_metric_v4_should_not_match")]
4407    #[test_case(
4408        Route::<Ipv4>{
4409            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4410        },
4411        Route::<Ipv4>{
4412            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4413        },
4414        false; "default_and_non_default_v4_should_not_match")]
4415    #[test_case(
4416        Route::<Ipv4>{
4417            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4418        },
4419        Route::<Ipv4>{
4420            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4421        },
4422        true; "default_and_default_v4_should_match")]
4423    #[test_case(
4424        Route::<Ipv6>{
4425            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4426        },
4427        Route::<Ipv6>{
4428            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4429        },
4430        false; "different_metric_v6_should_not_match")]
4431    #[test_case(
4432        Route::<Ipv4>{
4433            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4434        },
4435        Route::<Ipv4>{
4436            subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4437        },
4438        false; "different_subnet_v4_should_not_match")]
4439    #[test_case(
4440        Route::<Ipv6>{
4441            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4442        },
4443        Route::<Ipv6>{
4444            subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4445        },
4446        false; "different_subnet_v6_should_not_match")]
4447    #[test_case(
4448        Route::<Ipv4>{
4449            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4450        },
4451        Route::<Ipv4>{
4452            subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4453        },
4454        false; "different_subnet_prefixlen_v4_should_not_match")]
4455    #[test_case(
4456        Route::<Ipv6>{
4457            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4458        },
4459        Route::<Ipv6>{
4460            subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4461        },
4462        false; "different_subnet_prefixlen_v6_should_not_match")]
4463    #[test_case(
4464        Route::<Ipv6>{
4465            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4466        },
4467        Route::<Ipv6>{
4468            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4469        },
4470        false; "default_and_non_default_v6_should_not_match")]
4471    #[test_case(
4472        Route::<Ipv6>{
4473            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4474        },
4475        Route::<Ipv6>{
4476            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4477        },
4478        true; "default_and_default_v6_should_match")]
4479    fn test_new_route_matcher<I: Ip>(
4480        route1: Route<I>,
4481        route2: Route<I>,
4482        expected_to_conflict: bool,
4483    ) {
4484        let route1 = route1.to_installed_route(MAIN_FIDL_TABLE_ID);
4485        let route2 = route2.to_installed_route(MAIN_FIDL_TABLE_ID);
4486
4487        let got_conflict = routes_conflict::<I>(route1, route2.route, route2.table_id);
4488        assert_eq!(got_conflict, expected_to_conflict);
4489
4490        let got_conflict = routes_conflict::<I>(route2, route1.route, route1.table_id);
4491        assert_eq!(got_conflict, expected_to_conflict);
4492    }
4493
4494    // Calls `select_route_for_deletion` with the given args & existing_routes.
4495    //
4496    // Asserts that the return route matches the route in `existing_routes` at
4497    // `expected_index`.
4498    fn test_select_route_for_deletion_helper<
4499        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4500    >(
4501        args: UnicastDelRouteArgs<I>,
4502        existing_routes: &[Route<I>],
4503        // The index into `existing_routes` of the route that should be selected.
4504        expected_index: Option<usize>,
4505    ) {
4506        let mut fidl_route_map = FidlRouteMap::<I>::default();
4507
4508        // We create a bunch of proxies that go unused in this test. In order for this to succeed
4509        // we must have an executor.
4510        let _executor = fuchsia_async::TestExecutor::new();
4511
4512        let (main_route_table_proxy, _server_end) =
4513            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4514        let (own_route_table_proxy, _server_end) =
4515            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4516        let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4517        let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
4518            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4519        let (route_table_provider, _server_end) =
4520            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
4521
4522        let mut route_table_map = RouteTableMap::<I>::new(
4523            main_route_table_proxy,
4524            MAIN_FIDL_TABLE_ID,
4525            unmanaged_route_set_proxy,
4526            route_table_provider,
4527        );
4528
4529        route_table_map.insert(
4530            MANAGED_ROUTE_TABLE_INDEX,
4531            RouteTable::Managed(ManagedRouteTable {
4532                route_table_proxy: own_route_table_proxy,
4533                route_set_proxy,
4534                fidl_table_id: OTHER_FIDL_TABLE_ID,
4535                rule_set_authenticated: false,
4536            }),
4537        );
4538
4539        for Route { subnet, device, nexthop, metric } in existing_routes {
4540            let fnet_routes_ext::InstalledRoute { route, effective_properties, table_id } =
4541                create_installed_route::<I>(
4542                    *subnet,
4543                    *nexthop,
4544                    (*device).into(),
4545                    metric.map_or(0, NonZeroU32::get),
4546                    OTHER_FIDL_TABLE_ID,
4547                );
4548            assert_matches!(fidl_route_map.add(route, table_id, effective_properties), None);
4549        }
4550
4551        let existing_routes = existing_routes
4552            .iter()
4553            .map(|Route { subnet, device, nexthop, metric }| {
4554                // Don't populate the Destination NLA if this is the default route.
4555                let destination = (subnet.prefix() != 0).then_some(*subnet);
4556                create_netlink_route_message::<I>(
4557                    subnet.prefix(),
4558                    MANAGED_ROUTE_TABLE_INDEX,
4559                    create_nlas::<I>(
4560                        destination,
4561                        nexthop.to_owned(),
4562                        *device,
4563                        metric.map_or(0, NonZeroU32::get),
4564                        Some(MANAGED_ROUTE_TABLE_ID),
4565                    ),
4566                )
4567            })
4568            .collect::<Vec<_>>();
4569        let expected_route = expected_index.map(|index| {
4570            existing_routes
4571                .get(index)
4572                .expect("index should be within the bounds of `existing_routes`")
4573                .clone()
4574        });
4575
4576        assert_eq!(
4577            select_route_for_deletion(
4578                &fidl_route_map,
4579                &route_table_map,
4580                DelRouteArgs::Unicast(args).try_into().unwrap(),
4581            ),
4582            expected_route
4583        )
4584    }
4585
4586    #[test_case(
4587        UnicastDelRouteArgs::<Ipv4> {
4588            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4589            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4590                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4591            ),
4592        },
4593        Route::<Ipv4>{
4594            subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4595        },
4596        false; "subnet_does_not_match_v4")]
4597    #[test_case(
4598        UnicastDelRouteArgs::<Ipv4> {
4599            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4600            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4601                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4602            ),
4603        },
4604        Route::<Ipv4>{
4605            subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4606        },
4607        false; "subnet_prefix_len_does_not_match_v4")]
4608    #[test_case(
4609        UnicastDelRouteArgs::<Ipv4> {
4610            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4611            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4612                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4613            ),
4614        },
4615        Route::<Ipv4>{
4616            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4617        },
4618        true; "subnet_matches_v4")]
4619    #[test_case(
4620        UnicastDelRouteArgs::<Ipv4> {
4621            subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4622            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4623                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4624            ),
4625        },
4626        Route::<Ipv4>{
4627            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4628        },
4629        false; "interface_does_not_match_v4")]
4630    #[test_case(
4631        UnicastDelRouteArgs::<Ipv4> {
4632            subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4633            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4634                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4635            ),
4636        },
4637        Route::<Ipv4>{
4638            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4639        },
4640        true; "interface_matches_v4")]
4641    #[test_case(
4642        UnicastDelRouteArgs::<Ipv4> {
4643            subnet: V4_SUB1, outbound_interface: None,
4644            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4645            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4646                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4647            ),
4648        },
4649        Route::<Ipv4>{
4650            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4651        },
4652        false; "nexthop_absent_v4")]
4653    #[test_case(
4654        UnicastDelRouteArgs::<Ipv4> {
4655            subnet: V4_SUB1, outbound_interface: None,
4656            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4657            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4658                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4659            ),
4660        },
4661        Route::<Ipv4>{
4662            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4663        },
4664        false; "nexthop_does_not_match_v4")]
4665    #[test_case(
4666        UnicastDelRouteArgs::<Ipv4> {
4667            subnet: V4_SUB1, outbound_interface: None,
4668            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4669            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4670                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4671            ),
4672        },
4673        Route::<Ipv4>{
4674            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4675        },
4676        true; "nexthop_matches_v4")]
4677    #[test_case(
4678        UnicastDelRouteArgs::<Ipv4> {
4679            subnet: V4_SUB1, outbound_interface: None,
4680            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4681            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4682                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4683            ),
4684        },
4685        Route::<Ipv4>{
4686            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4687        },
4688        false; "metric_does_not_match_v4")]
4689    #[test_case(
4690        UnicastDelRouteArgs::<Ipv4> {
4691            subnet: V4_SUB1, outbound_interface: None,
4692            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4693            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4694                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4695            ),
4696        },
4697        Route::<Ipv4>{
4698            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4699        },
4700        true; "metric_matches_v4")]
4701    #[test_case(
4702        UnicastDelRouteArgs::<Ipv6> {
4703            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4704            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4705                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4706            ),
4707        },
4708        Route::<Ipv6>{
4709            subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4710        },
4711        false; "subnet_does_not_match_v6")]
4712    #[test_case(
4713        UnicastDelRouteArgs::<Ipv6> {
4714            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4715            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4716                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4717            ),
4718        },
4719        Route::<Ipv6>{
4720            subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4721        },
4722        false; "subnet_prefix_len_does_not_match_v6")]
4723    #[test_case(
4724        UnicastDelRouteArgs::<Ipv6> {
4725            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4726            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4727                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4728            ),
4729        },
4730        Route::<Ipv6>{
4731            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4732        },
4733        true; "subnet_matches_v6")]
4734    #[test_case(
4735        UnicastDelRouteArgs::<Ipv6> {
4736            subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4737            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4738                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4739            ),
4740        },
4741        Route::<Ipv6>{
4742            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4743        },
4744        false; "interface_does_not_match_v6")]
4745    #[test_case(
4746        UnicastDelRouteArgs::<Ipv6> {
4747            subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4748            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4749                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4750            ),
4751        },
4752        Route::<Ipv6>{
4753            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4754        },
4755        true; "interface_matches_v6")]
4756    #[test_case(
4757        UnicastDelRouteArgs::<Ipv6> {
4758            subnet: V6_SUB1, outbound_interface: None,
4759            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4760            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4761                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4762            ),
4763        },
4764        Route::<Ipv6>{
4765            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4766        },
4767        false; "nexthop_absent_v6")]
4768    #[test_case(
4769        UnicastDelRouteArgs::<Ipv6> {
4770            subnet: V6_SUB1, outbound_interface: None,
4771            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4772            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4773                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4774            ),
4775        },
4776        Route::<Ipv6>{
4777            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4778        },
4779        false; "nexthop_does_not_match_v6")]
4780    #[test_case(
4781        UnicastDelRouteArgs::<Ipv6> {
4782            subnet: V6_SUB1, outbound_interface: None,
4783            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4784            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4785                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4786            ),
4787        },
4788        Route::<Ipv6>{
4789            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4790        },
4791        true; "nexthop_matches_v6")]
4792    #[test_case(
4793        UnicastDelRouteArgs::<Ipv6> {
4794            subnet: V6_SUB1, outbound_interface: None,
4795            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4796            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4797                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4798            ),
4799        },
4800        Route::<Ipv6>{
4801            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4802        },
4803        false; "metric_does_not_match_v6")]
4804    #[test_case(
4805        UnicastDelRouteArgs::<Ipv6> {
4806            subnet: V6_SUB1, outbound_interface: None,
4807            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4808            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4809                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4810            ),
4811        },
4812        Route::<Ipv6>{
4813            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4814        },
4815        true; "metric_matches_v6")]
4816    fn test_select_route_for_deletion<
4817        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4818    >(
4819        args: UnicastDelRouteArgs<I>,
4820        existing_route: Route<I>,
4821        expect_match: bool,
4822    ) {
4823        test_select_route_for_deletion_helper(args, &[existing_route], expect_match.then_some(0))
4824    }
4825
4826    #[test_case(
4827        UnicastDelRouteArgs::<Ipv4> {
4828            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4829            table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4830        },
4831        &[
4832        Route::<Ipv4>{
4833            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4834        },
4835        Route::<Ipv4>{
4836            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4837        },
4838        Route::<Ipv4>{
4839            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4840        },
4841        ],
4842        Some(1); "multiple_matches_prefers_lowest_metric_v4")]
4843    #[test_case(
4844        UnicastDelRouteArgs::<Ipv6> {
4845            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4846            table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4847        },
4848        &[
4849        Route::<Ipv6>{
4850            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4851        },
4852        Route::<Ipv6>{
4853            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4854        },
4855        Route::<Ipv6>{
4856            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4857        },
4858        ],
4859        Some(1); "multiple_matches_prefers_lowest_metric_v6")]
4860    fn test_select_route_for_deletion_multiple_matches<
4861        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4862    >(
4863        args: UnicastDelRouteArgs<I>,
4864        existing_routes: &[Route<I>],
4865        expected_index: Option<usize>,
4866    ) {
4867        test_select_route_for_deletion_helper(args, existing_routes, expected_index);
4868    }
4869
4870    #[ip_test(I, test = false)]
4871    #[fuchsia::test]
4872    async fn garbage_collects_empty_table<
4873        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4874    >() {
4875        let (_route_sink, route_client, async_work_drain_task) =
4876            crate::client::testutil::new_fake_client::<NetlinkRoute>(
4877                crate::client::testutil::CLIENT_ID_1,
4878                [ModernGroup(match I::VERSION {
4879                    IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
4880                    IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
4881                })],
4882            );
4883        let join_handle = fasync::Task::spawn(async_work_drain_task);
4884        {
4885            // Move `route_client` into the scope so it gets dropped.
4886            let route_client = route_client;
4887            let route_clients = ClientTable::default();
4888            route_clients.add_client(route_client.clone());
4889
4890            let Setup {
4891                event_loop_inputs,
4892                watcher_stream,
4893                route_sets: (main_route_table_server_end, route_table_provider_server_end),
4894                interfaces_request_stream: _,
4895                mut request_sink,
4896                async_work_sink: _,
4897            } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
4898
4899            let mut main_route_table_fut = pin!(
4900                fnet_routes_ext::testutil::admin::serve_noop_route_sets_with_table_id::<I>(
4901                    main_route_table_server_end,
4902                    MAIN_FIDL_TABLE_ID
4903                )
4904                .fuse()
4905            );
4906
4907            let mut watcher_stream = pin!(watcher_stream.fuse());
4908            let mut route_table_provider_stream = route_table_provider_server_end.into_stream();
4909
4910            let mut event_loop = {
4911                let included_workers = match I::VERSION {
4912                    IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
4913                        routes_v4: EventLoopComponent::Present(()),
4914                        routes_v6: EventLoopComponent::Absent(Optional),
4915                        interfaces: EventLoopComponent::Absent(Optional),
4916                        rules_v4: EventLoopComponent::Absent(Optional),
4917                        rules_v6: EventLoopComponent::Absent(Optional),
4918                        nduseropt: EventLoopComponent::Absent(Optional),
4919                        neighbors: EventLoopComponent::Absent(Optional),
4920                    },
4921                    IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
4922                        routes_v4: EventLoopComponent::Absent(Optional),
4923                        routes_v6: EventLoopComponent::Present(()),
4924                        interfaces: EventLoopComponent::Absent(Optional),
4925                        rules_v4: EventLoopComponent::Absent(Optional),
4926                        rules_v6: EventLoopComponent::Absent(Optional),
4927                        nduseropt: EventLoopComponent::Absent(Optional),
4928                        neighbors: EventLoopComponent::Absent(Optional),
4929                    },
4930                };
4931
4932                let event_loop_fut = event_loop_inputs.initialize(included_workers).fuse();
4933                let watcher_fut = async {
4934                    let watch_req =
4935                        watcher_stream.by_ref().next().await.expect("should not have ended");
4936                    // Start with no routes.
4937                    fnet_routes_ext::testutil::handle_watch::<I>(
4938                        watch_req,
4939                        vec![fnet_routes_ext::Event::<I>::Idle.try_into().unwrap()],
4940                    )
4941                }
4942                .fuse();
4943
4944                futures::select! {
4945                    () = main_route_table_fut => unreachable!(),
4946                    (event_loop, ()) = futures::future::join(
4947                        event_loop_fut, watcher_fut
4948                    ) => event_loop,
4949                }
4950            };
4951
4952            let (completer, mut initial_add_request_waiter) = oneshot::channel();
4953
4954            let new_route_args = NewRouteArgs::Unicast(I::map_ip_out(
4955                (),
4956                |()| {
4957                    create_unicast_new_route_args(
4958                        V4_SUB1,
4959                        V4_NEXTHOP1,
4960                        DEV1.into(),
4961                        METRIC1,
4962                        MANAGED_ROUTE_TABLE_INDEX,
4963                    )
4964                },
4965                |()| {
4966                    create_unicast_new_route_args(
4967                        V6_SUB1,
4968                        V6_NEXTHOP1,
4969                        DEV1.into(),
4970                        METRIC1,
4971                        MANAGED_ROUTE_TABLE_INDEX,
4972                    )
4973                },
4974            ));
4975            let expected_route = fnet_routes_ext::Route::<I>::from(new_route_args);
4976
4977            // Request that a route is installed in a new table.
4978            request_sink
4979                .try_send(
4980                    Request {
4981                        args: RequestArgs::Route(RouteRequestArgs::New(new_route_args)),
4982                        sequence_number: TEST_SEQUENCE_NUMBER,
4983                        client: route_client.clone(),
4984                        completer,
4985                    }
4986                    .into(),
4987                )
4988                .expect("should succeed");
4989
4990            // Run the event loop and observe the new table get created and the
4991            // route set requests go out.
4992            let (mut route_table_stream, mut route_set_stream) = {
4993                let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
4994                let route_table_fut = async {
4995                    let server_end = match I::into_route_table_provider_request(
4996                        route_table_provider_stream
4997                            .try_next()
4998                            .await
4999                            .expect("should not have ended")
5000                            .expect("fidl error"),
5001                    ) {
5002                        fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
5003                            provider,
5004                            options: _,
5005                            control_handle: _,
5006                        } => provider,
5007                        r => panic!("unexpected request {r:?}"),
5008                    };
5009                    let mut route_table_stream = server_end.into_stream().boxed().fuse();
5010
5011                    let request = I::into_route_table_request_result(
5012                        route_table_stream.by_ref().next().await.expect("should not have ended"),
5013                    )
5014                    .expect("should not get error");
5015
5016                    let responder = match request {
5017                        RouteTableRequest::GetTableId { responder } => responder,
5018                        _ => panic!("should be GetTableId"),
5019                    };
5020                    responder.send(OTHER_FIDL_TABLE_ID.get()).expect("should succeed");
5021
5022                    let request = I::into_route_table_request_result(
5023                        route_table_stream.by_ref().next().await.expect("should not have ended"),
5024                    )
5025                    .expect("should not get error");
5026
5027                    let server_end = match request {
5028                        RouteTableRequest::NewRouteSet { route_set, control_handle: _ } => {
5029                            route_set
5030                        }
5031                        _ => panic!("should be NewRouteSet"),
5032                    };
5033                    let mut route_set_stream = server_end.into_stream().boxed().fuse();
5034
5035                    let request = I::into_route_set_request_result(
5036                        route_set_stream.by_ref().next().await.expect("should not have ended"),
5037                    )
5038                    .expect("should not get error");
5039
5040                    let (route, responder) = match request {
5041                        RouteSetRequest::AddRoute { route, responder } => (route, responder),
5042                        _ => panic!("should be AddRoute"),
5043                    };
5044                    let route = route.expect("should successfully convert FIDl");
5045                    assert_eq!(route, expected_route);
5046
5047                    responder.send(Ok(true)).expect("sending response should succeed");
5048                    (route_table_stream, route_set_stream)
5049                }
5050                .fuse();
5051                futures::select! {
5052                    () = main_route_table_fut => unreachable!(),
5053                    ((), streams) = futures::future::join(event_loop_fut, route_table_fut) => {
5054                        streams
5055                    }
5056                }
5057            };
5058
5059            {
5060                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5061                // The new route table should be present in the map.
5062                let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5063                    Some(RouteTable::Managed(table)) => table,
5064                    _ => panic!("table should be present"),
5065                };
5066                assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5067
5068                // But the new route won't be tracked because we haven't
5069                // confirmed it via the watcher yet.
5070                assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5071                    &expected_route,
5072                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5073                ));
5074            }
5075
5076            // The request won't be complete until we've confirmed addition via the watcher.
5077            assert_matches!(initial_add_request_waiter.try_recv(), Ok(None));
5078
5079            // Run the event loop while yielding the new route via the watcher.
5080            {
5081                let event_loop_fut = async {
5082                    // Handling two events, so run two steps.
5083                    event_loop.run_one_step_in_tests().await;
5084                    event_loop.run_one_step_in_tests().await;
5085                }
5086                .fuse();
5087                let watcher_fut = async {
5088                    let watch_req =
5089                        watcher_stream.by_ref().next().await.expect("should not have ended");
5090                    // Show that the route was added for both the main and the owned FIDL table.
5091                    fnet_routes_ext::testutil::handle_watch::<I>(
5092                        watch_req,
5093                        vec![
5094                            fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5095                                route: expected_route,
5096                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5097                                    metric: METRIC1,
5098                                },
5099                                table_id: MAIN_FIDL_TABLE_ID,
5100                            })
5101                            .try_into()
5102                            .unwrap(),
5103                            fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5104                                route: expected_route,
5105                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5106                                    metric: METRIC1,
5107                                },
5108                                table_id: OTHER_FIDL_TABLE_ID,
5109                            })
5110                            .try_into()
5111                            .unwrap(),
5112                        ],
5113                    );
5114                };
5115                let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5116            }
5117
5118            {
5119                let (routes_worker, _route_table_map) = event_loop.route_table_state::<I>();
5120
5121                // The route should be noted as stored in both tables now.
5122                assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5123                    &expected_route,
5124                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5125                ));
5126            }
5127
5128            assert_matches!(initial_add_request_waiter.try_recv(), Ok(Some(Ok(()))));
5129
5130            let (completer, mut del_request_waiter) = oneshot::channel();
5131
5132            let del_route_args = I::map_ip_out(
5133                (),
5134                |()| {
5135                    create_unicast_del_route_args(
5136                        V4_SUB1,
5137                        Some(V4_NEXTHOP1),
5138                        Some(DEV1.into()),
5139                        Some(METRIC1),
5140                        MANAGED_ROUTE_TABLE_INDEX,
5141                    )
5142                },
5143                |()| {
5144                    create_unicast_del_route_args(
5145                        V6_SUB1,
5146                        Some(V6_NEXTHOP1),
5147                        Some(DEV1.into()),
5148                        Some(METRIC1),
5149                        MANAGED_ROUTE_TABLE_INDEX,
5150                    )
5151                },
5152            );
5153
5154            // Request the route's removal.
5155            request_sink
5156                .try_send(
5157                    Request {
5158                        args: RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(
5159                            del_route_args,
5160                        ))),
5161                        sequence_number: TEST_SEQUENCE_NUMBER,
5162                        client: route_client.clone(),
5163                        completer,
5164                    }
5165                    .into(),
5166                )
5167                .expect("should succeed");
5168
5169            // Observe and handle the removal requests.
5170            {
5171                let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
5172                let route_set_fut = async {
5173                    let request = I::into_route_set_request_result(
5174                        route_set_stream.next().await.expect("should not have ended"),
5175                    )
5176                    .expect("should not get error");
5177                    let (route, responder) = match request {
5178                        RouteSetRequest::RemoveRoute { route, responder } => (route, responder),
5179                        _ => panic!("should be DelRoute"),
5180                    };
5181                    let route = route.expect("should successfully convert FIDl");
5182                    assert_eq!(route, expected_route);
5183
5184                    responder.send(Ok(true)).expect("sending response should succeed");
5185                }
5186                .fuse();
5187
5188                futures::select! {
5189                    () = main_route_table_fut => unreachable!(),
5190                    ((), ()) = futures::future::join(event_loop_fut, route_set_fut) => (),
5191                }
5192            }
5193
5194            // We still haven't confirmed the removal via the watcher.
5195            {
5196                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5197                // The route table should still be present in the map.
5198                let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5199                    Some(RouteTable::Managed(table)) => table,
5200                    _ => panic!("table should be present"),
5201                };
5202                assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5203
5204                assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5205                    &expected_route,
5206                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5207                ));
5208            }
5209            assert_matches!(del_request_waiter.try_recv(), Ok(None));
5210
5211            // Run the event loop while yielding the deleted route via the watcher.
5212            {
5213                let event_loop_fut = async {
5214                    // Handling two events, so run two steps.
5215                    event_loop.run_one_step_in_tests().await;
5216                    event_loop.run_one_step_in_tests().await;
5217                }
5218                .fuse();
5219                let watcher_fut = async {
5220                    let watch_req =
5221                        watcher_stream.by_ref().next().await.expect("should not have ended");
5222                    // Show that the route was removed for both the main and the owned FIDL table.
5223                    fnet_routes_ext::testutil::handle_watch::<I>(
5224                        watch_req,
5225                        vec![
5226                            fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5227                                route: expected_route,
5228                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5229                                    metric: 0,
5230                                },
5231                                table_id: MAIN_FIDL_TABLE_ID,
5232                            })
5233                            .try_into()
5234                            .unwrap(),
5235                            fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5236                                route: expected_route,
5237                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5238                                    metric: 0,
5239                                },
5240                                table_id: OTHER_FIDL_TABLE_ID,
5241                            })
5242                            .try_into()
5243                            .unwrap(),
5244                        ],
5245                    );
5246                };
5247                let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5248            }
5249
5250            {
5251                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5252
5253                // The route should be noted as being removed from both tables now.
5254                assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5255                    &expected_route,
5256                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5257                ));
5258
5259                // And the table should now be cleaned up from the map.
5260                assert_matches!(route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX), None);
5261            }
5262            assert_matches!(del_request_waiter.try_recv(), Ok(Some(Ok(()))));
5263
5264            // Because the table was dropped from the map, the route table
5265            // request stream should close.
5266            let route_table_request = route_table_stream.next().await;
5267            assert!(route_table_request.is_none());
5268        };
5269        join_handle.await;
5270    }
5271
5272    #[ip_test(I, test = false)]
5273    #[fuchsia::test]
5274    async fn process_stashed_routes<
5275        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
5276    >() {
5277        let (subnet, next_hop) =
5278            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
5279        let table_id = OTHER_FIDL_TABLE_ID;
5280        let installed_route =
5281            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
5282        let to_be_removed_route =
5283            create_installed_route(subnet, Some(next_hop), DEV2.into(), METRIC2, table_id);
5284
5285        let (route_table_proxy, _route_table_server_end) =
5286            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
5287        let (unmanaged_route_set_proxy, _server_end) =
5288            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5289        let (route_table_provider, _server_end) =
5290            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
5291        let mut route_table = RouteTableMap::new(
5292            route_table_proxy.clone(),
5293            MAIN_FIDL_TABLE_ID,
5294            unmanaged_route_set_proxy,
5295            route_table_provider,
5296        );
5297        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
5298
5299        let mut worker = RoutesWorker {
5300            fidl_route_map: FidlRouteMap::<I>::default(),
5301            stashed_routes: HashMap::new(),
5302        };
5303
5304        // 1. Add routes, but the table is unknown to netlink.
5305        assert_eq!(
5306            worker.handle_route_watcher_event(
5307                &mut route_table,
5308                &route_clients,
5309                fnet_routes_ext::Event::Added(installed_route.clone()),
5310            ),
5311            None
5312        );
5313        assert_eq!(
5314            worker.handle_route_watcher_event(
5315                &mut route_table,
5316                &route_clients,
5317                fnet_routes_ext::Event::Added(to_be_removed_route.clone()),
5318            ),
5319            None
5320        );
5321
5322        // Verify they are stashed.
5323        assert!(worker.stashed_routes.contains_key(&table_id));
5324        assert!(worker.stashed_routes[&table_id].contains(&installed_route));
5325        assert!(worker.stashed_routes[&table_id].contains(&to_be_removed_route));
5326        assert_eq!(
5327            // The routes should NOT be in the FIDL map yet.
5328            worker.fidl_route_map.iter_messages(&route_table, MAIN_ROUTE_TABLE_INDEX).count(),
5329            0
5330        );
5331
5332        assert_eq!(
5333            worker.handle_route_watcher_event(
5334                &mut route_table,
5335                &route_clients,
5336                fnet_routes_ext::Event::Removed(to_be_removed_route.clone()),
5337            ),
5338            None
5339        );
5340        assert!(!worker.stashed_routes[&table_id].contains(&to_be_removed_route));
5341
5342        // 2. Add the table mapping.
5343        let netlink_table_id = NetlinkRouteTableIndex::new(123);
5344        let (route_set_proxy, _) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5345        let table = RouteTable::Unmanaged(UnmanagedTable {
5346            route_table_proxy: route_table_proxy.clone(),
5347            route_set_proxy,
5348            fidl_table_id: table_id,
5349            rule_set_authenticated: false,
5350        });
5351        route_table.insert(netlink_table_id, table);
5352
5353        // 3. Process stashed routes.
5354        worker.process_stashed_routes(&mut route_table, &route_clients, netlink_table_id);
5355
5356        // Verify only one route is unstashed and added to fidl_route_map.
5357        assert!(!worker.stashed_routes.contains_key(&table_id));
5358        // Verify it is in the FIDL map.
5359        // We need to iterate messages for the new table.
5360        assert_eq!(worker.fidl_route_map.iter_messages(&route_table, netlink_table_id).count(), 1);
5361    }
5362
5363    #[ip_test(I, test = false)]
5364    #[fuchsia::test]
5365    async fn remove_non_empty_table<
5366        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
5367    >() {
5368        let (subnet, next_hop) =
5369            I::map_ip((), |()| (V4_SUB1, V4_NEXTHOP1), |()| (V6_SUB1, V6_NEXTHOP1));
5370        let table_id = OTHER_FIDL_TABLE_ID;
5371        let installed_route =
5372            create_installed_route(subnet, Some(next_hop), DEV1.into(), METRIC1, table_id);
5373
5374        let (route_table_proxy, _route_table_server_end) =
5375            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
5376        let (unmanaged_route_set_proxy, _server_end) =
5377            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5378        let (route_table_provider, _server_end) =
5379            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
5380        let mut route_table = RouteTableMap::new(
5381            route_table_proxy.clone(),
5382            MAIN_FIDL_TABLE_ID,
5383            unmanaged_route_set_proxy,
5384            route_table_provider,
5385        );
5386        let route_clients: ClientTable<NetlinkRoute, FakeSender<_>> = ClientTable::default();
5387
5388        let mut worker = RoutesWorker {
5389            fidl_route_map: FidlRouteMap::<I>::default(),
5390            stashed_routes: HashMap::new(),
5391        };
5392
5393        let netlink_table_id = NetlinkRouteTableIndex::new(123);
5394        let (route_set_proxy, _) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
5395
5396        // Create a non-empty netlink table.
5397        let table = RouteTable::Unmanaged(UnmanagedTable {
5398            route_table_proxy: route_table_proxy.clone(),
5399            route_set_proxy,
5400            fidl_table_id: table_id,
5401            rule_set_authenticated: false,
5402        });
5403
5404        route_table.insert(netlink_table_id, table);
5405        assert_eq!(
5406            worker.handle_route_watcher_event(
5407                &mut route_table,
5408                &route_clients,
5409                fnet_routes_ext::Event::Added(installed_route.clone()),
5410            ),
5411            None
5412        );
5413
5414        // Remove the table.
5415        assert_matches!(route_table.remove_table_by_fidl_id(table_id), Some(Ok(_)));
5416        // Asynchronously, netstack will generate a remove event for the route
5417        // in that table.
5418        assert_eq!(
5419            worker.handle_route_watcher_event(
5420                &mut route_table,
5421                &route_clients,
5422                fnet_routes_ext::Event::Removed(installed_route.clone()),
5423            ),
5424            None
5425        );
5426    }
5427}