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