1pub(crate) mod api;
17pub(crate) mod counters;
18pub(crate) mod packet_queue;
19pub(crate) mod route;
20pub(crate) mod state;
21
22use core::sync::atomic::Ordering;
23
24use net_types::ip::{GenericOverIp, Ip, IpVersionMarker};
25use netstack3_base::{
26 AnyDevice, AtomicInstant, BindingsSyncTypes, CounterContext, DeviceIdContext, EventContext,
27 HandleableTimer, InstantBindingsTypes, InstantContext, LocalFrameDestination, Marks,
28 TimerBindingsTypes, TimerContext, WeakDeviceIdentifier,
29};
30use packet_formats::ip::IpPacket;
31use zerocopy::SplitByteSlice;
32
33use crate::internal::multicast_forwarding::counters::MulticastForwardingCounters;
34use crate::internal::multicast_forwarding::packet_queue::QueuePacketOutcome;
35use crate::internal::multicast_forwarding::route::{
36 Action, MulticastRouteEntry, MulticastRouteTargets,
37};
38use crate::multicast_forwarding::{
39 MulticastForwardingPendingPacketsContext, MulticastForwardingState,
40 MulticastForwardingStateContext, MulticastRoute, MulticastRouteKey,
41 MulticastRouteTableContext as _,
42};
43use crate::{IpLayerEvent, IpLayerIpExt};
44
45pub trait MulticastForwardingBindingsTypes:
47 InstantBindingsTypes + TimerBindingsTypes + BindingsSyncTypes
48{
49}
50impl<BT: InstantBindingsTypes + TimerBindingsTypes + BindingsSyncTypes>
51 MulticastForwardingBindingsTypes for BT
52{
53}
54
55pub trait MulticastForwardingBindingsContext<I: IpLayerIpExt, D>:
57 MulticastForwardingBindingsTypes + InstantContext + TimerContext + EventContext<IpLayerEvent<D, I>>
58{
59}
60impl<
61 I: IpLayerIpExt,
62 D,
63 BC: MulticastForwardingBindingsTypes
64 + InstantContext
65 + TimerContext
66 + EventContext<IpLayerEvent<D, I>>,
67> MulticastForwardingBindingsContext<I, D> for BC
68{
69}
70
71pub trait MulticastForwardingDeviceContext<I: IpLayerIpExt>: DeviceIdContext<AnyDevice> {
73 fn is_device_multicast_forwarding_enabled(&mut self, dev: &Self::DeviceId) -> bool;
75}
76
77#[derive(Clone, Debug, Eq, GenericOverIp, Hash, PartialEq)]
79#[generic_over_ip(I, Ip)]
80pub enum MulticastForwardingTimerId<I: Ip> {
81 PendingPacketsGc(IpVersionMarker<I>),
83}
84
85impl<
86 I: IpLayerIpExt,
87 BC: MulticastForwardingBindingsContext<I, CC::DeviceId>,
88 CC: MulticastForwardingStateContext<I, BC> + CounterContext<MulticastForwardingCounters<I>>,
89> HandleableTimer<CC, BC> for MulticastForwardingTimerId<I>
90{
91 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
92 match self {
93 MulticastForwardingTimerId::PendingPacketsGc(_) => {
94 core_ctx.with_state(|state, ctx| match state {
95 MulticastForwardingState::Disabled => {}
98 MulticastForwardingState::Enabled(state) => {
99 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
100 .pending_table_gc
101 .increment();
102 let removed_count = ctx.with_pending_table_mut(state, |pending_table| {
103 pending_table.run_garbage_collection(bindings_ctx)
104 });
105 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
106 .pending_packet_drops_gc
107 .add(removed_count);
108 }
109 })
110 }
111 }
112 }
113}
114
115#[derive(Debug, Eq, Hash, PartialEq, GenericOverIp)]
117#[generic_over_ip(I, Ip)]
118pub enum MulticastForwardingEvent<I: IpLayerIpExt, D> {
119 MissingRoute {
121 key: MulticastRouteKey<I>,
123 input_interface: D,
125 },
126 WrongInputInterface {
128 key: MulticastRouteKey<I>,
130 actual_input_interface: D,
132 expected_input_interface: D,
135 },
136}
137
138impl<I: IpLayerIpExt, D> MulticastForwardingEvent<I, D> {
139 pub(crate) fn map_device<O, F: Fn(D) -> O>(self, map: F) -> MulticastForwardingEvent<I, O> {
140 match self {
141 MulticastForwardingEvent::MissingRoute { key, input_interface } => {
142 MulticastForwardingEvent::MissingRoute {
143 key,
144 input_interface: map(input_interface),
145 }
146 }
147 MulticastForwardingEvent::WrongInputInterface {
148 key,
149 actual_input_interface,
150 expected_input_interface,
151 } => MulticastForwardingEvent::WrongInputInterface {
152 key,
153 actual_input_interface: map(actual_input_interface),
154 expected_input_interface: map(expected_input_interface),
155 },
156 }
157 }
158}
159
160impl<I: IpLayerIpExt, D: WeakDeviceIdentifier> MulticastForwardingEvent<I, D> {
161 pub fn upgrade_device_id(self) -> Option<MulticastForwardingEvent<I, D::Strong>> {
163 match self {
164 MulticastForwardingEvent::MissingRoute { key, input_interface } => {
165 Some(MulticastForwardingEvent::MissingRoute {
166 key,
167 input_interface: input_interface.upgrade()?,
168 })
169 }
170 MulticastForwardingEvent::WrongInputInterface {
171 key,
172 actual_input_interface,
173 expected_input_interface,
174 } => Some(MulticastForwardingEvent::WrongInputInterface {
175 key,
176 actual_input_interface: actual_input_interface.upgrade()?,
177 expected_input_interface: expected_input_interface.upgrade()?,
178 }),
179 }
180 }
181}
182
183pub(crate) fn lookup_multicast_route_or_stash_packet<I, B, CC, BC>(
199 core_ctx: &mut CC,
200 bindings_ctx: &mut BC,
201 packet: &I::Packet<B>,
202 dev: &CC::DeviceId,
203 frame_dst: Option<LocalFrameDestination>,
204 max_fragment_len: Option<usize>,
205 marks: Marks,
206) -> Option<MulticastRouteTargets<CC::DeviceId>>
207where
208 I: IpLayerIpExt,
209 B: SplitByteSlice,
210 CC: MulticastForwardingStateContext<I, BC>
211 + MulticastForwardingDeviceContext<I>
212 + CounterContext<MulticastForwardingCounters<I>>,
213 BC: MulticastForwardingBindingsContext<I, CC::DeviceId>,
214{
215 CounterContext::<MulticastForwardingCounters<I>>::counters(core_ctx).rx.increment();
216 let Some(key) = MulticastRouteKey::new(packet.src_ip(), packet.dst_ip()) else {
219 CounterContext::<MulticastForwardingCounters<I>>::counters(core_ctx)
220 .no_tx_invalid_key
221 .increment();
222 return None;
223 };
224
225 if !core_ctx.is_device_multicast_forwarding_enabled(dev) {
227 CounterContext::<MulticastForwardingCounters<I>>::counters(core_ctx)
228 .no_tx_disabled_dev
229 .increment();
230 return None;
231 }
232
233 core_ctx.with_state(|state, ctx| {
234 let Some(state) = state.enabled() else {
236 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
237 .no_tx_disabled_stack_wide
238 .increment();
239 return None;
240 };
241 ctx.with_route_table(state, |route_table, ctx| {
242 if let Some(MulticastRouteEntry {
243 route: MulticastRoute { input_interface, action },
244 stats,
245 }) = route_table.get(&key)
246 {
247 if dev != input_interface {
248 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
249 .no_tx_wrong_dev
250 .increment();
251 bindings_ctx.on_event(
252 MulticastForwardingEvent::WrongInputInterface {
253 key,
254 actual_input_interface: dev.clone(),
255 expected_input_interface: input_interface.clone(),
256 }
257 .into(),
258 );
259 return None;
260 }
261
262 stats.last_used.store_max(bindings_ctx.now(), Ordering::Relaxed);
263
264 match action {
265 Action::Forward(targets) => {
266 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
267 .tx
268 .increment();
269 return Some(targets.clone());
270 }
271 }
272 }
273 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
274 .pending_packets
275 .increment();
276 match ctx.with_pending_table_mut(state, |pending_table| {
277 pending_table.try_queue_packet(
278 bindings_ctx,
279 key.clone(),
280 packet,
281 dev,
282 frame_dst,
283 max_fragment_len,
284 marks,
285 )
286 }) {
287 QueuePacketOutcome::QueuedInNewQueue => {
288 bindings_ctx.on_event(
289 MulticastForwardingEvent::MissingRoute {
290 key,
291 input_interface: dev.clone(),
292 }
293 .into(),
294 );
295 }
296 QueuePacketOutcome::QueuedInExistingQueue => {}
297 QueuePacketOutcome::ExistingQueueFull => {
298 CounterContext::<MulticastForwardingCounters<I>>::counters(ctx)
299 .pending_packet_drops_queue_full
300 .increment();
301 }
302 }
303 return None;
304 })
305 })
306}
307
308#[cfg(test)]
309mod testutil {
310 use super::*;
311
312 use alloc::rc::Rc;
313 use alloc::vec::Vec;
314 use core::cell::RefCell;
315 use derivative::Derivative;
316 use net_declare::{net_ip_v4, net_ip_v6};
317 use net_types::MulticastAddr;
318 use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Mtu};
319 use netstack3_base::socket::SocketIpAddr;
320 use netstack3_base::testutil::{FakeStrongDeviceId, MultipleDevicesId};
321 use netstack3_base::{
322 CoreTimerContext, CounterContext, CtxPair, Marks, NetworkSerializationContext,
323 NetworkSerializer, ResourceCounterContext,
324 };
325 use netstack3_filter::ProofOfEgressCheck;
326 use netstack3_hashmap::HashSet;
327 use packet::{BufferMut, InnerPacketBuilder, NestablePacketBuilder as _, Serializer};
328 use packet_formats::ip::{IpPacketBuilder, IpProto};
329
330 use crate::device::IpDeviceSendContext;
331 use crate::internal::base::DeviceIpLayerMetadata;
332 use crate::internal::icmp::IcmpErrorHandler;
333 use crate::multicast_forwarding::{
334 MulticastForwardingApi, MulticastForwardingEnabledState, MulticastForwardingPendingPackets,
335 MulticastForwardingPendingPacketsContext, MulticastForwardingState, MulticastRouteTable,
336 MulticastRouteTableContext,
337 };
338 use crate::{IpCounters, IpDeviceMtuContext, IpLayerEvent, IpPacketDestination};
339
340 pub(crate) trait TestIpExt: IpLayerIpExt {
342 const SRC1: Self::Addr;
343 const SRC2: Self::Addr;
344 const DST1: Self::Addr;
345 const DST2: Self::Addr;
346 }
347
348 impl TestIpExt for Ipv4 {
349 const SRC1: Ipv4Addr = net_ip_v4!("192.0.2.1");
350 const SRC2: Ipv4Addr = net_ip_v4!("192.0.2.2");
351 const DST1: Ipv4Addr = net_ip_v4!("224.0.1.1");
352 const DST2: Ipv4Addr = net_ip_v4!("224.0.1.2");
353 }
354
355 impl TestIpExt for Ipv6 {
356 const SRC1: Ipv6Addr = net_ip_v6!("2001:0DB8::1");
357 const SRC2: Ipv6Addr = net_ip_v6!("2001:0DB8::2");
358 const DST1: Ipv6Addr = net_ip_v6!("ff0e::1");
359 const DST2: Ipv6Addr = net_ip_v6!("ff0e::2");
360 }
361
362 pub(crate) fn new_ip_packet_buf<I: IpLayerIpExt>(
364 src_addr: I::Addr,
365 dst_addr: I::Addr,
366 ) -> impl AsRef<[u8]> {
367 const TTL: u8 = 255;
368 const IP_BODY: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
370 I::PacketBuilder::new(src_addr, dst_addr, TTL, IpProto::Udp.into())
371 .wrap_body(IP_BODY.into_serializer())
372 .serialize_vec_outer(&mut NetworkSerializationContext::default())
373 .unwrap()
374 }
375
376 #[derive(Debug, PartialEq)]
377 pub(crate) struct SentPacket<I: IpLayerIpExt, D> {
378 pub(crate) dst: MulticastAddr<I::Addr>,
379 pub(crate) device: D,
380 }
381
382 #[derive(Derivative)]
383 #[derivative(Default(bound = ""))]
384 pub(crate) struct FakeCoreCtxState<I: IpLayerIpExt, D: FakeStrongDeviceId> {
385 pub(crate) multicast_forwarding:
389 Rc<RefCell<MulticastForwardingState<I, D, FakeBindingsCtx<I, D>>>>,
390 pub(crate) forwarding_enabled_devices: HashSet<D>,
392 pub(crate) sent_packets: Vec<SentPacket<I, D>>,
394 stack_wide_counters: IpCounters<I>,
395 per_device_counters: IpCounters<I>,
396 multicast_forwarding_counters: MulticastForwardingCounters<I>,
397 }
398
399 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> FakeCoreCtxState<I, D> {
400 pub(crate) fn set_multicast_forwarding_enabled_for_dev(&mut self, dev: D, enabled: bool) {
401 if enabled {
402 let _: bool = self.forwarding_enabled_devices.insert(dev);
403 } else {
404 let _: bool = self.forwarding_enabled_devices.remove(&dev);
405 }
406 }
407
408 pub(crate) fn take_sent_packets(&mut self) -> Vec<SentPacket<I, D>> {
409 core::mem::take(&mut self.sent_packets)
410 }
411 }
412
413 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> CounterContext<IpCounters<I>>
414 for FakeCoreCtxState<I, D>
415 {
416 fn counters(&self) -> &IpCounters<I> {
417 &self.stack_wide_counters
418 }
419 }
420
421 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> ResourceCounterContext<D, IpCounters<I>>
422 for FakeCoreCtxState<I, D>
423 {
424 fn per_resource_counters(&self, _resource: &D) -> &IpCounters<I> {
425 &self.per_device_counters
426 }
427 }
428
429 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> CounterContext<MulticastForwardingCounters<I>>
430 for FakeCoreCtxState<I, D>
431 {
432 fn counters(&self) -> &MulticastForwardingCounters<I> {
433 &self.multicast_forwarding_counters
434 }
435 }
436
437 pub(crate) type FakeBindingsCtx<I, D> = netstack3_base::testutil::FakeBindingsCtx<
438 MulticastForwardingTimerId<I>,
439 IpLayerEvent<D, I>,
440 (),
441 (),
442 >;
443 pub(crate) type FakeCoreCtx<I, D> =
444 netstack3_base::testutil::FakeCoreCtx<FakeCoreCtxState<I, D>, (), D>;
445
446 impl<I: IpLayerIpExt, D: FakeStrongDeviceId>
447 MulticastForwardingStateContext<I, FakeBindingsCtx<I, D>> for FakeCoreCtx<I, D>
448 {
449 type Ctx<'a> = FakeCoreCtx<I, D>;
450 fn with_state<
451 O,
452 F: FnOnce(
453 &MulticastForwardingState<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
454 &mut Self::Ctx<'_>,
455 ) -> O,
456 >(
457 &mut self,
458 cb: F,
459 ) -> O {
460 let state = self.state.multicast_forwarding.clone();
461 let borrow = state.borrow();
462 cb(&borrow, self)
463 }
464 fn with_state_mut<
465 O,
466 F: FnOnce(
467 &mut MulticastForwardingState<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
468 &mut Self::Ctx<'_>,
469 ) -> O,
470 >(
471 &mut self,
472 cb: F,
473 ) -> O {
474 let state = self.state.multicast_forwarding.clone();
475 let mut borrow = state.borrow_mut();
476 cb(&mut borrow, self)
477 }
478 }
479
480 impl<I: IpLayerIpExt, D: FakeStrongDeviceId>
481 MulticastRouteTableContext<I, FakeBindingsCtx<I, D>> for FakeCoreCtx<I, D>
482 {
483 type Ctx<'a> = FakeCoreCtx<I, D>;
484 fn with_route_table<
485 O,
486 F: FnOnce(
487 &MulticastRouteTable<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
488 &mut Self::Ctx<'_>,
489 ) -> O,
490 >(
491 &mut self,
492 state: &MulticastForwardingEnabledState<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
493 cb: F,
494 ) -> O {
495 let route_table = state.route_table().read();
496 cb(&route_table, self)
497 }
498 fn with_route_table_mut<
499 O,
500 F: FnOnce(
501 &mut MulticastRouteTable<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
502 &mut Self::Ctx<'_>,
503 ) -> O,
504 >(
505 &mut self,
506 state: &MulticastForwardingEnabledState<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
507 cb: F,
508 ) -> O {
509 let mut route_table = state.route_table().write();
510 cb(&mut route_table, self)
511 }
512 }
513
514 impl<I: IpLayerIpExt, D: FakeStrongDeviceId>
515 MulticastForwardingPendingPacketsContext<I, FakeBindingsCtx<I, D>> for FakeCoreCtx<I, D>
516 {
517 fn with_pending_table_mut<
518 O,
519 F: FnOnce(
520 &mut MulticastForwardingPendingPackets<I, Self::WeakDeviceId, FakeBindingsCtx<I, D>>,
521 ) -> O,
522 >(
523 &mut self,
524 state: &MulticastForwardingEnabledState<I, Self::DeviceId, FakeBindingsCtx<I, D>>,
525 cb: F,
526 ) -> O {
527 let mut pending_table = state.pending_table().lock();
528 cb(&mut pending_table)
529 }
530 }
531
532 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> MulticastForwardingDeviceContext<I>
533 for FakeCoreCtx<I, D>
534 {
535 fn is_device_multicast_forwarding_enabled(&mut self, device_id: &Self::DeviceId) -> bool {
536 self.state.forwarding_enabled_devices.contains(device_id)
537 }
538 }
539
540 impl<I: IpLayerIpExt, D: FakeStrongDeviceId>
541 CoreTimerContext<MulticastForwardingTimerId<I>, FakeBindingsCtx<I, D>>
542 for FakeCoreCtx<I, D>
543 {
544 fn convert_timer(
545 dispatch_id: MulticastForwardingTimerId<I>,
546 ) -> MulticastForwardingTimerId<I> {
547 dispatch_id
548 }
549 }
550
551 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> IpDeviceSendContext<I, FakeBindingsCtx<I, D>>
552 for FakeCoreCtx<I, D>
553 {
554 fn send_ip_frame<S>(
555 &mut self,
556 _bindings_ctx: &mut FakeBindingsCtx<I, D>,
557 device_id: &D,
558 destination: IpPacketDestination<I, &D>,
559 _ip_layer_metadata: DeviceIpLayerMetadata<FakeBindingsCtx<I, D>>,
560 _body: S,
561 _egress_proof: ProofOfEgressCheck,
562 ) -> Result<(), netstack3_base::SendFrameError<S>>
563 where
564 S: NetworkSerializer,
565 S::Buffer: BufferMut,
566 {
567 let dst = match destination {
568 IpPacketDestination::Multicast(dst) => dst,
569 dst => panic!("unexpected sent packet: destination={dst:?}"),
570 };
571 self.state.sent_packets.push(SentPacket { dst, device: device_id.clone() });
572 Ok(())
573 }
574 }
575
576 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> IpDeviceMtuContext<I> for FakeCoreCtx<I, D> {
577 fn get_mtu(&mut self, _device_id: &Self::DeviceId) -> Mtu {
578 Mtu::max()
579 }
580 }
581
582 impl<I: IpLayerIpExt, D: FakeStrongDeviceId> IcmpErrorHandler<I, FakeBindingsCtx<I, D>>
583 for FakeCoreCtx<I, D>
584 {
585 fn send_icmp_error_message<B: BufferMut>(
586 &mut self,
587 _bindings_ctx: &mut FakeBindingsCtx<I, D>,
588 _device: Option<&D>,
589 _frame_dst: Option<LocalFrameDestination>,
590 _src_ip: SocketIpAddr<I::Addr>,
591 _dst_ip: SocketIpAddr<I::Addr>,
592 _original_packet: B,
593 _error: I::IcmpError,
594 _header_len: usize,
595 _proto: I::Proto,
596 _marks: &Marks,
597 ) {
598 unimplemented!()
599 }
600 }
601
602 pub(crate) fn new_api<I: IpLayerIpExt>() -> MulticastForwardingApi<
603 I,
604 CtxPair<FakeCoreCtx<I, MultipleDevicesId>, FakeBindingsCtx<I, MultipleDevicesId>>,
605 > {
606 MulticastForwardingApi::new(CtxPair::with_core_ctx(FakeCoreCtx::with_state(
607 Default::default(),
608 )))
609 }
610
611 pub(crate) fn with_pending_table<I, O, F, CC, BT>(core_ctx: &mut CC, cb: F) -> O
617 where
618 I: IpLayerIpExt,
619 CC: MulticastForwardingStateContext<I, BT>,
620 BT: MulticastForwardingBindingsTypes,
621 F: FnOnce(&mut MulticastForwardingPendingPackets<I, CC::WeakDeviceId, BT>) -> O,
622 {
623 core_ctx.with_state(|state, ctx| {
624 let state = state.enabled().unwrap();
625 ctx.with_route_table(state, |_routing_table, ctx| {
626 ctx.with_pending_table_mut(state, |pending_table| cb(pending_table))
627 })
628 })
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635
636 use alloc::vec;
637 use core::num::NonZeroU8;
638 use core::time::Duration;
639
640 use ip_test_macro::ip_test;
641 use netstack3_base::testutil::MultipleDevicesId;
642 use packet::ParseBuffer;
643 use test_case::test_case;
644 use testutil::TestIpExt;
645
646 use crate::internal::multicast_forwarding::route::MulticastRouteStats;
647 use crate::multicast_forwarding::MulticastRouteTarget;
648
649 struct LookupTestCase {
650 enabled: bool,
652 dev_enabled: bool,
654 right_key: bool,
656 right_dev: bool,
658 }
659 const LOOKUP_SUCCESS_CASE: LookupTestCase =
660 LookupTestCase { enabled: true, dev_enabled: true, right_key: true, right_dev: true };
661
662 #[ip_test(I)]
663 #[test_case(LOOKUP_SUCCESS_CASE => true; "success")]
664 #[test_case(LookupTestCase{enabled: false, ..LOOKUP_SUCCESS_CASE} => false; "disabled")]
665 #[test_case(LookupTestCase{dev_enabled: false, ..LOOKUP_SUCCESS_CASE} => false; "dev_disabled")]
666 #[test_case(LookupTestCase{right_key: false, ..LOOKUP_SUCCESS_CASE} => false; "wrong_key")]
667 #[test_case(LookupTestCase{right_dev: false, ..LOOKUP_SUCCESS_CASE} => false; "wrong_dev")]
668 fn lookup_route<I: TestIpExt>(test_case: LookupTestCase) -> bool {
669 let LookupTestCase { enabled, dev_enabled, right_key, right_dev } = test_case;
670 const FRAME_DST: Option<LocalFrameDestination> = None;
671 const MAX_FRAGMENT_LEN: Option<usize> = None;
672 let mut api = testutil::new_api::<I>();
673
674 let expected_key = MulticastRouteKey::new(I::SRC1, I::DST1).unwrap();
675 let actual_key = if right_key {
676 expected_key.clone()
677 } else {
678 MulticastRouteKey::new(I::SRC2, I::DST2).unwrap()
679 };
680
681 let expected_dev = MultipleDevicesId::A;
682 let actual_dev = if right_dev { expected_dev } else { MultipleDevicesId::B };
683
684 if enabled {
685 assert!(api.enable());
686 assert_eq!(
689 api.add_multicast_route(
690 expected_key.clone(),
691 MulticastRoute::new_forward(
692 expected_dev,
693 [MulticastRouteTarget {
694 output_interface: MultipleDevicesId::C,
695 min_ttl: NonZeroU8::new(1).unwrap(),
696 }]
697 .into()
698 )
699 .unwrap()
700 ),
701 Ok(None)
702 );
703 }
704
705 api.core_ctx().state.set_multicast_forwarding_enabled_for_dev(actual_dev, dev_enabled);
706
707 let (core_ctx, bindings_ctx) = api.contexts();
708 let creation_time = bindings_ctx.now();
709 bindings_ctx.timers.instant.sleep(Duration::from_secs(5));
710 let lookup_time = bindings_ctx.now();
711 assert!(lookup_time > creation_time);
712
713 let buf = testutil::new_ip_packet_buf::<I>(actual_key.src_addr(), actual_key.dst_addr());
714 let mut buf_ref = buf.as_ref();
715 let packet = buf_ref.parse::<I::Packet<_>>().expect("parse should succeed");
716
717 let route = lookup_multicast_route_or_stash_packet(
718 core_ctx,
719 bindings_ctx,
720 &packet,
721 &actual_dev,
722 FRAME_DST,
723 MAX_FRAGMENT_LEN,
724 Default::default(),
725 );
726
727 let mut expected_events = vec![];
729 if !right_key {
730 expected_events.push(IpLayerEvent::MulticastForwarding(
731 MulticastForwardingEvent::MissingRoute {
732 key: actual_key.clone(),
733 input_interface: actual_dev,
734 },
735 ));
736 }
737 if !right_dev {
738 expected_events.push(IpLayerEvent::MulticastForwarding(
739 MulticastForwardingEvent::WrongInputInterface {
740 key: actual_key,
741 actual_input_interface: actual_dev,
742 expected_input_interface: expected_dev,
743 },
744 ));
745 }
746 assert_eq!(bindings_ctx.take_events(), expected_events);
747
748 let lookup_succeeded = route.is_some();
749
750 if enabled {
751 let expected_stats = if lookup_succeeded {
753 MulticastRouteStats { last_used: lookup_time }
754 } else {
755 MulticastRouteStats { last_used: creation_time }
756 };
757 assert_eq!(api.get_route_stats(&expected_key), Ok(Some(expected_stats)));
758 }
759
760 let counters: &MulticastForwardingCounters<I> = api.core_ctx().counters();
762 assert_eq!(counters.rx.get(), 1);
763 assert_eq!(counters.tx.get(), if lookup_succeeded { 1 } else { 0 });
764 assert_eq!(counters.no_tx_disabled_dev.get(), if dev_enabled { 0 } else { 1 });
765 assert_eq!(counters.no_tx_disabled_stack_wide.get(), if enabled { 0 } else { 1 });
766 assert_eq!(counters.no_tx_wrong_dev.get(), if right_dev { 0 } else { 1 });
767 assert_eq!(counters.pending_packets.get(), if right_key { 0 } else { 1 });
768
769 lookup_succeeded
770 }
771}