1use core::hash::Hash;
10
11use derivative::Derivative;
12use net_types::LinkLocalUnicastAddr;
13use net_types::ip::{Ipv6Addr, Subnet};
14use netstack3_base::{
15 AnyDevice, CoreTimerContext, DeviceIdContext, HandleableTimer, InstantBindingsTypes,
16 LocalTimerHeap, TimerBindingsTypes, TimerContext, WeakDeviceIdentifier,
17};
18use netstack3_hashmap::HashSet;
19use packet_formats::icmp::ndp::NonZeroNdpLifetime;
20
21#[derive(Debug)]
23pub struct Ipv6RouteDiscoveryState<BT: Ipv6RouteDiscoveryBindingsTypes> {
24 routes: HashSet<Ipv6DiscoveredRoute>,
29 timers: LocalTimerHeap<Ipv6DiscoveredRoute, (), BT>,
30}
31
32impl<BT: Ipv6RouteDiscoveryBindingsTypes> Ipv6RouteDiscoveryState<BT> {
33 #[cfg(any(test, feature = "testutils"))]
35 pub fn timers(&self) -> &LocalTimerHeap<Ipv6DiscoveredRoute, (), BT> {
36 &self.timers
37 }
38}
39
40impl<BC: Ipv6RouteDiscoveryBindingsContext> Ipv6RouteDiscoveryState<BC> {
41 pub fn new<D: WeakDeviceIdentifier, CC: CoreTimerContext<Ipv6DiscoveredRouteTimerId<D>, BC>>(
43 bindings_ctx: &mut BC,
44 device_id: D,
45 ) -> Self {
46 Self {
47 routes: Default::default(),
48 timers: LocalTimerHeap::new_with_context::<_, CC>(
49 bindings_ctx,
50 Ipv6DiscoveredRouteTimerId { device_id },
51 ),
52 }
53 }
54}
55
56#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
58pub struct Ipv6DiscoveredRoute {
59 pub subnet: Subnet<Ipv6Addr>,
61
62 pub gateway: Option<LinkLocalUnicastAddr<Ipv6Addr>>,
66}
67
68#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
70pub struct Ipv6DiscoveredRouteTimerId<D: WeakDeviceIdentifier> {
71 device_id: D,
72}
73
74impl<D: WeakDeviceIdentifier> Ipv6DiscoveredRouteTimerId<D> {
75 pub(super) fn device_id(&self) -> &D {
76 &self.device_id
77 }
78}
79
80#[derive(Copy, Clone, Debug, Eq, PartialEq, Derivative)]
82#[derivative(Default)]
83pub struct RouteDiscoveryConfiguration {
84 #[derivative(Default(value = "true"))]
86 pub allow_default_route: bool,
87}
88
89#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
91#[allow(missing_docs)]
92pub struct RouteDiscoveryConfigurationUpdate {
93 pub allow_default_route: Option<bool>,
94}
95
96impl RouteDiscoveryConfiguration {
97 pub fn update(
101 &mut self,
102 update: RouteDiscoveryConfigurationUpdate,
103 ) -> RouteDiscoveryConfigurationUpdate {
104 let RouteDiscoveryConfigurationUpdate { allow_default_route } = update;
105 let allow_default_route =
106 allow_default_route.map(|new| core::mem::replace(&mut self.allow_default_route, new));
107 RouteDiscoveryConfigurationUpdate { allow_default_route }
108 }
109}
110
111pub trait Ipv6DiscoveredRoutesContext<BC>: DeviceIdContext<AnyDevice> {
116 fn add_discovered_ipv6_route(
118 &mut self,
119 bindings_ctx: &mut BC,
120 device_id: &Self::DeviceId,
121 route: Ipv6DiscoveredRoute,
122 );
123
124 fn del_discovered_ipv6_route(
127 &mut self,
128 bindings_ctx: &mut BC,
129 device_id: &Self::DeviceId,
130 route: Ipv6DiscoveredRoute,
131 );
132}
133
134pub trait Ipv6RouteDiscoveryContext<BT: Ipv6RouteDiscoveryBindingsTypes>:
136 DeviceIdContext<AnyDevice>
137{
138 type WithDiscoveredRoutesMutCtx<'a>: Ipv6DiscoveredRoutesContext<BT, DeviceId = Self::DeviceId>;
140
141 fn with_discovered_routes_mut<
143 O,
144 F: FnOnce(&mut Ipv6RouteDiscoveryState<BT>, &mut Self::WithDiscoveredRoutesMutCtx<'_>) -> O,
145 >(
146 &mut self,
147 device_id: &Self::DeviceId,
148 cb: F,
149 ) -> O;
150}
151
152pub trait Ipv6RouteDiscoveryBindingsTypes: TimerBindingsTypes + InstantBindingsTypes {}
154impl<BT> Ipv6RouteDiscoveryBindingsTypes for BT where BT: TimerBindingsTypes + InstantBindingsTypes {}
155
156pub trait Ipv6RouteDiscoveryBindingsContext:
158 Ipv6RouteDiscoveryBindingsTypes + TimerContext
159{
160}
161impl<BC> Ipv6RouteDiscoveryBindingsContext for BC where
162 BC: Ipv6RouteDiscoveryBindingsTypes + TimerContext
163{
164}
165
166pub trait RouteDiscoveryHandler<BC>: DeviceIdContext<AnyDevice> {
168 fn update_route(
175 &mut self,
176 bindings_ctx: &mut BC,
177 device_id: &Self::DeviceId,
178 route: Ipv6DiscoveredRoute,
179 lifetime: Option<NonZeroNdpLifetime>,
180 config: &RouteDiscoveryConfiguration,
181 );
182
183 fn invalidate_routes(&mut self, bindings_ctx: &mut BC, device_id: &Self::DeviceId);
185}
186
187impl<BC: Ipv6RouteDiscoveryBindingsContext, CC: Ipv6RouteDiscoveryContext<BC>>
188 RouteDiscoveryHandler<BC> for CC
189{
190 fn update_route(
191 &mut self,
192 bindings_ctx: &mut BC,
193 device_id: &CC::DeviceId,
194 route: Ipv6DiscoveredRoute,
195 lifetime: Option<NonZeroNdpLifetime>,
196 config: &RouteDiscoveryConfiguration,
197 ) {
198 self.with_discovered_routes_mut(device_id, |state, core_ctx| {
199 let Ipv6RouteDiscoveryState { routes, timers } = state;
200 match lifetime {
201 Some(lifetime) => {
202 if !config.allow_default_route && route.subnet.prefix() == 0 {
203 return;
204 }
205 let newly_added = routes.insert(route.clone());
206 if newly_added {
207 core_ctx.add_discovered_ipv6_route(bindings_ctx, device_id, route);
208 }
209
210 let prev_timer_fires_at = match lifetime {
211 NonZeroNdpLifetime::Finite(lifetime) => {
212 timers.schedule_after(bindings_ctx, route, (), lifetime.get())
213 }
214 NonZeroNdpLifetime::Infinite => timers.cancel(bindings_ctx, &route),
216 };
217
218 if newly_added {
219 if let Some((prev_timer_fires_at, ())) = prev_timer_fires_at {
220 panic!(
221 "newly added route {:?} should not have already been \
222 scheduled to fire at {:?}",
223 route, prev_timer_fires_at,
224 )
225 }
226 }
227 }
228 None => {
229 if routes.remove(&route) {
230 invalidate_route(core_ctx, bindings_ctx, device_id, state, route);
231 }
232 }
233 }
234 })
235 }
236
237 fn invalidate_routes(&mut self, bindings_ctx: &mut BC, device_id: &CC::DeviceId) {
238 self.with_discovered_routes_mut(device_id, |state, core_ctx| {
239 for route in core::mem::take(&mut state.routes).into_iter() {
240 invalidate_route(core_ctx, bindings_ctx, device_id, state, route);
241 }
242 })
243 }
244}
245
246impl<BC: Ipv6RouteDiscoveryBindingsContext, CC: Ipv6RouteDiscoveryContext<BC>>
247 HandleableTimer<CC, BC> for Ipv6DiscoveredRouteTimerId<CC::WeakDeviceId>
248{
249 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
250 let Self { device_id } = self;
251 let Some(device_id) = device_id.upgrade() else {
252 return;
253 };
254 core_ctx.with_discovered_routes_mut(
255 &device_id,
256 |Ipv6RouteDiscoveryState { routes, timers }, core_ctx| {
257 let Some((route, ())) = timers.pop(bindings_ctx) else {
258 return;
259 };
260 assert!(routes.remove(&route), "invalidated route should be discovered");
261 core_ctx.del_discovered_ipv6_route(bindings_ctx, &device_id, route);
262 },
263 )
264 }
265}
266
267fn invalidate_route<BC: Ipv6RouteDiscoveryBindingsContext, CC: Ipv6DiscoveredRoutesContext<BC>>(
268 core_ctx: &mut CC,
269 bindings_ctx: &mut BC,
270 device_id: &CC::DeviceId,
271 state: &mut Ipv6RouteDiscoveryState<BC>,
272 route: Ipv6DiscoveredRoute,
273) {
274 let _: Option<(BC::Instant, ())> = state.timers.cancel(bindings_ctx, &route);
276 core_ctx.del_discovered_ipv6_route(bindings_ctx, device_id, route)
277}
278
279#[cfg(test)]
280mod tests {
281 use netstack3_base::testutil::{
282 FakeBindingsCtx, FakeCoreCtx, FakeDeviceId, FakeInstant, FakeTimerCtxExt as _,
283 FakeWeakDeviceId,
284 };
285 use netstack3_base::{CtxPair, IntoCoreTimerCtx};
286 use packet_formats::utils::NonZeroDuration;
287
288 use super::*;
289 use crate::internal::base::IPV6_DEFAULT_SUBNET;
290
291 #[derive(Default)]
292 struct FakeWithDiscoveredRoutesMutCtx {
293 route_table: HashSet<Ipv6DiscoveredRoute>,
294 }
295
296 impl DeviceIdContext<AnyDevice> for FakeWithDiscoveredRoutesMutCtx {
297 type DeviceId = FakeDeviceId;
298 type WeakDeviceId = FakeWeakDeviceId<FakeDeviceId>;
299 }
300
301 impl<C> Ipv6DiscoveredRoutesContext<C> for FakeWithDiscoveredRoutesMutCtx {
302 fn add_discovered_ipv6_route(
303 &mut self,
304 _bindings_ctx: &mut C,
305 FakeDeviceId: &Self::DeviceId,
306 route: Ipv6DiscoveredRoute,
307 ) {
308 let Self { route_table } = self;
309 let _newly_inserted = route_table.insert(route);
310 }
311
312 fn del_discovered_ipv6_route(
313 &mut self,
314 _bindings_ctx: &mut C,
315 FakeDeviceId: &Self::DeviceId,
316 route: Ipv6DiscoveredRoute,
317 ) {
318 let Self { route_table } = self;
319 let _: bool = route_table.remove(&route);
320 }
321 }
322
323 struct FakeIpv6RouteDiscoveryContext {
324 state: Ipv6RouteDiscoveryState<FakeBindingsCtxImpl>,
325 route_table: FakeWithDiscoveredRoutesMutCtx,
326 }
327
328 type FakeCoreCtxImpl = FakeCoreCtx<FakeIpv6RouteDiscoveryContext, (), FakeDeviceId>;
329
330 type FakeBindingsCtxImpl =
331 FakeBindingsCtx<Ipv6DiscoveredRouteTimerId<FakeWeakDeviceId<FakeDeviceId>>, (), (), ()>;
332
333 impl Ipv6RouteDiscoveryContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
334 type WithDiscoveredRoutesMutCtx<'a> = FakeWithDiscoveredRoutesMutCtx;
335
336 fn with_discovered_routes_mut<
337 O,
338 F: FnOnce(
339 &mut Ipv6RouteDiscoveryState<FakeBindingsCtxImpl>,
340 &mut Self::WithDiscoveredRoutesMutCtx<'_>,
341 ) -> O,
342 >(
343 &mut self,
344 &FakeDeviceId: &Self::DeviceId,
345 cb: F,
346 ) -> O {
347 let FakeIpv6RouteDiscoveryContext { state, route_table, .. } = &mut self.state;
348 cb(state, route_table)
349 }
350 }
351
352 const ROUTE1: Ipv6DiscoveredRoute =
353 Ipv6DiscoveredRoute { subnet: IPV6_DEFAULT_SUBNET, gateway: None };
354 const ROUTE2: Ipv6DiscoveredRoute = Ipv6DiscoveredRoute {
355 subnet: unsafe {
356 Subnet::new_unchecked(Ipv6Addr::new([0x2620, 0x1012, 0x1000, 0x5000, 0, 0, 0, 0]), 64)
357 },
358 gateway: None,
359 };
360
361 const ONE_SECOND: NonZeroDuration = NonZeroDuration::from_secs(1).unwrap();
362 const TWO_SECONDS: NonZeroDuration = NonZeroDuration::from_secs(2).unwrap();
363
364 fn new_context() -> CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl> {
365 CtxPair::with_default_bindings_ctx(|bindings_ctx| {
366 FakeCoreCtxImpl::with_state(FakeIpv6RouteDiscoveryContext {
367 state: Ipv6RouteDiscoveryState::new::<_, IntoCoreTimerCtx>(
368 bindings_ctx,
369 FakeWeakDeviceId(FakeDeviceId),
370 ),
371 route_table: Default::default(),
372 })
373 })
374 }
375
376 #[test]
377 fn new_route_no_lifetime() {
378 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
379
380 RouteDiscoveryHandler::update_route(
381 &mut core_ctx,
382 &mut bindings_ctx,
383 &FakeDeviceId,
384 ROUTE1,
385 None,
386 &Default::default(),
387 );
388 bindings_ctx.timers.assert_no_timers_installed();
389 }
390
391 fn discover_new_route(
392 core_ctx: &mut FakeCoreCtxImpl,
393 bindings_ctx: &mut FakeBindingsCtxImpl,
394 route: Ipv6DiscoveredRoute,
395 duration: NonZeroNdpLifetime,
396 ) {
397 RouteDiscoveryHandler::update_route(
398 core_ctx,
399 bindings_ctx,
400 &FakeDeviceId,
401 route,
402 Some(duration),
403 &Default::default(),
404 );
405
406 let route_table = &core_ctx.state.route_table.route_table;
407 assert!(route_table.contains(&route), "route_table={route_table:?}");
408
409 let expect = match duration {
410 NonZeroNdpLifetime::Finite(duration) => Some((FakeInstant::from(duration.get()), &())),
411 NonZeroNdpLifetime::Infinite => None,
412 };
413 assert_eq!(core_ctx.state.state.timers.get(&route), expect);
414 }
415
416 fn trigger_next_timer(
417 core_ctx: &mut FakeCoreCtxImpl,
418 bindings_ctx: &mut FakeBindingsCtxImpl,
419 route: Ipv6DiscoveredRoute,
420 ) {
421 core_ctx.state.state.timers.assert_top(&route, &());
422 assert_eq!(
423 bindings_ctx.trigger_next_timer(core_ctx),
424 Some(Ipv6DiscoveredRouteTimerId { device_id: FakeWeakDeviceId(FakeDeviceId) })
425 );
426 }
427
428 fn assert_route_invalidated(
429 core_ctx: &mut FakeCoreCtxImpl,
430 bindings_ctx: &mut FakeBindingsCtxImpl,
431 route: Ipv6DiscoveredRoute,
432 ) {
433 let route_table = &core_ctx.state.route_table.route_table;
434 assert!(!route_table.contains(&route), "route_table={route_table:?}");
435 bindings_ctx.timers.assert_no_timers_installed();
436 }
437
438 fn assert_single_invalidation_timer(
439 core_ctx: &mut FakeCoreCtxImpl,
440 bindings_ctx: &mut FakeBindingsCtxImpl,
441 route: Ipv6DiscoveredRoute,
442 ) {
443 trigger_next_timer(core_ctx, bindings_ctx, route);
444 assert_route_invalidated(core_ctx, bindings_ctx, route);
445 }
446
447 #[test]
448 fn invalidated_route_not_found() {
449 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
450
451 discover_new_route(&mut core_ctx, &mut bindings_ctx, ROUTE1, NonZeroNdpLifetime::Infinite);
452
453 assert!(core_ctx.state.route_table.route_table.remove(&ROUTE1));
456 update_to_invalidate_check_invalidation(&mut core_ctx, &mut bindings_ctx, ROUTE1);
459 }
460
461 #[test]
462 fn new_route_with_infinite_lifetime() {
463 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
464
465 discover_new_route(&mut core_ctx, &mut bindings_ctx, ROUTE1, NonZeroNdpLifetime::Infinite);
466 bindings_ctx.timers.assert_no_timers_installed();
467 }
468
469 #[test]
470 fn update_route_from_infinite_to_finite_lifetime() {
471 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
472
473 discover_new_route(&mut core_ctx, &mut bindings_ctx, ROUTE1, NonZeroNdpLifetime::Infinite);
474 bindings_ctx.timers.assert_no_timers_installed();
475
476 RouteDiscoveryHandler::update_route(
477 &mut core_ctx,
478 &mut bindings_ctx,
479 &FakeDeviceId,
480 ROUTE1,
481 Some(NonZeroNdpLifetime::Finite(ONE_SECOND)),
482 &Default::default(),
483 );
484 assert_eq!(
485 core_ctx.state.state.timers.get(&ROUTE1),
486 Some((FakeInstant::from(ONE_SECOND.get()), &()))
487 );
488 assert_single_invalidation_timer(&mut core_ctx, &mut bindings_ctx, ROUTE1);
489 }
490
491 fn update_to_invalidate_check_invalidation(
492 core_ctx: &mut FakeCoreCtxImpl,
493 bindings_ctx: &mut FakeBindingsCtxImpl,
494 route: Ipv6DiscoveredRoute,
495 ) {
496 RouteDiscoveryHandler::update_route(
497 core_ctx,
498 bindings_ctx,
499 &FakeDeviceId,
500 ROUTE1,
501 None,
502 &Default::default(),
503 );
504 assert_route_invalidated(core_ctx, bindings_ctx, route);
505 }
506
507 #[test]
508 fn invalidate_route_with_infinite_lifetime() {
509 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
510
511 discover_new_route(&mut core_ctx, &mut bindings_ctx, ROUTE1, NonZeroNdpLifetime::Infinite);
512 bindings_ctx.timers.assert_no_timers_installed();
513
514 update_to_invalidate_check_invalidation(&mut core_ctx, &mut bindings_ctx, ROUTE1);
515 }
516 #[test]
517 fn new_route_with_finite_lifetime() {
518 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
519
520 discover_new_route(
521 &mut core_ctx,
522 &mut bindings_ctx,
523 ROUTE1,
524 NonZeroNdpLifetime::Finite(ONE_SECOND),
525 );
526 assert_single_invalidation_timer(&mut core_ctx, &mut bindings_ctx, ROUTE1);
527 }
528
529 #[test]
530 fn update_route_from_finite_to_infinite_lifetime() {
531 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
532
533 discover_new_route(
534 &mut core_ctx,
535 &mut bindings_ctx,
536 ROUTE1,
537 NonZeroNdpLifetime::Finite(ONE_SECOND),
538 );
539
540 RouteDiscoveryHandler::update_route(
541 &mut core_ctx,
542 &mut bindings_ctx,
543 &FakeDeviceId,
544 ROUTE1,
545 Some(NonZeroNdpLifetime::Infinite),
546 &Default::default(),
547 );
548 bindings_ctx.timers.assert_no_timers_installed();
549 }
550
551 #[test]
552 fn update_route_from_finite_to_finite_lifetime() {
553 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
554
555 discover_new_route(
556 &mut core_ctx,
557 &mut bindings_ctx,
558 ROUTE1,
559 NonZeroNdpLifetime::Finite(ONE_SECOND),
560 );
561
562 RouteDiscoveryHandler::update_route(
563 &mut core_ctx,
564 &mut bindings_ctx,
565 &FakeDeviceId,
566 ROUTE1,
567 Some(NonZeroNdpLifetime::Finite(TWO_SECONDS)),
568 &Default::default(),
569 );
570 assert_eq!(
571 core_ctx.state.state.timers.get(&ROUTE1),
572 Some((FakeInstant::from(TWO_SECONDS.get()), &()))
573 );
574 assert_single_invalidation_timer(&mut core_ctx, &mut bindings_ctx, ROUTE1);
575 }
576
577 #[test]
578 fn invalidate_route_with_finite_lifetime() {
579 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
580
581 discover_new_route(
582 &mut core_ctx,
583 &mut bindings_ctx,
584 ROUTE1,
585 NonZeroNdpLifetime::Finite(ONE_SECOND),
586 );
587
588 update_to_invalidate_check_invalidation(&mut core_ctx, &mut bindings_ctx, ROUTE1);
589 }
590
591 #[test]
592 fn invalidate_all_routes() {
593 let CtxPair { mut core_ctx, mut bindings_ctx } = new_context();
594 discover_new_route(
595 &mut core_ctx,
596 &mut bindings_ctx,
597 ROUTE1,
598 NonZeroNdpLifetime::Finite(ONE_SECOND),
599 );
600 discover_new_route(
601 &mut core_ctx,
602 &mut bindings_ctx,
603 ROUTE2,
604 NonZeroNdpLifetime::Finite(TWO_SECONDS),
605 );
606
607 RouteDiscoveryHandler::invalidate_routes(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId);
608 bindings_ctx.timers.assert_no_timers_installed();
609 let route_table = &core_ctx.state.route_table.route_table;
610 assert!(route_table.is_empty(), "route_table={route_table:?}");
611 }
612}