1use core::hash::Hash;
10
11use derivative::Derivative;
12use log::debug;
13use net_types::LinkLocalUnicastAddr;
14use net_types::ip::{Ipv6Addr, Subnet};
15use netstack3_base::{
16 AnyDevice, CoreTimerContext, DeviceIdContext, HandleableTimer, InstantBindingsTypes,
17 LocalTimerHeap, TimerBindingsTypes, TimerContext, TokenBucket, WeakDeviceIdentifier,
18};
19use netstack3_hashmap::HashMap;
20use netstack3_hashmap::hash_map::Entry;
21use packet_formats::icmp::ndp::NonZeroNdpLifetime;
22
23use crate::internal::types::RoutePreference;
24
25const MAX_DISCOVERED_ROUTES: usize = 4096;
29
30const MAX_DISCOVERIES_PER_SECOND: u64 = 256;
34
35#[derive(Debug)]
37pub struct Ipv6RouteDiscoveryState<BT: Ipv6RouteDiscoveryBindingsTypes> {
38 routes: HashMap<Ipv6DiscoveredRoute, Ipv6DiscoveredRouteProperties>,
43 timers: LocalTimerHeap<Ipv6DiscoveredRoute, (), BT>,
44 rate_limit: TokenBucket<BT::Instant>,
48}
49
50impl<BT: Ipv6RouteDiscoveryBindingsTypes> Ipv6RouteDiscoveryState<BT> {
51 #[cfg(any(test, feature = "testutils"))]
53 pub fn timers(&self) -> &LocalTimerHeap<Ipv6DiscoveredRoute, (), BT> {
54 &self.timers
55 }
56}
57
58impl<BC: Ipv6RouteDiscoveryBindingsContext> Ipv6RouteDiscoveryState<BC> {
59 pub fn new<D: WeakDeviceIdentifier, CC: CoreTimerContext<Ipv6DiscoveredRouteTimerId<D>, BC>>(
61 bindings_ctx: &mut BC,
62 device_id: D,
63 ) -> Self {
64 Self {
65 routes: Default::default(),
66 timers: LocalTimerHeap::new_with_context::<_, CC>(
67 bindings_ctx,
68 Ipv6DiscoveredRouteTimerId { device_id },
69 ),
70 rate_limit: TokenBucket::new(MAX_DISCOVERIES_PER_SECOND),
71 }
72 }
73}
74
75#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
77pub struct Ipv6DiscoveredRoute {
78 pub subnet: Subnet<Ipv6Addr>,
80
81 pub gateway: Option<LinkLocalUnicastAddr<Ipv6Addr>>,
85}
86
87#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
89pub struct Ipv6DiscoveredRouteProperties {
90 pub route_preference: RoutePreference,
92}
93
94#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
96pub struct Ipv6DiscoveredRouteTimerId<D: WeakDeviceIdentifier> {
97 device_id: D,
98}
99
100impl<D: WeakDeviceIdentifier> Ipv6DiscoveredRouteTimerId<D> {
101 pub(super) fn device_id(&self) -> &D {
102 &self.device_id
103 }
104}
105
106#[derive(Copy, Clone, Debug, Eq, PartialEq, Derivative)]
108#[derivative(Default)]
109pub struct RouteDiscoveryConfiguration {
110 #[derivative(Default(value = "true"))]
112 pub allow_default_route: bool,
113}
114
115#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
117#[allow(missing_docs)]
118pub struct RouteDiscoveryConfigurationUpdate {
119 pub allow_default_route: Option<bool>,
120}
121
122impl RouteDiscoveryConfiguration {
123 pub fn update(
127 &mut self,
128 update: RouteDiscoveryConfigurationUpdate,
129 ) -> RouteDiscoveryConfigurationUpdate {
130 let RouteDiscoveryConfigurationUpdate { allow_default_route } = update;
131 let allow_default_route =
132 allow_default_route.map(|new| core::mem::replace(&mut self.allow_default_route, new));
133 RouteDiscoveryConfigurationUpdate { allow_default_route }
134 }
135}
136
137pub trait Ipv6DiscoveredRoutesContext<BC>: DeviceIdContext<AnyDevice> {
142 fn add_discovered_ipv6_route(
144 &mut self,
145 bindings_ctx: &mut BC,
146 device_id: &Self::DeviceId,
147 route: Ipv6DiscoveredRoute,
148 properties: Ipv6DiscoveredRouteProperties,
149 );
150
151 fn del_discovered_ipv6_route(
154 &mut self,
155 bindings_ctx: &mut BC,
156 device_id: &Self::DeviceId,
157 route: Ipv6DiscoveredRoute,
158 );
159}
160
161pub trait Ipv6RouteDiscoveryContext<BT: Ipv6RouteDiscoveryBindingsTypes>:
163 DeviceIdContext<AnyDevice>
164{
165 type WithDiscoveredRoutesMutCtx<'a>: Ipv6DiscoveredRoutesContext<BT, DeviceId = Self::DeviceId>;
167
168 fn with_discovered_routes_mut<
170 O,
171 F: FnOnce(&mut Ipv6RouteDiscoveryState<BT>, &mut Self::WithDiscoveredRoutesMutCtx<'_>) -> O,
172 >(
173 &mut self,
174 device_id: &Self::DeviceId,
175 cb: F,
176 ) -> O;
177}
178
179pub trait Ipv6RouteDiscoveryBindingsTypes: TimerBindingsTypes + InstantBindingsTypes {}
181impl<BT> Ipv6RouteDiscoveryBindingsTypes for BT where BT: TimerBindingsTypes + InstantBindingsTypes {}
182
183pub trait Ipv6RouteDiscoveryBindingsContext:
185 Ipv6RouteDiscoveryBindingsTypes + TimerContext
186{
187}
188impl<BC> Ipv6RouteDiscoveryBindingsContext for BC where
189 BC: Ipv6RouteDiscoveryBindingsTypes + TimerContext
190{
191}
192
193pub trait RouteDiscoveryHandler<BC>: DeviceIdContext<AnyDevice> {
195 fn update_route(
202 &mut self,
203 bindings_ctx: &mut BC,
204 device_id: &Self::DeviceId,
205 route: Ipv6DiscoveredRoute,
206 properties: Ipv6DiscoveredRouteProperties,
207 lifetime: Option<NonZeroNdpLifetime>,
208 config: &RouteDiscoveryConfiguration,
209 );
210
211 fn invalidate_routes(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
213}
214
215impl<BC: Ipv6RouteDiscoveryBindingsContext, CC: Ipv6RouteDiscoveryContext<BC>>
216 RouteDiscoveryHandler<BC> for CC
217{
218 fn update_route(
219 &mut self,
220 bindings_ctx: &mut BC,
221 device_id: &CC::DeviceId,
222 route: Ipv6DiscoveredRoute,
223 properties: Ipv6DiscoveredRouteProperties,
224 lifetime: Option<NonZeroNdpLifetime>,
225 config: &RouteDiscoveryConfiguration,
226 ) {
227 self.with_discovered_routes_mut(device_id, |state, core_ctx| {
228 let Ipv6RouteDiscoveryState { routes, timers, rate_limit } = state;
229 match lifetime {
230 Some(lifetime) => {
231 if !config.allow_default_route && route.subnet.prefix() == 0 {
232 return;
233 }
234 if !rate_limit.try_take(bindings_ctx) {
235 debug!(
236 "IPv6 Discovered routes rate limited. Ignoring update for {route:?}"
237 );
238 return;
239 }
240
241 let num_routes = routes.len();
242 let newly_added = match routes.entry(route) {
243 Entry::Occupied(mut entry) => {
244 let old_properties = entry.get_mut();
245 if old_properties.route_preference != properties.route_preference {
246 core_ctx.del_discovered_ipv6_route(bindings_ctx, device_id, route);
247 core_ctx.add_discovered_ipv6_route(
248 bindings_ctx,
249 device_id,
250 route,
251 properties,
252 );
253 *old_properties = properties;
254 }
255 false
256 }
257 Entry::Vacant(entry) => {
258 if num_routes >= MAX_DISCOVERED_ROUTES {
259 debug!(
260 "IPv6 Discovered Routes table is full. Not adding {route:?}"
261 );
262 return;
263 }
264
265 core_ctx.add_discovered_ipv6_route(
266 bindings_ctx,
267 device_id,
268 route,
269 properties,
270 );
271 let _: &mut _ = entry.insert(properties);
272 true
273 }
274 };
275
276 let prev_timer_fires_at = match lifetime {
277 NonZeroNdpLifetime::Finite(lifetime) => {
278 timers.schedule_after(bindings_ctx, route, (), lifetime.get())
279 }
280 NonZeroNdpLifetime::Infinite => timers.cancel(bindings_ctx, &route),
282 };
283
284 if newly_added {
285 if let Some((prev_timer_fires_at, ())) = prev_timer_fires_at {
286 panic!(
287 "newly added route {:?} should not have already been \
288 scheduled to fire at {:?}",
289 route, prev_timer_fires_at,
290 )
291 }
292 }
293 }
294 None => {
295 if routes.remove(&route).is_some() {
296 invalidate_route(core_ctx, bindings_ctx, device_id, state, route);
297 }
298 }
299 }
300 })
301 }
302
303 fn invalidate_routes(&mut self, bindings_ctx: &mut BC, device_id: &CC::DeviceId) {
304 self.with_discovered_routes_mut(device_id, |state, core_ctx| {
305 for (route, _properties) in core::mem::take(&mut state.routes).into_iter() {
306 invalidate_route(core_ctx, bindings_ctx, device_id, state, route);
307 }
308 })
309 }
310}
311
312impl<BC: Ipv6RouteDiscoveryBindingsContext, CC: Ipv6RouteDiscoveryContext<BC>>
313 HandleableTimer<CC, BC> for Ipv6DiscoveredRouteTimerId<CC::WeakDeviceId>
314{
315 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
316 let Self { device_id } = self;
317 let Some(device_id) = device_id.upgrade() else {
318 return;
319 };
320 core_ctx.with_discovered_routes_mut(
321 &device_id,
322 |Ipv6RouteDiscoveryState { routes, timers, rate_limit: _ }, core_ctx| {
323 let Some((route, ())) = timers.pop(bindings_ctx) else {
324 return;
325 };
326 let _properties =
327 routes.remove(&route).expect("invalidated route should be discovered");
328 core_ctx.del_discovered_ipv6_route(bindings_ctx, &device_id, route);
329 },
330 )
331 }
332}
333
334fn invalidate_route<BC: Ipv6RouteDiscoveryBindingsContext, CC: Ipv6DiscoveredRoutesContext<BC>>(
335 core_ctx: &mut CC,
336 bindings_ctx: &mut BC,
337 device_id: &CC::DeviceId,
338 state: &mut Ipv6RouteDiscoveryState<BC>,
339 route: Ipv6DiscoveredRoute,
340) {
341 let _: Option<(BC::Instant, ())> = state.timers.cancel(bindings_ctx, &route);
343 core_ctx.del_discovered_ipv6_route(bindings_ctx, device_id, route)
344}
345
346#[cfg(test)]
347mod tests {
348 use netstack3_base::testutil::{
349 FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeInstant, FakeTimerCtxExt as _,
350 FakeWeakDeviceId,
351 };
352 use netstack3_base::{CtxPair, IntoCoreTimerCtx};
353 use packet_formats::utils::NonZeroDuration;
354
355 use super::*;
356 use crate::internal::base::IPV6_DEFAULT_SUBNET;
357
358 #[derive(Default)]
359 struct FakeWithDiscoveredRoutesMutCtx {
360 route_table: HashMap<Ipv6DiscoveredRoute, Ipv6DiscoveredRouteProperties>,
361 }
362
363 impl DeviceIdContext<AnyDevice> for FakeWithDiscoveredRoutesMutCtx {
364 type DeviceId = FakeDeviceId;
365 type WeakDeviceId = FakeWeakDeviceId<FakeDeviceId>;
366 }
367
368 impl<C> Ipv6DiscoveredRoutesContext<C> for FakeWithDiscoveredRoutesMutCtx {
369 fn add_discovered_ipv6_route(
370 &mut self,
371 _bindings_ctx: &mut C,
372 FakeDeviceId: &Self::DeviceId,
373 route: Ipv6DiscoveredRoute,
374 properties: Ipv6DiscoveredRouteProperties,
375 ) {
376 let Self { route_table } = self;
377 let _: Option<Ipv6DiscoveredRouteProperties> = route_table.insert(route, properties);
378 }
379
380 fn del_discovered_ipv6_route(
381 &mut self,
382 _bindings_ctx: &mut C,
383 FakeDeviceId: &Self::DeviceId,
384 route: Ipv6DiscoveredRoute,
385 ) {
386 let Self { route_table } = self;
387 let _: Option<Ipv6DiscoveredRouteProperties> = route_table.remove(&route);
388 }
389 }
390
391 struct FakeIpv6RouteDiscoveryContext {
392 state: Ipv6RouteDiscoveryState<FakeBindingsCtxImpl>,
393 route_table: FakeWithDiscoveredRoutesMutCtx,
394 }
395
396 type FakeCoreCtxImpl = FakeCoreCtx<FakeIpv6RouteDiscoveryContext, (), FakeDeviceId>;
397
398 type FakeBindingsCtxImpl =
399 FakeBindingsCtx<Ipv6DiscoveredRouteTimerId<FakeWeakDeviceId<FakeDeviceId>>, (), (), ()>;
400
401 impl Ipv6RouteDiscoveryContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
402 type WithDiscoveredRoutesMutCtx<'a> = FakeWithDiscoveredRoutesMutCtx;
403
404 fn with_discovered_routes_mut<
405 O,
406 F: FnOnce(
407 &mut Ipv6RouteDiscoveryState<FakeBindingsCtxImpl>,
408 &mut Self::WithDiscoveredRoutesMutCtx<'_>,
409 ) -> O,
410 >(
411 &mut self,
412 &FakeDeviceId: &Self::DeviceId,
413 cb: F,
414 ) -> O {
415 let FakeIpv6RouteDiscoveryContext { state, route_table, .. } = &mut self.state;
416 cb(state, route_table)
417 }
418 }
419
420 const ROUTE1: Ipv6DiscoveredRoute =
421 Ipv6DiscoveredRoute { subnet: IPV6_DEFAULT_SUBNET, gateway: None };
422 const PROP1: Ipv6DiscoveredRouteProperties =
423 Ipv6DiscoveredRouteProperties { route_preference: RoutePreference::Medium };
424 const ROUTE2: Ipv6DiscoveredRoute = Ipv6DiscoveredRoute {
425 subnet: unsafe {
426 Subnet::new_unchecked(Ipv6Addr::new([0x2620, 0x1012, 0x1000, 0x5000, 0, 0, 0, 0]), 64)
427 },
428 gateway: None,
429 };
430 const PROP2: Ipv6DiscoveredRouteProperties =
431 Ipv6DiscoveredRouteProperties { route_preference: RoutePreference::High };
432
433 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
434 const TWO_SECONDS: NonZeroDuration = NonZeroDuration::from_secs(2).unwrap();
435
436 fn new_context() -> CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl> {
437 CtxPair::with_default_bindings_ctx(|bindings_ctx| {
438 FakeCoreCtxImpl::with_state(FakeIpv6RouteDiscoveryContext {
439 state: Ipv6RouteDiscoveryState::new::<_, IntoCoreTimerCtx>(
440 bindings_ctx,
441 FakeWeakDeviceId(FakeDeviceId),
442 ),
443 route_table: Default::default(),
444 })
445 })
446 }
447
448 #[test]
449 fn new_route_no_lifetime() {
450 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
451
452 RouteDiscoveryHandler::update_route(
453 &mut core_ctx,
454 &mut bindings_ctx,
455 &FakeDeviceId,
456 ROUTE1,
457 PROP1,
458 None,
459 &Default::default(),
460 );
461 bindings_ctx.timers.assert_no_timers_installed();
462 }
463
464 fn discover_new_route(
465 core_ctx: &mut FakeCoreCtxImpl,
466 bindings_ctx: &mut FakeBindingsCtxImpl,
467 route: Ipv6DiscoveredRoute,
468 properties: Ipv6DiscoveredRouteProperties,
469 duration: NonZeroNdpLifetime,
470 ) {
471 RouteDiscoveryHandler::update_route(
472 core_ctx,
473 bindings_ctx,
474 &FakeDeviceId,
475 route,
476 properties,
477 Some(duration),
478 &Default::default(),
479 );
480
481 let route_table = &core_ctx.state.route_table.route_table;
482 assert_eq!(route_table.get(&route), Some(&properties), "route_table={route_table:?}");
483
484 let expect = match duration {
485 NonZeroNdpLifetime::Finite(duration) => Some((FakeInstant::from(duration.get()), &())),
486 NonZeroNdpLifetime::Infinite => None,
487 };
488 assert_eq!(core_ctx.state.state.timers.get(&route), expect);
489 }
490
491 fn trigger_next_timer(
492 core_ctx: &mut FakeCoreCtxImpl,
493 bindings_ctx: &mut FakeBindingsCtxImpl,
494 route: Ipv6DiscoveredRoute,
495 ) {
496 core_ctx.state.state.timers.assert_top(&route, &());
497 assert_eq!(
498 bindings_ctx.trigger_next_timer(core_ctx),
499 Some(Ipv6DiscoveredRouteTimerId { device_id: FakeWeakDeviceId(FakeDeviceId) })
500 );
501 }
502
503 fn assert_route_invalidated(
504 core_ctx: &mut FakeCoreCtxImpl,
505 bindings_ctx: &mut FakeBindingsCtxImpl,
506 route: Ipv6DiscoveredRoute,
507 ) {
508 let route_table = &core_ctx.state.route_table.route_table;
509 assert!(!route_table.contains_key(&route), "route_table={route_table:?}");
510 bindings_ctx.timers.assert_no_timers_installed();
511 }
512
513 fn assert_single_invalidation_timer(
514 core_ctx: &mut FakeCoreCtxImpl,
515 bindings_ctx: &mut FakeBindingsCtxImpl,
516 route: Ipv6DiscoveredRoute,
517 ) {
518 trigger_next_timer(core_ctx, bindings_ctx, route);
519 assert_route_invalidated(core_ctx, bindings_ctx, route);
520 }
521
522 #[test]
523 fn invalidated_route_not_found() {
524 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
525
526 discover_new_route(
527 &mut core_ctx,
528 &mut bindings_ctx,
529 ROUTE1,
530 PROP1,
531 NonZeroNdpLifetime::Infinite,
532 );
533
534 assert!(core_ctx.state.route_table.route_table.remove(&ROUTE1).is_some());
537 update_to_invalidate_check_invalidation(&mut core_ctx, &mut bindings_ctx, ROUTE1, PROP1);
540 }
541
542 #[test]
543 fn new_route_with_infinite_lifetime() {
544 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
545
546 discover_new_route(
547 &mut core_ctx,
548 &mut bindings_ctx,
549 ROUTE1,
550 PROP1,
551 NonZeroNdpLifetime::Infinite,
552 );
553 bindings_ctx.timers.assert_no_timers_installed();
554 }
555
556 #[test]
557 fn update_route_from_infinite_to_finite_lifetime() {
558 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
559
560 discover_new_route(
561 &mut core_ctx,
562 &mut bindings_ctx,
563 ROUTE1,
564 PROP1,
565 NonZeroNdpLifetime::Infinite,
566 );
567 bindings_ctx.timers.assert_no_timers_installed();
568
569 RouteDiscoveryHandler::update_route(
570 &mut core_ctx,
571 &mut bindings_ctx,
572 &FakeDeviceId,
573 ROUTE1,
574 PROP1,
575 Some(NonZeroNdpLifetime::Finite(ONE_SECOND)),
576 &Default::default(),
577 );
578 assert_eq!(
579 core_ctx.state.state.timers.get(&ROUTE1),
580 Some((FakeInstant::from(ONE_SECOND.get()), &()))
581 );
582 assert_single_invalidation_timer(&mut core_ctx, &mut bindings_ctx, ROUTE1);
583 }
584
585 fn update_to_invalidate_check_invalidation(
586 core_ctx: &mut FakeCoreCtxImpl,
587 bindings_ctx: &mut FakeBindingsCtxImpl,
588 route: Ipv6DiscoveredRoute,
589 properties: Ipv6DiscoveredRouteProperties,
590 ) {
591 RouteDiscoveryHandler::update_route(
592 core_ctx,
593 bindings_ctx,
594 &FakeDeviceId,
595 route,
596 properties,
597 None,
598 &Default::default(),
599 );
600 assert_route_invalidated(core_ctx, bindings_ctx, route);
601 }
602
603 #[test]
604 fn invalidate_route_with_infinite_lifetime() {
605 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
606
607 discover_new_route(
608 &mut core_ctx,
609 &mut bindings_ctx,
610 ROUTE1,
611 PROP1,
612 NonZeroNdpLifetime::Infinite,
613 );
614 bindings_ctx.timers.assert_no_timers_installed();
615
616 update_to_invalidate_check_invalidation(&mut core_ctx, &mut bindings_ctx, ROUTE1, PROP1);
617 }
618 #[test]
619 fn new_route_with_finite_lifetime() {
620 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
621
622 discover_new_route(
623 &mut core_ctx,
624 &mut bindings_ctx,
625 ROUTE1,
626 PROP1,
627 NonZeroNdpLifetime::Finite(ONE_SECOND),
628 );
629 assert_single_invalidation_timer(&mut core_ctx, &mut bindings_ctx, ROUTE1);
630 }
631
632 #[test]
633 fn update_route_from_finite_to_infinite_lifetime() {
634 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
635
636 discover_new_route(
637 &mut core_ctx,
638 &mut bindings_ctx,
639 ROUTE1,
640 PROP1,
641 NonZeroNdpLifetime::Finite(ONE_SECOND),
642 );
643
644 RouteDiscoveryHandler::update_route(
645 &mut core_ctx,
646 &mut bindings_ctx,
647 &FakeDeviceId,
648 ROUTE1,
649 PROP1,
650 Some(NonZeroNdpLifetime::Infinite),
651 &Default::default(),
652 );
653 bindings_ctx.timers.assert_no_timers_installed();
654 }
655
656 #[test]
657 fn update_route_from_finite_to_finite_lifetime() {
658 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
659
660 discover_new_route(
661 &mut core_ctx,
662 &mut bindings_ctx,
663 ROUTE1,
664 PROP1,
665 NonZeroNdpLifetime::Finite(ONE_SECOND),
666 );
667
668 RouteDiscoveryHandler::update_route(
669 &mut core_ctx,
670 &mut bindings_ctx,
671 &FakeDeviceId,
672 ROUTE1,
673 PROP1,
674 Some(NonZeroNdpLifetime::Finite(TWO_SECONDS)),
675 &Default::default(),
676 );
677 assert_eq!(
678 core_ctx.state.state.timers.get(&ROUTE1),
679 Some((FakeInstant::from(TWO_SECONDS.get()), &()))
680 );
681 assert_single_invalidation_timer(&mut core_ctx, &mut bindings_ctx, ROUTE1);
682 }
683
684 #[test]
685 fn invalidate_route_with_finite_lifetime() {
686 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
687
688 discover_new_route(
689 &mut core_ctx,
690 &mut bindings_ctx,
691 ROUTE1,
692 PROP1,
693 NonZeroNdpLifetime::Finite(ONE_SECOND),
694 );
695
696 update_to_invalidate_check_invalidation(&mut core_ctx, &mut bindings_ctx, ROUTE1, PROP1);
697 }
698
699 #[test]
700 fn invalidate_all_routes() {
701 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
702 discover_new_route(
703 &mut core_ctx,
704 &mut bindings_ctx,
705 ROUTE1,
706 PROP1,
707 NonZeroNdpLifetime::Finite(ONE_SECOND),
708 );
709 discover_new_route(
710 &mut core_ctx,
711 &mut bindings_ctx,
712 ROUTE2,
713 PROP2,
714 NonZeroNdpLifetime::Finite(TWO_SECONDS),
715 );
716
717 RouteDiscoveryHandler::invalidate_routes(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
718 bindings_ctx.timers.assert_no_timers_installed();
719 let route_table = &core_ctx.state.route_table.route_table;
720 assert!(route_table.is_empty(), "route_table={route_table:?}");
721 }
722
723 fn make_route(i: u16) -> Ipv6DiscoveredRoute {
724 Ipv6DiscoveredRoute {
725 subnet: Subnet::new(Ipv6Addr::new([0x2001, 0xdb8, 0, 0, 0, 0, 0, i]), 128)
726 .expect("should be a valid IPv6 Subnet"),
727 gateway: None,
728 }
729 }
730
731 #[test]
732 fn max_discovered_routes() {
733 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
734
735 for i in 0..MAX_DISCOVERED_ROUTES {
737 let route = make_route(i as u16);
738 discover_new_route(
739 &mut core_ctx,
740 &mut bindings_ctx,
741 route,
742 PROP1,
743 NonZeroNdpLifetime::Infinite,
744 );
745 bindings_ctx.timers.instant.sleep(core::time::Duration::from_secs(1));
747 }
748 assert_eq!(core_ctx.state.route_table.route_table.len(), MAX_DISCOVERED_ROUTES);
749
750 let extra_route = make_route(MAX_DISCOVERED_ROUTES as u16);
752 RouteDiscoveryHandler::update_route(
753 &mut core_ctx,
754 &mut bindings_ctx,
755 &FakeDeviceId,
756 extra_route,
757 PROP1,
758 Some(NonZeroNdpLifetime::Infinite),
759 &Default::default(),
760 );
761 assert_eq!(core_ctx.state.route_table.route_table.len(), MAX_DISCOVERED_ROUTES);
762 assert!(!core_ctx.state.route_table.route_table.contains_key(&extra_route));
763
764 let route_to_update = make_route(0);
766 RouteDiscoveryHandler::update_route(
767 &mut core_ctx,
768 &mut bindings_ctx,
769 &FakeDeviceId,
770 route_to_update,
771 PROP2,
772 Some(NonZeroNdpLifetime::Infinite),
773 &Default::default(),
774 );
775 assert_eq!(core_ctx.state.route_table.route_table.get(&route_to_update), Some(&PROP2));
776
777 let route_to_delete = make_route(0);
779 RouteDiscoveryHandler::update_route(
780 &mut core_ctx,
781 &mut bindings_ctx,
782 &FakeDeviceId,
783 route_to_delete,
784 PROP2,
785 None,
786 &Default::default(),
787 );
788 assert_eq!(core_ctx.state.route_table.route_table.len(), MAX_DISCOVERED_ROUTES - 1);
789 assert!(!core_ctx.state.route_table.route_table.contains_key(&route_to_delete));
790
791 discover_new_route(
793 &mut core_ctx,
794 &mut bindings_ctx,
795 extra_route,
796 PROP1,
797 NonZeroNdpLifetime::Infinite,
798 );
799 assert_eq!(core_ctx.state.route_table.route_table.len(), MAX_DISCOVERED_ROUTES);
800 assert!(core_ctx.state.route_table.route_table.contains_key(&extra_route));
801 }
802
803 #[test]
804 fn rate_limiting() {
805 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
806
807 for i in 0..MAX_DISCOVERIES_PER_SECOND {
809 let route = make_route(i as u16);
810 discover_new_route(
811 &mut core_ctx,
812 &mut bindings_ctx,
813 route,
814 PROP1,
815 NonZeroNdpLifetime::Infinite,
816 );
817 }
818 assert_eq!(
819 core_ctx.state.route_table.route_table.len(),
820 MAX_DISCOVERIES_PER_SECOND as usize
821 );
822
823 let extra_route = make_route(MAX_DISCOVERIES_PER_SECOND as u16);
825 RouteDiscoveryHandler::update_route(
826 &mut core_ctx,
827 &mut bindings_ctx,
828 &FakeDeviceId,
829 extra_route,
830 PROP1,
831 Some(NonZeroNdpLifetime::Infinite),
832 &Default::default(),
833 );
834 assert_eq!(
835 core_ctx.state.route_table.route_table.len(),
836 MAX_DISCOVERIES_PER_SECOND as usize
837 );
838 assert!(!core_ctx.state.route_table.route_table.contains_key(&extra_route));
839
840 bindings_ctx.timers.instant.sleep(core::time::Duration::from_secs(1));
843 discover_new_route(
844 &mut core_ctx,
845 &mut bindings_ctx,
846 extra_route,
847 PROP1,
848 NonZeroNdpLifetime::Infinite,
849 );
850 assert_eq!(
851 core_ctx.state.route_table.route_table.len(),
852 (MAX_DISCOVERIES_PER_SECOND + 1) as usize
853 );
854 assert!(core_ctx.state.route_table.route_table.contains_key(&extra_route));
855 }
856}