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