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