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    }
2166
2167    struct Setup<W, R> {
2168        pub event_loop_inputs: crate::route_eventloop::EventLoopInputs<
2169            FakeInterfacesHandler,
2170            FakeSender<RouteNetlinkMessage>,
2171            OnlyRoutes,
2172        >,
2173        pub watcher_stream: W,
2174        pub route_sets: R,
2175        pub interfaces_request_stream: fnet_root::InterfacesRequestStream,
2176        pub request_sink:
2177            mpsc::Sender<crate::route_eventloop::UnifiedRequest<FakeSender<RouteNetlinkMessage>>>,
2178        pub async_work_sink: mpsc::UnboundedSender<AsyncWorkItem<NetlinkRoute>>,
2179    }
2180
2181    fn setup_with_route_clients_yielding_admin_server_ends<
2182        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2183    >(
2184        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2185    ) -> Setup<
2186        impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2187        (ServerEnd<I::RouteTableMarker>, ServerEnd<I::RouteTableProviderMarker>),
2188    > {
2189        let (interfaces_handler, _interfaces_handler_sink) = FakeInterfacesHandler::new();
2190        let (request_sink, request_stream) = mpsc::channel(1);
2191        let (interfaces_proxy, interfaces) =
2192            fidl::endpoints::create_proxy::<fnet_root::InterfacesMarker>();
2193        let (async_work_sink, async_work_receiver) = mpsc::unbounded();
2194
2195        #[derive(GenericOverIp)]
2196        #[generic_over_ip(I, Ip)]
2197        struct ServerEnds<
2198            I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2199        > {
2200            routes_state: ServerEnd<I::StateMarker>,
2201            routes_set_provider: ServerEnd<I::RouteTableMarker>,
2202            route_table_provider: ServerEnd<I::RouteTableProviderMarker>,
2203        }
2204
2205        let base_inputs = crate::route_eventloop::EventLoopInputs {
2206            interfaces_handler: EventLoopComponent::Present(interfaces_handler),
2207            route_clients: EventLoopComponent::Present(route_clients),
2208            interfaces_proxy: EventLoopComponent::Present(interfaces_proxy),
2209            async_work_receiver,
2210
2211            interfaces_state_proxy: EventLoopComponent::Absent(Optional),
2212            v4_routes_state: EventLoopComponent::Absent(Optional),
2213            v6_routes_state: EventLoopComponent::Absent(Optional),
2214            v4_main_route_table: EventLoopComponent::Absent(Optional),
2215            v6_main_route_table: EventLoopComponent::Absent(Optional),
2216            v4_route_table_provider: EventLoopComponent::Absent(Optional),
2217            v6_route_table_provider: EventLoopComponent::Absent(Optional),
2218            v4_rule_table: EventLoopComponent::Absent(Optional),
2219            v6_rule_table: EventLoopComponent::Absent(Optional),
2220            ndp_option_watcher_provider: EventLoopComponent::Absent(Optional),
2221
2222            unified_request_stream: request_stream,
2223        };
2224
2225        let (IpInvariant(inputs), server_ends) = I::map_ip_out(
2226            base_inputs,
2227            |base_inputs| {
2228                let (v4_routes_state, routes_state) =
2229                    fidl::endpoints::create_proxy::<fnet_routes::StateV4Marker>();
2230                let (v4_main_route_table, routes_set_provider) =
2231                    fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV4Marker>();
2232                let (v4_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2233                    fnet_routes_admin::RouteTableProviderV4Marker,
2234                >();
2235                let inputs = crate::route_eventloop::EventLoopInputs {
2236                    v4_routes_state: EventLoopComponent::Present(v4_routes_state),
2237                    v4_main_route_table: EventLoopComponent::Present(v4_main_route_table),
2238                    v4_route_table_provider: EventLoopComponent::Present(v4_route_table_provider),
2239                    ..base_inputs
2240                };
2241                let server_ends =
2242                    ServerEnds::<Ipv4> { routes_state, routes_set_provider, route_table_provider };
2243                (IpInvariant(inputs), server_ends)
2244            },
2245            |base_inputs| {
2246                let (v6_routes_state, routes_state) =
2247                    fidl::endpoints::create_proxy::<fnet_routes::StateV6Marker>();
2248                let (v6_main_route_table, routes_set_provider) =
2249                    fidl::endpoints::create_proxy::<fnet_routes_admin::RouteTableV6Marker>();
2250                let (v6_route_table_provider, route_table_provider) = fidl::endpoints::create_proxy::<
2251                    fnet_routes_admin::RouteTableProviderV6Marker,
2252                >();
2253                let inputs = crate::route_eventloop::EventLoopInputs {
2254                    v6_routes_state: EventLoopComponent::Present(v6_routes_state),
2255                    v6_main_route_table: EventLoopComponent::Present(v6_main_route_table),
2256                    v6_route_table_provider: EventLoopComponent::Present(v6_route_table_provider),
2257                    ..base_inputs
2258                };
2259                let server_ends =
2260                    ServerEnds::<Ipv6> { routes_state, routes_set_provider, route_table_provider };
2261                (IpInvariant(inputs), server_ends)
2262            },
2263        );
2264
2265        let ServerEnds { routes_state, routes_set_provider, route_table_provider } = server_ends;
2266
2267        let state_stream = routes_state.into_stream().boxed_local();
2268
2269        let interfaces_request_stream = interfaces.into_stream();
2270
2271        #[derive(GenericOverIp)]
2272        #[generic_over_ip(I, Ip)]
2273        struct StateRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2274            request: <<I::StateMarker as ProtocolMarker>::RequestStream as futures::Stream>::Item,
2275        }
2276
2277        #[derive(GenericOverIp)]
2278        #[generic_over_ip(I, Ip)]
2279        struct WatcherRequestWrapper<I: fnet_routes_ext::FidlRouteIpExt> {
2280            watcher: <I::WatcherMarker as ProtocolMarker>::RequestStream,
2281        }
2282
2283        let watcher_stream = state_stream
2284            .map(|request| {
2285                let wrapper = I::map_ip(
2286                    StateRequestWrapper { request },
2287                    |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2288                        fnet_routes::StateV4Request::GetWatcherV4 {
2289                            options: _,
2290                            watcher,
2291                            control_handle: _,
2292                        } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2293                        fnet_routes::StateV4Request::GetRuleWatcherV4 {
2294                            options: _,
2295                            watcher: _,
2296                            control_handle: _,
2297                        } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2298                    },
2299                    |StateRequestWrapper { request }| match request.expect("watcher stream error") {
2300                        fnet_routes::StateV6Request::GetWatcherV6 {
2301                            options: _,
2302                            watcher,
2303                            control_handle: _,
2304                        } => WatcherRequestWrapper { watcher: watcher.into_stream() },
2305                        fnet_routes::StateV6Request::GetRuleWatcherV6 {
2306                            options: _,
2307                            watcher: _,
2308                            control_handle: _,
2309                        } => todo!("TODO(https://fxbug.dev/336204757): Implement rules watcher"),
2310                    },
2311                );
2312                wrapper
2313            })
2314            .map(|WatcherRequestWrapper { watcher }| watcher)
2315            // For testing, we only expect there to be a single connection to the watcher, so the
2316            // stream is condensed into a single `WatchRequest` stream.
2317            .flatten()
2318            .fuse();
2319
2320        Setup {
2321            event_loop_inputs: inputs,
2322            watcher_stream,
2323            route_sets: (routes_set_provider, route_table_provider),
2324            interfaces_request_stream,
2325            request_sink,
2326            async_work_sink,
2327        }
2328    }
2329
2330    fn setup_with_route_clients<
2331        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2332    >(
2333        route_clients: ClientTable<NetlinkRoute, FakeSender<RouteNetlinkMessage>>,
2334    ) -> Setup<
2335        impl Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2336        impl Stream<
2337            Item = (
2338                fnet_routes_ext::TableId,
2339                <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2340            ),
2341        >,
2342    > {
2343        let Setup {
2344            event_loop_inputs,
2345            watcher_stream,
2346            route_sets: (routes_set_provider, route_table_provider),
2347            interfaces_request_stream,
2348            request_sink,
2349            async_work_sink,
2350        } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
2351        let route_set_stream =
2352            fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2353                routes_set_provider,
2354                Some(MAIN_FIDL_TABLE_ID),
2355            )
2356            .map(|item| (MAIN_FIDL_TABLE_ID, item));
2357
2358        let route_table_provider_request_stream = route_table_provider.into_stream();
2359
2360        let table_id = AtomicU32::new(OTHER_FIDL_TABLE_ID.get());
2361
2362        let route_sets_from_route_table_provider =
2363            futures::TryStreamExt::map_ok(route_table_provider_request_stream, move |request| {
2364                match I::into_route_table_provider_request(request) {
2365                    fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
2366                        provider,
2367                        options: _,
2368                        control_handle: _,
2369                    } => {
2370                        let table_id =
2371                            fnet_routes_ext::TableId::new(table_id.fetch_add(1, Ordering::SeqCst));
2372                        fnet_routes_ext::testutil::admin::serve_all_route_sets_with_table_id::<I>(
2373                            provider,
2374                            Some(table_id),
2375                        )
2376                        .map(move |route_set_request| (table_id, route_set_request))
2377                    }
2378                    r => panic!("unexpected request {r:?}"),
2379                }
2380            })
2381            .map(|result| result.expect("should not get FIDL error"))
2382            .flatten_unordered(None)
2383            .fuse();
2384        let route_set_stream = futures::stream::select_all([
2385            route_set_stream.left_stream(),
2386            route_sets_from_route_table_provider.right_stream(),
2387        ])
2388        .fuse();
2389
2390        Setup {
2391            event_loop_inputs,
2392            watcher_stream,
2393            route_sets: route_set_stream,
2394            interfaces_request_stream,
2395            request_sink,
2396            async_work_sink,
2397        }
2398    }
2399
2400    async fn respond_to_watcher<
2401        I: fnet_routes_ext::FidlRouteIpExt,
2402        S: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>,
2403    >(
2404        stream: S,
2405        updates: impl IntoIterator<Item = I::WatchEvent>,
2406    ) {
2407        #[derive(GenericOverIp)]
2408        #[generic_over_ip(I, Ip)]
2409        struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
2410            request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2411            update: I::WatchEvent,
2412        }
2413        stream
2414            .zip(futures::stream::iter(updates.into_iter()))
2415            .for_each(|(request, update)| async move {
2416                I::map_ip_in(
2417                    HandleInputs { request, update },
2418                    |HandleInputs { request, update }| match request
2419                        .expect("failed to receive `Watch` request")
2420                    {
2421                        fnet_routes::WatcherV4Request::Watch { responder } => {
2422                            responder.send(&[update]).expect("failed to respond to `Watch`")
2423                        }
2424                    },
2425                    |HandleInputs { request, update }| match request
2426                        .expect("failed to receive `Watch` request")
2427                    {
2428                        fnet_routes::WatcherV6Request::Watch { responder } => {
2429                            responder.send(&[update]).expect("failed to respond to `Watch`")
2430                        }
2431                    },
2432                );
2433            })
2434            .await;
2435    }
2436
2437    async fn run_event_loop<I: Ip>(
2438        inputs: crate::route_eventloop::EventLoopInputs<
2439            FakeInterfacesHandler,
2440            FakeSender<RouteNetlinkMessage>,
2441            OnlyRoutes,
2442        >,
2443    ) -> Never {
2444        let included_workers = match I::VERSION {
2445            IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
2446                routes_v4: EventLoopComponent::Present(()),
2447                routes_v6: EventLoopComponent::Absent(Optional),
2448                interfaces: EventLoopComponent::Absent(Optional),
2449                rules_v4: EventLoopComponent::Absent(Optional),
2450                rules_v6: EventLoopComponent::Absent(Optional),
2451                nduseropt: EventLoopComponent::Absent(Optional),
2452            },
2453            IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
2454                routes_v4: EventLoopComponent::Absent(Optional),
2455                routes_v6: EventLoopComponent::Present(()),
2456                interfaces: EventLoopComponent::Absent(Optional),
2457                rules_v4: EventLoopComponent::Absent(Optional),
2458                rules_v6: EventLoopComponent::Absent(Optional),
2459                nduseropt: EventLoopComponent::Absent(Optional),
2460            },
2461        };
2462
2463        let event_loop = inputs.initialize(included_workers).await;
2464        event_loop.run().await
2465    }
2466
2467    fn get_test_route_events_new_route_args<A: IpAddress>(
2468        subnet: Subnet<A>,
2469        next_hop1: A,
2470        next_hop2: A,
2471    ) -> [RequestArgs<A::Version>; 2]
2472    where
2473        A::Version: fnet_routes_ext::FidlRouteIpExt,
2474    {
2475        [
2476            RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2477                create_unicast_new_route_args(
2478                    subnet,
2479                    next_hop1,
2480                    DEV1.into(),
2481                    METRIC1,
2482                    MANAGED_ROUTE_TABLE_INDEX,
2483                ),
2484            ))),
2485            RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
2486                create_unicast_new_route_args(
2487                    subnet,
2488                    next_hop2,
2489                    DEV2.into(),
2490                    METRIC2,
2491                    MANAGED_ROUTE_TABLE_INDEX,
2492                ),
2493            ))),
2494        ]
2495    }
2496
2497    fn create_unicast_new_route_args<A: IpAddress>(
2498        subnet: Subnet<A>,
2499        next_hop: A,
2500        interface_id: u64,
2501        priority: u32,
2502        table: NetlinkRouteTableIndex,
2503    ) -> UnicastNewRouteArgs<A::Version> {
2504        UnicastNewRouteArgs {
2505            subnet,
2506            target: fnet_routes_ext::RouteTarget {
2507                outbound_interface: interface_id,
2508                next_hop: SpecifiedAddr::new(next_hop),
2509            },
2510            priority: NonZeroU32::new(priority),
2511            table,
2512        }
2513    }
2514
2515    fn create_unicast_del_route_args<A: IpAddress>(
2516        subnet: Subnet<A>,
2517        next_hop: Option<A>,
2518        interface_id: Option<u64>,
2519        priority: Option<u32>,
2520        table: NetlinkRouteTableIndex,
2521    ) -> UnicastDelRouteArgs<A::Version> {
2522        UnicastDelRouteArgs {
2523            subnet,
2524            outbound_interface: interface_id.map(NonZeroU64::new).flatten(),
2525            next_hop: next_hop.map(SpecifiedAddr::new).flatten(),
2526            priority: priority.map(NonZeroU32::new).flatten(),
2527            table: NonZeroNetlinkRouteTableIndex::new(table).unwrap(),
2528        }
2529    }
2530
2531    #[derive(Debug, PartialEq)]
2532    struct TestRequestResult {
2533        messages: Vec<SentMessage<RouteNetlinkMessage>>,
2534        waiter_results: Vec<Result<(), RequestError>>,
2535    }
2536
2537    /// Test helper to handle an iterator of route requests
2538    /// using the same clients and event loop.
2539    ///
2540    /// `root_handler` returns a future that handles
2541    /// `fnet_root::InterfacesRequest`s.
2542    async fn test_requests<
2543        A: IpAddress,
2544        Fut: Future<Output = ()>,
2545        F: FnOnce(fnet_root::InterfacesRequestStream) -> Fut,
2546    >(
2547        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
2548        root_handler: F,
2549        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2550        subnet: Subnet<A>,
2551        next_hop1: A,
2552        next_hop2: A,
2553        num_sink_messages: usize,
2554    ) -> TestRequestResult
2555    where
2556        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2557    {
2558        let scope = fasync::Scope::new();
2559        let result = {
2560            let (mut route_sink, route_client, async_work_drain_task) =
2561                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2562                    crate::client::testutil::CLIENT_ID_1,
2563                    [ModernGroup(match A::Version::VERSION {
2564                        IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
2565                        IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
2566                    })],
2567                );
2568            let _join_handle = scope.spawn(async_work_drain_task);
2569            let (mut other_sink, other_client, async_work_drain_task) =
2570                crate::client::testutil::new_fake_client::<NetlinkRoute>(
2571                    crate::client::testutil::CLIENT_ID_2,
2572                    [ModernGroup(rtnetlink_groups_RTNLGRP_LINK)],
2573                );
2574            let _join_handle = scope.spawn(async_work_drain_task);
2575            let Setup {
2576                event_loop_inputs,
2577                mut watcher_stream,
2578                route_sets: mut route_set_stream,
2579                interfaces_request_stream,
2580                request_sink,
2581                async_work_sink: _,
2582            } = setup_with_route_clients::<A::Version>({
2583                let route_clients = ClientTable::default();
2584                route_clients.add_client(route_client.clone());
2585                route_clients.add_client(other_client);
2586                route_clients
2587            });
2588
2589            let mut event_loop_fut = pin!(run_event_loop::<A::Version>(event_loop_inputs).fuse());
2590
2591            let watcher_stream_fut = respond_to_watcher::<A::Version, _>(
2592                watcher_stream.by_ref(),
2593                std::iter::once(fnet_routes_ext::Event::<A::Version>::Idle.try_into().unwrap()),
2594            );
2595            futures::select! {
2596                () = watcher_stream_fut.fuse() => {},
2597                err = event_loop_fut => unreachable!("eventloop should not return: {err:?}"),
2598            }
2599            assert_eq!(&route_sink.take_messages()[..], &[]);
2600            assert_eq!(&other_sink.take_messages()[..], &[]);
2601
2602            let route_client = &route_client;
2603            let fut = async {
2604                // Add some initial route state by sending through PendingRequests.
2605                let initial_new_routes =
2606                    get_test_route_events_new_route_args(subnet, next_hop1, next_hop2);
2607                let count_initial_new_routes = initial_new_routes.len();
2608
2609                let request_sink = futures::stream::iter(initial_new_routes)
2610                    .fold(request_sink, |mut request_sink, args| async move {
2611                        let (completer, waiter) = oneshot::channel();
2612                        request_sink
2613                            .send(
2614                                Request {
2615                                    args,
2616                                    sequence_number: TEST_SEQUENCE_NUMBER,
2617                                    client: route_client.clone(),
2618                                    completer,
2619                                }
2620                                .into(),
2621                            )
2622                            .await
2623                            .unwrap();
2624                        assert_matches!(waiter.await.unwrap(), Ok(()));
2625                        request_sink
2626                    })
2627                    .await;
2628
2629                // Ensure these messages to load the initial route state are
2630                // received prior to handling the next requests. The messages for
2631                // these requests are not needed by the callers, so drop them.
2632                for _ in 0..count_initial_new_routes {
2633                    let _ = route_sink.next_message().await;
2634                }
2635                assert_eq!(route_sink.next_message().now_or_never(), None);
2636
2637                let (results, _request_sink) = futures::stream::iter(args)
2638                    .fold(
2639                        (Vec::new(), request_sink),
2640                        |(mut results, mut request_sink), args| async move {
2641                            let (completer, waiter) = oneshot::channel();
2642                            request_sink
2643                                .send(
2644                                    Request {
2645                                        args,
2646                                        sequence_number: TEST_SEQUENCE_NUMBER,
2647                                        client: route_client.clone(),
2648                                        completer,
2649                                    }
2650                                    .into(),
2651                                )
2652                                .await
2653                                .unwrap();
2654                            results.push(waiter.await.unwrap());
2655                            (results, request_sink)
2656                        },
2657                    )
2658                    .await;
2659
2660                let messages = {
2661                    assert_eq!(&other_sink.take_messages()[..], &[]);
2662                    let mut messages = Vec::new();
2663                    while messages.len() < num_sink_messages {
2664                        messages.push(route_sink.next_message().await);
2665                    }
2666                    assert_eq!(route_sink.next_message().now_or_never(), None);
2667                    messages
2668                };
2669
2670                (messages, results)
2671            };
2672
2673            let route_set_fut = respond_to_route_set_modifications::<A::Version, _, _>(
2674                route_set_stream.by_ref(),
2675                watcher_stream.by_ref(),
2676                route_set_results,
2677            )
2678            .fuse();
2679
2680            let root_interfaces_fut = root_handler(interfaces_request_stream).fuse();
2681
2682            let (messages, results) = futures::select! {
2683                (messages, results) = fut.fuse() => (messages, results),
2684                res = futures::future::join3(
2685                        route_set_fut,
2686                        root_interfaces_fut,
2687                        event_loop_fut,
2688                    ) => {
2689                    unreachable!("eventloop/stream handlers should not return: {res:?}")
2690                }
2691            };
2692
2693            TestRequestResult { messages, waiter_results: results }
2694        };
2695        scope.join().await;
2696        result
2697    }
2698
2699    #[test_case(V4_SUB1, V4_NEXTHOP1, V4_NEXTHOP2; "v4_route_dump")]
2700    #[test_case(V6_SUB1, V6_NEXTHOP1, V6_NEXTHOP2; "v6_route_dump")]
2701    #[fuchsia::test]
2702    async fn test_get_route<A: IpAddress>(subnet: Subnet<A>, next_hop1: A, next_hop2: A)
2703    where
2704        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2705    {
2706        let expected_messages = vec![
2707            SentMessage::unicast(
2708                create_netlink_route_message::<A::Version>(
2709                    subnet.prefix(),
2710                    MANAGED_ROUTE_TABLE_INDEX,
2711                    create_nlas::<A::Version>(
2712                        Some(subnet),
2713                        Some(next_hop1),
2714                        DEV1,
2715                        METRIC1,
2716                        Some(MANAGED_ROUTE_TABLE_ID),
2717                    ),
2718                )
2719                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2720            ),
2721            SentMessage::unicast(
2722                create_netlink_route_message::<A::Version>(
2723                    subnet.prefix(),
2724                    MANAGED_ROUTE_TABLE_INDEX,
2725                    create_nlas::<A::Version>(
2726                        Some(subnet),
2727                        Some(next_hop2),
2728                        DEV2,
2729                        METRIC2,
2730                        Some(MANAGED_ROUTE_TABLE_ID),
2731                    ),
2732                )
2733                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
2734            ),
2735        ];
2736
2737        pretty_assertions::assert_eq!(
2738            {
2739                let mut test_request_result = test_requests(
2740                    [RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump))],
2741                    |interfaces_request_stream| async {
2742                        interfaces_request_stream
2743                            .for_each(|req| async move {
2744                                panic!("unexpected InterfacesRequest: {req:?}")
2745                            })
2746                            .await;
2747                    },
2748                    HashMap::new(),
2749                    subnet,
2750                    next_hop1,
2751                    next_hop2,
2752                    expected_messages.len(),
2753                )
2754                .await;
2755                test_request_result.messages.sort_by_key(|message| {
2756                    assert_matches!(
2757                        &message.message.payload,
2758                        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m)) => {
2759                            // We expect there to be exactly one Oif NLA present
2760                            // for the given inputs.
2761                            m.attributes.clone().into_iter().filter_map(|nla|
2762                                match nla {
2763                                    RouteAttribute::Oif(interface_id) =>
2764                                        Some((m.header.address_family, interface_id)),
2765                                    RouteAttribute::Destination(_)
2766                                    | RouteAttribute::Gateway(_)
2767                                    | RouteAttribute::Priority(_)
2768                                    | RouteAttribute::Table(_) => None,
2769                                    _ => panic!("unexpected NLA {nla:?} present in payload"),
2770                                }
2771                            ).next()
2772                        }
2773                    )
2774                });
2775                test_request_result
2776            },
2777            TestRequestResult { messages: expected_messages, waiter_results: vec![Ok(())] },
2778        )
2779    }
2780
2781    #[derive(Debug, Clone, Copy)]
2782    enum RouteSetResult {
2783        AddResult(Result<bool, fnet_routes_admin::RouteSetError>),
2784        DelResult(Result<bool, fnet_routes_admin::RouteSetError>),
2785        AuthenticationResult(Result<(), fnet_routes_admin::AuthenticateForInterfaceError>),
2786    }
2787
2788    fn route_event_from_route<
2789        I: Ip + fnet_routes_ext::FidlRouteIpExt,
2790        F: FnOnce(fnet_routes_ext::InstalledRoute<I>) -> fnet_routes_ext::Event<I>,
2791    >(
2792        route: I::Route,
2793        table_id: fnet_routes_ext::TableId,
2794        event_fn: F,
2795    ) -> I::WatchEvent {
2796        let route: fnet_routes_ext::Route<I> = route.try_into().unwrap();
2797
2798        let metric = match route.properties.specified_properties.metric {
2799            fnet_routes::SpecifiedMetric::ExplicitMetric(metric) => metric,
2800            fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty) => {
2801                panic!("metric should be explicit")
2802            }
2803        };
2804
2805        event_fn(fnet_routes_ext::InstalledRoute {
2806            route,
2807            effective_properties: fnet_routes_ext::EffectiveRouteProperties { metric },
2808            // TODO(https://fxbug.dev/336382905): The tests should use the ID.
2809            table_id,
2810        })
2811        .try_into()
2812        .unwrap()
2813    }
2814
2815    // Handle RouteSet API requests then feed the returned
2816    // `fuchsia.net.routes.ext/Event`s to the routes watcher.
2817    async fn respond_to_route_set_modifications<
2818        I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
2819        RS: Stream<
2820            Item = (
2821                fnet_routes_ext::TableId,
2822                <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2823            ),
2824        >,
2825        WS: Stream<Item = <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item>
2826            + std::marker::Unpin,
2827    >(
2828        route_stream: RS,
2829        watcher_stream: WS,
2830        mut route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
2831    ) {
2832        #[derive(GenericOverIp)]
2833        #[generic_over_ip(I, Ip)]
2834        struct RouteSetInputs<I: fnet_routes_ext::admin::FidlRouteAdminIpExt> {
2835            request: <<I::RouteSetMarker as ProtocolMarker>::RequestStream as Stream>::Item,
2836            route_set_result: RouteSetResult,
2837        }
2838        #[derive(GenericOverIp)]
2839        #[generic_over_ip(I, Ip)]
2840        struct RouteSetOutputs<I: fnet_routes_ext::FidlRouteIpExt> {
2841            event: Option<I::WatchEvent>,
2842        }
2843
2844        let mut route_stream = std::pin::pin!(route_stream);
2845        let mut watcher_stream = std::pin::pin!(watcher_stream);
2846
2847        {
2848            let queue = route_set_results.entry(OTHER_FIDL_TABLE_ID).or_default();
2849            queue.push_front(RouteSetResult::AddResult(Ok(true)));
2850            queue.push_front(RouteSetResult::AddResult(Ok(true)));
2851        }
2852
2853        while let Some((table_id, request)) = route_stream.next().await {
2854            let route_set_result = route_set_results
2855                .get_mut(&table_id)
2856                .unwrap_or_else(|| panic!("missing result for {table_id:?}"))
2857                .pop_front()
2858                .unwrap_or_else(|| panic!("missing result for {table_id:?}"));
2859            let RouteSetOutputs { event } = I::map_ip(
2860                RouteSetInputs { request, route_set_result },
2861                |RouteSetInputs { request, route_set_result }| {
2862                    let request = request.expect("failed to receive request");
2863                    crate::logging::log_debug!(
2864                        "responding on {table_id:?} to route set request {request:?} \
2865                        with result {route_set_result:?}"
2866                    );
2867                    match request {
2868                        fnet_routes_admin::RouteSetV4Request::AddRoute { route, responder } => {
2869                            let route_set_result = assert_matches!(
2870                                route_set_result,
2871                                RouteSetResult::AddResult(res) => res
2872                            );
2873
2874                            responder
2875                                .send(route_set_result)
2876                                .expect("failed to respond to `AddRoute`");
2877
2878                            RouteSetOutputs {
2879                                event: match route_set_result {
2880                                    Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2881                                        route,
2882                                        table_id,
2883                                        fnet_routes_ext::Event::<Ipv4>::Added,
2884                                    )),
2885                                    _ => None,
2886                                },
2887                            }
2888                        }
2889                        fnet_routes_admin::RouteSetV4Request::RemoveRoute { route, responder } => {
2890                            let route_set_result = assert_matches!(
2891                                route_set_result,
2892                                RouteSetResult::DelResult(res) => res
2893                            );
2894
2895                            responder
2896                                .send(route_set_result)
2897                                .expect("failed to respond to `RemoveRoute`");
2898
2899                            RouteSetOutputs {
2900                                event: match route_set_result {
2901                                    Ok(true) => Some(route_event_from_route::<Ipv4, _>(
2902                                        route,
2903                                        table_id,
2904                                        fnet_routes_ext::Event::<Ipv4>::Removed,
2905                                    )),
2906                                    _ => None,
2907                                },
2908                            }
2909                        }
2910                        fnet_routes_admin::RouteSetV4Request::AuthenticateForInterface {
2911                            credential: _,
2912                            responder,
2913                        } => {
2914                            let route_set_result = assert_matches!(
2915                                route_set_result,
2916                                RouteSetResult::AuthenticationResult(res) => res
2917                            );
2918
2919                            responder
2920                                .send(route_set_result)
2921                                .expect("failed to respond to `AuthenticateForInterface`");
2922                            RouteSetOutputs { event: None }
2923                        }
2924                    }
2925                },
2926                |RouteSetInputs { request, route_set_result }| {
2927                    let request = request.expect("failed to receive request");
2928                    crate::logging::log_debug!(
2929                        "responding on {table_id:?} to route set request {request:?} \
2930                        with result {route_set_result:?}"
2931                    );
2932                    match request {
2933                        fnet_routes_admin::RouteSetV6Request::AddRoute { route, responder } => {
2934                            let route_set_result = assert_matches!(
2935                                route_set_result,
2936                                RouteSetResult::AddResult(res) => res
2937                            );
2938
2939                            responder
2940                                .send(route_set_result)
2941                                .expect("failed to respond to `AddRoute`");
2942
2943                            RouteSetOutputs {
2944                                event: match route_set_result {
2945                                    Ok(true) => Some(route_event_from_route::<Ipv6, _>(
2946                                        route,
2947                                        table_id,
2948                                        fnet_routes_ext::Event::<Ipv6>::Added,
2949                                    )),
2950                                    _ => None,
2951                                },
2952                            }
2953                        }
2954                        fnet_routes_admin::RouteSetV6Request::RemoveRoute { route, responder } => {
2955                            let route_set_result = assert_matches!(
2956                                route_set_result,
2957                                RouteSetResult::DelResult(res) => res
2958                            );
2959
2960                            responder
2961                                .send(route_set_result)
2962                                .expect("failed to respond to `RemoveRoute`");
2963
2964                            RouteSetOutputs {
2965                                event: match route_set_result {
2966                                    Ok(true) => Some(route_event_from_route::<Ipv6, _>(
2967                                        route,
2968                                        table_id,
2969                                        fnet_routes_ext::Event::<Ipv6>::Removed,
2970                                    )),
2971                                    _ => None,
2972                                },
2973                            }
2974                        }
2975                        fnet_routes_admin::RouteSetV6Request::AuthenticateForInterface {
2976                            credential: _,
2977                            responder,
2978                        } => {
2979                            let route_set_result = assert_matches!(
2980                                route_set_result,
2981                                RouteSetResult::AuthenticationResult(res) => res
2982                            );
2983
2984                            responder
2985                                .send(route_set_result)
2986                                .expect("failed to respond to `AuthenticateForInterface`");
2987                            RouteSetOutputs { event: None }
2988                        }
2989                    }
2990                },
2991            );
2992
2993            if let Some(update) = event {
2994                let request = watcher_stream.next().await.expect("watcher stream should not end");
2995
2996                #[derive(GenericOverIp)]
2997                #[generic_over_ip(I, Ip)]
2998                struct HandleInputs<I: fnet_routes_ext::FidlRouteIpExt> {
2999                    request: <<I::WatcherMarker as ProtocolMarker>::RequestStream as Stream>::Item,
3000                    update: I::WatchEvent,
3001                }
3002
3003                I::map_ip_in(
3004                    HandleInputs { request, update },
3005                    |HandleInputs { request, update }| match request
3006                        .expect("failed to receive `Watch` request")
3007                    {
3008                        fnet_routes::WatcherV4Request::Watch { responder } => {
3009                            responder.send(&[update]).expect("failed to respond to `Watch`")
3010                        }
3011                    },
3012                    |HandleInputs { request, update }| match request
3013                        .expect("failed to receive `Watch` request")
3014                    {
3015                        fnet_routes::WatcherV6Request::Watch { responder } => {
3016                            responder.send(&[update]).expect("failed to respond to `Watch`")
3017                        }
3018                    },
3019                );
3020            }
3021        }
3022
3023        if route_set_results.values().any(|value| !value.is_empty()) {
3024            panic!("unused route_set_results entries: {route_set_results:?}");
3025        }
3026    }
3027
3028    /// A test helper to exercise multiple route requests.
3029    ///
3030    /// A test helper that calls the provided callback with a
3031    /// [`fnet_interfaces_admin::ControlRequest`] as they arrive.
3032    async fn test_route_requests<
3033        A: IpAddress,
3034        Fut: Future<Output = ()>,
3035        F: FnMut(fnet_interfaces_admin::ControlRequest) -> Fut,
3036    >(
3037        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3038        mut control_request_handler: F,
3039        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3040        subnet: Subnet<A>,
3041        next_hop1: A,
3042        next_hop2: A,
3043        num_sink_messages: usize,
3044    ) -> TestRequestResult
3045    where
3046        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3047    {
3048        test_requests(
3049            args,
3050            |interfaces_request_stream| async move {
3051                interfaces_request_stream
3052                    .filter_map(|req| {
3053                        futures::future::ready(match req.unwrap() {
3054                            fnet_root::InterfacesRequest::GetAdmin {
3055                                id,
3056                                control,
3057                                control_handle: _,
3058                            } => {
3059                                pretty_assertions::assert_eq!(id, DEV1 as u64);
3060                                Some(control.into_stream())
3061                            }
3062                            req => unreachable!("unexpected interfaces request: {req:?}"),
3063                        })
3064                    })
3065                    .flatten()
3066                    .next()
3067                    .then(|req| control_request_handler(req.unwrap().unwrap()))
3068                    .await
3069            },
3070            route_set_results,
3071            subnet,
3072            next_hop1,
3073            next_hop2,
3074            num_sink_messages,
3075        )
3076        .await
3077    }
3078
3079    // A test helper that calls `test_route_requests()` with the provided
3080    // inputs and expected values.
3081    async fn test_route_requests_helper<A: IpAddress>(
3082        args: impl IntoIterator<Item = RequestArgs<A::Version>>,
3083        expected_messages: Vec<SentMessage<RouteNetlinkMessage>>,
3084        route_set_results: HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>>,
3085        waiter_results: Vec<Result<(), RequestError>>,
3086        subnet: Subnet<A>,
3087    ) where
3088        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3089    {
3090        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3091            (),
3092            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3093            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3094        );
3095
3096        pretty_assertions::assert_eq!(
3097            {
3098                let mut test_request_result = test_route_requests(
3099                    args,
3100                    |req| async {
3101                        match req {
3102                            fnet_interfaces_admin::ControlRequest::GetAuthorizationForInterface {
3103                                responder,
3104                            } => {
3105                                let token = fidl::Event::create();
3106                                let grant = fnet_resources::GrantForInterfaceAuthorization {
3107                                    interface_id: DEV1 as u64,
3108                                    token,
3109                                };
3110                                responder.send(grant).unwrap();
3111                            }
3112                            req => panic!("unexpected request {req:?}"),
3113                        }
3114                    },
3115                    route_set_results,
3116                    subnet,
3117                    next_hop1,
3118                    next_hop2,
3119                    expected_messages.len(),
3120                )
3121                .await;
3122                test_request_result.messages.sort_by_key(|message| {
3123                    // The sequence number sorts multicast messages prior to
3124                    // unicast messages.
3125                    let sequence_number = message.message.header.sequence_number;
3126                    assert_matches!(
3127                        &message.message.payload,
3128                        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(m))
3129                        | NetlinkPayload::InnerMessage(RouteNetlinkMessage::DelRoute(m)) => {
3130                            // We expect there to be exactly one Priority NLA present
3131                            // for the given inputs.
3132                            m.attributes.clone().into_iter().filter_map(|nla|
3133                                match nla {
3134                                    RouteAttribute::Priority(priority) =>
3135                                        Some((sequence_number, priority)),
3136                                    RouteAttribute::Destination(_)
3137                                    | RouteAttribute::Gateway(_)
3138                                    | RouteAttribute::Oif(_)
3139                                    | RouteAttribute::Table(_) => None,
3140                                    _ => panic!("unexpected NLA {nla:?} present in payload"),
3141                                }
3142                            ).next()
3143                        }
3144                    )
3145                });
3146                test_request_result
3147            },
3148            TestRequestResult { messages: expected_messages, waiter_results },
3149        )
3150    }
3151
3152    enum RouteRequestKind {
3153        New,
3154        Del,
3155    }
3156
3157    fn route_set_for_table_id(
3158        results: Vec<RouteSetResult>,
3159        table_id: fnet_routes_ext::TableId,
3160    ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3161        HashMap::from_iter([(table_id, results.into())])
3162    }
3163
3164    fn route_set_for_first_new_table(
3165        results: Vec<RouteSetResult>,
3166    ) -> HashMap<fnet_routes_ext::TableId, VecDeque<RouteSetResult>> {
3167        route_set_for_table_id(results, OTHER_FIDL_TABLE_ID)
3168    }
3169
3170    // Tests RTM_NEWROUTE with all interesting responses to add a route.
3171    #[test_case(
3172        RouteRequestKind::New,
3173        vec![
3174            RouteSetResult::AddResult(Ok(true))
3175        ],
3176        Ok(()),
3177        V4_SUB1,
3178        Some(METRIC3),
3179        DEV1;
3180        "v4_new_success")]
3181    #[test_case(
3182        RouteRequestKind::New,
3183        vec![
3184            RouteSetResult::AddResult(Ok(true))
3185        ],
3186        Ok(()),
3187        V6_SUB1,
3188        Some(METRIC3),
3189        DEV1;
3190        "v6_new_success")]
3191    #[test_case(
3192        RouteRequestKind::New,
3193        vec![
3194            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3195            RouteSetResult::AuthenticationResult(Err(
3196                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3197            )),
3198        ],
3199        Err(RequestError::UnrecognizedInterface),
3200        V4_SUB1,
3201        Some(METRIC3),
3202        DEV1;
3203        "v4_new_failed_auth")]
3204    #[test_case(
3205        RouteRequestKind::New,
3206        vec![
3207            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3208            RouteSetResult::AuthenticationResult(Err(
3209                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3210            )),
3211        ],
3212        Err(RequestError::UnrecognizedInterface),
3213        V6_SUB1,
3214        Some(METRIC3),
3215        DEV1;
3216        "v6_new_failed_auth")]
3217    #[test_case(
3218        RouteRequestKind::New,
3219        vec![
3220            RouteSetResult::AddResult(Ok(false))
3221        ],
3222        Err(RequestError::AlreadyExists),
3223        V4_SUB1,
3224        Some(METRIC3),
3225        DEV1;
3226        "v4_new_failed_netstack_reports_exists")]
3227    #[test_case(
3228        RouteRequestKind::New,
3229        vec![
3230            RouteSetResult::AddResult(Ok(false))
3231        ],
3232        Err(RequestError::AlreadyExists),
3233        V6_SUB1,
3234        Some(METRIC3),
3235        DEV1;
3236        "v6_new_failed_netstack_reports_exists")]
3237    #[test_case(
3238        RouteRequestKind::New,
3239        vec![],
3240        Err(RequestError::AlreadyExists),
3241        V4_SUB1,
3242        Some(METRIC1),
3243        DEV1;
3244        "v4_new_failed_netlink_reports_exists")]
3245    #[test_case(
3246        RouteRequestKind::New,
3247        vec![],
3248        Err(RequestError::AlreadyExists),
3249        V4_SUB1,
3250        Some(METRIC1),
3251        DEV2;
3252        "v4_new_failed_netlink_reports_exists_different_interface")]
3253    #[test_case(
3254        RouteRequestKind::New,
3255        vec![],
3256        Err(RequestError::AlreadyExists),
3257        V6_SUB1,
3258        Some(METRIC1),
3259        DEV1;
3260        "v6_new_failed_netlink_reports_exists")]
3261    #[test_case(
3262        RouteRequestKind::New,
3263        vec![],
3264        Err(RequestError::AlreadyExists),
3265        V6_SUB1,
3266        Some(METRIC1),
3267        DEV2;
3268        "v6_new_failed_netlink_reports_exists_different_interface")]
3269    #[test_case(
3270        RouteRequestKind::New,
3271        vec![
3272            RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3273        ],
3274        Err(RequestError::InvalidRequest),
3275        V4_SUB1,
3276        Some(METRIC3),
3277        DEV1;
3278        "v4_new_invalid_dest")]
3279    #[test_case(
3280        RouteRequestKind::New,
3281        vec![
3282            RouteSetResult::AddResult(Err(RouteSetError::InvalidDestinationSubnet))
3283        ],
3284        Err(RequestError::InvalidRequest),
3285        V6_SUB1,
3286        Some(METRIC3),
3287        DEV1;
3288        "v6_new_invalid_dest")]
3289    #[test_case(
3290        RouteRequestKind::New,
3291        vec![
3292            RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3293        ],
3294        Err(RequestError::InvalidRequest),
3295        V4_SUB1,
3296        Some(METRIC3),
3297        DEV1;
3298        "v4_new_invalid_hop")]
3299    #[test_case(
3300        RouteRequestKind::New,
3301        vec![
3302            RouteSetResult::AddResult(Err(RouteSetError::InvalidNextHop))
3303        ],
3304        Err(RequestError::InvalidRequest),
3305        V6_SUB1,
3306        Some(METRIC3),
3307        DEV1;
3308        "v6_new_invalid_hop")]
3309    // Tests RTM_DELROUTE with all interesting responses to remove a route.
3310    #[test_case(
3311        RouteRequestKind::Del,
3312        vec![
3313            RouteSetResult::DelResult(Ok(true))
3314        ],
3315        Ok(()),
3316        V4_SUB1,
3317        None,
3318        DEV1;
3319        "v4_del_success_only_subnet")]
3320    #[test_case(
3321        RouteRequestKind::Del,
3322        vec![
3323            RouteSetResult::DelResult(Ok(true))
3324        ],
3325        Ok(()),
3326        V4_SUB1,
3327        Some(METRIC1),
3328        DEV1;
3329        "v4_del_success_only_subnet_metric")]
3330    #[test_case(
3331        RouteRequestKind::Del,
3332        vec![
3333            RouteSetResult::DelResult(Ok(true))
3334        ],
3335        Ok(()),
3336        V6_SUB1,
3337        None,
3338        DEV1;
3339        "v6_del_success_only_subnet")]
3340    #[test_case(
3341        RouteRequestKind::Del,
3342        vec![
3343            RouteSetResult::DelResult(Ok(true))
3344        ],
3345        Ok(()),
3346        V6_SUB1,
3347        Some(METRIC1),
3348        DEV1;
3349        "v6_del_success_only_subnet_metric")]
3350    #[test_case(
3351        RouteRequestKind::Del,
3352        vec![
3353            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3354            RouteSetResult::AuthenticationResult(Err(
3355                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3356            )),
3357        ],
3358        Err(RequestError::UnrecognizedInterface),
3359        V4_SUB1,
3360        None,
3361        DEV1;
3362        "v4_del_failed_auth")]
3363    #[test_case(
3364        RouteRequestKind::Del,
3365        vec![
3366            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3367            RouteSetResult::AuthenticationResult(Err(
3368                fnet_routes_admin::AuthenticateForInterfaceError::InvalidAuthentication
3369            )),
3370        ],
3371        Err(RequestError::UnrecognizedInterface),
3372        V6_SUB1,
3373        None,
3374        DEV1;
3375        "v6_del_failed_auth")]
3376    #[test_case(
3377        RouteRequestKind::Del,
3378        vec![
3379            RouteSetResult::DelResult(Ok(false))
3380        ],
3381        Err(RequestError::DeletionNotAllowed),
3382        V4_SUB1,
3383        None,
3384        DEV1;
3385        "v4_del_failed_attempt_to_delete_route_from_global_set")]
3386    #[test_case(
3387        RouteRequestKind::Del,
3388        vec![
3389            RouteSetResult::DelResult(Ok(false))
3390        ],
3391        Err(RequestError::DeletionNotAllowed),
3392        V6_SUB1,
3393        None,
3394        DEV1;
3395        "v6_del_failed_attempt_to_delete_route_from_global_set")]
3396    // This deliberately only includes one case where a route is
3397    // not selected for deletion, `test_select_route_for_deletion`
3398    // covers these cases.
3399    // No route with `METRIC3` exists, so this extra selector causes the
3400    // `NotFound` result.
3401    #[test_case(
3402        RouteRequestKind::Del,
3403        vec![],
3404        Err(RequestError::NotFound),
3405        V4_SUB1,
3406        Some(METRIC3),
3407        DEV1;
3408        "v4_del_no_matching_route")]
3409    #[test_case(
3410        RouteRequestKind::Del,
3411        vec![],
3412        Err(RequestError::NotFound),
3413        V6_SUB1,
3414        Some(METRIC3),
3415        DEV1;
3416        "v6_del_no_matching_route")]
3417    #[test_case(
3418        RouteRequestKind::Del,
3419        vec![
3420            RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3421        ],
3422        Err(RequestError::InvalidRequest),
3423        V4_SUB1,
3424        None,
3425        DEV1;
3426        "v4_del_invalid_dest")]
3427    #[test_case(
3428        RouteRequestKind::Del,
3429        vec![
3430            RouteSetResult::DelResult(Err(RouteSetError::InvalidDestinationSubnet))
3431        ],
3432        Err(RequestError::InvalidRequest),
3433        V6_SUB1,
3434        None,
3435        DEV1;
3436        "v6_del_invalid_dest")]
3437    #[test_case(
3438        RouteRequestKind::Del,
3439        vec![
3440            RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3441        ],
3442        Err(RequestError::InvalidRequest),
3443        V4_SUB1,
3444        None,
3445        DEV1;
3446        "v4_del_invalid_hop")]
3447    #[test_case(
3448        RouteRequestKind::Del,
3449        vec![
3450            RouteSetResult::DelResult(Err(RouteSetError::InvalidNextHop))
3451        ],
3452        Err(RequestError::InvalidRequest),
3453        V6_SUB1,
3454        None,
3455        DEV1;
3456        "v6_del_invalid_hop")]
3457    #[fuchsia::test]
3458    async fn test_new_del_route<A: IpAddress>(
3459        kind: RouteRequestKind,
3460        route_set_results: Vec<RouteSetResult>,
3461        waiter_result: Result<(), RequestError>,
3462        subnet: Subnet<A>,
3463        metric: Option<u32>,
3464        interface_id: u32,
3465    ) where
3466        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3467    {
3468        let route_group = match A::Version::VERSION {
3469            IpVersion::V4 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3470            IpVersion::V6 => ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3471        };
3472
3473        let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3474
3475        // There are two pre-set routes in `test_route_requests`.
3476        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3477        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3478        let route_req_args = match kind {
3479            RouteRequestKind::New => {
3480                // Add a route that is not already present.
3481                RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3482                    subnet,
3483                    next_hop,
3484                    interface_id.into(),
3485                    metric.expect("add cases should be Some"),
3486                    MANAGED_ROUTE_TABLE_INDEX,
3487                )))
3488            }
3489            RouteRequestKind::Del => {
3490                // Remove an existing route.
3491                RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3492                    subnet,
3493                    None,
3494                    None,
3495                    metric,
3496                    MANAGED_ROUTE_TABLE_INDEX,
3497                )))
3498            }
3499        };
3500
3501        // When the waiter result is Ok(()), then we know that the add or delete
3502        // was successful and we got a message.
3503        let messages = match waiter_result {
3504            Ok(()) => {
3505                let build_message = |table| {
3506                    let route_message = create_netlink_route_message::<A::Version>(
3507                        subnet.prefix(),
3508                        table,
3509                        create_nlas::<A::Version>(
3510                            Some(subnet),
3511                            Some(next_hop),
3512                            DEV1,
3513                            match kind {
3514                                RouteRequestKind::New => metric.expect("add cases should be some"),
3515                                // When a route is found for deletion, we expect that route to have
3516                                // a metric value of `METRIC1`. Even though there are two different
3517                                // routes with `subnet`, deletion prefers to select the route with
3518                                // the lowest metric.
3519                                RouteRequestKind::Del => METRIC1,
3520                            },
3521                            (table != MAIN_ROUTE_TABLE_INDEX).then_some(table.get()),
3522                        ),
3523                    );
3524                    let netlink_message = match kind {
3525                        RouteRequestKind::New => {
3526                            route_message.into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false)
3527                        }
3528                        RouteRequestKind::Del => route_message.into_rtnl_del_route(),
3529                    };
3530                    SentMessage::multicast(netlink_message, route_group)
3531                };
3532
3533                let route_message_in_managed_table = build_message(MANAGED_ROUTE_TABLE_INDEX);
3534
3535                vec![route_message_in_managed_table]
3536            }
3537            Err(_) => Vec::new(),
3538        };
3539
3540        test_route_requests_helper(
3541            [RequestArgs::Route(route_req_args)],
3542            messages,
3543            route_set_for_first_new_table(route_set_results),
3544            vec![waiter_result],
3545            subnet,
3546        )
3547        .await;
3548    }
3549
3550    // Tests RTM_NEWROUTE and RTM_DELROUTE when two unauthentication events are received - once
3551    // prior to making an attempt to authenticate and once after attempting to authenticate.
3552    #[test_case(
3553        RouteRequestKind::New,
3554        vec![
3555            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3556            RouteSetResult::AuthenticationResult(Ok(())),
3557            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3558        ],
3559        Err(RequestError::InvalidRequest),
3560        V4_SUB1;
3561        "v4_new_unauthenticated")]
3562    #[test_case(
3563        RouteRequestKind::New,
3564        vec![
3565            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3566            RouteSetResult::AuthenticationResult(Ok(())),
3567            RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated)),
3568        ],
3569        Err(RequestError::InvalidRequest),
3570        V6_SUB1;
3571        "v6_new_unauthenticated")]
3572    #[test_case(
3573        RouteRequestKind::Del,
3574        vec![
3575            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3576            RouteSetResult::AuthenticationResult(Ok(())),
3577            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3578        ],
3579        Err(RequestError::InvalidRequest),
3580        V4_SUB1;
3581        "v4_del_unauthenticated")]
3582    #[test_case(
3583        RouteRequestKind::Del,
3584        vec![
3585            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3586            RouteSetResult::AuthenticationResult(Ok(())),
3587            RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated)),
3588        ],
3589        Err(RequestError::InvalidRequest),
3590        V6_SUB1;
3591        "v6_del_unauthenticated")]
3592    #[should_panic(expected = "received unauthentication error from route set for route")]
3593    #[fuchsia::test]
3594    async fn test_new_del_route_failed<A: IpAddress>(
3595        kind: RouteRequestKind,
3596        route_set_results: Vec<RouteSetResult>,
3597        waiter_result: Result<(), RequestError>,
3598        subnet: Subnet<A>,
3599    ) where
3600        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3601    {
3602        let route_req_args = match kind {
3603            RouteRequestKind::New => {
3604                let next_hop: A = A::Version::map_ip((), |()| V4_NEXTHOP1, |()| V6_NEXTHOP1);
3605                // Add a route that is not already present.
3606                RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
3607                    subnet,
3608                    next_hop,
3609                    DEV1.into(),
3610                    METRIC3,
3611                    MANAGED_ROUTE_TABLE_INDEX,
3612                )))
3613            }
3614            RouteRequestKind::Del => {
3615                // Remove an existing route.
3616                RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3617                    subnet,
3618                    None,
3619                    None,
3620                    None,
3621                    MANAGED_ROUTE_TABLE_INDEX,
3622                )))
3623            }
3624        };
3625        test_route_requests_helper(
3626            [RequestArgs::Route(route_req_args)],
3627            Vec::new(),
3628            route_set_for_first_new_table(route_set_results),
3629            vec![waiter_result],
3630            subnet,
3631        )
3632        .await;
3633    }
3634
3635    #[test_case(
3636        Err(RequestError::NotFound),
3637        V4_SUB1; "v4_del")]
3638    #[test_case(
3639        Err(RequestError::NotFound),
3640        V6_SUB1; "v6_del")]
3641    #[fuchsia::test]
3642    async fn test_del_route_nonexistent_table<A: IpAddress>(
3643        waiter_result: Result<(), RequestError>,
3644        subnet: Subnet<A>,
3645    ) where
3646        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3647    {
3648        // Remove a route from a table that doesn't exist yet.
3649        let route_req_args =
3650            RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
3651                subnet,
3652                None,
3653                None,
3654                None,
3655                NetlinkRouteTableIndex::new(1234),
3656            )));
3657        test_route_requests_helper(
3658            [RequestArgs::Route(route_req_args)],
3659            Vec::new(),
3660            HashMap::new(),
3661            vec![waiter_result],
3662            subnet,
3663        )
3664        .await;
3665    }
3666
3667    /// A test to exercise a `RTM_NEWROUTE` followed by a `RTM_GETROUTE`
3668    /// route request, ensuring that the new route is included in the
3669    /// dump request.
3670    #[test_case(
3671        V4_SUB1,
3672        ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3673        MANAGED_ROUTE_TABLE_INDEX;
3674        "v4_new_same_table_dump")]
3675    #[test_case(
3676        V6_SUB1,
3677        ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3678        MANAGED_ROUTE_TABLE_INDEX;
3679        "v6_new_same_table_dump")]
3680    #[test_case(
3681        V4_SUB1,
3682        ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE),
3683        NetlinkRouteTableIndex::new(1234);
3684        "v4_new_different_table_dump")]
3685    #[test_case(
3686        V6_SUB1,
3687        ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE),
3688        NetlinkRouteTableIndex::new(1234);
3689        "v6_new_different_table_dump")]
3690    #[fuchsia::test]
3691    async fn test_new_then_get_dump_request<A: IpAddress>(
3692        subnet: Subnet<A>,
3693        group: ModernGroup,
3694        table: NetlinkRouteTableIndex,
3695    ) where
3696        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3697    {
3698        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3699            (),
3700            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3701            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3702        );
3703
3704        // There are two pre-set routes in `test_route_requests`.
3705        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3706        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3707        // To add a new route that does not get rejected by the handler due to it
3708        // already existing, we use a route that has METRIC3.
3709        let unicast_route_args =
3710            create_unicast_new_route_args(subnet, next_hop1, DEV1.into(), METRIC3, table);
3711
3712        // We expect to see 1 multicast message, representing the route that was added to
3713        // a managed table.
3714        // Then, three unicast messages, representing the two routes that existed already in the
3715        // route set, and the one new route that was added.
3716        let messages = vec![
3717            SentMessage::multicast(
3718                create_netlink_route_message::<A::Version>(
3719                    subnet.prefix(),
3720                    table,
3721                    create_nlas::<A::Version>(
3722                        Some(subnet),
3723                        Some(next_hop1),
3724                        DEV1,
3725                        METRIC3,
3726                        Some(table.get()),
3727                    ),
3728                )
3729                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3730                group,
3731            ),
3732            SentMessage::unicast(
3733                create_netlink_route_message::<A::Version>(
3734                    subnet.prefix(),
3735                    MANAGED_ROUTE_TABLE_INDEX,
3736                    create_nlas::<A::Version>(
3737                        Some(subnet),
3738                        Some(next_hop1),
3739                        DEV1,
3740                        METRIC1,
3741                        Some(MANAGED_ROUTE_TABLE_ID),
3742                    ),
3743                )
3744                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3745            ),
3746            SentMessage::unicast(
3747                create_netlink_route_message::<A::Version>(
3748                    subnet.prefix(),
3749                    MANAGED_ROUTE_TABLE_INDEX,
3750                    create_nlas::<A::Version>(
3751                        Some(subnet),
3752                        Some(next_hop2),
3753                        DEV2,
3754                        METRIC2,
3755                        Some(MANAGED_ROUTE_TABLE_ID),
3756                    ),
3757                )
3758                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3759            ),
3760            SentMessage::unicast(
3761                create_netlink_route_message::<A::Version>(
3762                    subnet.prefix(),
3763                    table,
3764                    create_nlas::<A::Version>(
3765                        Some(subnet),
3766                        Some(next_hop1),
3767                        DEV1,
3768                        METRIC3,
3769                        Some(table.get()),
3770                    ),
3771                )
3772                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3773            ),
3774        ];
3775
3776        test_route_requests_helper(
3777            [
3778                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
3779                    unicast_route_args,
3780                ))),
3781                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
3782            ],
3783            messages,
3784            route_set_for_table_id(
3785                vec![RouteSetResult::AddResult(Ok(true))],
3786                if table == MANAGED_ROUTE_TABLE_INDEX {
3787                    OTHER_FIDL_TABLE_ID
3788                } else {
3789                    fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1)
3790                },
3791            ),
3792            vec![Ok(()), Ok(())],
3793            subnet,
3794        )
3795        .await;
3796    }
3797
3798    /// TODO(https://fxbug.dev/336382905): Once otherwise equivalent
3799    /// routes can be inserted into different tables, update the
3800    /// assertions to recognize the route as being added successfully.
3801    ///
3802    /// A test to exercise a `RTM_NEWROUTE` with a route that already
3803    /// exists, but in a different routing table, followed by a `RTM_GETROUTE`
3804    /// route request, ensuring that the new route does not initiate a
3805    /// multicast message and is not included in the dump request.
3806    #[test_case(V4_SUB1; "v4_new_dump")]
3807    #[test_case(V6_SUB1; "v6_new_dump")]
3808    #[fuchsia::test]
3809    async fn test_new_route_different_table_then_get_dump_request<A: IpAddress>(subnet: Subnet<A>)
3810    where
3811        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3812    {
3813        let (next_hop1, next_hop2, IpInvariant(group)): (A, A, IpInvariant<ModernGroup>) =
3814            A::Version::map_ip(
3815                (),
3816                |()| {
3817                    (
3818                        V4_NEXTHOP1,
3819                        V4_NEXTHOP2,
3820                        IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE)),
3821                    )
3822                },
3823                |()| {
3824                    (
3825                        V6_NEXTHOP1,
3826                        V6_NEXTHOP2,
3827                        IpInvariant(ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE)),
3828                    )
3829                },
3830            );
3831
3832        const ALTERNATIVE_ROUTE_TABLE: NetlinkRouteTableIndex = NetlinkRouteTableIndex::new(1337);
3833
3834        // There are two pre-set routes in `test_route_requests`.
3835        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3836        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3837        // Attempt to install the same first route, but with a different table.
3838        // Table id isn't important, as long as it is different
3839        // than MANAGED_ROUTE_TABLE.
3840        let unicast_route_args = create_unicast_new_route_args(
3841            subnet,
3842            next_hop1,
3843            DEV1.into(),
3844            METRIC1,
3845            ALTERNATIVE_ROUTE_TABLE,
3846        );
3847
3848        // We expect to see one multicast message for having added the new route, then three unicast
3849        // messages, for dumping the two routes that existed already in the route set, plus the new
3850        // one we added.
3851        let messages = vec![
3852            SentMessage::multicast(
3853                create_netlink_route_message::<A::Version>(
3854                    subnet.prefix(),
3855                    ALTERNATIVE_ROUTE_TABLE,
3856                    create_nlas::<A::Version>(
3857                        Some(subnet),
3858                        Some(next_hop1),
3859                        DEV1,
3860                        METRIC1,
3861                        Some(ALTERNATIVE_ROUTE_TABLE.get()),
3862                    ),
3863                )
3864                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3865                group,
3866            ),
3867            SentMessage::unicast(
3868                create_netlink_route_message::<A::Version>(
3869                    subnet.prefix(),
3870                    MANAGED_ROUTE_TABLE_INDEX,
3871                    create_nlas::<A::Version>(
3872                        Some(subnet),
3873                        Some(next_hop1),
3874                        DEV1,
3875                        METRIC1,
3876                        Some(MANAGED_ROUTE_TABLE_ID),
3877                    ),
3878                )
3879                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3880            ),
3881            SentMessage::unicast(
3882                create_netlink_route_message::<A::Version>(
3883                    subnet.prefix(),
3884                    ALTERNATIVE_ROUTE_TABLE,
3885                    create_nlas::<A::Version>(
3886                        Some(subnet),
3887                        Some(next_hop1),
3888                        DEV1,
3889                        METRIC1,
3890                        Some(ALTERNATIVE_ROUTE_TABLE.get()),
3891                    ),
3892                )
3893                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3894            ),
3895            SentMessage::unicast(
3896                create_netlink_route_message::<A::Version>(
3897                    subnet.prefix(),
3898                    MANAGED_ROUTE_TABLE_INDEX,
3899                    create_nlas::<A::Version>(
3900                        Some(subnet),
3901                        Some(next_hop2),
3902                        DEV2,
3903                        METRIC2,
3904                        Some(MANAGED_ROUTE_TABLE_ID),
3905                    ),
3906                )
3907                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
3908            ),
3909        ];
3910
3911        test_route_requests_helper(
3912            [
3913                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(
3914                    unicast_route_args,
3915                ))),
3916                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
3917            ],
3918            messages,
3919            HashMap::from_iter([
3920                // The added route already existed in the main table.
3921                (MAIN_FIDL_TABLE_ID, vec![RouteSetResult::AddResult(Ok(false))].into()),
3922                // But it is new to the other table.
3923                (
3924                    fnet_routes_ext::TableId::new(OTHER_FIDL_TABLE_ID.get() + 1),
3925                    vec![RouteSetResult::AddResult(Ok(true))].into(),
3926                ),
3927            ]),
3928            vec![Ok(()), Ok(())],
3929            subnet,
3930        )
3931        .await;
3932    }
3933
3934    /// A test to exercise a `RTM_NEWROUTE` followed by a `RTM_DELROUTE` for the same route, then a
3935    /// `RTM_GETROUTE` request, ensuring that the route added created a multicast message, but does
3936    /// not appear in the dump.
3937    #[test_case(V4_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV4_ROUTE); "v4_new_del_dump")]
3938    #[test_case(V6_SUB1, ModernGroup(rtnetlink_groups_RTNLGRP_IPV6_ROUTE); "v6_new_del_dump")]
3939    #[fuchsia::test]
3940    async fn test_new_then_del_then_get_dump_request<A: IpAddress>(
3941        subnet: Subnet<A>,
3942        group: ModernGroup,
3943    ) where
3944        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
3945    {
3946        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
3947            (),
3948            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
3949            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
3950        );
3951
3952        // There are two pre-set routes in `test_route_requests`.
3953        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
3954        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
3955        // To add a new route that does not get rejected by the handler due to it
3956        // already existing, we use a route that has METRIC3.
3957        let new_route_args = create_unicast_new_route_args(
3958            subnet,
3959            next_hop1,
3960            DEV1.into(),
3961            METRIC3,
3962            MANAGED_ROUTE_TABLE_INDEX,
3963        );
3964
3965        // The subnet and metric are enough to uniquely identify the above route.
3966        let del_route_args = create_unicast_del_route_args(
3967            subnet,
3968            None,
3969            None,
3970            Some(METRIC3),
3971            MANAGED_ROUTE_TABLE_INDEX,
3972        );
3973
3974        // We expect to see 2 multicast messages, the first representing the route that was
3975        // added and the other representing the same route being removed. Then, two unicast
3976        // messages, representing the two routes that existed already in the route set.
3977        let messages = vec![
3978            SentMessage::multicast(
3979                create_netlink_route_message::<A::Version>(
3980                    subnet.prefix(),
3981                    MANAGED_ROUTE_TABLE_INDEX,
3982                    create_nlas::<A::Version>(
3983                        Some(subnet),
3984                        Some(next_hop1),
3985                        DEV1,
3986                        METRIC3,
3987                        Some(MANAGED_ROUTE_TABLE_ID),
3988                    ),
3989                )
3990                .into_rtnl_new_route(UNSPECIFIED_SEQUENCE_NUMBER, false),
3991                group,
3992            ),
3993            SentMessage::multicast(
3994                create_netlink_route_message::<A::Version>(
3995                    subnet.prefix(),
3996                    MANAGED_ROUTE_TABLE_INDEX,
3997                    create_nlas::<A::Version>(
3998                        Some(subnet),
3999                        Some(next_hop1),
4000                        DEV1,
4001                        METRIC3,
4002                        Some(MANAGED_ROUTE_TABLE_ID),
4003                    ),
4004                )
4005                .into_rtnl_del_route(),
4006                group,
4007            ),
4008            SentMessage::unicast(
4009                create_netlink_route_message::<A::Version>(
4010                    subnet.prefix(),
4011                    MANAGED_ROUTE_TABLE_INDEX,
4012                    create_nlas::<A::Version>(
4013                        Some(subnet),
4014                        Some(next_hop1),
4015                        DEV1,
4016                        METRIC1,
4017                        Some(MANAGED_ROUTE_TABLE_ID),
4018                    ),
4019                )
4020                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4021            ),
4022            SentMessage::unicast(
4023                create_netlink_route_message::<A::Version>(
4024                    subnet.prefix(),
4025                    MANAGED_ROUTE_TABLE_INDEX,
4026                    create_nlas::<A::Version>(
4027                        Some(subnet),
4028                        Some(next_hop2),
4029                        DEV2,
4030                        METRIC2,
4031                        Some(MANAGED_ROUTE_TABLE_ID),
4032                    ),
4033                )
4034                .into_rtnl_new_route(TEST_SEQUENCE_NUMBER, true),
4035            ),
4036        ];
4037
4038        test_route_requests_helper(
4039            [
4040                RequestArgs::Route(RouteRequestArgs::New(NewRouteArgs::Unicast(new_route_args))),
4041                RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(del_route_args))),
4042                RequestArgs::Route(RouteRequestArgs::Get(GetRouteArgs::Dump)),
4043            ],
4044            messages,
4045            route_set_for_first_new_table(vec![
4046                RouteSetResult::AddResult(Ok(true)),
4047                RouteSetResult::DelResult(Ok(true)),
4048            ]),
4049            vec![Ok(()), Ok(()), Ok(())],
4050            subnet,
4051        )
4052        .await;
4053    }
4054
4055    /// Tests RTM_NEWROUTE and RTM_DELROUTE when the interface is removed,
4056    /// indicated by the closure of the admin Control's server-end.
4057    /// The specific cause of the interface removal is unimportant
4058    /// for this test.
4059    #[test_case(RouteRequestKind::New, V4_SUB1; "v4_new_if_removed")]
4060    #[test_case(RouteRequestKind::New, V6_SUB1; "v6_new_if_removed")]
4061    #[test_case(RouteRequestKind::Del, V4_SUB1; "v4_del_if_removed")]
4062    #[test_case(RouteRequestKind::Del, V6_SUB1; "v6_del_if_removed")]
4063    #[fuchsia::test]
4064    async fn test_new_del_route_interface_removed<A: IpAddress>(
4065        kind: RouteRequestKind,
4066        subnet: Subnet<A>,
4067    ) where
4068        A::Version: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
4069    {
4070        let (next_hop1, next_hop2): (A, A) = A::Version::map_ip(
4071            (),
4072            |()| (V4_NEXTHOP1, V4_NEXTHOP2),
4073            |()| (V6_NEXTHOP1, V6_NEXTHOP2),
4074        );
4075
4076        // There are two pre-set routes in `test_route_requests`.
4077        // * subnet, next_hop1, DEV1, METRIC1, MANAGED_ROUTE_TABLE
4078        // * subnet, next_hop2, DEV2, METRIC2, MANAGED_ROUTE_TABLE
4079        let (route_req_args, route_set_result) = match kind {
4080            RouteRequestKind::New => {
4081                // Add a route that is not already present.
4082                let args =
4083                    RouteRequestArgs::New(NewRouteArgs::Unicast(create_unicast_new_route_args(
4084                        subnet,
4085                        next_hop1,
4086                        DEV1.into(),
4087                        METRIC3,
4088                        MANAGED_ROUTE_TABLE_INDEX,
4089                    )));
4090                let res = RouteSetResult::AddResult(Err(RouteSetError::Unauthenticated));
4091                (args, res)
4092            }
4093            RouteRequestKind::Del => {
4094                // Remove an existing route.
4095                let args =
4096                    RouteRequestArgs::Del(DelRouteArgs::Unicast(create_unicast_del_route_args(
4097                        subnet,
4098                        None,
4099                        None,
4100                        None,
4101                        MANAGED_ROUTE_TABLE_INDEX,
4102                    )));
4103                let res = RouteSetResult::DelResult(Err(RouteSetError::Unauthenticated));
4104                (args, res)
4105            }
4106        };
4107
4108        // No routes will be added or removed successfully, so there are no expected messages.
4109        let expected_messages = Vec::new();
4110
4111        pretty_assertions::assert_eq!(
4112            test_requests(
4113                [RequestArgs::Route(route_req_args)],
4114                |interfaces_request_stream| async move {
4115                    interfaces_request_stream
4116                        .for_each(|req| {
4117                            futures::future::ready(match req.unwrap() {
4118                                fnet_root::InterfacesRequest::GetAdmin {
4119                                    id,
4120                                    control,
4121                                    control_handle: _,
4122                                } => {
4123                                    pretty_assertions::assert_eq!(id, DEV1 as u64);
4124                                    let control = control.into_stream();
4125                                    let control = control.control_handle();
4126                                    control.shutdown();
4127                                }
4128                                req => unreachable!("unexpected interfaces request: {req:?}"),
4129                            })
4130                        })
4131                        .await
4132                },
4133                route_set_for_first_new_table(vec![route_set_result]),
4134                subnet,
4135                next_hop1,
4136                next_hop2,
4137                expected_messages.len(),
4138            )
4139            .await,
4140            TestRequestResult {
4141                messages: expected_messages,
4142                waiter_results: vec![Err(RequestError::UnrecognizedInterface)],
4143            },
4144        )
4145    }
4146
4147    // A flattened view of Route, convenient for holding testdata.
4148    #[derive(Clone)]
4149    struct Route<I: Ip> {
4150        subnet: Subnet<I::Addr>,
4151        device: u32,
4152        nexthop: Option<I::Addr>,
4153        metric: Option<NonZeroU32>,
4154    }
4155
4156    impl<I: Ip> Route<I> {
4157        fn to_route(self) -> fnet_routes_ext::Route<I> {
4158            let Self { subnet, device, nexthop, metric } = self;
4159            fnet_routes_ext::Route {
4160                destination: subnet,
4161                action: fnet_routes_ext::RouteAction::Forward(fnet_routes_ext::RouteTarget {
4162                    outbound_interface: device.into(),
4163                    next_hop: nexthop
4164                        .map(|a| SpecifiedAddr::new(a).expect("nexthop should be specified")),
4165                }),
4166                properties: fnet_routes_ext::RouteProperties {
4167                    specified_properties: fnet_routes_ext::SpecifiedRouteProperties {
4168                        metric: netlink_priority_to_specified_metric(metric, I::VERSION),
4169                    },
4170                },
4171            }
4172        }
4173
4174        fn to_installed_route(
4175            self,
4176            table_id: fnet_routes_ext::TableId,
4177        ) -> fnet_routes_ext::InstalledRoute<I> {
4178            const DEFAULT_INTERFACE_METRIC: u32 = 100000;
4179            let effective_metric = match self.metric {
4180                None => DEFAULT_INTERFACE_METRIC,
4181                Some(metric) => metric.into(),
4182            };
4183            let route = self.to_route();
4184            fnet_routes_ext::InstalledRoute {
4185                route,
4186                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
4187                    metric: effective_metric,
4188                },
4189                table_id,
4190            }
4191        }
4192    }
4193
4194    const ROUTE_METRIC1: NonZeroU32 = NonZeroU32::new(METRIC1).unwrap();
4195    const ROUTE_METRIC2: NonZeroU32 = NonZeroU32::new(METRIC2).unwrap();
4196    const ROUTE_METRIC3: NonZeroU32 = NonZeroU32::new(METRIC3).unwrap();
4197
4198    #[test_case(
4199        Route::<Ipv4>{
4200            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4201        },
4202        Route::<Ipv4>{
4203            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4204        },
4205        true; "all_fields_the_same_v4_should_match")]
4206    #[test_case(
4207        Route::<Ipv6>{
4208            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4209        },
4210        Route::<Ipv6>{
4211            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4212        },
4213        true; "all_fields_the_same_v6_should_match")]
4214    #[test_case(
4215        Route::<Ipv4>{
4216            subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4217        },
4218        Route::<Ipv4>{
4219            subnet: V4_DFLT, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4220        },
4221        true; "default_route_v4_should_match")]
4222    #[test_case(
4223        Route::<Ipv6>{
4224            subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4225        },
4226        Route::<Ipv6>{
4227            subnet: V6_DFLT, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4228        },
4229        true; "default_route_v6_should_match")]
4230    #[test_case(
4231        Route::<Ipv4>{
4232            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4233        },
4234        Route::<Ipv4>{
4235            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4236        },
4237        true; "different_device_v4_should_match")]
4238    #[test_case(
4239        Route::<Ipv6>{
4240            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4241        },
4242        Route::<Ipv6>{
4243            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4244        },
4245        true; "different_device_v6_should_match")]
4246    #[test_case(
4247        Route::<Ipv4>{
4248            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4249        },
4250        Route::<Ipv4>{
4251            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4252        },
4253        true; "different_nexthop_v4_should_match")]
4254    #[test_case(
4255        Route::<Ipv6>{
4256            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4257        },
4258        Route::<Ipv6>{
4259            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4260        },
4261        true; "different_nexthop_v6_should_match")]
4262    #[test_case(
4263        Route::<Ipv4>{
4264            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4265        },
4266        Route::<Ipv4>{
4267            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4268        },
4269        true; "different_device_and_nexthop_v4_should_match")]
4270    #[test_case(
4271        Route::<Ipv6>{
4272            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4273        },
4274        Route::<Ipv6>{
4275            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4276        },
4277        true; "different_device_and_nexthop_v6_should_match")]
4278    #[test_case(
4279        Route::<Ipv4>{
4280            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4281        },
4282        Route::<Ipv4>{
4283            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4284        },
4285        true; "nexthop_newly_unset_v4_should_match")]
4286    #[test_case(
4287        Route::<Ipv6>{
4288            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4289        },
4290        Route::<Ipv6>{
4291            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4292        },
4293        true; "nexthop_newly_unset_v6_should_match")]
4294    #[test_case(
4295        Route::<Ipv4>{
4296            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4297        },
4298        Route::<Ipv4>{
4299            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4300        },
4301        true; "nexthop_previously_unset_v4_should_match")]
4302    #[test_case(
4303        Route::<Ipv6>{
4304            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4305        },
4306        Route::<Ipv6>{
4307            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4308        },
4309        true; "nexthop_previously_unset_v6_should_match")]
4310    #[test_case(
4311        Route::<Ipv4>{
4312            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4313        },
4314        Route::<Ipv4>{
4315            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4316        },
4317        false; "different_metric_v4_should_not_match")]
4318    #[test_case(
4319        Route::<Ipv4>{
4320            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4321        },
4322        Route::<Ipv4>{
4323            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4324        },
4325        false; "default_and_non_default_v4_should_not_match")]
4326    #[test_case(
4327        Route::<Ipv4>{
4328            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4329        },
4330        Route::<Ipv4>{
4331            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: None,
4332        },
4333        true; "default_and_default_v4_should_match")]
4334    #[test_case(
4335        Route::<Ipv6>{
4336            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4337        },
4338        Route::<Ipv6>{
4339            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4340        },
4341        false; "different_metric_v6_should_not_match")]
4342    #[test_case(
4343        Route::<Ipv4>{
4344            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4345        },
4346        Route::<Ipv4>{
4347            subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4348        },
4349        false; "different_subnet_v4_should_not_match")]
4350    #[test_case(
4351        Route::<Ipv6>{
4352            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4353        },
4354        Route::<Ipv6>{
4355            subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4356        },
4357        false; "different_subnet_v6_should_not_match")]
4358    #[test_case(
4359        Route::<Ipv4>{
4360            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4361        },
4362        Route::<Ipv4>{
4363            subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4364        },
4365        false; "different_subnet_prefixlen_v4_should_not_match")]
4366    #[test_case(
4367        Route::<Ipv6>{
4368            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4369        },
4370        Route::<Ipv6>{
4371            subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4372        },
4373        false; "different_subnet_prefixlen_v6_should_not_match")]
4374    #[test_case(
4375        Route::<Ipv6>{
4376            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4377        },
4378        Route::<Ipv6>{
4379            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4380        },
4381        false; "default_and_non_default_v6_should_not_match")]
4382    #[test_case(
4383        Route::<Ipv6>{
4384            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4385        },
4386        Route::<Ipv6>{
4387            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: None,
4388        },
4389        true; "default_and_default_v6_should_match")]
4390    fn test_new_route_matcher<I: Ip>(
4391        route1: Route<I>,
4392        route2: Route<I>,
4393        expected_to_conflict: bool,
4394    ) {
4395        let route1 = route1.to_installed_route(MAIN_FIDL_TABLE_ID);
4396        let route2 = route2.to_installed_route(MAIN_FIDL_TABLE_ID);
4397
4398        let got_conflict = routes_conflict::<I>(route1, route2.route, route2.table_id);
4399        assert_eq!(got_conflict, expected_to_conflict);
4400
4401        let got_conflict = routes_conflict::<I>(route2, route1.route, route1.table_id);
4402        assert_eq!(got_conflict, expected_to_conflict);
4403    }
4404
4405    // Calls `select_route_for_deletion` with the given args & existing_routes.
4406    //
4407    // Asserts that the return route matches the route in `existing_routes` at
4408    // `expected_index`.
4409    fn test_select_route_for_deletion_helper<
4410        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4411    >(
4412        args: UnicastDelRouteArgs<I>,
4413        existing_routes: &[Route<I>],
4414        // The index into `existing_routes` of the route that should be selected.
4415        expected_index: Option<usize>,
4416    ) {
4417        let mut fidl_route_map = FidlRouteMap::<I>::default();
4418
4419        // We create a bunch of proxies that go unused in this test. In order for this to succeed
4420        // we must have an executor.
4421        let _executor = fuchsia_async::TestExecutor::new();
4422
4423        let (main_route_table_proxy, _server_end) =
4424            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4425        let (own_route_table_proxy, _server_end) =
4426            fidl::endpoints::create_proxy::<I::RouteTableMarker>();
4427        let (route_set_proxy, _server_end) = fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4428        let (unmanaged_route_set_proxy, _unmanaged_route_set_server_end) =
4429            fidl::endpoints::create_proxy::<I::RouteSetMarker>();
4430        let (route_table_provider, _server_end) =
4431            fidl::endpoints::create_proxy::<I::RouteTableProviderMarker>();
4432
4433        let mut route_table_map = RouteTableMap::<I>::new(
4434            main_route_table_proxy,
4435            MAIN_FIDL_TABLE_ID,
4436            unmanaged_route_set_proxy,
4437            route_table_provider,
4438        );
4439
4440        route_table_map.insert(
4441            MANAGED_ROUTE_TABLE_INDEX,
4442            RouteTable::Managed(ManagedRouteTable {
4443                route_table_proxy: own_route_table_proxy,
4444                route_set_proxy,
4445                fidl_table_id: OTHER_FIDL_TABLE_ID,
4446                rule_set_authenticated: false,
4447            }),
4448        );
4449
4450        for Route { subnet, device, nexthop, metric } in existing_routes {
4451            let fnet_routes_ext::InstalledRoute { route, effective_properties, table_id } =
4452                create_installed_route::<I>(
4453                    *subnet,
4454                    *nexthop,
4455                    (*device).into(),
4456                    metric.map_or(0, NonZeroU32::get),
4457                    OTHER_FIDL_TABLE_ID,
4458                );
4459            assert_matches!(fidl_route_map.add(route, table_id, effective_properties), None);
4460        }
4461
4462        let existing_routes = existing_routes
4463            .iter()
4464            .map(|Route { subnet, device, nexthop, metric }| {
4465                // Don't populate the Destination NLA if this is the default route.
4466                let destination = (subnet.prefix() != 0).then_some(*subnet);
4467                create_netlink_route_message::<I>(
4468                    subnet.prefix(),
4469                    MANAGED_ROUTE_TABLE_INDEX,
4470                    create_nlas::<I>(
4471                        destination,
4472                        nexthop.to_owned(),
4473                        *device,
4474                        metric.map_or(0, NonZeroU32::get),
4475                        Some(MANAGED_ROUTE_TABLE_ID),
4476                    ),
4477                )
4478            })
4479            .collect::<Vec<_>>();
4480        let expected_route = expected_index.map(|index| {
4481            existing_routes
4482                .get(index)
4483                .expect("index should be within the bounds of `existing_routes`")
4484                .clone()
4485        });
4486
4487        assert_eq!(
4488            select_route_for_deletion(
4489                &fidl_route_map,
4490                &route_table_map,
4491                DelRouteArgs::Unicast(args).try_into().unwrap(),
4492            ),
4493            expected_route
4494        )
4495    }
4496
4497    #[test_case(
4498        UnicastDelRouteArgs::<Ipv4> {
4499            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4500            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4501                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4502            ),
4503        },
4504        Route::<Ipv4>{
4505            subnet: V4_SUB2, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4506        },
4507        false; "subnet_does_not_match_v4")]
4508    #[test_case(
4509        UnicastDelRouteArgs::<Ipv4> {
4510            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4511            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4512                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4513            ),
4514        },
4515        Route::<Ipv4>{
4516            subnet: V4_SUB3, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4517        },
4518        false; "subnet_prefix_len_does_not_match_v4")]
4519    #[test_case(
4520        UnicastDelRouteArgs::<Ipv4> {
4521            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4522            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4523                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4524            ),
4525        },
4526        Route::<Ipv4>{
4527            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4528        },
4529        true; "subnet_matches_v4")]
4530    #[test_case(
4531        UnicastDelRouteArgs::<Ipv4> {
4532            subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4533            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4534                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4535            ),
4536        },
4537        Route::<Ipv4>{
4538            subnet: V4_SUB1, device: DEV2, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4539        },
4540        false; "interface_does_not_match_v4")]
4541    #[test_case(
4542        UnicastDelRouteArgs::<Ipv4> {
4543            subnet: V4_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4544            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4545                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4546            ),
4547        },
4548        Route::<Ipv4>{
4549            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4550        },
4551        true; "interface_matches_v4")]
4552    #[test_case(
4553        UnicastDelRouteArgs::<Ipv4> {
4554            subnet: V4_SUB1, outbound_interface: None,
4555            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4556            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4557                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4558            ),
4559        },
4560        Route::<Ipv4>{
4561            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4562        },
4563        false; "nexthop_absent_v4")]
4564    #[test_case(
4565        UnicastDelRouteArgs::<Ipv4> {
4566            subnet: V4_SUB1, outbound_interface: None,
4567            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4568            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4569                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4570            ),
4571        },
4572        Route::<Ipv4>{
4573            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4574        },
4575        false; "nexthop_does_not_match_v4")]
4576    #[test_case(
4577        UnicastDelRouteArgs::<Ipv4> {
4578            subnet: V4_SUB1, outbound_interface: None,
4579            next_hop: Some(SpecifiedAddr::new(V4_NEXTHOP1).unwrap()), priority: None,
4580            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4581                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4582            ),
4583        },
4584        Route::<Ipv4>{
4585            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4586        },
4587        true; "nexthop_matches_v4")]
4588    #[test_case(
4589        UnicastDelRouteArgs::<Ipv4> {
4590            subnet: V4_SUB1, outbound_interface: None,
4591            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4592            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4593                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4594            ),
4595        },
4596        Route::<Ipv4>{
4597            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4598        },
4599        false; "metric_does_not_match_v4")]
4600    #[test_case(
4601        UnicastDelRouteArgs::<Ipv4> {
4602            subnet: V4_SUB1, outbound_interface: None,
4603            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4604            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4605                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4606            ),
4607        },
4608        Route::<Ipv4>{
4609            subnet: V4_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4610        },
4611        true; "metric_matches_v4")]
4612    #[test_case(
4613        UnicastDelRouteArgs::<Ipv6> {
4614            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4615            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4616                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4617            ),
4618        },
4619        Route::<Ipv6>{
4620            subnet: V6_SUB2, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4621        },
4622        false; "subnet_does_not_match_v6")]
4623    #[test_case(
4624        UnicastDelRouteArgs::<Ipv6> {
4625            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4626            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4627                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4628            ),
4629        },
4630        Route::<Ipv6>{
4631            subnet: V6_SUB3, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4632        },
4633        false; "subnet_prefix_len_does_not_match_v6")]
4634    #[test_case(
4635        UnicastDelRouteArgs::<Ipv6> {
4636            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4637            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4638                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4639            ),
4640        },
4641        Route::<Ipv6>{
4642            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4643        },
4644        true; "subnet_matches_v6")]
4645    #[test_case(
4646        UnicastDelRouteArgs::<Ipv6> {
4647            subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4648            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4649                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4650            ),
4651        },
4652        Route::<Ipv6>{
4653            subnet: V6_SUB1, device: DEV2, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4654        },
4655        false; "interface_does_not_match_v6")]
4656    #[test_case(
4657        UnicastDelRouteArgs::<Ipv6> {
4658            subnet: V6_SUB1, outbound_interface: Some(NonZeroU64::new(DEV1.into()).unwrap()),
4659            next_hop: None, priority: None, table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4660                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4661            ),
4662        },
4663        Route::<Ipv6>{
4664            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4665        },
4666        true; "interface_matches_v6")]
4667    #[test_case(
4668        UnicastDelRouteArgs::<Ipv6> {
4669            subnet: V6_SUB1, outbound_interface: None,
4670            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4671            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4672                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4673            ),
4674        },
4675        Route::<Ipv6>{
4676            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4677        },
4678        false; "nexthop_absent_v6")]
4679    #[test_case(
4680        UnicastDelRouteArgs::<Ipv6> {
4681            subnet: V6_SUB1, outbound_interface: None,
4682            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4683            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4684                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4685            ),
4686        },
4687        Route::<Ipv6>{
4688            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP2), metric: Some(ROUTE_METRIC1),
4689        },
4690        false; "nexthop_does_not_match_v6")]
4691    #[test_case(
4692        UnicastDelRouteArgs::<Ipv6> {
4693            subnet: V6_SUB1, outbound_interface: None,
4694            next_hop: Some(SpecifiedAddr::new(V6_NEXTHOP1).unwrap()), priority: None,
4695            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4696                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4697            ),
4698        },
4699        Route::<Ipv6>{
4700            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4701        },
4702        true; "nexthop_matches_v6")]
4703    #[test_case(
4704        UnicastDelRouteArgs::<Ipv6> {
4705            subnet: V6_SUB1, outbound_interface: None,
4706            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4707            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4708                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4709            ),
4710        },
4711        Route::<Ipv6>{
4712            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC2),
4713        },
4714        false; "metric_does_not_match_v6")]
4715    #[test_case(
4716        UnicastDelRouteArgs::<Ipv6> {
4717            subnet: V6_SUB1, outbound_interface: None,
4718            next_hop: None, priority: Some(NonZeroU32::new(METRIC1).unwrap()),
4719            table: NonZeroNetlinkRouteTableIndex::new_non_zero(
4720                NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()
4721            ),
4722        },
4723        Route::<Ipv6>{
4724            subnet: V6_SUB1, device: DEV1, nexthop: None, metric: Some(ROUTE_METRIC1),
4725        },
4726        true; "metric_matches_v6")]
4727    fn test_select_route_for_deletion<
4728        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4729    >(
4730        args: UnicastDelRouteArgs<I>,
4731        existing_route: Route<I>,
4732        expect_match: bool,
4733    ) {
4734        test_select_route_for_deletion_helper(args, &[existing_route], expect_match.then_some(0))
4735    }
4736
4737    #[test_case(
4738        UnicastDelRouteArgs::<Ipv4> {
4739            subnet: V4_SUB1, outbound_interface: None, next_hop: None, priority: None,
4740            table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4741        },
4742        &[
4743        Route::<Ipv4>{
4744            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4745        },
4746        Route::<Ipv4>{
4747            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4748        },
4749        Route::<Ipv4>{
4750            subnet: V4_SUB1, device: DEV1, nexthop: Some(V4_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4751        },
4752        ],
4753        Some(1); "multiple_matches_prefers_lowest_metric_v4")]
4754    #[test_case(
4755        UnicastDelRouteArgs::<Ipv6> {
4756            subnet: V6_SUB1, outbound_interface: None, next_hop: None, priority: None,
4757            table: NonZeroNetlinkRouteTableIndex::new_non_zero(NonZeroU32::new(MANAGED_ROUTE_TABLE_INDEX.get()).unwrap()),
4758        },
4759        &[
4760        Route::<Ipv6>{
4761            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC2),
4762        },
4763        Route::<Ipv6>{
4764            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC1),
4765        },
4766        Route::<Ipv6>{
4767            subnet: V6_SUB1, device: DEV1, nexthop: Some(V6_NEXTHOP1), metric: Some(ROUTE_METRIC3),
4768        },
4769        ],
4770        Some(1); "multiple_matches_prefers_lowest_metric_v6")]
4771    fn test_select_route_for_deletion_multiple_matches<
4772        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4773    >(
4774        args: UnicastDelRouteArgs<I>,
4775        existing_routes: &[Route<I>],
4776        expected_index: Option<usize>,
4777    ) {
4778        test_select_route_for_deletion_helper(args, existing_routes, expected_index);
4779    }
4780
4781    #[ip_test(I, test = false)]
4782    #[fuchsia::test]
4783    async fn garbage_collects_empty_table<
4784        I: Ip + fnet_routes_ext::admin::FidlRouteAdminIpExt + fnet_routes_ext::FidlRouteIpExt,
4785    >() {
4786        let (_route_sink, route_client, async_work_drain_task) =
4787            crate::client::testutil::new_fake_client::<NetlinkRoute>(
4788                crate::client::testutil::CLIENT_ID_1,
4789                [ModernGroup(match I::VERSION {
4790                    IpVersion::V4 => rtnetlink_groups_RTNLGRP_IPV4_ROUTE,
4791                    IpVersion::V6 => rtnetlink_groups_RTNLGRP_IPV6_ROUTE,
4792                })],
4793            );
4794        let join_handle = fasync::Task::spawn(async_work_drain_task);
4795        {
4796            // Move `route_client` into the scope so it gets dropped.
4797            let route_client = route_client;
4798            let route_clients = ClientTable::default();
4799            route_clients.add_client(route_client.clone());
4800
4801            let Setup {
4802                event_loop_inputs,
4803                watcher_stream,
4804                route_sets: (main_route_table_server_end, route_table_provider_server_end),
4805                interfaces_request_stream: _,
4806                mut request_sink,
4807                async_work_sink: _,
4808            } = setup_with_route_clients_yielding_admin_server_ends::<I>(route_clients);
4809
4810            let mut main_route_table_fut = pin!(
4811                fnet_routes_ext::testutil::admin::serve_noop_route_sets_with_table_id::<I>(
4812                    main_route_table_server_end,
4813                    MAIN_FIDL_TABLE_ID
4814                )
4815                .fuse()
4816            );
4817
4818            let mut watcher_stream = pin!(watcher_stream.fuse());
4819            let mut route_table_provider_stream = route_table_provider_server_end.into_stream();
4820
4821            let mut event_loop = {
4822                let included_workers = match I::VERSION {
4823                    IpVersion::V4 => crate::route_eventloop::IncludedWorkers {
4824                        routes_v4: EventLoopComponent::Present(()),
4825                        routes_v6: EventLoopComponent::Absent(Optional),
4826                        interfaces: EventLoopComponent::Absent(Optional),
4827                        rules_v4: EventLoopComponent::Absent(Optional),
4828                        rules_v6: EventLoopComponent::Absent(Optional),
4829                        nduseropt: EventLoopComponent::Absent(Optional),
4830                    },
4831                    IpVersion::V6 => crate::route_eventloop::IncludedWorkers {
4832                        routes_v4: EventLoopComponent::Absent(Optional),
4833                        routes_v6: EventLoopComponent::Present(()),
4834                        interfaces: EventLoopComponent::Absent(Optional),
4835                        rules_v4: EventLoopComponent::Absent(Optional),
4836                        rules_v6: EventLoopComponent::Absent(Optional),
4837                        nduseropt: EventLoopComponent::Absent(Optional),
4838                    },
4839                };
4840
4841                let event_loop_fut = event_loop_inputs.initialize(included_workers).fuse();
4842                let watcher_fut = async {
4843                    let watch_req =
4844                        watcher_stream.by_ref().next().await.expect("should not have ended");
4845                    // Start with no routes.
4846                    fnet_routes_ext::testutil::handle_watch::<I>(
4847                        watch_req,
4848                        vec![fnet_routes_ext::Event::<I>::Idle.try_into().unwrap()],
4849                    )
4850                }
4851                .fuse();
4852
4853                futures::select! {
4854                    () = main_route_table_fut => unreachable!(),
4855                    (event_loop, ()) = futures::future::join(
4856                        event_loop_fut, watcher_fut
4857                    ) => event_loop,
4858                }
4859            };
4860
4861            let (completer, mut initial_add_request_waiter) = oneshot::channel();
4862
4863            let new_route_args = NewRouteArgs::Unicast(I::map_ip_out(
4864                (),
4865                |()| {
4866                    create_unicast_new_route_args(
4867                        V4_SUB1,
4868                        V4_NEXTHOP1,
4869                        DEV1.into(),
4870                        METRIC1,
4871                        MANAGED_ROUTE_TABLE_INDEX,
4872                    )
4873                },
4874                |()| {
4875                    create_unicast_new_route_args(
4876                        V6_SUB1,
4877                        V6_NEXTHOP1,
4878                        DEV1.into(),
4879                        METRIC1,
4880                        MANAGED_ROUTE_TABLE_INDEX,
4881                    )
4882                },
4883            ));
4884            let expected_route = fnet_routes_ext::Route::<I>::from(new_route_args);
4885
4886            // Request that a route is installed in a new table.
4887            request_sink
4888                .try_send(
4889                    Request {
4890                        args: RequestArgs::Route(RouteRequestArgs::New(new_route_args)),
4891                        sequence_number: TEST_SEQUENCE_NUMBER,
4892                        client: route_client.clone(),
4893                        completer,
4894                    }
4895                    .into(),
4896                )
4897                .expect("should succeed");
4898
4899            // Run the event loop and observe the new table get created and the
4900            // route set requests go out.
4901            let (mut route_table_stream, mut route_set_stream) = {
4902                let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
4903                let route_table_fut = async {
4904                    let server_end = match I::into_route_table_provider_request(
4905                        route_table_provider_stream
4906                            .try_next()
4907                            .await
4908                            .expect("should not have ended")
4909                            .expect("fidl error"),
4910                    ) {
4911                        fnet_routes_ext::admin::RouteTableProviderRequest::NewRouteTable {
4912                            provider,
4913                            options: _,
4914                            control_handle: _,
4915                        } => provider,
4916                        r => panic!("unexpected request {r:?}"),
4917                    };
4918                    let mut route_table_stream = server_end.into_stream().boxed().fuse();
4919
4920                    let request = I::into_route_table_request_result(
4921                        route_table_stream.by_ref().next().await.expect("should not have ended"),
4922                    )
4923                    .expect("should not get error");
4924
4925                    let responder = match request {
4926                        RouteTableRequest::GetTableId { responder } => responder,
4927                        _ => panic!("should be GetTableId"),
4928                    };
4929                    responder.send(OTHER_FIDL_TABLE_ID.get()).expect("should succeed");
4930
4931                    let request = I::into_route_table_request_result(
4932                        route_table_stream.by_ref().next().await.expect("should not have ended"),
4933                    )
4934                    .expect("should not get error");
4935
4936                    let server_end = match request {
4937                        RouteTableRequest::NewRouteSet { route_set, control_handle: _ } => {
4938                            route_set
4939                        }
4940                        _ => panic!("should be NewRouteSet"),
4941                    };
4942                    let mut route_set_stream = server_end.into_stream().boxed().fuse();
4943
4944                    let request = I::into_route_set_request_result(
4945                        route_set_stream.by_ref().next().await.expect("should not have ended"),
4946                    )
4947                    .expect("should not get error");
4948
4949                    let (route, responder) = match request {
4950                        RouteSetRequest::AddRoute { route, responder } => (route, responder),
4951                        _ => panic!("should be AddRoute"),
4952                    };
4953                    let route = route.expect("should successfully convert FIDl");
4954                    assert_eq!(route, expected_route);
4955
4956                    responder.send(Ok(true)).expect("sending response should succeed");
4957                    (route_table_stream, route_set_stream)
4958                }
4959                .fuse();
4960                futures::select! {
4961                    () = main_route_table_fut => unreachable!(),
4962                    ((), streams) = futures::future::join(event_loop_fut, route_table_fut) => {
4963                        streams
4964                    }
4965                }
4966            };
4967
4968            {
4969                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
4970                // The new route table should be present in the map.
4971                let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
4972                    Some(RouteTable::Managed(table)) => table,
4973                    _ => panic!("table should be present"),
4974                };
4975                assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
4976
4977                // But the new route won't be tracked because we haven't
4978                // confirmed it via the watcher yet.
4979                assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
4980                    &expected_route,
4981                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
4982                ));
4983            }
4984
4985            // The request won't be complete until we've confirmed addition via the watcher.
4986            assert_matches!(initial_add_request_waiter.try_recv(), Ok(None));
4987
4988            // Run the event loop while yielding the new route via the watcher.
4989            {
4990                let event_loop_fut = async {
4991                    // Handling two events, so run two steps.
4992                    event_loop.run_one_step_in_tests().await;
4993                    event_loop.run_one_step_in_tests().await;
4994                }
4995                .fuse();
4996                let watcher_fut = async {
4997                    let watch_req =
4998                        watcher_stream.by_ref().next().await.expect("should not have ended");
4999                    // Show that the route was added for both the main and the owned FIDL table.
5000                    fnet_routes_ext::testutil::handle_watch::<I>(
5001                        watch_req,
5002                        vec![
5003                            fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5004                                route: expected_route,
5005                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5006                                    metric: METRIC1,
5007                                },
5008                                table_id: MAIN_FIDL_TABLE_ID,
5009                            })
5010                            .try_into()
5011                            .unwrap(),
5012                            fnet_routes_ext::Event::<I>::Added(fnet_routes_ext::InstalledRoute {
5013                                route: expected_route,
5014                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5015                                    metric: METRIC1,
5016                                },
5017                                table_id: OTHER_FIDL_TABLE_ID,
5018                            })
5019                            .try_into()
5020                            .unwrap(),
5021                        ],
5022                    );
5023                };
5024                let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5025            }
5026
5027            {
5028                let (routes_worker, _route_table_map) = event_loop.route_table_state::<I>();
5029
5030                // The route should be noted as stored in both tables now.
5031                assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5032                    &expected_route,
5033                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5034                ));
5035            }
5036
5037            assert_matches!(initial_add_request_waiter.try_recv(), Ok(Some(Ok(()))));
5038
5039            let (completer, mut del_request_waiter) = oneshot::channel();
5040
5041            let del_route_args = I::map_ip_out(
5042                (),
5043                |()| {
5044                    create_unicast_del_route_args(
5045                        V4_SUB1,
5046                        Some(V4_NEXTHOP1),
5047                        Some(DEV1.into()),
5048                        Some(METRIC1),
5049                        MANAGED_ROUTE_TABLE_INDEX,
5050                    )
5051                },
5052                |()| {
5053                    create_unicast_del_route_args(
5054                        V6_SUB1,
5055                        Some(V6_NEXTHOP1),
5056                        Some(DEV1.into()),
5057                        Some(METRIC1),
5058                        MANAGED_ROUTE_TABLE_INDEX,
5059                    )
5060                },
5061            );
5062
5063            // Request the route's removal.
5064            request_sink
5065                .try_send(
5066                    Request {
5067                        args: RequestArgs::Route(RouteRequestArgs::Del(DelRouteArgs::Unicast(
5068                            del_route_args,
5069                        ))),
5070                        sequence_number: TEST_SEQUENCE_NUMBER,
5071                        client: route_client.clone(),
5072                        completer,
5073                    }
5074                    .into(),
5075                )
5076                .expect("should succeed");
5077
5078            // Observe and handle the removal requests.
5079            {
5080                let event_loop_fut = event_loop.run_one_step_in_tests().fuse();
5081                let route_set_fut = async {
5082                    let request = I::into_route_set_request_result(
5083                        route_set_stream.next().await.expect("should not have ended"),
5084                    )
5085                    .expect("should not get error");
5086                    let (route, responder) = match request {
5087                        RouteSetRequest::RemoveRoute { route, responder } => (route, responder),
5088                        _ => panic!("should be DelRoute"),
5089                    };
5090                    let route = route.expect("should successfully convert FIDl");
5091                    assert_eq!(route, expected_route);
5092
5093                    responder.send(Ok(true)).expect("sending response should succeed");
5094                }
5095                .fuse();
5096
5097                futures::select! {
5098                    () = main_route_table_fut => unreachable!(),
5099                    ((), ()) = futures::future::join(event_loop_fut, route_set_fut) => (),
5100                }
5101            }
5102
5103            // We still haven't confirmed the removal via the watcher.
5104            {
5105                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5106                // The route table should still be present in the map.
5107                let table = match route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX) {
5108                    Some(RouteTable::Managed(table)) => table,
5109                    _ => panic!("table should be present"),
5110                };
5111                assert_eq!(table.fidl_table_id, OTHER_FIDL_TABLE_ID);
5112
5113                assert!(routes_worker.fidl_route_map.route_is_installed_in_tables(
5114                    &expected_route,
5115                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5116                ));
5117            }
5118            assert_matches!(del_request_waiter.try_recv(), Ok(None));
5119
5120            // Run the event loop while yielding the deleted route via the watcher.
5121            {
5122                let event_loop_fut = async {
5123                    // Handling two events, so run two steps.
5124                    event_loop.run_one_step_in_tests().await;
5125                    event_loop.run_one_step_in_tests().await;
5126                }
5127                .fuse();
5128                let watcher_fut = async {
5129                    let watch_req =
5130                        watcher_stream.by_ref().next().await.expect("should not have ended");
5131                    // Show that the route was removed for both the main and the owned FIDL table.
5132                    fnet_routes_ext::testutil::handle_watch::<I>(
5133                        watch_req,
5134                        vec![
5135                            fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5136                                route: expected_route,
5137                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5138                                    metric: 0,
5139                                },
5140                                table_id: MAIN_FIDL_TABLE_ID,
5141                            })
5142                            .try_into()
5143                            .unwrap(),
5144                            fnet_routes_ext::Event::<I>::Removed(fnet_routes_ext::InstalledRoute {
5145                                route: expected_route,
5146                                effective_properties: fnet_routes_ext::EffectiveRouteProperties {
5147                                    metric: 0,
5148                                },
5149                                table_id: OTHER_FIDL_TABLE_ID,
5150                            })
5151                            .try_into()
5152                            .unwrap(),
5153                        ],
5154                    );
5155                };
5156                let ((), ()) = futures::join!(event_loop_fut, watcher_fut);
5157            }
5158
5159            {
5160                let (routes_worker, route_table_map) = event_loop.route_table_state::<I>();
5161
5162                // The route should be noted as being removed from both tables now.
5163                assert!(routes_worker.fidl_route_map.route_is_uninstalled_in_tables(
5164                    &expected_route,
5165                    [&OTHER_FIDL_TABLE_ID, &MAIN_FIDL_TABLE_ID]
5166                ));
5167
5168                // And the table should now be cleaned up from the map.
5169                assert_matches!(route_table_map.get(&MANAGED_ROUTE_TABLE_INDEX), None);
5170            }
5171            assert_matches!(del_request_waiter.try_recv(), Ok(Some(Ok(()))));
5172
5173            // Because the table was dropped from the map, the route table
5174            // request stream should close.
5175            let route_table_request = route_table_stream.next().await;
5176            assert!(route_table_request.is_none());
5177        };
5178        join_handle.await;
5179    }
5180}