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