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