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