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