Skip to main content

netstack3_ip/
routing.rs

1// Copyright 2018 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//! IP routing definitions.
6pub(crate) mod rules;
7
8use alloc::vec::Vec;
9use core::fmt::Debug;
10
11use log::debug;
12use net_types::ip::{GenericOverIp, Ip, IpAddress as _, Ipv4, Ipv4Addr, Subnet};
13use net_types::{SpecifiedAddr, Witness as _};
14use netstack3_base::{AnyDevice, BroadcastIpExt, DeviceIdContext, ExistsError};
15use thiserror::Error;
16
17use crate::internal::base::{IpLayerBindingsContext, IpLayerEvent, IpLayerIpExt};
18use crate::internal::types::{
19    AddableEntry, Destination, Entry, EntryAndGeneration, NextHop, OrderedEntry, RawMetric,
20};
21
22/// Provides access to a device for the purposes of IP routing.
23pub trait IpRoutingDeviceContext<I: Ip>: DeviceIdContext<AnyDevice> {
24    /// Returns the routing metric for the device.
25    fn get_routing_metric(&mut self, device_id: &Self::DeviceId) -> RawMetric;
26
27    /// Returns true if the IP device is enabled.
28    fn is_ip_device_enabled(&mut self, device_id: &Self::DeviceId) -> bool;
29}
30
31/// Bindings types for IP routing.
32pub trait IpRoutingBindingsTypes {
33    /// An opaque type that represents a routing table ID from the Bindings. The Bindings
34    /// implementation is responsible for providing a unique ID for each routing table.
35    type RoutingTableId: Send + Sync + Debug + 'static;
36}
37
38/// An error encountered when adding a routing entry.
39#[derive(Error, Debug, PartialEq)]
40pub enum AddRouteError {
41    /// Indicates that the route already exists.
42    #[error("already exists")]
43    AlreadyExists,
44
45    /// Indicates the gateway is not a neighbor of this node.
46    #[error("gateway is not a neighbor")]
47    GatewayNotNeighbor,
48}
49
50impl From<ExistsError> for AddRouteError {
51    fn from(ExistsError: ExistsError) -> AddRouteError {
52        AddRouteError::AlreadyExists
53    }
54}
55
56/// Requests that a route be added to the routing table.
57pub fn request_context_add_route<
58    I: IpLayerIpExt,
59    DeviceId,
60    BC: IpLayerBindingsContext<I, DeviceId>,
61>(
62    bindings_ctx: &mut BC,
63    entry: AddableEntry<I::Addr, DeviceId>,
64) {
65    bindings_ctx.on_event(IpLayerEvent::AddRoute(entry))
66}
67
68/// Requests that routes matching these specifiers be removed from the
69/// routing table.
70pub fn request_context_del_routes<
71    I: IpLayerIpExt,
72    DeviceId,
73    BC: IpLayerBindingsContext<I, DeviceId>,
74>(
75    bindings_ctx: &mut BC,
76    del_subnet: Subnet<I::Addr>,
77    del_device: DeviceId,
78    del_gateway: Option<SpecifiedAddr<I::Addr>>,
79) {
80    bindings_ctx.on_event(IpLayerEvent::RemoveRoutes {
81        subnet: del_subnet,
82        device: del_device,
83        gateway: del_gateway,
84    })
85}
86
87/// An IP routing table.
88///
89/// `RoutingTable` maps destination subnets to the nearest IP hosts (on the
90/// local network) able to route IP packets to those subnets.
91#[derive(GenericOverIp)]
92#[generic_over_ip(I, Ip)]
93#[derive(Debug)]
94pub struct RoutingTable<I: Ip, D> {
95    /// All the routes available to route a packet.
96    ///
97    /// `table` may have redundant, but unique, paths to the same
98    /// destination.
99    ///
100    /// Entries in the table are sorted from most-preferred to least preferred.
101    /// See [`OrderedEntry`] for the ordering rules.
102    pub(super) table: Vec<EntryAndGeneration<I::Addr, D>>,
103}
104
105impl<I: Ip, D> Default for RoutingTable<I, D> {
106    fn default() -> RoutingTable<I, D> {
107        RoutingTable { table: Vec::default() }
108    }
109}
110
111impl<I: BroadcastIpExt, D: Clone + Debug + PartialEq> RoutingTable<I, D> {
112    /// Adds `entry` to the routing table if it does not already exist.
113    ///
114    /// On success, a reference to the inserted entry is returned.
115    pub fn add_entry(
116        &mut self,
117        entry: EntryAndGeneration<I::Addr, D>,
118    ) -> Result<&EntryAndGeneration<I::Addr, D>, ExistsError>
119    where
120        D: PartialOrd,
121    {
122        debug!("adding route: {}", entry);
123        let Self { table } = self;
124
125        if table.contains(&entry) {
126            // If we already have this exact route, don't add it again.
127            return Err(ExistsError);
128        }
129
130        let ordered_entry: OrderedEntry<'_, _, _> = (&entry).into();
131        // Note, compare with "greater than or equal to" here, to ensure
132        // that existing entries are preferred over new entries.
133        let index = table.partition_point(|entry| ordered_entry.ge(&entry.into()));
134
135        table.insert(index, entry);
136
137        Ok(&table[index])
138    }
139
140    // Applies the given predicate to the entries in the routing table,
141    // removing (and returning) those that yield `true` while retaining those
142    // that yield `false`.
143    #[cfg(any(test, feature = "testutils"))]
144    fn del_entries<F: Fn(&Entry<I::Addr, D>) -> bool>(
145        &mut self,
146        predicate: F,
147    ) -> alloc::vec::Vec<Entry<I::Addr, D>> {
148        let Self { table } = self;
149        table.extract_if(.., |entry| predicate(&entry.entry)).map(|entry| entry.entry).collect()
150    }
151
152    /// Get an iterator over all of the routing entries ([`Entry`]) this
153    /// `RoutingTable` knows about.
154    pub fn iter_table(&self) -> impl Iterator<Item = &Entry<I::Addr, D>> {
155        self.table.iter().map(|entry| &entry.entry)
156    }
157
158    /// Look up the routing entry for an address in the table.
159    ///
160    /// Look up the routing entry for an address in the table, returning the
161    /// next hop and device over which the address is reachable.
162    ///
163    /// If `device` is specified, the available routes are limited to those that
164    /// egress over the device.
165    ///
166    /// If multiple entries match `address` or the first entry will be selected.
167    /// See [`RoutingTable`] for more details of how entries are sorted.
168    pub(crate) fn lookup<CC: IpRoutingDeviceContext<I, DeviceId = D>>(
169        &self,
170        core_ctx: &mut CC,
171        local_device: Option<&D>,
172        address: I::Addr,
173    ) -> Option<Destination<I::Addr, D>> {
174        self.lookup_filter_map(core_ctx, local_device, address, |_: &mut CC, _: &D| Some(()))
175            .map(|(Destination { device, next_hop }, ())| Destination {
176                device: device.clone(),
177                next_hop,
178            })
179            .next()
180    }
181
182    pub(crate) fn lookup_filter_map<'a, CC: IpRoutingDeviceContext<I, DeviceId = D>, R>(
183        &'a self,
184        core_ctx: &'a mut CC,
185        local_device: Option<&'a D>,
186        address: I::Addr,
187        mut f: impl FnMut(&mut CC, &D) -> Option<R> + 'a,
188    ) -> impl Iterator<Item = (Destination<I::Addr, &D>, R)> + 'a {
189        let Self { table } = self;
190
191        #[derive(GenericOverIp)]
192        #[generic_over_ip(I, Ip)]
193        enum BroadcastCase<I: BroadcastIpExt> {
194            AllOnes(I::BroadcastMarker),
195            Subnet(I::BroadcastMarker),
196            NotBroadcast,
197        }
198
199        let bound_device_all_ones_broadcast_exemption = core::iter::once_with(move || {
200            // If we're bound to a device and trying to broadcast on the local
201            // network, then provide a matched broadcast route.
202            let local_device = local_device?;
203            let next_hop = I::map_ip::<_, Option<NextHop<I::Addr>>>(
204                address,
205                |address| {
206                    (address == Ipv4::LIMITED_BROADCAST_ADDRESS.get())
207                        .then_some(NextHop::Broadcast(()))
208                },
209                |_address| None,
210            )?;
211            Some(Destination { next_hop, device: local_device })
212        })
213        .filter_map(|x| x);
214
215        let viable_table_entries = table.iter().filter_map(move |entry| {
216            let EntryAndGeneration {
217                entry: Entry { subnet, device, gateway, metric: _, route_preference: _ },
218                generation: _,
219            } = entry;
220            if !subnet.contains(&address) {
221                return None;
222            }
223            if local_device.is_some_and(|local_device| local_device != device) {
224                return None;
225            }
226
227            let broadcast_case = I::map_ip::<_, BroadcastCase<I>>(
228                (address, *subnet),
229                |(address, subnet)| {
230                    // As per RFC 919 section 7,
231                    //      The address 255.255.255.255 denotes a broadcast on a local hardware
232                    //      network, which must not be forwarded.
233                    if address == Ipv4::LIMITED_BROADCAST_ADDRESS.get() {
234                        BroadcastCase::AllOnes(())
235                    // Or the destination address is the highest address in the subnet.
236                    // Per RFC 922,
237                    //       Since the local network layer can always map an IP address into data
238                    //       link layer address, the choice of an IP "broadcast host number" is
239                    //       somewhat arbitrary.  For simplicity, it should be one not likely to be
240                    //       assigned to a real host.  The number whose bits are all ones has this
241                    //       property; this assignment was first proposed in [6].  In the few cases
242                    //       where a host has been assigned an address with a host-number part of
243                    //       all ones, it does not seem onerous to require renumbering.
244                    // We require that the subnet contain more than one address (i.e. that the
245                    // prefix length is not 32) in order to decide that an address is a subnet
246                    // broadcast address.
247                    } else if subnet.prefix() < Ipv4Addr::BYTES * 8 && subnet.broadcast() == address
248                    {
249                        BroadcastCase::Subnet(())
250                    } else {
251                        BroadcastCase::NotBroadcast
252                    }
253                },
254                // IPv6 has no notion of "broadcast".
255                |(_address, _subnet)| BroadcastCase::NotBroadcast,
256            );
257
258            let next_hop = match broadcast_case {
259                // Always broadcast to the all-ones destination.
260                BroadcastCase::AllOnes(marker) => NextHop::Broadcast(marker),
261                // Only broadcast to the subnet broadcast address if the route does not have a
262                // gateway.
263                BroadcastCase::Subnet(marker) => {
264                    gateway.map_or(NextHop::Broadcast(marker), NextHop::Gateway)
265                }
266                BroadcastCase::NotBroadcast => {
267                    gateway.map_or(NextHop::RemoteAsNeighbor, NextHop::Gateway)
268                }
269            };
270
271            Some(Destination { next_hop, device })
272        });
273
274        bound_device_all_ones_broadcast_exemption.chain(viable_table_entries).filter_map(
275            move |destination| {
276                let device = &destination.device;
277                if !core_ctx.is_ip_device_enabled(device) {
278                    return None;
279                }
280                f(core_ctx, device).map(|r| (destination, r))
281            },
282        )
283    }
284}
285
286/// Tells whether a non local IP address is allowed as the source address for route selection.
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum NonLocalSrcAddrPolicy {
289    /// Allows to use a non local IP address as source.
290    Allow,
291    /// Denies using a non local IP address as source.
292    Deny,
293}
294
295/// Tells whether the packet being routed is generated by us or someone else.
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub enum PacketOrigin<I: Ip, D> {
298    /// This packet is generated by us.
299    Local {
300        /// The generating socket's bound address, if any.
301        bound_address: Option<SpecifiedAddr<I::Addr>>,
302        /// The generating socket's bound device, if any.
303        bound_device: Option<D>,
304    },
305    /// This packet is received/forwarded to us.
306    NonLocal {
307        /// The packet's source address. Note that this must be specified because we don't allow
308        /// forwarding a packet with an unspecified source IP address.
309        source_address: SpecifiedAddr<I::Addr>,
310        /// The device the packet was received on.
311        incoming_device: D,
312    },
313}
314
315#[cfg(any(test, feature = "testutils"))]
316pub(crate) mod testutil {
317    use derivative::Derivative;
318    use net_types::ip::IpAddress;
319    use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
320    use netstack3_base::{MatcherBindingsTypes, NotFoundError, StrongDeviceIdentifier};
321    use netstack3_hashmap::HashSet;
322
323    use crate::internal::base::{IpRouteTablesContext, IpStateContext};
324    use crate::internal::routing::rules::Rule;
325    use crate::internal::types::{AddableMetric, Generation, Metric, RoutePreference};
326
327    use super::*;
328
329    impl<TimerId: Debug, Event: Debug, State, FrameMeta> IpRoutingBindingsTypes
330        for FakeBindingsCtx<TimerId, Event, State, FrameMeta>
331    {
332        type RoutingTableId = ();
333    }
334
335    // Converts the given [`AddableMetric`] into the corresponding [`Metric`],
336    // observing the device's metric, if applicable.
337    fn observe_metric<I: Ip, CC: IpRoutingDeviceContext<I>>(
338        core_ctx: &mut CC,
339        device: &CC::DeviceId,
340        metric: AddableMetric,
341    ) -> Metric {
342        match metric {
343            AddableMetric::ExplicitMetric(value) => Metric::ExplicitMetric(value),
344            AddableMetric::MetricTracksInterface => {
345                Metric::MetricTracksInterface(core_ctx.get_routing_metric(device))
346            }
347        }
348    }
349
350    /// Add a route directly to the routing table, instead of merely
351    /// dispatching an event requesting that the route be added.
352    pub fn add_route<
353        I: IpLayerIpExt,
354        BT: IpRoutingBindingsTypes + MatcherBindingsTypes,
355        CC: IpStateContext<I, BT>,
356    >(
357        core_ctx: &mut CC,
358        entry: AddableEntry<I::Addr, CC::DeviceId>,
359    ) -> Result<(), AddRouteError>
360    where
361        CC::DeviceId: PartialOrd,
362    {
363        let AddableEntry { subnet, device, gateway, metric, route_preference } = entry;
364        core_ctx.with_main_ip_routing_table_mut(|core_ctx, table| {
365            let metric = observe_metric(core_ctx, &device, metric);
366            let _entry = table.add_entry(EntryAndGeneration {
367                entry: Entry { subnet, device, gateway, metric, route_preference },
368                generation: Generation::initial(),
369            })?;
370            Ok(())
371        })
372    }
373
374    /// Install and replace any existing rules.
375    pub fn set_rules<
376        I: IpLayerIpExt,
377        BC: IpLayerBindingsContext<I, CC::DeviceId>,
378        CC: IpStateContext<I, BC>,
379    >(
380        core_ctx: &mut CC,
381        rules: Vec<Rule<I, CC::DeviceId, BC>>,
382    ) {
383        core_ctx.with_rules_table_mut(|_core_ctx, rules_table| {
384            *rules_table.rules_mut() = rules;
385        })
386    }
387
388    /// Delete all routes to a subnet, returning `Err` if no route was found to
389    /// be deleted.
390    ///
391    /// Note, `del_routes_to_subnet` will remove *all* routes to a
392    /// `subnet`, including routes that consider `subnet` on-link for some device
393    /// and routes that require packets destined to a node within `subnet` to be
394    /// routed through some next-hop node.
395    // TODO(https://fxbug.dev/42077399): Unify this with other route removal methods.
396    pub fn del_routes_to_subnet<
397        I: IpLayerIpExt,
398        BT: IpRoutingBindingsTypes,
399        CC: IpRouteTablesContext<I, BT>,
400    >(
401        core_ctx: &mut CC,
402        del_subnet: Subnet<I::Addr>,
403    ) -> Result<(), NotFoundError> {
404        core_ctx.with_main_ip_routing_table_mut(|_core_ctx, table| {
405            let removed = table.del_entries(
406                |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
407                    subnet == &del_subnet
408                },
409            );
410            if removed.is_empty() {
411                return Err(NotFoundError);
412            } else {
413                Ok(())
414            }
415        })
416    }
417
418    /// Deletes all routes referencing `del_device` from the routing table.
419    pub fn del_device_routes<
420        I: IpLayerIpExt,
421        BT: IpRoutingBindingsTypes,
422        CC: IpRouteTablesContext<I, BT>,
423    >(
424        core_ctx: &mut CC,
425        del_device: &CC::DeviceId,
426    ) {
427        debug!("deleting routes on device: {del_device:?}");
428
429        let _: Vec<_> = core_ctx.with_main_ip_routing_table_mut(|_core_ctx, table| {
430            table.del_entries(
431                |Entry { subnet: _, device, gateway: _, metric: _, route_preference: _ }| {
432                    device == del_device
433                },
434            )
435        });
436    }
437
438    /// Adds an on-link routing entry for the specified address and device.
439    pub(crate) fn add_on_link_routing_entry<A: IpAddress, D: Clone + Debug + PartialEq + Ord>(
440        table: &mut RoutingTable<A::Version, D>,
441        ip: SpecifiedAddr<A>,
442        device: D,
443    ) where
444        A::Version: BroadcastIpExt,
445    {
446        let subnet = Subnet::new(*ip, A::BYTES * 8).unwrap();
447        let entry = Entry {
448            subnet,
449            device,
450            gateway: None,
451            metric: Metric::ExplicitMetric(RawMetric(0)),
452            route_preference: RoutePreference::Medium,
453        };
454        assert_eq!(add_entry(table, entry.clone()), Ok(&entry));
455    }
456
457    // Provide tests with access to the private `RoutingTable.add_entry` fn.
458    pub(crate) fn add_entry<I: BroadcastIpExt, D: Clone + Debug + PartialEq + Ord>(
459        table: &mut RoutingTable<I, D>,
460        entry: Entry<I::Addr, D>,
461    ) -> Result<&Entry<I::Addr, D>, ExistsError> {
462        table
463            .add_entry(EntryAndGeneration { entry, generation: Generation::initial() })
464            .map(|entry| &entry.entry)
465    }
466
467    #[derive(Derivative)]
468    #[derivative(Default(bound = ""))]
469    pub(crate) struct FakeIpRoutingContext<D> {
470        disabled_devices: HashSet<D>,
471    }
472
473    impl<D> FakeIpRoutingContext<D> {
474        #[cfg(test)]
475        pub(crate) fn disabled_devices_mut(&mut self) -> &mut HashSet<D> {
476            &mut self.disabled_devices
477        }
478    }
479
480    pub(crate) type FakeIpRoutingCtx<D> = FakeCoreCtx<FakeIpRoutingContext<D>, (), D>;
481
482    impl<I: Ip, D: StrongDeviceIdentifier> IpRoutingDeviceContext<I> for FakeIpRoutingCtx<D>
483    where
484        Self: DeviceIdContext<AnyDevice, DeviceId = D>,
485    {
486        fn get_routing_metric(&mut self, _device_id: &Self::DeviceId) -> RawMetric {
487            unimplemented!()
488        }
489
490        fn is_ip_device_enabled(&mut self, device_id: &Self::DeviceId) -> bool {
491            !self.state.disabled_devices.contains(device_id)
492        }
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use ip_test_macro::ip_test;
499    use itertools::Itertools;
500    use log::trace;
501    use net_declare::{net_ip_v4, net_ip_v6, net_subnet_v4, net_subnet_v6};
502    use net_types::ip::{Ipv6, Ipv6Addr};
503    use netstack3_base::testutil::{MultipleDevicesId, TestAddrs};
504    use netstack3_hashmap::HashSet;
505    use test_case::test_case;
506
507    use super::*;
508    use crate::internal::routing::testutil::FakeIpRoutingCtx;
509    use crate::internal::types::{Metric, RoutePreference};
510
511    type FakeCtx = FakeIpRoutingCtx<MultipleDevicesId>;
512
513    impl<I: BroadcastIpExt, D: Clone + Debug + PartialEq> RoutingTable<I, D> {
514        /// Print the table.
515        fn print_table(&self) {
516            trace!("Installed Routing table:");
517
518            if self.table.is_empty() {
519                trace!("    No Routes");
520                return;
521            }
522
523            for entry in self.iter_table() {
524                trace!("    {}", entry)
525            }
526        }
527    }
528
529    trait TestIpExt: netstack3_base::testutil::TestIpExt + BroadcastIpExt {
530        fn subnet(v: u8, neg_prefix: u8) -> Subnet<Self::Addr>;
531
532        fn next_hop_addr_sub(
533            v: u8,
534            neg_prefix: u8,
535        ) -> (SpecifiedAddr<Self::Addr>, Subnet<Self::Addr>);
536    }
537
538    impl TestIpExt for Ipv4 {
539        fn subnet(v: u8, neg_prefix: u8) -> Subnet<Ipv4Addr> {
540            Subnet::new(Ipv4Addr::new([v, 0, 0, 0]), 32 - neg_prefix).unwrap()
541        }
542
543        fn next_hop_addr_sub(v: u8, neg_prefix: u8) -> (SpecifiedAddr<Ipv4Addr>, Subnet<Ipv4Addr>) {
544            (SpecifiedAddr::new(Ipv4Addr::new([v, 0, 0, 1])).unwrap(), Ipv4::subnet(v, neg_prefix))
545        }
546    }
547
548    impl TestIpExt for Ipv6 {
549        fn subnet(v: u8, neg_prefix: u8) -> Subnet<Ipv6Addr> {
550            Subnet::new(
551                Ipv6Addr::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, v, 0, 0, 0]),
552                128 - neg_prefix,
553            )
554            .unwrap()
555        }
556
557        fn next_hop_addr_sub(v: u8, neg_prefix: u8) -> (SpecifiedAddr<Ipv6Addr>, Subnet<Ipv6Addr>) {
558            (
559                SpecifiedAddr::new(Ipv6Addr::from([
560                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, v, 0, 0, 1,
561                ]))
562                .unwrap(),
563                Ipv6::subnet(v, neg_prefix),
564            )
565        }
566    }
567
568    fn simple_setup<I: TestIpExt>() -> (
569        RoutingTable<I, MultipleDevicesId>,
570        TestAddrs<I::Addr>,
571        SpecifiedAddr<I::Addr>,
572        Subnet<I::Addr>,
573        MultipleDevicesId,
574        Metric,
575    ) {
576        let mut table = RoutingTable::<I, MultipleDevicesId>::default();
577
578        let config = I::TEST_ADDRS;
579        let subnet = config.subnet;
580        let device = MultipleDevicesId::A;
581        // `neg_prefix` passed here must be at least 2 (as with a neg_prefix of
582        // 1 we end up constructing the broadcast address instead).
583        let (next_hop, next_hop_subnet) = I::next_hop_addr_sub(1, 2);
584        let metric = Metric::ExplicitMetric(RawMetric(9999));
585
586        // Should add the route successfully.
587        let entry = Entry {
588            subnet,
589            device: device.clone(),
590            gateway: None,
591            metric,
592            route_preference: RoutePreference::Medium,
593        };
594        assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
595        assert_eq!(table.iter_table().collect::<Vec<_>>(), &[&entry]);
596
597        // Attempting to add the route again should fail.
598        assert_eq!(super::testutil::add_entry(&mut table, entry.clone()).unwrap_err(), ExistsError);
599        assert_eq!(table.iter_table().collect::<Vec<_>>(), &[&entry]);
600
601        // Add the route but as a next hop route.
602        let entry2 = Entry {
603            subnet: next_hop_subnet,
604            device: device.clone(),
605            gateway: None,
606            metric,
607            route_preference: RoutePreference::Medium,
608        };
609        assert_eq!(super::testutil::add_entry(&mut table, entry2.clone()), Ok(&entry2));
610        let entry3 = Entry {
611            subnet: subnet,
612            device: device.clone(),
613            gateway: Some(next_hop),
614            metric,
615            route_preference: RoutePreference::Medium,
616        };
617        assert_eq!(super::testutil::add_entry(&mut table, entry3.clone()), Ok(&entry3));
618        assert_eq!(
619            table.iter_table().collect::<HashSet<_>>(),
620            HashSet::from([&entry, &entry2, &entry3])
621        );
622
623        // Attempting to add the route again should fail.
624        assert_eq!(
625            super::testutil::add_entry(&mut table, entry3.clone()).unwrap_err(),
626            ExistsError
627        );
628        assert_eq!(
629            table.iter_table().collect::<HashSet<_>>(),
630            HashSet::from([&entry, &entry2, &entry3,])
631        );
632
633        (table, config, next_hop, next_hop_subnet, device, metric)
634    }
635
636    #[ip_test(I)]
637    fn test_simple_add_del<I: TestIpExt>() {
638        let (mut table, config, next_hop, next_hop_subnet, device, metric) = simple_setup::<I>();
639        assert_eq!(table.iter_table().count(), 3);
640
641        // Delete all routes to subnet.
642        assert_eq!(
643            table
644                .del_entries(
645                    |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
646                        subnet == &config.subnet
647                    }
648                )
649                .into_iter()
650                .collect::<HashSet<_>>(),
651            HashSet::from([
652                Entry {
653                    subnet: config.subnet,
654                    device: device.clone(),
655                    gateway: None,
656                    metric,
657                    route_preference: RoutePreference::Medium
658                },
659                Entry {
660                    subnet: config.subnet,
661                    device: device.clone(),
662                    gateway: Some(next_hop),
663                    metric,
664                    route_preference: RoutePreference::Medium,
665                }
666            ])
667        );
668
669        assert_eq!(
670            table.iter_table().collect::<Vec<_>>(),
671            &[&Entry {
672                subnet: next_hop_subnet,
673                device: device.clone(),
674                gateway: None,
675                metric,
676                route_preference: RoutePreference::Medium
677            }]
678        );
679    }
680
681    #[ip_test(I)]
682    fn test_simple_lookup<I: TestIpExt>() {
683        let (mut table, config, next_hop, _next_hop_subnet, device, metric) = simple_setup::<I>();
684        let mut core_ctx = FakeCtx::default();
685
686        // Do lookup for our next hop (should be the device).
687        assert_eq!(
688            table.lookup(&mut core_ctx, None, *next_hop),
689            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
690        );
691
692        // Do lookup for some address within `subnet`.
693        assert_eq!(
694            table.lookup(&mut core_ctx, None, *config.local_ip),
695            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
696        );
697        assert_eq!(
698            table.lookup(&mut core_ctx, None, *config.remote_ip),
699            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
700        );
701
702        // Add a default route to facilitate testing the limited broadcast address.
703        // Without a default route being present, the all-ones broadcast address won't match any
704        // route's destination subnet, so the route lookup will fail.
705        let default_route_entry = Entry {
706            subnet: Subnet::new(I::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
707            device: device.clone(),
708            gateway: None,
709            metric,
710            route_preference: RoutePreference::Medium,
711        };
712        assert_eq!(
713            super::testutil::add_entry(&mut table, default_route_entry.clone()),
714            Ok(&default_route_entry)
715        );
716
717        // Do lookup for broadcast addresses.
718        I::map_ip::<_, ()>(
719            (&table, &config),
720            |(table, config)| {
721                assert_eq!(
722                    table.lookup(&mut core_ctx, None, config.subnet.broadcast()),
723                    Some(Destination { next_hop: NextHop::Broadcast(()), device: device.clone() })
724                );
725
726                assert_eq!(
727                    table.lookup(&mut core_ctx, None, Ipv4::LIMITED_BROADCAST_ADDRESS.get()),
728                    Some(Destination { next_hop: NextHop::Broadcast(()), device: device.clone() })
729                );
730            },
731            |(_table, _config)| {
732                // Do nothing since IPv6 doesn't have broadcast.
733            },
734        );
735
736        // Remove the default route.
737        assert_eq!(
738            table
739                .del_entries(
740                    |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
741                        subnet.prefix() == 0
742                    }
743                )
744                .into_iter()
745                .collect::<Vec<_>>(),
746            alloc::vec![default_route_entry.clone()]
747        );
748
749        // Delete routes to the subnet and make sure that we can no longer route
750        // to destinations in the subnet.
751        assert_eq!(
752            table
753                .del_entries(
754                    |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
755                        subnet == &config.subnet
756                    }
757                )
758                .into_iter()
759                .collect::<HashSet<_>>(),
760            HashSet::from([
761                Entry {
762                    subnet: config.subnet,
763                    device: device.clone(),
764                    gateway: None,
765                    metric,
766                    route_preference: RoutePreference::Medium
767                },
768                Entry {
769                    subnet: config.subnet,
770                    device: device.clone(),
771                    gateway: Some(next_hop),
772                    metric,
773                    route_preference: RoutePreference::Medium,
774                }
775            ])
776        );
777        assert_eq!(
778            table.lookup(&mut core_ctx, None, *next_hop),
779            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
780        );
781        assert_eq!(table.lookup(&mut core_ctx, None, *config.local_ip), None);
782        assert_eq!(table.lookup(&mut core_ctx, None, *config.remote_ip), None);
783        I::map_ip::<_, ()>(
784            (&table, &config),
785            |(table, config)| {
786                assert_eq!(table.lookup(&mut core_ctx, None, config.subnet.broadcast()), None);
787                assert_eq!(
788                    table.lookup(&mut core_ctx, None, Ipv4::LIMITED_BROADCAST_ADDRESS.get()),
789                    None
790                );
791            },
792            |(_table, _config)| {
793                // Do nothing since IPv6 doesn't have broadcast.
794            },
795        );
796
797        // Make the subnet routable again but through a gateway.
798        let gateway_entry = Entry {
799            subnet: config.subnet,
800            device: device.clone(),
801            gateway: Some(next_hop),
802            metric: Metric::ExplicitMetric(RawMetric(0)),
803            route_preference: RoutePreference::Medium,
804        };
805        assert_eq!(
806            super::testutil::add_entry(&mut table, gateway_entry.clone()),
807            Ok(&gateway_entry)
808        );
809        assert_eq!(
810            table.lookup(&mut core_ctx, None, *next_hop),
811            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
812        );
813        assert_eq!(
814            table.lookup(&mut core_ctx, None, *config.local_ip),
815            Some(Destination { next_hop: NextHop::Gateway(next_hop), device: device.clone() })
816        );
817        assert_eq!(
818            table.lookup(&mut core_ctx, None, *config.remote_ip),
819            Some(Destination { next_hop: NextHop::Gateway(next_hop), device: device.clone() })
820        );
821
822        // Add a default route to facilitate testing the limited broadcast address.
823        let default_route_entry = Entry {
824            subnet: Subnet::new(I::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
825            device: device.clone(),
826            gateway: Some(next_hop),
827            metric,
828            route_preference: RoutePreference::Medium,
829        };
830        assert_eq!(
831            super::testutil::add_entry(&mut table, default_route_entry.clone()),
832            Ok(&default_route_entry)
833        );
834
835        // Do lookup for broadcast addresses.
836        I::map_ip::<_, ()>(
837            (&table, &config, next_hop),
838            |(table, config, next_hop)| {
839                assert_eq!(
840                    table.lookup(&mut core_ctx, None, config.subnet.broadcast()),
841                    Some(Destination {
842                        next_hop: NextHop::Gateway(next_hop),
843                        device: device.clone()
844                    })
845                );
846
847                assert_eq!(
848                    table.lookup(&mut core_ctx, None, Ipv4::LIMITED_BROADCAST_ADDRESS.get()),
849                    Some(Destination { next_hop: NextHop::Broadcast(()), device: device.clone() })
850                );
851            },
852            |(_table, _config, _next_hop)| {
853                // Do nothing since IPv6 doesn't have broadcast.
854            },
855        );
856    }
857
858    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
859    enum BroadcastCaseNextHop {
860        Neighbor,
861        Gateway,
862    }
863
864    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
865    enum LookupResultNextHop {
866        Neighbor,
867        Gateway,
868        Broadcast,
869    }
870
871    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
872    struct LookupResult {
873        next_hop: LookupResultNextHop,
874        device: MultipleDevicesId,
875    }
876
877    #[test_case::test_matrix(
878        [None, Some(BroadcastCaseNextHop::Neighbor), Some(BroadcastCaseNextHop::Gateway)],
879        [None, Some(MultipleDevicesId::A), Some(MultipleDevicesId::B)]
880    )]
881    fn all_ones_broadcast_lookup(
882        default_route: Option<BroadcastCaseNextHop>,
883        bind_device: Option<MultipleDevicesId>,
884    ) {
885        let mut core_ctx = FakeCtx::default();
886        let expected_lookup_result = match (default_route, bind_device) {
887            // Sending to all-ones with a bound device always results in a broadcast.
888            (_, Some(device)) => {
889                Some(LookupResult { next_hop: LookupResultNextHop::Broadcast, device })
890            }
891            // With no matching route and no bound device, we don't know where to broadcast to,
892            // so the lookup fails.
893            (None, None) => None,
894            (Some(_next_hop), None) => {
895                // Regardless of the default route's configured next hop, sending to all-ones
896                // should result in a broadcast.
897                Some(LookupResult {
898                    next_hop: LookupResultNextHop::Broadcast,
899                    device: MultipleDevicesId::A,
900                })
901            }
902        };
903
904        let mut table = RoutingTable::<Ipv4, MultipleDevicesId>::default();
905        if let Some(next_hop) = default_route {
906            let entry = Entry {
907                subnet: Subnet::new(Ipv4::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
908                device: MultipleDevicesId::A,
909                gateway: match next_hop {
910                    BroadcastCaseNextHop::Neighbor => None,
911                    BroadcastCaseNextHop::Gateway => {
912                        Some(SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap())
913                    }
914                },
915                metric: Metric::ExplicitMetric(RawMetric(0)),
916                route_preference: RoutePreference::Medium,
917            };
918            assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
919        }
920
921        let got_lookup_result = table
922            .lookup(&mut core_ctx, bind_device.as_ref(), Ipv4::LIMITED_BROADCAST_ADDRESS.get())
923            .map(|Destination { next_hop, device }| LookupResult {
924                next_hop: match next_hop {
925                    NextHop::RemoteAsNeighbor => LookupResultNextHop::Neighbor,
926                    NextHop::Gateway(_) => LookupResultNextHop::Gateway,
927                    NextHop::Broadcast(()) => LookupResultNextHop::Broadcast,
928                },
929                device,
930            });
931
932        assert_eq!(got_lookup_result, expected_lookup_result);
933    }
934
935    #[test_case::test_matrix(
936        [None, Some(BroadcastCaseNextHop::Neighbor), Some(BroadcastCaseNextHop::Gateway)],
937        [None, Some(BroadcastCaseNextHop::Neighbor), Some(BroadcastCaseNextHop::Gateway)],
938        [None, Some(MultipleDevicesId::A), Some(MultipleDevicesId::B)]
939    )]
940    fn subnet_broadcast_lookup(
941        default_route: Option<BroadcastCaseNextHop>,
942        subnet_route: Option<BroadcastCaseNextHop>,
943        bind_device: Option<MultipleDevicesId>,
944    ) {
945        let mut core_ctx = FakeCtx::default();
946        let expected_lookup_result = match bind_device {
947            // Binding to a device not matching any routes in the table will fail the lookup.
948            Some(MultipleDevicesId::B) | Some(MultipleDevicesId::C) => None,
949            Some(MultipleDevicesId::A) | None => match (default_route, subnet_route) {
950                // No matching routes.
951                (None, None) => None,
952                // The subnet route will take precedence over the default route.
953                (None | Some(_), Some(next_hop)) => {
954                    Some(LookupResult {
955                        device: MultipleDevicesId::A,
956                        next_hop: match next_hop {
957                            // Allow broadcasting when this route is on-link.
958                            BroadcastCaseNextHop::Neighbor => LookupResultNextHop::Broadcast,
959                            // Continue to unicast when the route has a gateway, even though this is
960                            // the subnet's broadcast address.
961                            BroadcastCaseNextHop::Gateway => LookupResultNextHop::Gateway,
962                        },
963                    })
964                }
965                (Some(next_hop), None) => {
966                    Some(LookupResult {
967                        device: MultipleDevicesId::A,
968                        next_hop: match next_hop {
969                            // Since this is just matching the default route, it looks like
970                            // a regular unicast route rather than a broadcast one.
971                            BroadcastCaseNextHop::Neighbor => LookupResultNextHop::Neighbor,
972                            BroadcastCaseNextHop::Gateway => LookupResultNextHop::Gateway,
973                        },
974                    })
975                }
976            },
977        };
978
979        let subnet = net_declare::net_subnet_v4!("192.168.0.0/24");
980        let gateway = SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap();
981
982        let mut table = RoutingTable::<Ipv4, MultipleDevicesId>::default();
983        if let Some(next_hop) = default_route {
984            let entry = Entry {
985                subnet: Subnet::new(Ipv4::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
986                device: MultipleDevicesId::A,
987                gateway: match next_hop {
988                    BroadcastCaseNextHop::Neighbor => None,
989                    BroadcastCaseNextHop::Gateway => Some(gateway),
990                },
991                metric: Metric::ExplicitMetric(RawMetric(0)),
992                route_preference: RoutePreference::Medium,
993            };
994            assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
995        }
996
997        if let Some(next_hop) = subnet_route {
998            let entry = Entry {
999                subnet,
1000                device: MultipleDevicesId::A,
1001                gateway: match next_hop {
1002                    BroadcastCaseNextHop::Neighbor => None,
1003                    BroadcastCaseNextHop::Gateway => Some(gateway),
1004                },
1005                metric: Metric::ExplicitMetric(RawMetric(0)),
1006                route_preference: RoutePreference::Medium,
1007            };
1008            assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
1009        }
1010
1011        let got_lookup_result = table
1012            .lookup(&mut core_ctx, bind_device.as_ref(), subnet.broadcast())
1013            .map(|Destination { next_hop, device }| LookupResult {
1014                next_hop: match next_hop {
1015                    NextHop::RemoteAsNeighbor => LookupResultNextHop::Neighbor,
1016                    NextHop::Gateway(_) => LookupResultNextHop::Gateway,
1017                    NextHop::Broadcast(()) => LookupResultNextHop::Broadcast,
1018                },
1019                device,
1020            });
1021
1022        assert_eq!(got_lookup_result, expected_lookup_result);
1023    }
1024
1025    #[ip_test(I)]
1026    fn test_default_route_ip<I: TestIpExt>() {
1027        let mut core_ctx = FakeCtx::default();
1028        let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1029        let device0 = MultipleDevicesId::A;
1030        let (addr1, sub1) = I::next_hop_addr_sub(1, 24);
1031        let (addr2, _) = I::next_hop_addr_sub(2, 24);
1032        let (addr3, _) = I::next_hop_addr_sub(3, 24);
1033        let metric = Metric::ExplicitMetric(RawMetric(0));
1034
1035        // Add the following routes:
1036        //  sub1 -> device0
1037        //
1038        // Our expected routing table should look like:
1039        //  sub1 -> device0
1040
1041        let entry = Entry {
1042            subnet: sub1,
1043            device: device0.clone(),
1044            gateway: None,
1045            metric,
1046            route_preference: RoutePreference::Medium,
1047        };
1048        assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
1049        table.print_table();
1050        assert_eq!(
1051            table.lookup(&mut core_ctx, None, *addr1).unwrap(),
1052            Destination { next_hop: NextHop::RemoteAsNeighbor, device: device0.clone() }
1053        );
1054        assert_eq!(table.lookup(&mut core_ctx, None, *addr2), None);
1055
1056        // Add a default route.
1057        //
1058        // Our expected routing table should look like:
1059        //  sub1 -> device0
1060        //  default -> addr1 w/ device0
1061
1062        let default_sub = Subnet::new(I::UNSPECIFIED_ADDRESS, 0).unwrap();
1063        let default_entry = Entry {
1064            subnet: default_sub,
1065            device: device0.clone(),
1066            gateway: Some(addr1),
1067            metric,
1068            route_preference: RoutePreference::Medium,
1069        };
1070
1071        assert_eq!(
1072            super::testutil::add_entry(&mut table, default_entry.clone()),
1073            Ok(&default_entry)
1074        );
1075        assert_eq!(
1076            table.lookup(&mut core_ctx, None, *addr1).unwrap(),
1077            Destination { next_hop: NextHop::RemoteAsNeighbor, device: device0.clone() }
1078        );
1079        assert_eq!(
1080            table.lookup(&mut core_ctx, None, *addr2).unwrap(),
1081            Destination { next_hop: NextHop::Gateway(addr1), device: device0.clone() }
1082        );
1083        assert_eq!(
1084            table.lookup(&mut core_ctx, None, *addr3).unwrap(),
1085            Destination { next_hop: NextHop::Gateway(addr1), device: device0.clone() }
1086        );
1087        assert_eq!(
1088            table.lookup(&mut core_ctx, None, I::UNSPECIFIED_ADDRESS).unwrap(),
1089            Destination { next_hop: NextHop::Gateway(addr1), device: device0.clone() }
1090        );
1091    }
1092
1093    #[ip_test(I)]
1094    fn test_device_filter_with_varying_prefix_lengths<I: TestIpExt>() {
1095        const MORE_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::A;
1096        const LESS_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::B;
1097
1098        let mut core_ctx = FakeCtx::default();
1099        let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1100        // `neg_prefix` passed here must be at least 2 (as with a neg_prefix of
1101        // 1 we end up constructing the broadcast address instead).
1102        let (remote, more_specific_sub) = I::next_hop_addr_sub(1, 2);
1103        let less_specific_sub = {
1104            let (addr, sub) = I::next_hop_addr_sub(1, 3);
1105            assert_eq!(remote, addr);
1106            sub
1107        };
1108        let metric = Metric::ExplicitMetric(RawMetric(0));
1109        let less_specific_entry = Entry {
1110            subnet: less_specific_sub,
1111            device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1112            gateway: None,
1113            metric,
1114            route_preference: RoutePreference::Medium,
1115        };
1116        assert_eq!(
1117            super::testutil::add_entry(&mut table, less_specific_entry.clone()),
1118            Ok(&less_specific_entry)
1119        );
1120        assert_eq!(
1121            table.lookup(&mut core_ctx, None, *remote),
1122            Some(Destination {
1123                next_hop: NextHop::RemoteAsNeighbor,
1124                device: LESS_SPECIFIC_SUB_DEVICE.clone()
1125            }),
1126            "matches route"
1127        );
1128        assert_eq!(
1129            table.lookup(&mut core_ctx, Some(&LESS_SPECIFIC_SUB_DEVICE), *remote),
1130            Some(Destination {
1131                next_hop: NextHop::RemoteAsNeighbor,
1132                device: LESS_SPECIFIC_SUB_DEVICE.clone()
1133            }),
1134            "route matches specified device"
1135        );
1136        assert_eq!(
1137            table.lookup(&mut core_ctx, Some(&MORE_SPECIFIC_SUB_DEVICE), *remote),
1138            None,
1139            "no route with the specified device"
1140        );
1141
1142        let more_specific_entry = Entry {
1143            subnet: more_specific_sub,
1144            device: MORE_SPECIFIC_SUB_DEVICE.clone(),
1145            gateway: None,
1146            metric,
1147            route_preference: RoutePreference::Medium,
1148        };
1149        assert_eq!(
1150            super::testutil::add_entry(&mut table, more_specific_entry.clone()),
1151            Ok(&more_specific_entry)
1152        );
1153        assert_eq!(
1154            table.lookup(&mut core_ctx, None, *remote).unwrap(),
1155            Destination {
1156                next_hop: NextHop::RemoteAsNeighbor,
1157                device: MORE_SPECIFIC_SUB_DEVICE.clone()
1158            },
1159            "matches most specific route"
1160        );
1161        assert_eq!(
1162            table.lookup(&mut core_ctx, Some(&LESS_SPECIFIC_SUB_DEVICE), *remote),
1163            Some(Destination {
1164                next_hop: NextHop::RemoteAsNeighbor,
1165                device: LESS_SPECIFIC_SUB_DEVICE.clone()
1166            }),
1167            "matches less specific route with the specified device"
1168        );
1169        assert_eq!(
1170            table.lookup(&mut core_ctx, Some(&MORE_SPECIFIC_SUB_DEVICE), *remote).unwrap(),
1171            Destination {
1172                next_hop: NextHop::RemoteAsNeighbor,
1173                device: MORE_SPECIFIC_SUB_DEVICE.clone()
1174            },
1175            "matches the most specific route with the specified device"
1176        );
1177    }
1178
1179    #[ip_test(I)]
1180    fn test_lookup_filter_map<I: TestIpExt>() {
1181        let mut core_ctx = FakeCtx::default();
1182        let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1183
1184        // `neg_prefix` passed here must be at least 2 (as with a neg_prefix of
1185        // 1 we end up constructing the broadcast address instead).
1186        let (next_hop, more_specific_sub) = I::next_hop_addr_sub(1, 2);
1187        let less_specific_sub = {
1188            let (addr, sub) = I::next_hop_addr_sub(1, 3);
1189            assert_eq!(next_hop, addr);
1190            sub
1191        };
1192
1193        // MultipleDevicesId::A always has a more specific route than B or C.
1194        {
1195            let metric = Metric::ExplicitMetric(RawMetric(0));
1196            let more_specific_entry = Entry {
1197                subnet: more_specific_sub,
1198                device: MultipleDevicesId::A,
1199                gateway: None,
1200                metric,
1201                route_preference: RoutePreference::Medium,
1202            };
1203            let _: &_ =
1204                super::testutil::add_entry(&mut table, more_specific_entry).expect("was added");
1205        }
1206        // B and C have the same route but with different metrics.
1207        for (device, metric) in [(MultipleDevicesId::B, 100), (MultipleDevicesId::C, 200)] {
1208            let less_specific_entry = Entry {
1209                subnet: less_specific_sub,
1210                device,
1211                gateway: None,
1212                metric: Metric::ExplicitMetric(RawMetric(metric)),
1213                route_preference: RoutePreference::Medium,
1214            };
1215            let _: &_ =
1216                super::testutil::add_entry(&mut table, less_specific_entry).expect("was added");
1217        }
1218
1219        fn lookup_with_devices<I: BroadcastIpExt>(
1220            table: &RoutingTable<I, MultipleDevicesId>,
1221            next_hop: SpecifiedAddr<I::Addr>,
1222            core_ctx: &mut FakeCtx,
1223            devices: &[MultipleDevicesId],
1224        ) -> Vec<Destination<I::Addr, MultipleDevicesId>> {
1225            table
1226                .lookup_filter_map(core_ctx, None, *next_hop, |_, d| {
1227                    devices.iter().contains(d).then_some(())
1228                })
1229                .map(|(Destination { next_hop, device }, ())| Destination {
1230                    next_hop,
1231                    device: device.clone(),
1232                })
1233                .collect::<Vec<_>>()
1234        }
1235
1236        // Looking up the address without constraints should always give a route
1237        // through device A.
1238        assert_eq!(
1239            table.lookup(&mut core_ctx, None, *next_hop),
1240            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::A })
1241        );
1242        // Without filtering, we should get A, then B, then C.
1243        assert_eq!(
1244            lookup_with_devices(&table, next_hop, &mut core_ctx, &MultipleDevicesId::all()),
1245            &[
1246                Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::A },
1247                Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::B },
1248                Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::C },
1249            ]
1250        );
1251
1252        // If we filter out A, we get B and C.
1253        assert_eq!(
1254            lookup_with_devices(
1255                &table,
1256                next_hop,
1257                &mut core_ctx,
1258                &[MultipleDevicesId::B, MultipleDevicesId::C]
1259            ),
1260            &[
1261                Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::B },
1262                Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::C }
1263            ]
1264        );
1265
1266        // If we only allow C, we won't get the other devices.
1267        assert_eq!(
1268            lookup_with_devices(&table, next_hop, &mut core_ctx, &[MultipleDevicesId::C]),
1269            &[Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::C }]
1270        );
1271    }
1272
1273    #[ip_test(I)]
1274    fn test_multiple_routes_to_subnet_through_different_devices<I: TestIpExt>() {
1275        const DEVICE1: MultipleDevicesId = MultipleDevicesId::A;
1276        const DEVICE2: MultipleDevicesId = MultipleDevicesId::B;
1277
1278        let mut core_ctx = FakeCtx::default();
1279        let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1280        // `neg_prefix` passed here must be at least 2 (as with a neg_prefix of
1281        // 1 we end up constructing the broadcast address instead).
1282        let (remote, sub) = I::next_hop_addr_sub(1, 2);
1283        let metric = Metric::ExplicitMetric(RawMetric(0));
1284
1285        let entry1 = Entry {
1286            subnet: sub,
1287            device: DEVICE1.clone(),
1288            gateway: None,
1289            metric,
1290            route_preference: RoutePreference::Medium,
1291        };
1292        assert_eq!(super::testutil::add_entry(&mut table, entry1.clone()), Ok(&entry1));
1293        let entry2 = Entry {
1294            subnet: sub,
1295            device: DEVICE2.clone(),
1296            gateway: None,
1297            metric,
1298            route_preference: RoutePreference::Medium,
1299        };
1300        assert_eq!(super::testutil::add_entry(&mut table, entry2.clone()), Ok(&entry2));
1301        let lookup = table.lookup(&mut core_ctx, None, *remote);
1302        assert!(
1303            [
1304                Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE1.clone() }),
1305                Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE2.clone() })
1306            ]
1307            .contains(&lookup),
1308            "lookup = {:?}",
1309            lookup
1310        );
1311        assert_eq!(
1312            table.lookup(&mut core_ctx, Some(&DEVICE1), *remote),
1313            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE1.clone() }),
1314        );
1315        assert_eq!(
1316            table.lookup(&mut core_ctx, Some(&DEVICE2), *remote),
1317            Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE2.clone() }),
1318        );
1319    }
1320
1321    #[ip_test(I)]
1322    #[test_case(|core_ctx, device, device_unusable| {
1323        let disabled_devices = core_ctx.state.disabled_devices_mut();
1324        if device_unusable {
1325            let _: bool = disabled_devices.insert(device);
1326        } else {
1327            let _: bool = disabled_devices.remove(&device);
1328        }
1329    }; "device_disabled")]
1330    fn test_usable_device<I: TestIpExt>(set_inactive: fn(&mut FakeCtx, MultipleDevicesId, bool)) {
1331        const MORE_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::A;
1332        const LESS_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::B;
1333
1334        let mut core_ctx = FakeCtx::default();
1335        let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1336        // `neg_prefix` passed here must be at least 2 (as with a neg_prefix of
1337        // 1 we end up constructing the broadcast address instead).
1338        let (remote, more_specific_sub) = I::next_hop_addr_sub(1, 2);
1339        let less_specific_sub = {
1340            let (addr, sub) = I::next_hop_addr_sub(1, 3);
1341            assert_eq!(remote, addr);
1342            sub
1343        };
1344        let metric = Metric::ExplicitMetric(RawMetric(0));
1345
1346        let less_specific_entry = Entry {
1347            subnet: less_specific_sub,
1348            device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1349            gateway: None,
1350            metric,
1351            route_preference: RoutePreference::Medium,
1352        };
1353        assert_eq!(
1354            super::testutil::add_entry(&mut table, less_specific_entry.clone()),
1355            Ok(&less_specific_entry)
1356        );
1357        for (device_unusable, expected) in [
1358            // If the device is unusable, then we cannot use routes through it.
1359            (true, None),
1360            (
1361                false,
1362                Some(Destination {
1363                    next_hop: NextHop::RemoteAsNeighbor,
1364                    device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1365                }),
1366            ),
1367        ] {
1368            set_inactive(&mut core_ctx, LESS_SPECIFIC_SUB_DEVICE, device_unusable);
1369            assert_eq!(
1370                table.lookup(&mut core_ctx, None, *remote),
1371                expected,
1372                "device_unusable={}",
1373                device_unusable,
1374            );
1375        }
1376
1377        let more_specific_entry = Entry {
1378            subnet: more_specific_sub,
1379            device: MORE_SPECIFIC_SUB_DEVICE.clone(),
1380            gateway: None,
1381            metric,
1382            route_preference: RoutePreference::Medium,
1383        };
1384        assert_eq!(
1385            super::testutil::add_entry(&mut table, more_specific_entry.clone()),
1386            Ok(&more_specific_entry)
1387        );
1388        for (device_unusable, expected) in [
1389            (
1390                false,
1391                Some(Destination {
1392                    next_hop: NextHop::RemoteAsNeighbor,
1393                    device: MORE_SPECIFIC_SUB_DEVICE.clone(),
1394                }),
1395            ),
1396            // If the device is unusable, then we cannot use routes through it,
1397            // but can use routes through other (active) devices.
1398            (
1399                true,
1400                Some(Destination {
1401                    next_hop: NextHop::RemoteAsNeighbor,
1402                    device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1403                }),
1404            ),
1405        ] {
1406            set_inactive(&mut core_ctx, MORE_SPECIFIC_SUB_DEVICE, device_unusable);
1407            assert_eq!(
1408                table.lookup(&mut core_ctx, None, *remote),
1409                expected,
1410                "device_unusable={}",
1411                device_unusable,
1412            );
1413        }
1414
1415        // If no devices are usable, then we can't get a route.
1416        set_inactive(&mut core_ctx, LESS_SPECIFIC_SUB_DEVICE, true);
1417        assert_eq!(table.lookup(&mut core_ctx, None, *remote), None,);
1418    }
1419
1420    #[ip_test(I)]
1421    fn test_add_entry_keeps_table_sorted<I: BroadcastIpExt>() {
1422        const DEVICE_A: MultipleDevicesId = MultipleDevicesId::A;
1423        const DEVICE_B: MultipleDevicesId = MultipleDevicesId::B;
1424        let (more_specific_sub, less_specific_sub) = I::map_ip(
1425            (),
1426            |()| (net_subnet_v4!("192.168.0.0/24"), net_subnet_v4!("192.168.0.0/16")),
1427            |()| (net_subnet_v6!("fe80::/64"), net_subnet_v6!("fe80::/16")),
1428        );
1429        let lower_metric = Metric::ExplicitMetric(RawMetric(0));
1430        let higher_metric = Metric::ExplicitMetric(RawMetric(1));
1431        let on_link = None;
1432        let off_link = Some(SpecifiedAddr::<I::Addr>::new(I::map_ip(
1433            (),
1434            |()| net_ip_v4!("192.168.0.1"),
1435            |()| net_ip_v6!("fe80::1"),
1436        )))
1437        .unwrap();
1438
1439        use RoutePreference::{High, Low, Medium};
1440
1441        const fn entry<I: Ip, D>(
1442            d: D,
1443            s: Subnet<I::Addr>,
1444            g: Option<SpecifiedAddr<I::Addr>>,
1445            p: RoutePreference,
1446            m: Metric,
1447        ) -> Entry<I::Addr, D> {
1448            Entry { device: d, subnet: s, metric: m, gateway: g, route_preference: p }
1449        }
1450
1451        // Expected sorted order: prefix, then locality (on-link > off-link),
1452        // then metric, then preference (High > Medium > Low), then insertion.
1453        let mut expected_table = alloc::vec![];
1454        for subnet in [more_specific_sub, less_specific_sub] {
1455            for locality in [on_link, off_link] {
1456                for metric in [lower_metric, higher_metric] {
1457                    for preference in [High, Medium, Low] {
1458                        for device in [DEVICE_A, DEVICE_B] {
1459                            expected_table
1460                                .push(entry::<I, _>(device, subnet, locality, preference, metric));
1461                        }
1462                    }
1463                }
1464            }
1465        }
1466
1467        let len = expected_table.len();
1468
1469        proptest::proptest!(
1470            move |(shuffle in proptest::sample::subsequence(expected_table.clone(), len))| {
1471                // the proptest macros want this.
1472                use alloc::format;
1473                let mut table = RoutingTable::<I, _>::default();
1474                for entry in shuffle.iter() {
1475                    proptest::prop_assert_eq!(
1476                        super::testutil::add_entry(&mut table, entry.clone()),
1477                        Ok(entry)
1478                    );
1479                }
1480                proptest::prop_assert_eq!(
1481                    table.iter_table().cloned().collect::<Vec<_>>(),
1482                    &expected_table[..]
1483                );
1484            }
1485        );
1486    }
1487}