1use alloc::vec::Vec;
8use arrayvec::ArrayVec;
9use core::time::Duration;
10use derivative::Derivative;
11use lru_cache::LruCache;
12use net_types::ip::{Ip, IpVersionMarker};
13use netstack3_base::{
14 CoreTimerContext, Inspectable, Inspector, Instant as _, LocalFrameDestination,
15 StrongDeviceIdentifier as _, WeakDeviceIdentifier,
16};
17use packet::{Buf, ParseBufferMut};
18use packet_formats::ip::IpPacket;
19use zerocopy::SplitByteSlice;
20
21use crate::IpLayerIpExt;
22use crate::internal::multicast_forwarding::{
23 MulticastForwardingBindingsContext, MulticastForwardingBindingsTypes,
24 MulticastForwardingTimerId,
25};
26use crate::multicast_forwarding::MulticastRouteKey;
27
28pub(crate) const PACKET_QUEUE_LEN: usize = 3;
33
34const MAX_PENDING_ROUTES: usize = 1000;
40
41const PENDING_ROUTE_EXPIRATION: Duration = Duration::from_secs(10);
46
47const PENDING_ROUTE_GC_PERIOD: Duration = Duration::from_secs(10);
53
54#[derive(Derivative)]
60#[derivative(Debug(bound = ""))]
61pub struct MulticastForwardingPendingPackets<
62 I: IpLayerIpExt,
63 D: WeakDeviceIdentifier,
64 BT: MulticastForwardingBindingsTypes,
65> {
66 table: LruCache<MulticastRouteKey<I>, PacketQueue<I, D, BT>>,
67 gc_timer: BT::Timer,
77}
78
79impl<I: IpLayerIpExt, D: WeakDeviceIdentifier, BC: MulticastForwardingBindingsContext<I, D::Strong>>
80 MulticastForwardingPendingPackets<I, D, BC>
81{
82 pub(crate) fn new<CC>(bindings_ctx: &mut BC) -> Self
83 where
84 CC: CoreTimerContext<MulticastForwardingTimerId<I>, BC>,
85 {
86 Self {
87 table: LruCache::new(MAX_PENDING_ROUTES),
88 gc_timer: CC::new_timer(
89 bindings_ctx,
90 MulticastForwardingTimerId::PendingPacketsGc(IpVersionMarker::<I>::new()),
91 ),
92 }
93 }
94
95 pub(crate) fn try_queue_packet<B>(
99 &mut self,
100 bindings_ctx: &mut BC,
101 key: MulticastRouteKey<I>,
102 packet: &I::Packet<B>,
103 dev: &D::Strong,
104 frame_dst: Option<LocalFrameDestination>,
105 ) -> QueuePacketOutcome
106 where
107 B: SplitByteSlice,
108 {
109 let was_empty = self.table.is_empty();
110 let outcome = if let Some(queue) = self.table.get_mut(&key) {
111 match queue.try_push(|| QueuedPacket::new(dev, packet, frame_dst)) {
112 Ok(()) => QueuePacketOutcome::QueuedInExistingQueue,
113 Err(PacketQueueFullError) => QueuePacketOutcome::ExistingQueueFull,
114 }
115 } else {
116 let mut queue = PacketQueue::new(bindings_ctx);
117 queue
118 .try_push(|| QueuedPacket::new(dev, packet, frame_dst))
119 .expect("newly instantiated queue must have capacity");
120
121 let prev = self.table.insert(key, queue);
122 debug_assert!(prev.is_none());
123 QueuePacketOutcome::QueuedInNewQueue
124 };
125
126 if was_empty && !self.table.is_empty() {
129 let prev = bindings_ctx.schedule_timer(PENDING_ROUTE_GC_PERIOD, &mut self.gc_timer);
130 debug_assert!(prev.is_none());
131 }
132
133 outcome
134 }
135
136 #[cfg(any(debug_assertions, test))]
137 pub(crate) fn contains(&self, key: &MulticastRouteKey<I>) -> bool {
138 self.table.iter().any(|(k, _)| k == key)
139 }
140
141 pub(crate) fn remove(
145 &mut self,
146 key: &MulticastRouteKey<I>,
147 bindings_ctx: &mut BC,
148 ) -> Option<PacketQueue<I, D, BC>> {
149 let was_empty = self.table.is_empty();
150 let queue = self.table.remove(key);
151
152 if !was_empty && self.table.is_empty() {
156 let _: Option<BC::Instant> = bindings_ctx.cancel_timer(&mut self.gc_timer);
157 }
158
159 queue
160 }
161
162 pub(crate) fn run_garbage_collection(&mut self, bindings_ctx: &mut BC) -> u64 {
166 let now = bindings_ctx.now();
167 let mut removed_count = 0u64;
168 let expired_keys: Vec<_> = self
169 .table
170 .iter()
171 .filter_map(
172 |(key, queue)| if queue.expires_at <= now { Some(key.clone()) } else { None },
173 )
174 .collect();
175
176 for key in expired_keys {
177 let queue = self.table.remove(&key).expect("expired key must be present");
178 removed_count += queue.queue.len() as u64;
181 }
182
183 if !self.table.is_empty() {
187 let _: Option<BC::Instant> =
188 bindings_ctx.schedule_timer(PENDING_ROUTE_GC_PERIOD, &mut self.gc_timer);
189 }
190
191 removed_count
192 }
193}
194
195impl<I: IpLayerIpExt, D: WeakDeviceIdentifier, BT: MulticastForwardingBindingsTypes> Inspectable
196 for MulticastForwardingPendingPackets<I, D, BT>
197{
198 fn record<II: Inspector>(&self, inspector: &mut II) {
199 let MulticastForwardingPendingPackets { table, gc_timer: _ } = self;
200 inspector.record_usize("NumRoutes", table.len())
203 }
204}
205
206#[derive(Debug, PartialEq)]
208pub(crate) enum QueuePacketOutcome {
209 QueuedInNewQueue,
212 QueuedInExistingQueue,
215 ExistingQueueFull,
218}
219
220#[derive(Derivative)]
222#[derivative(Debug(bound = ""))]
223pub struct PacketQueue<I: Ip, D: WeakDeviceIdentifier, BT: MulticastForwardingBindingsTypes> {
224 queue: ArrayVec<QueuedPacket<I, D>, PACKET_QUEUE_LEN>,
225 expires_at: BT::Instant,
227}
228
229impl<I: IpLayerIpExt, D: WeakDeviceIdentifier, BC: MulticastForwardingBindingsContext<I, D::Strong>>
230 PacketQueue<I, D, BC>
231{
232 fn new(bindings_ctx: &mut BC) -> Self {
233 Self {
234 queue: Default::default(),
235 expires_at: bindings_ctx.now().panicking_add(PENDING_ROUTE_EXPIRATION),
236 }
237 }
238
239 fn try_push(
246 &mut self,
247 packet_builder: impl FnOnce() -> QueuedPacket<I, D>,
248 ) -> Result<(), PacketQueueFullError> {
249 if self.queue.is_full() {
250 return Err(PacketQueueFullError);
251 }
252 self.queue.push(packet_builder());
253 Ok(())
254 }
255}
256
257#[derive(Debug)]
258struct PacketQueueFullError;
259
260impl<I: Ip, D: WeakDeviceIdentifier, BT: MulticastForwardingBindingsTypes> IntoIterator
261 for PacketQueue<I, D, BT>
262{
263 type Item = QueuedPacket<I, D>;
264 type IntoIter = <ArrayVec<QueuedPacket<I, D>, PACKET_QUEUE_LEN> as IntoIterator>::IntoIter;
265 fn into_iter(self) -> Self::IntoIter {
266 let Self { queue, expires_at: _ } = self;
267 queue.into_iter()
268 }
269}
270
271#[derive(Debug, PartialEq)]
273pub struct QueuedPacket<I: Ip, D: WeakDeviceIdentifier> {
274 pub(crate) device: D,
276 pub(crate) packet: ValidIpPacketBuf<I>,
278 pub(crate) frame_dst: Option<LocalFrameDestination>,
281}
282
283impl<I: IpLayerIpExt, D: WeakDeviceIdentifier> QueuedPacket<I, D> {
284 fn new<B: SplitByteSlice>(
285 device: &D::Strong,
286 packet: &I::Packet<B>,
287 frame_dst: Option<LocalFrameDestination>,
288 ) -> Self {
289 QueuedPacket {
290 device: device.downgrade(),
291 packet: ValidIpPacketBuf::new(packet),
292 frame_dst,
293 }
294 }
295}
296
297#[derive(Clone, Debug, PartialEq)]
302pub(crate) struct ValidIpPacketBuf<I: Ip> {
303 buffer: Buf<Vec<u8>>,
304 _version_marker: IpVersionMarker<I>,
305}
306
307impl<I: IpLayerIpExt> ValidIpPacketBuf<I> {
308 fn new<B: SplitByteSlice>(packet: &I::Packet<B>) -> Self {
309 Self { buffer: Buf::new(packet.to_vec(), ..), _version_marker: Default::default() }
310 }
311
312 pub(crate) fn parse_ip_packet_mut(&mut self) -> I::Packet<&mut [u8]> {
320 self.buffer.parse_mut().unwrap()
322 }
323
324 pub(crate) fn into_inner(self) -> Buf<Vec<u8>> {
325 let Self { buffer, _version_marker } = self;
326 buffer
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 use assert_matches::assert_matches;
335 use ip_test_macro::ip_test;
336 use netstack3_base::testutil::{
337 FakeInstant, FakeTimerCtxExt, FakeWeakDeviceId, MultipleDevicesId,
338 };
339 use netstack3_base::{CounterContext, InstantContext, StrongDeviceIdentifier, TimerContext};
340 use packet::ParseBuffer;
341 use static_assertions::const_assert;
342 use test_case::test_case;
343
344 use crate::internal::multicast_forwarding;
345 use crate::internal::multicast_forwarding::counters::MulticastForwardingCounters;
346 use crate::internal::multicast_forwarding::testutil::{
347 FakeBindingsCtx, FakeCoreCtx, TestIpExt,
348 };
349
350 #[ip_test(I)]
351 #[test_case(None; "no_frame_dst")]
352 #[test_case(Some(LocalFrameDestination::Multicast); "some_frame_dst")]
353 fn queue_packet<I: TestIpExt>(frame_dst: Option<LocalFrameDestination>) {
354 const DEV: MultipleDevicesId = MultipleDevicesId::A;
355 let key1 = MulticastRouteKey::new(I::SRC1, I::DST1).unwrap();
356 let key2 = MulticastRouteKey::new(I::SRC2, I::DST2).unwrap();
357 let key3 = MulticastRouteKey::new(I::SRC1, I::DST2).unwrap();
358
359 let buf = multicast_forwarding::testutil::new_ip_packet_buf::<I>(I::SRC1, I::DST1);
362 let mut buf_ref = buf.as_ref();
363 let packet = buf_ref.parse::<I::Packet<_>>().expect("parse should succeed");
364
365 let mut bindings_ctx = FakeBindingsCtx::<I, MultipleDevicesId>::default();
366
367 let mut pending_table =
368 MulticastForwardingPendingPackets::<
369 I,
370 <MultipleDevicesId as StrongDeviceIdentifier>::Weak,
371 _,
372 >::new::<FakeCoreCtx<I, MultipleDevicesId>>(&mut bindings_ctx);
373
374 assert_eq!(
376 pending_table.try_queue_packet(
377 &mut bindings_ctx,
378 key1.clone(),
379 &packet,
380 &DEV,
381 frame_dst
382 ),
383 QueuePacketOutcome::QueuedInNewQueue
384 );
385 for _ in 1..PACKET_QUEUE_LEN {
387 assert_eq!(
388 pending_table.try_queue_packet(
389 &mut bindings_ctx,
390 key1.clone(),
391 &packet,
392 &DEV,
393 frame_dst
394 ),
395 QueuePacketOutcome::QueuedInExistingQueue
396 );
397 }
398 assert_eq!(
400 pending_table.try_queue_packet(
401 &mut bindings_ctx,
402 key1.clone(),
403 &packet,
404 &DEV,
405 frame_dst
406 ),
407 QueuePacketOutcome::ExistingQueueFull
408 );
409
410 assert_eq!(
412 pending_table.try_queue_packet(
413 &mut bindings_ctx,
414 key2.clone(),
415 &packet,
416 &DEV,
417 frame_dst
418 ),
419 QueuePacketOutcome::QueuedInNewQueue
420 );
421
422 let expected_packet = QueuedPacket::new(&DEV, &packet, frame_dst);
426 let queue =
427 pending_table.remove(&key1, &mut bindings_ctx).expect("key1 should have a queue");
428 assert_eq!(queue.queue.len(), PACKET_QUEUE_LEN);
429 for packet in queue.queue.as_slice() {
430 assert_eq!(packet, &expected_packet);
431 }
432
433 let queue =
434 pending_table.remove(&key2, &mut bindings_ctx).expect("key2 should have a queue");
435 let packet = assert_matches!(&queue.queue[..], [p] => p);
436 assert_eq!(packet, &expected_packet);
437
438 assert_matches!(pending_table.remove(&key3, &mut bindings_ctx), None);
439 }
440
441 fn next_gc_time<I: TestIpExt>(
443 core_ctx: &mut FakeCoreCtx<I, MultipleDevicesId>,
444 bindings_ctx: &mut FakeBindingsCtx<I, MultipleDevicesId>,
445 ) -> Option<FakeInstant> {
446 multicast_forwarding::testutil::with_pending_table(core_ctx, |pending_table| {
447 bindings_ctx.scheduled_instant(&mut pending_table.gc_timer)
448 })
449 }
450
451 fn try_queue_packet<I: TestIpExt>(
453 core_ctx: &mut FakeCoreCtx<I, MultipleDevicesId>,
454 bindings_ctx: &mut FakeBindingsCtx<I, MultipleDevicesId>,
455 key: MulticastRouteKey<I>,
456 dev: &MultipleDevicesId,
457 frame_dst: Option<LocalFrameDestination>,
458 ) -> QueuePacketOutcome {
459 let buf =
460 multicast_forwarding::testutil::new_ip_packet_buf::<I>(key.src_addr(), key.dst_addr());
461 let mut buf_ref = buf.as_ref();
462 let packet = buf_ref.parse::<I::Packet<_>>().expect("parse should succeed");
463 multicast_forwarding::testutil::with_pending_table(core_ctx, |pending_table| {
464 pending_table.try_queue_packet(bindings_ctx, key, &packet, dev, frame_dst)
465 })
466 }
467
468 fn remove_packet_queue<I: TestIpExt>(
470 core_ctx: &mut FakeCoreCtx<I, MultipleDevicesId>,
471 bindings_ctx: &mut FakeBindingsCtx<I, MultipleDevicesId>,
472 key: &MulticastRouteKey<I>,
473 ) -> Option<
474 PacketQueue<I, FakeWeakDeviceId<MultipleDevicesId>, FakeBindingsCtx<I, MultipleDevicesId>>,
475 > {
476 multicast_forwarding::testutil::with_pending_table(core_ctx, |pending_table| {
477 pending_table.remove(key, bindings_ctx)
478 })
479 }
480
481 fn run_gc<I: TestIpExt>(
483 core_ctx: &mut FakeCoreCtx<I, MultipleDevicesId>,
484 bindings_ctx: &mut FakeBindingsCtx<I, MultipleDevicesId>,
485 ) {
486 assert_matches!(
487 &bindings_ctx.trigger_timers_until_instant(bindings_ctx.now(), core_ctx)[..],
488 [MulticastForwardingTimerId::PendingPacketsGc(_)]
489 );
490 }
491
492 #[ip_test(I)]
493 fn garbage_collection<I: TestIpExt>() {
494 const DEV: MultipleDevicesId = MultipleDevicesId::A;
495 const FRAME_DST: Option<LocalFrameDestination> = None;
496 let key1 = MulticastRouteKey::<I>::new(I::SRC1, I::DST1).unwrap();
497 let key2 = MulticastRouteKey::<I>::new(I::SRC2, I::DST2).unwrap();
498
499 let mut api = multicast_forwarding::testutil::new_api();
500 assert!(api.enable());
501 let (core_ctx, bindings_ctx) = api.contexts();
502
503 const_assert!(PENDING_ROUTE_GC_PERIOD.checked_sub(PENDING_ROUTE_EXPIRATION).is_some());
509 const_assert!(!PENDING_ROUTE_EXPIRATION.is_zero());
510
511 assert!(next_gc_time(core_ctx, bindings_ctx).is_none());
513 let counters: &MulticastForwardingCounters<I> = core_ctx.counters();
514 assert_eq!(counters.pending_table_gc.get(), 0);
515 assert_eq!(counters.pending_packet_drops_gc.get(), 0);
516
517 let expected_first_gc = bindings_ctx.now() + PENDING_ROUTE_GC_PERIOD;
519 assert_eq!(
520 try_queue_packet(core_ctx, bindings_ctx, key1.clone(), &DEV, FRAME_DST),
521 QueuePacketOutcome::QueuedInNewQueue
522 );
523 assert_eq!(next_gc_time(core_ctx, bindings_ctx), Some(expected_first_gc));
524
525 bindings_ctx.timers.instant.sleep(PENDING_ROUTE_GC_PERIOD);
529 assert_eq!(
530 try_queue_packet(core_ctx, bindings_ctx, key2.clone(), &DEV, FRAME_DST),
531 QueuePacketOutcome::QueuedInNewQueue
532 );
533 assert_eq!(next_gc_time(core_ctx, bindings_ctx), Some(expected_first_gc));
534
535 run_gc(core_ctx, bindings_ctx);
538 let expected_second_gc = bindings_ctx.timers.instant.now() + PENDING_ROUTE_GC_PERIOD;
539 assert_eq!(next_gc_time(core_ctx, bindings_ctx), Some(expected_second_gc));
540
541 let counters: &MulticastForwardingCounters<I> = core_ctx.counters();
543 assert_eq!(counters.pending_table_gc.get(), 1);
544 assert_eq!(counters.pending_packet_drops_gc.get(), 1);
545 assert_matches!(remove_packet_queue(core_ctx, bindings_ctx, &key1), None);
546 assert_matches!(remove_packet_queue(core_ctx, bindings_ctx, &key2), Some(_));
547
548 assert!(next_gc_time(core_ctx, bindings_ctx).is_none());
551
552 assert_eq!(
555 try_queue_packet(core_ctx, bindings_ctx, key1.clone(), &DEV, FRAME_DST),
556 QueuePacketOutcome::QueuedInNewQueue
557 );
558 assert_eq!(next_gc_time(core_ctx, bindings_ctx), Some(expected_second_gc));
559 bindings_ctx.timers.instant.sleep(PENDING_ROUTE_GC_PERIOD);
560 run_gc(core_ctx, bindings_ctx);
561 let counters: &MulticastForwardingCounters<I> = core_ctx.counters();
562 assert_eq!(counters.pending_table_gc.get(), 2);
563 assert_eq!(counters.pending_packet_drops_gc.get(), 2);
564 assert_matches!(remove_packet_queue(core_ctx, bindings_ctx, &key1), None);
565 assert!(next_gc_time(core_ctx, bindings_ctx).is_none());
566 }
567}