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
31pub trait IpRoutingBindingsTypes {
33 type RoutingTableId: Send + Sync + Debug + 'static;
36}
37
38#[derive(Error, Debug, PartialEq)]
40pub enum AddRouteError {
41 #[error("already exists")]
43 AlreadyExists,
44
45 #[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
56pub 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
68pub 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#[derive(GenericOverIp)]
92#[generic_over_ip(I, Ip)]
93#[derive(Debug)]
94pub struct RoutingTable<I: Ip, D> {
95 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 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 return Err(ExistsError);
130 }
131
132 let ordered_entry: OrderedEntry<'_, _, _> = (&entry).into();
133 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 #[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 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 pub fn iter_table(&self) -> impl Iterator<Item = &Entry<I::Addr, D>> {
163 self.table.iter().map(|entry| &entry.entry)
164 }
165
166 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 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 if address == Ipv4::LIMITED_BROADCAST_ADDRESS.get() {
242 BroadcastCase::AllOnes(())
243 } else if subnet.prefix() < Ipv4Addr::BYTES * 8 && subnet.broadcast() == address
256 {
257 BroadcastCase::Subnet(())
258 } else {
259 BroadcastCase::NotBroadcast
260 }
261 },
262 |(_address, _subnet)| BroadcastCase::NotBroadcast,
264 );
265
266 let next_hop = match broadcast_case {
267 BroadcastCase::AllOnes(marker) => NextHop::Broadcast(marker),
269 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum NonLocalSrcAddrPolicy {
297 Allow,
299 Deny,
301}
302
303#[derive(Debug, Clone, PartialEq, Eq)]
305pub enum PacketOrigin<I: Ip, D> {
306 Local {
308 bound_address: Option<SpecifiedAddr<I::Addr>>,
310 bound_device: Option<D>,
312 },
313 NonLocal {
315 source_address: SpecifiedAddr<I::Addr>,
318 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 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 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 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 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 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 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 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 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 let (next_hop, next_hop_subnet) = I::next_hop_addr_sub(1, 2);
584 let metric = Metric::ExplicitMetric(RawMetric(9999));
585
586 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 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()).unwrap_err(), ExistsError);
593 assert_eq!(table.iter_table().collect::<Vec<_>>(), &[&entry]);
594
595 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 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 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 assert_eq!(
657 table.lookup(&mut core_ctx, None, *next_hop),
658 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
659 );
660
661 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 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 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 },
702 );
703
704 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 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 },
752 );
753
754 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 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 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 },
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 (_, Some(device)) => {
844 Some(LookupResult { next_hop: LookupResultNextHop::Broadcast, device })
845 }
846 (None, None) => None,
849 (Some(_next_hop), None) => {
850 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 Some(MultipleDevicesId::B) | Some(MultipleDevicesId::C) => None,
903 Some(MultipleDevicesId::A) | None => match (default_route, subnet_route) {
904 (None, None) => None,
906 (None | Some(_), Some(next_hop)) => {
908 Some(LookupResult {
909 device: MultipleDevicesId::A,
910 next_hop: match next_hop {
911 BroadcastCaseNextHop::Neighbor => LookupResultNextHop::Broadcast,
913 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 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 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 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 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 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 {
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 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 assert_eq!(
1176 table.lookup(&mut core_ctx, None, *next_hop),
1177 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::A })
1178 );
1179 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 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 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 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 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 (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 (
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 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 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 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}