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>>,
103}
104
105impl<I: Ip, D> Default for RoutingTable<I, D> {
106 fn default() -> RoutingTable<I, D> {
107 RoutingTable { table: Vec::default() }
108 }
109}
110
111impl<I: BroadcastIpExt, D: Clone + Debug + PartialEq> RoutingTable<I, D> {
112 pub fn add_entry(
116 &mut self,
117 entry: EntryAndGeneration<I::Addr, D>,
118 ) -> Result<&EntryAndGeneration<I::Addr, D>, ExistsError>
119 where
120 D: PartialOrd,
121 {
122 debug!("adding route: {}", entry);
123 let Self { table } = self;
124
125 if table.contains(&entry) {
126 return Err(ExistsError);
128 }
129
130 let ordered_entry: OrderedEntry<'_, _, _> = (&entry).into();
131 let index = table.partition_point(|entry| ordered_entry.ge(&entry.into()));
134
135 table.insert(index, entry);
136
137 Ok(&table[index])
138 }
139
140 #[cfg(any(test, feature = "testutils"))]
144 fn del_entries<F: Fn(&Entry<I::Addr, D>) -> bool>(
145 &mut self,
146 predicate: F,
147 ) -> alloc::vec::Vec<Entry<I::Addr, D>> {
148 let Self { table } = self;
149 table.extract_if(.., |entry| predicate(&entry.entry)).map(|entry| entry.entry).collect()
150 }
151
152 pub fn iter_table(&self) -> impl Iterator<Item = &Entry<I::Addr, D>> {
155 self.table.iter().map(|entry| &entry.entry)
156 }
157
158 pub(crate) fn lookup<CC: IpRoutingDeviceContext<I, DeviceId = D>>(
169 &self,
170 core_ctx: &mut CC,
171 local_device: Option<&D>,
172 address: I::Addr,
173 ) -> Option<Destination<I::Addr, D>> {
174 self.lookup_filter_map(core_ctx, local_device, address, |_: &mut CC, _: &D| Some(()))
175 .map(|(Destination { device, next_hop }, ())| Destination {
176 device: device.clone(),
177 next_hop,
178 })
179 .next()
180 }
181
182 pub(crate) fn lookup_filter_map<'a, CC: IpRoutingDeviceContext<I, DeviceId = D>, R>(
183 &'a self,
184 core_ctx: &'a mut CC,
185 local_device: Option<&'a D>,
186 address: I::Addr,
187 mut f: impl FnMut(&mut CC, &D) -> Option<R> + 'a,
188 ) -> impl Iterator<Item = (Destination<I::Addr, &D>, R)> + 'a {
189 let Self { table } = self;
190
191 #[derive(GenericOverIp)]
192 #[generic_over_ip(I, Ip)]
193 enum BroadcastCase<I: BroadcastIpExt> {
194 AllOnes(I::BroadcastMarker),
195 Subnet(I::BroadcastMarker),
196 NotBroadcast,
197 }
198
199 let bound_device_all_ones_broadcast_exemption = core::iter::once_with(move || {
200 let local_device = local_device?;
203 let next_hop = I::map_ip::<_, Option<NextHop<I::Addr>>>(
204 address,
205 |address| {
206 (address == Ipv4::LIMITED_BROADCAST_ADDRESS.get())
207 .then_some(NextHop::Broadcast(()))
208 },
209 |_address| None,
210 )?;
211 Some(Destination { next_hop, device: local_device })
212 })
213 .filter_map(|x| x);
214
215 let viable_table_entries = table.iter().filter_map(move |entry| {
216 let EntryAndGeneration {
217 entry: Entry { subnet, device, gateway, metric: _, route_preference: _ },
218 generation: _,
219 } = entry;
220 if !subnet.contains(&address) {
221 return None;
222 }
223 if local_device.is_some_and(|local_device| local_device != device) {
224 return None;
225 }
226
227 let broadcast_case = I::map_ip::<_, BroadcastCase<I>>(
228 (address, *subnet),
229 |(address, subnet)| {
230 if address == Ipv4::LIMITED_BROADCAST_ADDRESS.get() {
234 BroadcastCase::AllOnes(())
235 } else if subnet.prefix() < Ipv4Addr::BYTES * 8 && subnet.broadcast() == address
248 {
249 BroadcastCase::Subnet(())
250 } else {
251 BroadcastCase::NotBroadcast
252 }
253 },
254 |(_address, _subnet)| BroadcastCase::NotBroadcast,
256 );
257
258 let next_hop = match broadcast_case {
259 BroadcastCase::AllOnes(marker) => NextHop::Broadcast(marker),
261 BroadcastCase::Subnet(marker) => {
264 gateway.map_or(NextHop::Broadcast(marker), NextHop::Gateway)
265 }
266 BroadcastCase::NotBroadcast => {
267 gateway.map_or(NextHop::RemoteAsNeighbor, NextHop::Gateway)
268 }
269 };
270
271 Some(Destination { next_hop, device })
272 });
273
274 bound_device_all_ones_broadcast_exemption.chain(viable_table_entries).filter_map(
275 move |destination| {
276 let device = &destination.device;
277 if !core_ctx.is_ip_device_enabled(device) {
278 return None;
279 }
280 f(core_ctx, device).map(|r| (destination, r))
281 },
282 )
283 }
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum NonLocalSrcAddrPolicy {
289 Allow,
291 Deny,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297pub enum PacketOrigin<I: Ip, D> {
298 Local {
300 bound_address: Option<SpecifiedAddr<I::Addr>>,
302 bound_device: Option<D>,
304 },
305 NonLocal {
307 source_address: SpecifiedAddr<I::Addr>,
310 incoming_device: D,
312 },
313}
314
315#[cfg(any(test, feature = "testutils"))]
316pub(crate) mod testutil {
317 use derivative::Derivative;
318 use net_types::ip::IpAddress;
319 use netstack3_base::testutil::{FakeBindingsCtx, FakeCoreCtx};
320 use netstack3_base::{MatcherBindingsTypes, NotFoundError, StrongDeviceIdentifier};
321 use netstack3_hashmap::HashSet;
322
323 use crate::internal::base::{IpRouteTablesContext, IpStateContext};
324 use crate::internal::routing::rules::Rule;
325 use crate::internal::types::{AddableMetric, Generation, Metric, RoutePreference};
326
327 use super::*;
328
329 impl<TimerId: Debug, Event: Debug, State, FrameMeta> IpRoutingBindingsTypes
330 for FakeBindingsCtx<TimerId, Event, State, FrameMeta>
331 {
332 type RoutingTableId = ();
333 }
334
335 fn observe_metric<I: Ip, CC: IpRoutingDeviceContext<I>>(
338 core_ctx: &mut CC,
339 device: &CC::DeviceId,
340 metric: AddableMetric,
341 ) -> Metric {
342 match metric {
343 AddableMetric::ExplicitMetric(value) => Metric::ExplicitMetric(value),
344 AddableMetric::MetricTracksInterface => {
345 Metric::MetricTracksInterface(core_ctx.get_routing_metric(device))
346 }
347 }
348 }
349
350 pub fn add_route<
353 I: IpLayerIpExt,
354 BT: IpRoutingBindingsTypes + MatcherBindingsTypes,
355 CC: IpStateContext<I, BT>,
356 >(
357 core_ctx: &mut CC,
358 entry: AddableEntry<I::Addr, CC::DeviceId>,
359 ) -> Result<(), AddRouteError>
360 where
361 CC::DeviceId: PartialOrd,
362 {
363 let AddableEntry { subnet, device, gateway, metric, route_preference } = entry;
364 core_ctx.with_main_ip_routing_table_mut(|core_ctx, table| {
365 let metric = observe_metric(core_ctx, &device, metric);
366 let _entry = table.add_entry(EntryAndGeneration {
367 entry: Entry { subnet, device, gateway, metric, route_preference },
368 generation: Generation::initial(),
369 })?;
370 Ok(())
371 })
372 }
373
374 pub fn set_rules<
376 I: IpLayerIpExt,
377 BC: IpLayerBindingsContext<I, CC::DeviceId>,
378 CC: IpStateContext<I, BC>,
379 >(
380 core_ctx: &mut CC,
381 rules: Vec<Rule<I, CC::DeviceId, BC>>,
382 ) {
383 core_ctx.with_rules_table_mut(|_core_ctx, rules_table| {
384 *rules_table.rules_mut() = rules;
385 })
386 }
387
388 pub fn del_routes_to_subnet<
397 I: IpLayerIpExt,
398 BT: IpRoutingBindingsTypes,
399 CC: IpRouteTablesContext<I, BT>,
400 >(
401 core_ctx: &mut CC,
402 del_subnet: Subnet<I::Addr>,
403 ) -> Result<(), NotFoundError> {
404 core_ctx.with_main_ip_routing_table_mut(|_core_ctx, table| {
405 let removed = table.del_entries(
406 |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
407 subnet == &del_subnet
408 },
409 );
410 if removed.is_empty() {
411 return Err(NotFoundError);
412 } else {
413 Ok(())
414 }
415 })
416 }
417
418 pub fn del_device_routes<
420 I: IpLayerIpExt,
421 BT: IpRoutingBindingsTypes,
422 CC: IpRouteTablesContext<I, BT>,
423 >(
424 core_ctx: &mut CC,
425 del_device: &CC::DeviceId,
426 ) {
427 debug!("deleting routes on device: {del_device:?}");
428
429 let _: Vec<_> = core_ctx.with_main_ip_routing_table_mut(|_core_ctx, table| {
430 table.del_entries(
431 |Entry { subnet: _, device, gateway: _, metric: _, route_preference: _ }| {
432 device == del_device
433 },
434 )
435 });
436 }
437
438 pub(crate) fn add_on_link_routing_entry<A: IpAddress, D: Clone + Debug + PartialEq + Ord>(
440 table: &mut RoutingTable<A::Version, D>,
441 ip: SpecifiedAddr<A>,
442 device: D,
443 ) where
444 A::Version: BroadcastIpExt,
445 {
446 let subnet = Subnet::new(*ip, A::BYTES * 8).unwrap();
447 let entry = Entry {
448 subnet,
449 device,
450 gateway: None,
451 metric: Metric::ExplicitMetric(RawMetric(0)),
452 route_preference: RoutePreference::Medium,
453 };
454 assert_eq!(add_entry(table, entry.clone()), Ok(&entry));
455 }
456
457 pub(crate) fn add_entry<I: BroadcastIpExt, D: Clone + Debug + PartialEq + Ord>(
459 table: &mut RoutingTable<I, D>,
460 entry: Entry<I::Addr, D>,
461 ) -> Result<&Entry<I::Addr, D>, ExistsError> {
462 table
463 .add_entry(EntryAndGeneration { entry, generation: Generation::initial() })
464 .map(|entry| &entry.entry)
465 }
466
467 #[derive(Derivative)]
468 #[derivative(Default(bound = ""))]
469 pub(crate) struct FakeIpRoutingContext<D> {
470 disabled_devices: HashSet<D>,
471 }
472
473 impl<D> FakeIpRoutingContext<D> {
474 #[cfg(test)]
475 pub(crate) fn disabled_devices_mut(&mut self) -> &mut HashSet<D> {
476 &mut self.disabled_devices
477 }
478 }
479
480 pub(crate) type FakeIpRoutingCtx<D> = FakeCoreCtx<FakeIpRoutingContext<D>, (), D>;
481
482 impl<I: Ip, D: StrongDeviceIdentifier> IpRoutingDeviceContext<I> for FakeIpRoutingCtx<D>
483 where
484 Self: DeviceIdContext<AnyDevice, DeviceId = D>,
485 {
486 fn get_routing_metric(&mut self, _device_id: &Self::DeviceId) -> RawMetric {
487 unimplemented!()
488 }
489
490 fn is_ip_device_enabled(&mut self, device_id: &Self::DeviceId) -> bool {
491 !self.state.disabled_devices.contains(device_id)
492 }
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use ip_test_macro::ip_test;
499 use itertools::Itertools;
500 use log::trace;
501 use net_declare::{net_ip_v4, net_ip_v6, net_subnet_v4, net_subnet_v6};
502 use net_types::ip::{Ipv6, Ipv6Addr};
503 use netstack3_base::testutil::{MultipleDevicesId, TestAddrs};
504 use netstack3_hashmap::HashSet;
505 use test_case::test_case;
506
507 use super::*;
508 use crate::internal::routing::testutil::FakeIpRoutingCtx;
509 use crate::internal::types::{Metric, RoutePreference};
510
511 type FakeCtx = FakeIpRoutingCtx<MultipleDevicesId>;
512
513 impl<I: BroadcastIpExt, D: Clone + Debug + PartialEq> RoutingTable<I, D> {
514 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 {
588 subnet,
589 device: device.clone(),
590 gateway: None,
591 metric,
592 route_preference: RoutePreference::Medium,
593 };
594 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
595 assert_eq!(table.iter_table().collect::<Vec<_>>(), &[&entry]);
596
597 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()).unwrap_err(), ExistsError);
599 assert_eq!(table.iter_table().collect::<Vec<_>>(), &[&entry]);
600
601 let entry2 = Entry {
603 subnet: next_hop_subnet,
604 device: device.clone(),
605 gateway: None,
606 metric,
607 route_preference: RoutePreference::Medium,
608 };
609 assert_eq!(super::testutil::add_entry(&mut table, entry2.clone()), Ok(&entry2));
610 let entry3 = Entry {
611 subnet: subnet,
612 device: device.clone(),
613 gateway: Some(next_hop),
614 metric,
615 route_preference: RoutePreference::Medium,
616 };
617 assert_eq!(super::testutil::add_entry(&mut table, entry3.clone()), Ok(&entry3));
618 assert_eq!(
619 table.iter_table().collect::<HashSet<_>>(),
620 HashSet::from([&entry, &entry2, &entry3])
621 );
622
623 assert_eq!(
625 super::testutil::add_entry(&mut table, entry3.clone()).unwrap_err(),
626 ExistsError
627 );
628 assert_eq!(
629 table.iter_table().collect::<HashSet<_>>(),
630 HashSet::from([&entry, &entry2, &entry3,])
631 );
632
633 (table, config, next_hop, next_hop_subnet, device, metric)
634 }
635
636 #[ip_test(I)]
637 fn test_simple_add_del<I: TestIpExt>() {
638 let (mut table, config, next_hop, next_hop_subnet, device, metric) = simple_setup::<I>();
639 assert_eq!(table.iter_table().count(), 3);
640
641 assert_eq!(
643 table
644 .del_entries(
645 |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
646 subnet == &config.subnet
647 }
648 )
649 .into_iter()
650 .collect::<HashSet<_>>(),
651 HashSet::from([
652 Entry {
653 subnet: config.subnet,
654 device: device.clone(),
655 gateway: None,
656 metric,
657 route_preference: RoutePreference::Medium
658 },
659 Entry {
660 subnet: config.subnet,
661 device: device.clone(),
662 gateway: Some(next_hop),
663 metric,
664 route_preference: RoutePreference::Medium,
665 }
666 ])
667 );
668
669 assert_eq!(
670 table.iter_table().collect::<Vec<_>>(),
671 &[&Entry {
672 subnet: next_hop_subnet,
673 device: device.clone(),
674 gateway: None,
675 metric,
676 route_preference: RoutePreference::Medium
677 }]
678 );
679 }
680
681 #[ip_test(I)]
682 fn test_simple_lookup<I: TestIpExt>() {
683 let (mut table, config, next_hop, _next_hop_subnet, device, metric) = simple_setup::<I>();
684 let mut core_ctx = FakeCtx::default();
685
686 assert_eq!(
688 table.lookup(&mut core_ctx, None, *next_hop),
689 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
690 );
691
692 assert_eq!(
694 table.lookup(&mut core_ctx, None, *config.local_ip),
695 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
696 );
697 assert_eq!(
698 table.lookup(&mut core_ctx, None, *config.remote_ip),
699 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
700 );
701
702 let default_route_entry = Entry {
706 subnet: Subnet::new(I::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
707 device: device.clone(),
708 gateway: None,
709 metric,
710 route_preference: RoutePreference::Medium,
711 };
712 assert_eq!(
713 super::testutil::add_entry(&mut table, default_route_entry.clone()),
714 Ok(&default_route_entry)
715 );
716
717 I::map_ip::<_, ()>(
719 (&table, &config),
720 |(table, config)| {
721 assert_eq!(
722 table.lookup(&mut core_ctx, None, config.subnet.broadcast()),
723 Some(Destination { next_hop: NextHop::Broadcast(()), device: device.clone() })
724 );
725
726 assert_eq!(
727 table.lookup(&mut core_ctx, None, Ipv4::LIMITED_BROADCAST_ADDRESS.get()),
728 Some(Destination { next_hop: NextHop::Broadcast(()), device: device.clone() })
729 );
730 },
731 |(_table, _config)| {
732 },
734 );
735
736 assert_eq!(
738 table
739 .del_entries(
740 |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
741 subnet.prefix() == 0
742 }
743 )
744 .into_iter()
745 .collect::<Vec<_>>(),
746 alloc::vec![default_route_entry.clone()]
747 );
748
749 assert_eq!(
752 table
753 .del_entries(
754 |Entry { subnet, device: _, gateway: _, metric: _, route_preference: _ }| {
755 subnet == &config.subnet
756 }
757 )
758 .into_iter()
759 .collect::<HashSet<_>>(),
760 HashSet::from([
761 Entry {
762 subnet: config.subnet,
763 device: device.clone(),
764 gateway: None,
765 metric,
766 route_preference: RoutePreference::Medium
767 },
768 Entry {
769 subnet: config.subnet,
770 device: device.clone(),
771 gateway: Some(next_hop),
772 metric,
773 route_preference: RoutePreference::Medium,
774 }
775 ])
776 );
777 assert_eq!(
778 table.lookup(&mut core_ctx, None, *next_hop),
779 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
780 );
781 assert_eq!(table.lookup(&mut core_ctx, None, *config.local_ip), None);
782 assert_eq!(table.lookup(&mut core_ctx, None, *config.remote_ip), None);
783 I::map_ip::<_, ()>(
784 (&table, &config),
785 |(table, config)| {
786 assert_eq!(table.lookup(&mut core_ctx, None, config.subnet.broadcast()), None);
787 assert_eq!(
788 table.lookup(&mut core_ctx, None, Ipv4::LIMITED_BROADCAST_ADDRESS.get()),
789 None
790 );
791 },
792 |(_table, _config)| {
793 },
795 );
796
797 let gateway_entry = Entry {
799 subnet: config.subnet,
800 device: device.clone(),
801 gateway: Some(next_hop),
802 metric: Metric::ExplicitMetric(RawMetric(0)),
803 route_preference: RoutePreference::Medium,
804 };
805 assert_eq!(
806 super::testutil::add_entry(&mut table, gateway_entry.clone()),
807 Ok(&gateway_entry)
808 );
809 assert_eq!(
810 table.lookup(&mut core_ctx, None, *next_hop),
811 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: device.clone() })
812 );
813 assert_eq!(
814 table.lookup(&mut core_ctx, None, *config.local_ip),
815 Some(Destination { next_hop: NextHop::Gateway(next_hop), device: device.clone() })
816 );
817 assert_eq!(
818 table.lookup(&mut core_ctx, None, *config.remote_ip),
819 Some(Destination { next_hop: NextHop::Gateway(next_hop), device: device.clone() })
820 );
821
822 let default_route_entry = Entry {
824 subnet: Subnet::new(I::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
825 device: device.clone(),
826 gateway: Some(next_hop),
827 metric,
828 route_preference: RoutePreference::Medium,
829 };
830 assert_eq!(
831 super::testutil::add_entry(&mut table, default_route_entry.clone()),
832 Ok(&default_route_entry)
833 );
834
835 I::map_ip::<_, ()>(
837 (&table, &config, next_hop),
838 |(table, config, next_hop)| {
839 assert_eq!(
840 table.lookup(&mut core_ctx, None, config.subnet.broadcast()),
841 Some(Destination {
842 next_hop: NextHop::Gateway(next_hop),
843 device: device.clone()
844 })
845 );
846
847 assert_eq!(
848 table.lookup(&mut core_ctx, None, Ipv4::LIMITED_BROADCAST_ADDRESS.get()),
849 Some(Destination { next_hop: NextHop::Broadcast(()), device: device.clone() })
850 );
851 },
852 |(_table, _config, _next_hop)| {
853 },
855 );
856 }
857
858 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
859 enum BroadcastCaseNextHop {
860 Neighbor,
861 Gateway,
862 }
863
864 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
865 enum LookupResultNextHop {
866 Neighbor,
867 Gateway,
868 Broadcast,
869 }
870
871 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
872 struct LookupResult {
873 next_hop: LookupResultNextHop,
874 device: MultipleDevicesId,
875 }
876
877 #[test_case::test_matrix(
878 [None, Some(BroadcastCaseNextHop::Neighbor), Some(BroadcastCaseNextHop::Gateway)],
879 [None, Some(MultipleDevicesId::A), Some(MultipleDevicesId::B)]
880 )]
881 fn all_ones_broadcast_lookup(
882 default_route: Option<BroadcastCaseNextHop>,
883 bind_device: Option<MultipleDevicesId>,
884 ) {
885 let mut core_ctx = FakeCtx::default();
886 let expected_lookup_result = match (default_route, bind_device) {
887 (_, Some(device)) => {
889 Some(LookupResult { next_hop: LookupResultNextHop::Broadcast, device })
890 }
891 (None, None) => None,
894 (Some(_next_hop), None) => {
895 Some(LookupResult {
898 next_hop: LookupResultNextHop::Broadcast,
899 device: MultipleDevicesId::A,
900 })
901 }
902 };
903
904 let mut table = RoutingTable::<Ipv4, MultipleDevicesId>::default();
905 if let Some(next_hop) = default_route {
906 let entry = Entry {
907 subnet: Subnet::new(Ipv4::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
908 device: MultipleDevicesId::A,
909 gateway: match next_hop {
910 BroadcastCaseNextHop::Neighbor => None,
911 BroadcastCaseNextHop::Gateway => {
912 Some(SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap())
913 }
914 },
915 metric: Metric::ExplicitMetric(RawMetric(0)),
916 route_preference: RoutePreference::Medium,
917 };
918 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
919 }
920
921 let got_lookup_result = table
922 .lookup(&mut core_ctx, bind_device.as_ref(), Ipv4::LIMITED_BROADCAST_ADDRESS.get())
923 .map(|Destination { next_hop, device }| LookupResult {
924 next_hop: match next_hop {
925 NextHop::RemoteAsNeighbor => LookupResultNextHop::Neighbor,
926 NextHop::Gateway(_) => LookupResultNextHop::Gateway,
927 NextHop::Broadcast(()) => LookupResultNextHop::Broadcast,
928 },
929 device,
930 });
931
932 assert_eq!(got_lookup_result, expected_lookup_result);
933 }
934
935 #[test_case::test_matrix(
936 [None, Some(BroadcastCaseNextHop::Neighbor), Some(BroadcastCaseNextHop::Gateway)],
937 [None, Some(BroadcastCaseNextHop::Neighbor), Some(BroadcastCaseNextHop::Gateway)],
938 [None, Some(MultipleDevicesId::A), Some(MultipleDevicesId::B)]
939 )]
940 fn subnet_broadcast_lookup(
941 default_route: Option<BroadcastCaseNextHop>,
942 subnet_route: Option<BroadcastCaseNextHop>,
943 bind_device: Option<MultipleDevicesId>,
944 ) {
945 let mut core_ctx = FakeCtx::default();
946 let expected_lookup_result = match bind_device {
947 Some(MultipleDevicesId::B) | Some(MultipleDevicesId::C) => None,
949 Some(MultipleDevicesId::A) | None => match (default_route, subnet_route) {
950 (None, None) => None,
952 (None | Some(_), Some(next_hop)) => {
954 Some(LookupResult {
955 device: MultipleDevicesId::A,
956 next_hop: match next_hop {
957 BroadcastCaseNextHop::Neighbor => LookupResultNextHop::Broadcast,
959 BroadcastCaseNextHop::Gateway => LookupResultNextHop::Gateway,
962 },
963 })
964 }
965 (Some(next_hop), None) => {
966 Some(LookupResult {
967 device: MultipleDevicesId::A,
968 next_hop: match next_hop {
969 BroadcastCaseNextHop::Neighbor => LookupResultNextHop::Neighbor,
972 BroadcastCaseNextHop::Gateway => LookupResultNextHop::Gateway,
973 },
974 })
975 }
976 },
977 };
978
979 let subnet = net_declare::net_subnet_v4!("192.168.0.0/24");
980 let gateway = SpecifiedAddr::new(net_ip_v4!("192.168.0.1")).unwrap();
981
982 let mut table = RoutingTable::<Ipv4, MultipleDevicesId>::default();
983 if let Some(next_hop) = default_route {
984 let entry = Entry {
985 subnet: Subnet::new(Ipv4::UNSPECIFIED_ADDRESS, 0).expect("default subnet"),
986 device: MultipleDevicesId::A,
987 gateway: match next_hop {
988 BroadcastCaseNextHop::Neighbor => None,
989 BroadcastCaseNextHop::Gateway => Some(gateway),
990 },
991 metric: Metric::ExplicitMetric(RawMetric(0)),
992 route_preference: RoutePreference::Medium,
993 };
994 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
995 }
996
997 if let Some(next_hop) = subnet_route {
998 let entry = Entry {
999 subnet,
1000 device: MultipleDevicesId::A,
1001 gateway: match next_hop {
1002 BroadcastCaseNextHop::Neighbor => None,
1003 BroadcastCaseNextHop::Gateway => Some(gateway),
1004 },
1005 metric: Metric::ExplicitMetric(RawMetric(0)),
1006 route_preference: RoutePreference::Medium,
1007 };
1008 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
1009 }
1010
1011 let got_lookup_result = table
1012 .lookup(&mut core_ctx, bind_device.as_ref(), subnet.broadcast())
1013 .map(|Destination { next_hop, device }| LookupResult {
1014 next_hop: match next_hop {
1015 NextHop::RemoteAsNeighbor => LookupResultNextHop::Neighbor,
1016 NextHop::Gateway(_) => LookupResultNextHop::Gateway,
1017 NextHop::Broadcast(()) => LookupResultNextHop::Broadcast,
1018 },
1019 device,
1020 });
1021
1022 assert_eq!(got_lookup_result, expected_lookup_result);
1023 }
1024
1025 #[ip_test(I)]
1026 fn test_default_route_ip<I: TestIpExt>() {
1027 let mut core_ctx = FakeCtx::default();
1028 let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1029 let device0 = MultipleDevicesId::A;
1030 let (addr1, sub1) = I::next_hop_addr_sub(1, 24);
1031 let (addr2, _) = I::next_hop_addr_sub(2, 24);
1032 let (addr3, _) = I::next_hop_addr_sub(3, 24);
1033 let metric = Metric::ExplicitMetric(RawMetric(0));
1034
1035 let entry = Entry {
1042 subnet: sub1,
1043 device: device0.clone(),
1044 gateway: None,
1045 metric,
1046 route_preference: RoutePreference::Medium,
1047 };
1048 assert_eq!(super::testutil::add_entry(&mut table, entry.clone()), Ok(&entry));
1049 table.print_table();
1050 assert_eq!(
1051 table.lookup(&mut core_ctx, None, *addr1).unwrap(),
1052 Destination { next_hop: NextHop::RemoteAsNeighbor, device: device0.clone() }
1053 );
1054 assert_eq!(table.lookup(&mut core_ctx, None, *addr2), None);
1055
1056 let default_sub = Subnet::new(I::UNSPECIFIED_ADDRESS, 0).unwrap();
1063 let default_entry = Entry {
1064 subnet: default_sub,
1065 device: device0.clone(),
1066 gateway: Some(addr1),
1067 metric,
1068 route_preference: RoutePreference::Medium,
1069 };
1070
1071 assert_eq!(
1072 super::testutil::add_entry(&mut table, default_entry.clone()),
1073 Ok(&default_entry)
1074 );
1075 assert_eq!(
1076 table.lookup(&mut core_ctx, None, *addr1).unwrap(),
1077 Destination { next_hop: NextHop::RemoteAsNeighbor, device: device0.clone() }
1078 );
1079 assert_eq!(
1080 table.lookup(&mut core_ctx, None, *addr2).unwrap(),
1081 Destination { next_hop: NextHop::Gateway(addr1), device: device0.clone() }
1082 );
1083 assert_eq!(
1084 table.lookup(&mut core_ctx, None, *addr3).unwrap(),
1085 Destination { next_hop: NextHop::Gateway(addr1), device: device0.clone() }
1086 );
1087 assert_eq!(
1088 table.lookup(&mut core_ctx, None, I::UNSPECIFIED_ADDRESS).unwrap(),
1089 Destination { next_hop: NextHop::Gateway(addr1), device: device0.clone() }
1090 );
1091 }
1092
1093 #[ip_test(I)]
1094 fn test_device_filter_with_varying_prefix_lengths<I: TestIpExt>() {
1095 const MORE_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::A;
1096 const LESS_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::B;
1097
1098 let mut core_ctx = FakeCtx::default();
1099 let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1100 let (remote, more_specific_sub) = I::next_hop_addr_sub(1, 2);
1103 let less_specific_sub = {
1104 let (addr, sub) = I::next_hop_addr_sub(1, 3);
1105 assert_eq!(remote, addr);
1106 sub
1107 };
1108 let metric = Metric::ExplicitMetric(RawMetric(0));
1109 let less_specific_entry = Entry {
1110 subnet: less_specific_sub,
1111 device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1112 gateway: None,
1113 metric,
1114 route_preference: RoutePreference::Medium,
1115 };
1116 assert_eq!(
1117 super::testutil::add_entry(&mut table, less_specific_entry.clone()),
1118 Ok(&less_specific_entry)
1119 );
1120 assert_eq!(
1121 table.lookup(&mut core_ctx, None, *remote),
1122 Some(Destination {
1123 next_hop: NextHop::RemoteAsNeighbor,
1124 device: LESS_SPECIFIC_SUB_DEVICE.clone()
1125 }),
1126 "matches route"
1127 );
1128 assert_eq!(
1129 table.lookup(&mut core_ctx, Some(&LESS_SPECIFIC_SUB_DEVICE), *remote),
1130 Some(Destination {
1131 next_hop: NextHop::RemoteAsNeighbor,
1132 device: LESS_SPECIFIC_SUB_DEVICE.clone()
1133 }),
1134 "route matches specified device"
1135 );
1136 assert_eq!(
1137 table.lookup(&mut core_ctx, Some(&MORE_SPECIFIC_SUB_DEVICE), *remote),
1138 None,
1139 "no route with the specified device"
1140 );
1141
1142 let more_specific_entry = Entry {
1143 subnet: more_specific_sub,
1144 device: MORE_SPECIFIC_SUB_DEVICE.clone(),
1145 gateway: None,
1146 metric,
1147 route_preference: RoutePreference::Medium,
1148 };
1149 assert_eq!(
1150 super::testutil::add_entry(&mut table, more_specific_entry.clone()),
1151 Ok(&more_specific_entry)
1152 );
1153 assert_eq!(
1154 table.lookup(&mut core_ctx, None, *remote).unwrap(),
1155 Destination {
1156 next_hop: NextHop::RemoteAsNeighbor,
1157 device: MORE_SPECIFIC_SUB_DEVICE.clone()
1158 },
1159 "matches most specific route"
1160 );
1161 assert_eq!(
1162 table.lookup(&mut core_ctx, Some(&LESS_SPECIFIC_SUB_DEVICE), *remote),
1163 Some(Destination {
1164 next_hop: NextHop::RemoteAsNeighbor,
1165 device: LESS_SPECIFIC_SUB_DEVICE.clone()
1166 }),
1167 "matches less specific route with the specified device"
1168 );
1169 assert_eq!(
1170 table.lookup(&mut core_ctx, Some(&MORE_SPECIFIC_SUB_DEVICE), *remote).unwrap(),
1171 Destination {
1172 next_hop: NextHop::RemoteAsNeighbor,
1173 device: MORE_SPECIFIC_SUB_DEVICE.clone()
1174 },
1175 "matches the most specific route with the specified device"
1176 );
1177 }
1178
1179 #[ip_test(I)]
1180 fn test_lookup_filter_map<I: TestIpExt>() {
1181 let mut core_ctx = FakeCtx::default();
1182 let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1183
1184 let (next_hop, more_specific_sub) = I::next_hop_addr_sub(1, 2);
1187 let less_specific_sub = {
1188 let (addr, sub) = I::next_hop_addr_sub(1, 3);
1189 assert_eq!(next_hop, addr);
1190 sub
1191 };
1192
1193 {
1195 let metric = Metric::ExplicitMetric(RawMetric(0));
1196 let more_specific_entry = Entry {
1197 subnet: more_specific_sub,
1198 device: MultipleDevicesId::A,
1199 gateway: None,
1200 metric,
1201 route_preference: RoutePreference::Medium,
1202 };
1203 let _: &_ =
1204 super::testutil::add_entry(&mut table, more_specific_entry).expect("was added");
1205 }
1206 for (device, metric) in [(MultipleDevicesId::B, 100), (MultipleDevicesId::C, 200)] {
1208 let less_specific_entry = Entry {
1209 subnet: less_specific_sub,
1210 device,
1211 gateway: None,
1212 metric: Metric::ExplicitMetric(RawMetric(metric)),
1213 route_preference: RoutePreference::Medium,
1214 };
1215 let _: &_ =
1216 super::testutil::add_entry(&mut table, less_specific_entry).expect("was added");
1217 }
1218
1219 fn lookup_with_devices<I: BroadcastIpExt>(
1220 table: &RoutingTable<I, MultipleDevicesId>,
1221 next_hop: SpecifiedAddr<I::Addr>,
1222 core_ctx: &mut FakeCtx,
1223 devices: &[MultipleDevicesId],
1224 ) -> Vec<Destination<I::Addr, MultipleDevicesId>> {
1225 table
1226 .lookup_filter_map(core_ctx, None, *next_hop, |_, d| {
1227 devices.iter().contains(d).then_some(())
1228 })
1229 .map(|(Destination { next_hop, device }, ())| Destination {
1230 next_hop,
1231 device: device.clone(),
1232 })
1233 .collect::<Vec<_>>()
1234 }
1235
1236 assert_eq!(
1239 table.lookup(&mut core_ctx, None, *next_hop),
1240 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::A })
1241 );
1242 assert_eq!(
1244 lookup_with_devices(&table, next_hop, &mut core_ctx, &MultipleDevicesId::all()),
1245 &[
1246 Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::A },
1247 Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::B },
1248 Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::C },
1249 ]
1250 );
1251
1252 assert_eq!(
1254 lookup_with_devices(
1255 &table,
1256 next_hop,
1257 &mut core_ctx,
1258 &[MultipleDevicesId::B, MultipleDevicesId::C]
1259 ),
1260 &[
1261 Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::B },
1262 Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::C }
1263 ]
1264 );
1265
1266 assert_eq!(
1268 lookup_with_devices(&table, next_hop, &mut core_ctx, &[MultipleDevicesId::C]),
1269 &[Destination { next_hop: NextHop::RemoteAsNeighbor, device: MultipleDevicesId::C }]
1270 );
1271 }
1272
1273 #[ip_test(I)]
1274 fn test_multiple_routes_to_subnet_through_different_devices<I: TestIpExt>() {
1275 const DEVICE1: MultipleDevicesId = MultipleDevicesId::A;
1276 const DEVICE2: MultipleDevicesId = MultipleDevicesId::B;
1277
1278 let mut core_ctx = FakeCtx::default();
1279 let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1280 let (remote, sub) = I::next_hop_addr_sub(1, 2);
1283 let metric = Metric::ExplicitMetric(RawMetric(0));
1284
1285 let entry1 = Entry {
1286 subnet: sub,
1287 device: DEVICE1.clone(),
1288 gateway: None,
1289 metric,
1290 route_preference: RoutePreference::Medium,
1291 };
1292 assert_eq!(super::testutil::add_entry(&mut table, entry1.clone()), Ok(&entry1));
1293 let entry2 = Entry {
1294 subnet: sub,
1295 device: DEVICE2.clone(),
1296 gateway: None,
1297 metric,
1298 route_preference: RoutePreference::Medium,
1299 };
1300 assert_eq!(super::testutil::add_entry(&mut table, entry2.clone()), Ok(&entry2));
1301 let lookup = table.lookup(&mut core_ctx, None, *remote);
1302 assert!(
1303 [
1304 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE1.clone() }),
1305 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE2.clone() })
1306 ]
1307 .contains(&lookup),
1308 "lookup = {:?}",
1309 lookup
1310 );
1311 assert_eq!(
1312 table.lookup(&mut core_ctx, Some(&DEVICE1), *remote),
1313 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE1.clone() }),
1314 );
1315 assert_eq!(
1316 table.lookup(&mut core_ctx, Some(&DEVICE2), *remote),
1317 Some(Destination { next_hop: NextHop::RemoteAsNeighbor, device: DEVICE2.clone() }),
1318 );
1319 }
1320
1321 #[ip_test(I)]
1322 #[test_case(|core_ctx, device, device_unusable| {
1323 let disabled_devices = core_ctx.state.disabled_devices_mut();
1324 if device_unusable {
1325 let _: bool = disabled_devices.insert(device);
1326 } else {
1327 let _: bool = disabled_devices.remove(&device);
1328 }
1329 }; "device_disabled")]
1330 fn test_usable_device<I: TestIpExt>(set_inactive: fn(&mut FakeCtx, MultipleDevicesId, bool)) {
1331 const MORE_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::A;
1332 const LESS_SPECIFIC_SUB_DEVICE: MultipleDevicesId = MultipleDevicesId::B;
1333
1334 let mut core_ctx = FakeCtx::default();
1335 let mut table = RoutingTable::<I, MultipleDevicesId>::default();
1336 let (remote, more_specific_sub) = I::next_hop_addr_sub(1, 2);
1339 let less_specific_sub = {
1340 let (addr, sub) = I::next_hop_addr_sub(1, 3);
1341 assert_eq!(remote, addr);
1342 sub
1343 };
1344 let metric = Metric::ExplicitMetric(RawMetric(0));
1345
1346 let less_specific_entry = Entry {
1347 subnet: less_specific_sub,
1348 device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1349 gateway: None,
1350 metric,
1351 route_preference: RoutePreference::Medium,
1352 };
1353 assert_eq!(
1354 super::testutil::add_entry(&mut table, less_specific_entry.clone()),
1355 Ok(&less_specific_entry)
1356 );
1357 for (device_unusable, expected) in [
1358 (true, None),
1360 (
1361 false,
1362 Some(Destination {
1363 next_hop: NextHop::RemoteAsNeighbor,
1364 device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1365 }),
1366 ),
1367 ] {
1368 set_inactive(&mut core_ctx, LESS_SPECIFIC_SUB_DEVICE, device_unusable);
1369 assert_eq!(
1370 table.lookup(&mut core_ctx, None, *remote),
1371 expected,
1372 "device_unusable={}",
1373 device_unusable,
1374 );
1375 }
1376
1377 let more_specific_entry = Entry {
1378 subnet: more_specific_sub,
1379 device: MORE_SPECIFIC_SUB_DEVICE.clone(),
1380 gateway: None,
1381 metric,
1382 route_preference: RoutePreference::Medium,
1383 };
1384 assert_eq!(
1385 super::testutil::add_entry(&mut table, more_specific_entry.clone()),
1386 Ok(&more_specific_entry)
1387 );
1388 for (device_unusable, expected) in [
1389 (
1390 false,
1391 Some(Destination {
1392 next_hop: NextHop::RemoteAsNeighbor,
1393 device: MORE_SPECIFIC_SUB_DEVICE.clone(),
1394 }),
1395 ),
1396 (
1399 true,
1400 Some(Destination {
1401 next_hop: NextHop::RemoteAsNeighbor,
1402 device: LESS_SPECIFIC_SUB_DEVICE.clone(),
1403 }),
1404 ),
1405 ] {
1406 set_inactive(&mut core_ctx, MORE_SPECIFIC_SUB_DEVICE, device_unusable);
1407 assert_eq!(
1408 table.lookup(&mut core_ctx, None, *remote),
1409 expected,
1410 "device_unusable={}",
1411 device_unusable,
1412 );
1413 }
1414
1415 set_inactive(&mut core_ctx, LESS_SPECIFIC_SUB_DEVICE, true);
1417 assert_eq!(table.lookup(&mut core_ctx, None, *remote), None,);
1418 }
1419
1420 #[ip_test(I)]
1421 fn test_add_entry_keeps_table_sorted<I: BroadcastIpExt>() {
1422 const DEVICE_A: MultipleDevicesId = MultipleDevicesId::A;
1423 const DEVICE_B: MultipleDevicesId = MultipleDevicesId::B;
1424 let (more_specific_sub, less_specific_sub) = I::map_ip(
1425 (),
1426 |()| (net_subnet_v4!("192.168.0.0/24"), net_subnet_v4!("192.168.0.0/16")),
1427 |()| (net_subnet_v6!("fe80::/64"), net_subnet_v6!("fe80::/16")),
1428 );
1429 let lower_metric = Metric::ExplicitMetric(RawMetric(0));
1430 let higher_metric = Metric::ExplicitMetric(RawMetric(1));
1431 let on_link = None;
1432 let off_link = Some(SpecifiedAddr::<I::Addr>::new(I::map_ip(
1433 (),
1434 |()| net_ip_v4!("192.168.0.1"),
1435 |()| net_ip_v6!("fe80::1"),
1436 )))
1437 .unwrap();
1438
1439 use RoutePreference::{High, Low, Medium};
1440
1441 const fn entry<I: Ip, D>(
1442 d: D,
1443 s: Subnet<I::Addr>,
1444 g: Option<SpecifiedAddr<I::Addr>>,
1445 p: RoutePreference,
1446 m: Metric,
1447 ) -> Entry<I::Addr, D> {
1448 Entry { device: d, subnet: s, metric: m, gateway: g, route_preference: p }
1449 }
1450
1451 let mut expected_table = alloc::vec![];
1454 for subnet in [more_specific_sub, less_specific_sub] {
1455 for locality in [on_link, off_link] {
1456 for metric in [lower_metric, higher_metric] {
1457 for preference in [High, Medium, Low] {
1458 for device in [DEVICE_A, DEVICE_B] {
1459 expected_table
1460 .push(entry::<I, _>(device, subnet, locality, preference, metric));
1461 }
1462 }
1463 }
1464 }
1465 }
1466
1467 let len = expected_table.len();
1468
1469 proptest::proptest!(
1470 move |(shuffle in proptest::sample::subsequence(expected_table.clone(), len))| {
1471 use alloc::format;
1473 let mut table = RoutingTable::<I, _>::default();
1474 for entry in shuffle.iter() {
1475 proptest::prop_assert_eq!(
1476 super::testutil::add_entry(&mut table, entry.clone()),
1477 Ok(entry)
1478 );
1479 }
1480 proptest::prop_assert_eq!(
1481 table.iter_table().cloned().collect::<Vec<_>>(),
1482 &expected_table[..]
1483 );
1484 }
1485 );
1486 }
1487}