1pub(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
22pub trait IpRoutingDeviceContext<I: Ip>: DeviceIdContext<AnyDevice> {
24 fn get_routing_metric(&mut self, device_id: &Self::DeviceId) -> RawMetric;
26
27 fn is_ip_device_enabled(&mut self, device_id: &Self::DeviceId) -> bool;
29}
30
31#[derive(Error, Debug, PartialEq)]
33pub enum AddRouteError {
34 #[error("already exists")]
36 AlreadyExists,
37
38 #[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
49pub 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
61pub 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#[derive(GenericOverIp)]
85#[generic_over_ip(I, Ip)]
86#[derive(Debug)]
87pub struct RoutingTable<I: Ip, D> {
88 pub(super) table: Vec<EntryAndGeneration<I::Addr, D>>,
98
99 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 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 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 return Err(ExistsError);
139 }
140
141 let ordered_entry: OrderedEntry<'_, _, _> = (&entry).into();
142 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 #[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 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 pub fn iter_table(&self) -> impl Iterator<Item = &Entry<I::Addr, D>> {
172 self.table.iter().map(|entry| &entry.entry)
173 }
174
175 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 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 if address == Ipv4::LIMITED_BROADCAST_ADDRESS.get() {
251 BroadcastCase::AllOnes(())
252 } else if subnet.prefix() < Ipv4Addr::BYTES * 8 && subnet.broadcast() == address
265 {
266 BroadcastCase::Subnet(())
267 } else {
268 BroadcastCase::NotBroadcast
269 }
270 },
271 |(_address, _subnet)| BroadcastCase::NotBroadcast,
273 );
274
275 let next_hop = match broadcast_case {
276 BroadcastCase::AllOnes(marker) => NextHop::Broadcast(marker),
278 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub enum NonLocalSrcAddrPolicy {
306 Allow,
308 Deny,
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
314pub enum PacketOrigin<I: Ip, D> {
315 Local {
317 bound_address: Option<SpecifiedAddr<I::Addr>>,
319 bound_device: Option<D>,
321 },
322 NonLocal {
324 source_address: SpecifiedAddr<I::Addr>,
327 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 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 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 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 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 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 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 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 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 let (next_hop, next_hop_subnet) = I::next_hop_addr_sub(1, 2);
573 let metric = Metric::ExplicitMetric(RawMetric(9999));
574
575 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 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()).unwrap_err(), ExistsError);
582 assert_eq!(table.iter_table().collect::<Vec<_>>(), &[&entry]);
583
584 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 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 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 assert_eq!(
646 table.lookup(&mut core_ctx, None, *next_hop),
647 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
648 );
649
650 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 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 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 },
691 );
692
693 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 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 },
741 );
742
743 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 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 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 },
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 (_, Some(device)) => {
833 Some(LookupResult { next_hop: LookupResultNextHop::Broadcast, device })
834 }
835 (None, None) => None,
838 (Some(_next_hop), None) => {
839 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 Some(MultipleDevicesId::B) | Some(MultipleDevicesId::C) => None,
892 Some(MultipleDevicesId::A) | None => match (default_route, subnet_route) {
893 (None, None) => None,
895 (None | Some(_), Some(next_hop)) => {
897 Some(LookupResult {
898 device: MultipleDevicesId::A,
899 next_hop: match next_hop {
900 BroadcastCaseNextHop::Neighbor => LookupResultNextHop::Broadcast,
902 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 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 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 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 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 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 {
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 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 assert_eq!(
1165 table.lookup(&mut core_ctx, None, *next_hop),
1166 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::A })
1167 );
1168 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 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 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 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 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 (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 (
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 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 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 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}