1use core::fmt::Debug;
11use core::time::Duration;
12
13use log::{debug, error};
14use net_declare::net_ip_v4;
15use net_types::ip::{AddrSubnet, Ip as _, Ipv4, Ipv4Addr, Ipv4SourceAddr};
16use net_types::{MulticastAddr, MulticastAddress as _, SpecifiedAddr, Witness};
17use netstack3_base::{
18 AnyDevice, Counter, DeviceIdContext, ErrorAndSerializer, HandleableTimer, Inspectable,
19 InspectableValue, Inspector, InspectorExt, Instant, InstantContext, Ipv4DeviceAddr,
20 ResourceCounterContext, WeakDeviceIdentifier,
21};
22use netstack3_filter::DynTransportSerializer;
23use packet::{BufferMut, EmptyBuf, InnerPacketBuilder, PacketBuilder};
24use packet_formats::error::ParseError;
25use packet_formats::igmp::messages::{
26 IgmpLeaveGroup, IgmpMembershipQueryV2, IgmpMembershipQueryV3, IgmpMembershipReportV1,
27 IgmpMembershipReportV2, IgmpMembershipReportV3Builder, IgmpPacket,
28};
29use packet_formats::igmp::{IgmpMessage, IgmpPacketBuilder, MessageType};
30use packet_formats::ip::{DscpAndEcn, Ipv4Proto};
31use packet_formats::ipv4::options::Ipv4Option;
32use packet_formats::ipv4::{
33 Ipv4OptionsTooLongError, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions,
34};
35use packet_formats::utils::NonZeroDuration;
36use thiserror::Error;
37use zerocopy::SplitByteSlice;
38
39use crate::internal::base::{IpDeviceMtuContext, IpLayerHandler, IpPacketDestination};
40use crate::internal::gmp::{
41 self, GmpBindingsContext, GmpBindingsTypes, GmpContext, GmpContextInner, GmpEnabledGroup,
42 GmpGroupState, GmpMode, GmpState, GmpStateContext, GmpStateRef, GmpTimerId, GmpTypeLayout,
43 IpExt, MulticastGroupSet, NotAMemberErr, v2,
44};
45use crate::internal::local_delivery::{IpHeaderInfo, LocalDeliveryPacketInfo};
46
47const ALL_IGMPV3_CAPABLE_ROUTERS: MulticastAddr<Ipv4Addr> =
54 unsafe { MulticastAddr::new_unchecked(net_ip_v4!("224.0.0.22")) };
55
56pub trait IgmpBindingsTypes: GmpBindingsTypes {}
58impl<BT> IgmpBindingsTypes for BT where BT: GmpBindingsTypes {}
59
60pub trait IgmpBindingsContext: GmpBindingsContext + 'static {}
62impl<BC> IgmpBindingsContext for BC where BC: GmpBindingsContext + 'static {}
63
64#[derive(Debug, Eq, PartialEq, Copy, Clone)]
66#[allow(missing_docs)]
67pub enum IgmpConfigMode {
68 V1,
69 V2,
70 V3,
71}
72
73pub trait IgmpContextMarker {}
75
76pub trait IgmpStateContext<BT: IgmpBindingsTypes>:
78 DeviceIdContext<AnyDevice> + IgmpContextMarker
79{
80 fn with_igmp_state<
83 O,
84 F: FnOnce(
85 &MulticastGroupSet<Ipv4Addr, GmpGroupState<Ipv4, BT>>,
86 &GmpState<Ipv4, IgmpTypeLayout, BT>,
87 ) -> O,
88 >(
89 &mut self,
90 device: &Self::DeviceId,
91 cb: F,
92 ) -> O;
93}
94
95pub trait IgmpSendContext<BT: IgmpBindingsTypes>:
97 DeviceIdContext<AnyDevice>
98 + IpLayerHandler<Ipv4, BT>
99 + IpDeviceMtuContext<Ipv4>
100 + ResourceCounterContext<Self::DeviceId, IgmpCounters>
101{
102 fn get_ip_addr_subnet(
104 &mut self,
105 device: &Self::DeviceId,
106 ) -> Option<AddrSubnet<Ipv4Addr, Ipv4DeviceAddr>>;
107}
108
109pub trait IgmpContext<BT: IgmpBindingsTypes>:
111 DeviceIdContext<AnyDevice>
112 + IgmpContextMarker
113 + ResourceCounterContext<Self::DeviceId, IgmpCounters>
114{
115 type SendContext<'a>: IgmpSendContext<BT, DeviceId = Self::DeviceId> + 'a;
117
118 fn with_igmp_state_mut<
121 O,
122 F: for<'a> FnOnce(Self::SendContext<'a>, GmpStateRef<'a, Ipv4, IgmpTypeLayout, BT>) -> O,
123 >(
124 &mut self,
125 device: &Self::DeviceId,
126 cb: F,
127 ) -> O;
128}
129
130pub trait IgmpPacketHandler<BC, DeviceId> {
134 fn receive_igmp_packet<B: BufferMut, H: IpHeaderInfo<Ipv4>>(
136 &mut self,
137 bindings_ctx: &mut BC,
138 device: &DeviceId,
139 src_ip: Ipv4SourceAddr,
140 dst_ip: SpecifiedAddr<Ipv4Addr>,
141 buffer: B,
142 info: &LocalDeliveryPacketInfo<Ipv4, H>,
143 );
144}
145
146impl<BC: IgmpBindingsContext, CC: IgmpContext<BC>> IgmpPacketHandler<BC, CC::DeviceId> for CC {
147 fn receive_igmp_packet<B: BufferMut, H: IpHeaderInfo<Ipv4>>(
148 &mut self,
149 bindings_ctx: &mut BC,
150 device: &CC::DeviceId,
151 _src_ip: Ipv4SourceAddr,
152 dst_ip: SpecifiedAddr<Ipv4Addr>,
153 buffer: B,
154 info: &LocalDeliveryPacketInfo<Ipv4, H>,
155 ) {
156 receive_igmp_packet(self, bindings_ctx, device, dst_ip, buffer, info).unwrap_or_else(|e| {
157 debug!("Error occurred when handling IGMPv2 message: {}", e);
158 });
159 }
160}
161
162fn receive_igmp_packet<
163 B: BufferMut,
164 H: IpHeaderInfo<Ipv4>,
165 CC: IgmpContext<BC>,
166 BC: IgmpBindingsContext,
167>(
168 core_ctx: &mut CC,
169 bindings_ctx: &mut BC,
170 device: &CC::DeviceId,
171 dst_ip: SpecifiedAddr<Ipv4Addr>,
172 mut buffer: B,
173 info: &LocalDeliveryPacketInfo<Ipv4, H>,
174) -> Result<(), IgmpError> {
175 let LocalDeliveryPacketInfo { meta: _, header_info, marks: _ } = info;
176 let dst_ip = dst_ip.into_addr();
177 let ttl = header_info.hop_limit();
178
179 if ttl != IGMP_IP_TTL {
188 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_err_bad_ttl);
189 return Err(IgmpError::BadTtl(ttl));
190 }
191
192 let packet = match buffer.parse_with::<_, IgmpPacket<&[u8]>>(()) {
193 Ok(packet) => packet,
194 Err(e) => {
195 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_err_parse);
196 return Err(IgmpError::Parse(e));
197 }
198 };
199
200 match packet {
201 IgmpPacket::MembershipQueryV2(msg) => {
202 if msg.is_igmpv1_query() {
203 core_ctx
204 .increment_both(device, |counters: &IgmpCounters| &counters.rx_igmpv1_query);
205 } else {
206 core_ctx
207 .increment_both(device, |counters: &IgmpCounters| &counters.rx_igmpv2_query);
208 }
209 if msg.group_addr() == Ipv4::UNSPECIFIED_ADDRESS
214 && dst_ip.is_multicast()
215 && dst_ip != *Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS.as_ref()
216 {
217 core_ctx.increment_both(device, |counters: &IgmpCounters| {
218 &counters.rx_err_rejected_general_query
219 });
220 return Err(IgmpError::RejectedGeneralQuery { dst_ip });
221 }
222 if !msg.is_igmpv1_query() && !header_info.router_alert() {
227 core_ctx.increment_both(device, |counters: &IgmpCounters| {
228 &counters.rx_err_missing_router_alert_in_query
229 });
230 return Err(IgmpError::MissingRouterAlertInQuery);
231 }
232 gmp::v1::handle_query_message(core_ctx, bindings_ctx, device, &msg).map_err(Into::into)
233 }
234 IgmpPacket::MembershipQueryV3(msg) => {
235 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_igmpv3_query);
236 if msg.header().group_address() == Ipv4::UNSPECIFIED_ADDRESS
241 && dst_ip.is_multicast()
242 && dst_ip != *Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS.as_ref()
243 {
244 core_ctx.increment_both(device, |counters: &IgmpCounters| {
245 &counters.rx_err_rejected_general_query
246 });
247 return Err(IgmpError::RejectedGeneralQuery { dst_ip });
248 }
249 if !header_info.router_alert() {
254 core_ctx.increment_both(device, |counters: &IgmpCounters| {
255 &counters.rx_err_missing_router_alert_in_query
256 });
257 return Err(IgmpError::MissingRouterAlertInQuery);
258 }
259
260 gmp::v2::handle_query_message(core_ctx, bindings_ctx, device, &msg).map_err(Into::into)
261 }
262 IgmpPacket::MembershipReportV1(msg) => {
263 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_igmpv1_report);
264 let addr = msg.group_addr();
265 MulticastAddr::new(addr).map_or(Err(IgmpError::NotAMember { addr }), |group_addr| {
266 gmp::v1::handle_report_message(core_ctx, bindings_ctx, device, group_addr)
267 .map_err(Into::into)
268 })
269 }
270 IgmpPacket::MembershipReportV2(msg) => {
271 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_igmpv2_report);
272 let addr = msg.group_addr();
273 MulticastAddr::new(addr).map_or(Err(IgmpError::NotAMember { addr }), |group_addr| {
274 gmp::v1::handle_report_message(core_ctx, bindings_ctx, device, group_addr)
275 .map_err(Into::into)
276 })
277 }
278 IgmpPacket::LeaveGroup(_) => {
279 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_leave_group);
280 debug!("Hosts are not interested in Leave Group messages");
281 return Ok(());
282 }
283 IgmpPacket::MembershipReportV3(_) => {
284 core_ctx.increment_both(device, |counters: &IgmpCounters| &counters.rx_igmpv3_report);
285 debug!("Hosts are not interested in IGMPv3 report messages");
286 return Ok(());
287 }
288 }
289}
290
291impl<B: SplitByteSlice> gmp::v1::QueryMessage<Ipv4> for IgmpMessage<B, IgmpMembershipQueryV2> {
292 fn group_addr(&self) -> Ipv4Addr {
293 self.group_addr()
294 }
295
296 fn max_response_time(&self) -> Duration {
297 self.max_response_time().into()
298 }
299}
300
301impl<B: SplitByteSlice> gmp::v2::QueryMessage<Ipv4> for IgmpMessage<B, IgmpMembershipQueryV3> {
302 fn as_v1(&self) -> impl gmp::v1::QueryMessage<Ipv4> + '_ {
303 self.as_v2_query()
304 }
305
306 fn robustness_variable(&self) -> u8 {
307 self.header().querier_robustness_variable()
308 }
309
310 fn query_interval(&self) -> Duration {
311 self.header().querier_query_interval()
312 }
313
314 fn group_address(&self) -> Ipv4Addr {
315 self.header().group_address()
316 }
317
318 fn max_response_time(&self) -> Duration {
319 self.max_response_time().into()
320 }
321
322 fn sources(&self) -> impl Iterator<Item = Ipv4Addr> + '_ {
323 self.body().iter().copied()
324 }
325}
326
327#[derive(Eq, PartialEq, Copy, Clone, Debug)]
329pub enum IgmpV1Mode<I: Instant> {
330 Forced,
332 V2Compat { until: I },
336 V3Compat { until: I },
340}
341
342#[derive(Eq, PartialEq, Copy, Clone, Debug, Default)]
344pub enum IgmpMode<I: Instant> {
345 V1(IgmpV1Mode<I>),
347 V2 { compat: bool },
352 #[default]
354 V3,
355}
356
357impl<I: Instant> From<IgmpMode<I>> for GmpMode {
358 fn from(value: IgmpMode<I>) -> Self {
359 match value {
360 IgmpMode::V1(v1) => {
361 let compat = match v1 {
362 IgmpV1Mode::Forced | IgmpV1Mode::V2Compat { .. } => false,
366 IgmpV1Mode::V3Compat { .. } => true,
367 };
368 Self::V1 { compat }
369 }
370 IgmpMode::V2 { compat } => Self::V1 { compat },
371 IgmpMode::V3 => Self::V2,
372 }
373 }
374}
375
376impl<I: Instant> IgmpMode<I> {
377 fn should_send_v1<BC: InstantContext<Instant = I>>(&self, bindings_ctx: &mut BC) -> bool {
380 match self {
381 Self::V1(IgmpV1Mode::Forced) => true,
382 Self::V1(IgmpV1Mode::V2Compat { until } | IgmpV1Mode::V3Compat { until }) => {
383 bindings_ctx.now() < *until
384 }
385 Self::V2 { .. } | Self::V3 => false,
386 }
387 }
388}
389
390impl<I: Instant> InspectableValue for IgmpMode<I> {
391 fn record<X: Inspector>(&self, name: &str, inspector: &mut X) {
392 let v = match self {
393 IgmpMode::V1(IgmpV1Mode::Forced) => "IGMPv1",
394 IgmpMode::V1(IgmpV1Mode::V2Compat { .. }) => "IGMPv1(v2-compat)",
395 IgmpMode::V1(IgmpV1Mode::V3Compat { .. }) => "IGMPv1(v3-compat)",
396 IgmpMode::V2 { compat: true } => "IGMPv2(compat)",
397 IgmpMode::V2 { compat: false } => "IGMPv2",
398 IgmpMode::V3 => "IGMPv3",
399 };
400 inspector.record_str(name, v);
401 }
402}
403
404impl IpExt for Ipv4 {
405 type GmpProtoConfigMode = IgmpConfigMode;
406
407 fn should_perform_gmp(addr: MulticastAddr<Ipv4Addr>) -> bool {
408 addr != Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS
422 }
423}
424
425pub enum IgmpTypeLayout {}
427
428impl<BT: IgmpBindingsTypes> GmpTypeLayout<Ipv4, BT> for IgmpTypeLayout {
429 type Config = IgmpConfig;
430 type ProtoMode = IgmpMode<BT::Instant>;
431}
432
433impl<BT: IgmpBindingsTypes, CC: IgmpStateContext<BT>> GmpStateContext<Ipv4, BT> for CC {
434 type TypeLayout = IgmpTypeLayout;
435 fn with_gmp_state<
436 O,
437 F: FnOnce(
438 &MulticastGroupSet<Ipv4Addr, GmpGroupState<Ipv4, BT>>,
439 &GmpState<Ipv4, IgmpTypeLayout, BT>,
440 ) -> O,
441 >(
442 &mut self,
443 device: &Self::DeviceId,
444 cb: F,
445 ) -> O {
446 self.with_igmp_state(device, cb)
447 }
448}
449
450impl<BC: IgmpBindingsContext, CC: IgmpContext<BC>> GmpContext<Ipv4, BC> for CC {
451 type Inner<'a> = CC::SendContext<'a>;
452 type TypeLayout = IgmpTypeLayout;
453
454 fn with_gmp_state_mut_and_ctx<
455 O,
456 F: FnOnce(Self::Inner<'_>, GmpStateRef<'_, Ipv4, IgmpTypeLayout, BC>) -> O,
457 >(
458 &mut self,
459 device: &Self::DeviceId,
460 cb: F,
461 ) -> O {
462 self.with_igmp_state_mut(device, cb)
463 }
464}
465
466impl<CC, BC> GmpContextInner<Ipv4, BC> for CC
467where
468 CC: IgmpSendContext<BC>,
469 BC: IgmpBindingsContext,
470{
471 type TypeLayout = IgmpTypeLayout;
472
473 fn send_message_v1(
474 &mut self,
475 bindings_ctx: &mut BC,
476 device: &Self::DeviceId,
477 cur_mode: &IgmpMode<BC::Instant>,
478 group_addr: GmpEnabledGroup<Ipv4Addr>,
479 msg_type: gmp::v1::GmpMessageType,
480 ) {
481 let group_addr = group_addr.into_multicast_addr();
482 let result = match msg_type {
483 gmp::v1::GmpMessageType::Report => {
484 if cur_mode.should_send_v1(bindings_ctx) {
485 self.increment_both(device, |counters: &IgmpCounters| {
486 &counters.tx_igmpv1_report
487 });
488 send_igmp_v2_message::<_, _, IgmpMembershipReportV1>(
489 self,
490 bindings_ctx,
491 device,
492 group_addr,
493 group_addr,
494 (),
495 )
496 } else {
497 self.increment_both(device, |counters: &IgmpCounters| {
498 &counters.tx_igmpv2_report
499 });
500 send_igmp_v2_message::<_, _, IgmpMembershipReportV2>(
501 self,
502 bindings_ctx,
503 device,
504 group_addr,
505 group_addr,
506 (),
507 )
508 }
509 }
510 gmp::v1::GmpMessageType::Leave => {
511 self.increment_both(device, |counters: &IgmpCounters| &counters.tx_leave_group);
512 send_igmp_v2_message::<_, _, IgmpLeaveGroup>(
513 self,
514 bindings_ctx,
515 device,
516 group_addr,
517 Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS,
518 (),
519 )
520 }
521 };
522
523 match result {
524 Ok(()) => {}
525 Err(err) => {
526 self.increment_both(device, |counters: &IgmpCounters| &counters.tx_err);
527 debug!(
528 "error sending IGMP message ({msg_type:?}) on device {device:?} for group \
529 {group_addr}: {err}",
530 )
531 }
532 }
533 }
534
535 fn send_report_v2(
536 &mut self,
537 bindings_ctx: &mut BC,
538 device: &Self::DeviceId,
539 groups: impl Iterator<Item: gmp::v2::VerifiedReportGroupRecord<Ipv4Addr> + Clone> + Clone,
540 ) {
541 let dst_ip = ALL_IGMPV3_CAPABLE_ROUTERS;
542 let mut header = new_ip_header_builder(self, device, dst_ip);
543 header.prefix_builder_mut().dscp_and_ecn(IGMPV3_DSCP_AND_ECN);
544 let avail_len =
545 usize::from(self.get_mtu(device)).saturating_sub(header.constraints().header_len());
546 let reports = match IgmpMembershipReportV3Builder::new(groups).with_len_limits(avail_len) {
547 Ok(msg) => msg,
548 Err(e) => {
549 error!("MTU too small to send IGMP reports: {e:?}");
556 self.increment_both(device, |counters: &IgmpCounters| &counters.tx_err);
557 return;
558 }
559 };
560 for report in reports {
561 self.increment_both(device, |counters: &IgmpCounters| &counters.tx_igmpv3_report);
562 let destination = IpPacketDestination::Multicast(dst_ip);
563 let mut report = report.into_serializer();
564 let ip_frame = header.clone().wrap_body(DynTransportSerializer::new(&mut report));
565 IpLayerHandler::send_ip_frame(self, bindings_ctx, device, destination, ip_frame)
566 .unwrap_or_else(|ErrorAndSerializer { error, .. }| {
567 self.increment_both(device, |counters: &IgmpCounters| &counters.tx_err);
568 debug!("failed to send IGMPv3 report over {device:?}: {error:?}")
569 });
570 }
571 }
572
573 fn mode_update_from_v1_query<Q: gmp::v1::QueryMessage<Ipv4>>(
574 &mut self,
575 bindings_ctx: &mut BC,
576 query: &Q,
577 gmp_state: &GmpState<Ipv4, IgmpTypeLayout, BC>,
578 config: &IgmpConfig,
579 ) -> IgmpMode<BC::Instant> {
580 if query.max_response_time() != Duration::ZERO {
589 return match gmp_state.mode {
590 mode @ IgmpMode::V1(_) | mode @ IgmpMode::V2 { compat: false } => mode,
591 IgmpMode::V2 { compat: true } | IgmpMode::V3 => IgmpMode::V2 { compat: true },
592 };
593 }
594
595 match gmp_state.mode {
597 mode @ IgmpMode::V1(IgmpV1Mode::Forced) => mode,
598 IgmpMode::V2 { compat: false } | IgmpMode::V1(IgmpV1Mode::V2Compat { .. }) => {
599 let duration = config.v1_router_present_timeout;
606 IgmpMode::V1(IgmpV1Mode::V2Compat {
607 until: bindings_ctx.now().saturating_add(duration),
608 })
609 }
610 IgmpMode::V3
611 | IgmpMode::V2 { compat: true }
612 | IgmpMode::V1(IgmpV1Mode::V3Compat { .. }) => {
613 let duration =
619 gmp_state.v2_proto.older_version_querier_present_timeout(config).get();
620 IgmpMode::V1(IgmpV1Mode::V3Compat {
621 until: bindings_ctx.now().saturating_add(duration),
622 })
623 }
624 }
625 }
626
627 fn mode_to_config(mode: &IgmpMode<BC::Instant>) -> IgmpConfigMode {
628 match mode {
629 IgmpMode::V1(IgmpV1Mode::Forced) => IgmpConfigMode::V1,
630 IgmpMode::V1(IgmpV1Mode::V2Compat { .. }) | IgmpMode::V2 { compat: false } => {
631 IgmpConfigMode::V2
632 }
633 IgmpMode::V1(IgmpV1Mode::V3Compat { .. })
634 | IgmpMode::V2 { compat: true }
635 | IgmpMode::V3 => IgmpConfigMode::V3,
636 }
637 }
638
639 fn config_to_mode(
640 cur_mode: &IgmpMode<BC::Instant>,
641 config: IgmpConfigMode,
642 ) -> IgmpMode<BC::Instant> {
643 match config {
644 IgmpConfigMode::V1 => IgmpMode::V1(IgmpV1Mode::Forced),
645 IgmpConfigMode::V2 => match cur_mode {
646 IgmpMode::V1(IgmpV1Mode::V2Compat { .. }) => {
647 *cur_mode
649 }
650 IgmpMode::V1(IgmpV1Mode::V3Compat { until }) => {
651 IgmpMode::V1(IgmpV1Mode::V2Compat { until: *until })
653 }
654 IgmpMode::V1(IgmpV1Mode::Forced) | IgmpMode::V2 { .. } | IgmpMode::V3 => {
655 IgmpMode::V2 { compat: false }
656 }
657 },
658 IgmpConfigMode::V3 => {
659 match cur_mode {
660 IgmpMode::V1(IgmpV1Mode::V2Compat { until }) => {
661 IgmpMode::V1(IgmpV1Mode::V3Compat { until: *until })
663 }
664 IgmpMode::V1(IgmpV1Mode::V3Compat { .. }) | IgmpMode::V2 { compat: true } => {
665 *cur_mode
667 }
668 IgmpMode::V1(IgmpV1Mode::Forced)
669 | IgmpMode::V2 { compat: false }
670 | IgmpMode::V3 => IgmpMode::V3,
671 }
672 }
673 }
674 }
675
676 fn mode_on_disable(cur_mode: &IgmpMode<BC::Instant>) -> IgmpMode<BC::Instant> {
677 match cur_mode {
678 m @ IgmpMode::V1(IgmpV1Mode::Forced)
679 | m @ IgmpMode::V2 { compat: false }
680 | m @ IgmpMode::V3 => *m,
681 IgmpMode::V1(IgmpV1Mode::V2Compat { .. }) => IgmpMode::V2 { compat: false },
682 IgmpMode::V1(IgmpV1Mode::V3Compat { .. }) | IgmpMode::V2 { compat: true } => {
683 IgmpMode::V3
684 }
685 }
686 }
687
688 fn mode_on_exit_compat() -> IgmpMode<BC::Instant> {
689 IgmpMode::V3
690 }
691}
692
693#[derive(Debug, Error)]
694#[cfg_attr(test, derive(PartialEq))]
695pub(crate) enum IgmpError {
696 #[error("the host has not already been a member of the address: {}", addr)]
699 NotAMember { addr: Ipv4Addr },
700 #[error("failed to send out an IGMP packet to address: {}", addr)]
702 SendFailure { addr: Ipv4Addr },
703 #[error("IGMP is disabled on interface")]
705 Disabled,
706 #[error("failed to parse: {0}")]
707 Parse(ParseError),
708 #[error("message with incorrect ttl: {0}")]
709 BadTtl(u8),
710 #[error("rejected general query to {dst_ip}")]
711 RejectedGeneralQuery { dst_ip: Ipv4Addr },
712 #[error("router alert not present in query")]
713 MissingRouterAlertInQuery,
714}
715
716impl From<NotAMemberErr<Ipv4>> for IgmpError {
717 fn from(NotAMemberErr(addr): NotAMemberErr<Ipv4>) -> Self {
718 Self::NotAMember { addr }
719 }
720}
721
722impl From<v2::QueryError<Ipv4>> for IgmpError {
723 fn from(err: v2::QueryError<Ipv4>) -> Self {
724 match err {
725 v2::QueryError::NotAMember(addr) => Self::NotAMember { addr },
726 v2::QueryError::Disabled => Self::Disabled,
727 }
728 }
729}
730
731pub(crate) type IgmpResult<T> = Result<T, IgmpError>;
732
733#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
735pub struct IgmpTimerId<D: WeakDeviceIdentifier>(GmpTimerId<Ipv4, D>);
736
737impl<D: WeakDeviceIdentifier> IgmpTimerId<D> {
738 pub(crate) fn device_id(&self) -> &D {
739 let Self(inner) = self;
740 inner.device_id()
741 }
742
743 #[cfg(any(test, feature = "testutils"))]
745 pub const fn new(device: D) -> Self {
746 Self(GmpTimerId::new(device))
747 }
748}
749
750impl<D: WeakDeviceIdentifier> From<GmpTimerId<Ipv4, D>> for IgmpTimerId<D> {
751 fn from(id: GmpTimerId<Ipv4, D>) -> IgmpTimerId<D> {
752 Self(id)
753 }
754}
755
756impl<BC: IgmpBindingsContext, CC: IgmpContext<BC>> HandleableTimer<CC, BC>
757 for IgmpTimerId<CC::WeakDeviceId>
758{
759 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
760 let Self(gmp) = self;
761 gmp::handle_timer(core_ctx, bindings_ctx, gmp);
762 }
763}
764
765#[derive(Debug, Clone, Default)]
778struct IgmpIpOptions(bool);
779
780impl Iterator for IgmpIpOptions {
781 type Item = Ipv4Option<'static>;
782
783 fn next(&mut self) -> Option<Self::Item> {
784 let Self(yielded) = self;
785 if core::mem::replace(yielded, true) {
786 None
787 } else {
788 Some(Ipv4Option::RouterAlert { data: 0 })
789 }
790 }
791}
792
793const IGMP_IP_TTL: u8 = 1;
802
803const IGMPV3_DSCP_AND_ECN: DscpAndEcn = DscpAndEcn::new_with_raw(0xc0);
812
813fn new_ip_header_builder<BC: IgmpBindingsContext, CC: IgmpSendContext<BC>>(
814 core_ctx: &mut CC,
815 device: &CC::DeviceId,
816 dst_ip: MulticastAddr<Ipv4Addr>,
817) -> Ipv4PacketBuilderWithOptions<'static, IgmpIpOptions> {
818 let src_ip =
831 core_ctx.get_ip_addr_subnet(device).map_or(Ipv4::UNSPECIFIED_ADDRESS, |a| a.addr().get());
832 Ipv4PacketBuilderWithOptions::new(
833 Ipv4PacketBuilder::new(src_ip, dst_ip, IGMP_IP_TTL, Ipv4Proto::Igmp),
834 IgmpIpOptions::default(),
835 )
836 .unwrap_or_else(|Ipv4OptionsTooLongError| unreachable!("router alert always fits"))
837}
838
839fn send_igmp_v2_message<BC: IgmpBindingsContext, CC: IgmpSendContext<BC>, M>(
840 core_ctx: &mut CC,
841 bindings_ctx: &mut BC,
842 device: &CC::DeviceId,
843 group_addr: MulticastAddr<Ipv4Addr>,
844 dst_ip: MulticastAddr<Ipv4Addr>,
845 max_resp_time: M::MaxRespTime,
846) -> IgmpResult<()>
847where
848 M: MessageType<EmptyBuf, FixedHeader = Ipv4Addr, VariableBody = ()>,
849{
850 let header = new_ip_header_builder(core_ctx, device, dst_ip);
851 let mut body =
852 IgmpPacketBuilder::<EmptyBuf, M>::new_with_resp_time(group_addr.get(), max_resp_time)
853 .into_serializer();
854 let ip_frame = header.wrap_body(DynTransportSerializer::new(&mut body));
855 let destination = IpPacketDestination::Multicast(dst_ip);
856 IpLayerHandler::send_ip_frame(core_ctx, bindings_ctx, &device, destination, ip_frame)
857 .map_err(|_| IgmpError::SendFailure { addr: *group_addr })
858}
859
860#[derive(Debug)]
861pub struct IgmpConfig {
862 unsolicited_report_interval: Duration,
865 send_leave_anyway: bool,
868 v1_router_present_timeout: Duration,
870}
871
872pub const IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL: Duration = Duration::from_secs(10);
877const DEFAULT_V1_ROUTER_PRESENT_TIMEOUT: Duration = Duration::from_secs(400);
882const DEFAULT_V1_QUERY_MAX_RESP_TIME: NonZeroDuration =
888 NonZeroDuration::new(Duration::from_secs(10)).unwrap();
889
890impl Default for IgmpConfig {
891 fn default() -> Self {
892 IgmpConfig {
893 unsolicited_report_interval: IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL,
894 send_leave_anyway: false,
895 v1_router_present_timeout: DEFAULT_V1_ROUTER_PRESENT_TIMEOUT,
896 }
897 }
898}
899
900impl gmp::v1::ProtocolConfig for IgmpConfig {
901 fn unsolicited_report_interval(&self) -> Duration {
902 self.unsolicited_report_interval
903 }
904
905 fn send_leave_anyway(&self) -> bool {
906 self.send_leave_anyway
907 }
908
909 fn get_max_resp_time(&self, resp_time: Duration) -> Option<NonZeroDuration> {
910 Some(NonZeroDuration::new(resp_time).unwrap_or(DEFAULT_V1_QUERY_MAX_RESP_TIME))
920 }
921}
922
923impl gmp::v2::ProtocolConfig for IgmpConfig {
924 fn query_response_interval(&self) -> NonZeroDuration {
925 gmp::v2::DEFAULT_QUERY_RESPONSE_INTERVAL
926 }
927
928 fn unsolicited_report_interval(&self) -> NonZeroDuration {
929 gmp::v2::DEFAULT_UNSOLICITED_REPORT_INTERVAL
930 }
931}
932
933#[derive(Default, Debug)]
937#[cfg_attr(
938 any(test, feature = "testutils"),
939 derive(PartialEq, netstack3_macros::CounterCollection)
940)]
941pub struct IgmpCounters<C = Counter> {
942 rx_igmpv1_query: C,
944 rx_igmpv2_query: C,
946 rx_igmpv3_query: C,
948 rx_igmpv1_report: C,
950 rx_igmpv2_report: C,
952 rx_igmpv3_report: C,
954 rx_leave_group: C,
956 rx_err_parse: C,
958 rx_err_missing_router_alert_in_query: C,
961 rx_err_rejected_general_query: C,
964 rx_err_bad_ttl: C,
967 tx_igmpv1_report: C,
969 tx_igmpv2_report: C,
971 tx_igmpv3_report: C,
973 tx_leave_group: C,
975 tx_err: C,
977}
978
979impl Inspectable for IgmpCounters {
980 fn record<I: Inspector>(&self, inspector: &mut I) {
981 let Self {
982 rx_igmpv1_query,
983 rx_igmpv2_query,
984 rx_igmpv3_query,
985 rx_igmpv1_report,
986 rx_igmpv2_report,
987 rx_igmpv3_report,
988 rx_leave_group,
989 rx_err_parse,
990 rx_err_missing_router_alert_in_query,
991 rx_err_rejected_general_query,
992 rx_err_bad_ttl,
993 tx_igmpv1_report,
994 tx_igmpv2_report,
995 tx_igmpv3_report,
996 tx_leave_group,
997 tx_err,
998 } = self;
999 inspector.record_child("Rx", |inspector| {
1000 inspector.record_counter("IGMPv1Query", rx_igmpv1_query);
1001 inspector.record_counter("IGMPv2Query", rx_igmpv2_query);
1002 inspector.record_counter("IGMPv3Query", rx_igmpv3_query);
1003 inspector.record_counter("IGMPv1Report", rx_igmpv1_report);
1004 inspector.record_counter("IGMPv2Report", rx_igmpv2_report);
1005 inspector.record_counter("IGMPv3Report", rx_igmpv3_report);
1006 inspector.record_counter("LeaveGroup", rx_leave_group);
1007 inspector.record_child("Errors", |inspector| {
1008 inspector.record_counter("ParseFailed", rx_err_parse);
1009 inspector.record_counter(
1010 "MissingRouterAlertInQuery",
1011 rx_err_missing_router_alert_in_query,
1012 );
1013 inspector.record_counter("RejectedGeneralQuery", rx_err_rejected_general_query);
1014 inspector.record_counter("BadTTL", rx_err_bad_ttl);
1015 });
1016 });
1017 inspector.record_child("Tx", |inspector| {
1018 inspector.record_counter("IGMPv1Report", tx_igmpv1_report);
1019 inspector.record_counter("IGMPv2Report", tx_igmpv2_report);
1020 inspector.record_counter("IGMPv3Report", tx_igmpv3_report);
1021 inspector.record_counter("LeaveGroup", tx_leave_group);
1022 inspector.record_child("Errors", |inspector| {
1023 inspector.record_counter("SendFailed", tx_err);
1024 });
1025 })
1026 }
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use core::cell::RefCell;
1032
1033 use alloc::rc::Rc;
1034 use alloc::vec;
1035 use alloc::vec::Vec;
1036 use assert_matches::assert_matches;
1037
1038 use net_types::ip::{Ip, Mtu};
1039 use netstack3_base::testutil::{
1040 FakeDeviceId, FakeTimerCtxExt, FakeWeakDeviceId, TestIpExt as _, assert_empty,
1041 run_with_many_seeds,
1042 };
1043 use netstack3_base::{
1044 CounterCollection, CounterContext, CtxPair, Instant as _, IntoCoreTimerCtx,
1045 SendFrameContext as _,
1046 };
1047 use packet::serialize::Buf;
1048 use packet::{ParsablePacket as _, ParseBuffer, Serializer};
1049 use packet_formats::gmp::GroupRecordType;
1050 use packet_formats::igmp::IgmpResponseTimeV3;
1051 use packet_formats::igmp::messages::{
1052 IgmpMembershipQueryV2, IgmpMembershipQueryV3Builder, Igmpv3QQIC, Igmpv3QRV,
1053 };
1054 use packet_formats::ipv4::{Ipv4Header, Ipv4Packet};
1055 use packet_formats::testutil::parse_ip_packet;
1056 use test_case::test_case;
1057
1058 use super::*;
1059 use crate::internal::base::{IpPacketDestination, IpSendFrameError, SendIpPacketMeta};
1060 use crate::internal::fragmentation::FragmentableIpSerializer;
1061 use crate::internal::gmp::{
1062 GmpEnabledGroup, GmpHandler as _, GmpState, GroupJoinResult, GroupLeaveResult,
1063 };
1064 use crate::testutil::FakeIpHeaderInfo;
1065
1066 #[derive(Debug, PartialEq)]
1068 pub(crate) struct IgmpPacketMetadata<D> {
1069 pub(crate) device: D,
1070 pub(crate) dst_ip: MulticastAddr<Ipv4Addr>,
1071 }
1072
1073 impl<D> IgmpPacketMetadata<D> {
1074 fn new(device: D, dst_ip: MulticastAddr<Ipv4Addr>) -> IgmpPacketMetadata<D> {
1075 IgmpPacketMetadata { device, dst_ip }
1076 }
1077 }
1078
1079 struct FakeIgmpCtx {
1083 igmp_enabled: bool,
1084 shared: Rc<RefCell<Shared>>,
1085 addr_subnet: Option<AddrSubnet<Ipv4Addr, Ipv4DeviceAddr>>,
1086 stack_wide_counters: IgmpCounters,
1087 device_specific_counters: IgmpCounters,
1088 }
1089
1090 struct Shared {
1092 groups: MulticastGroupSet<Ipv4Addr, GmpGroupState<Ipv4, FakeBindingsCtx>>,
1093 gmp_state: GmpState<Ipv4, IgmpTypeLayout, FakeBindingsCtx>,
1094 config: IgmpConfig,
1095 }
1096
1097 impl FakeIgmpCtx {
1098 fn gmp_state(&mut self) -> &mut GmpState<Ipv4, IgmpTypeLayout, FakeBindingsCtx> {
1099 &mut Rc::get_mut(&mut self.shared).unwrap().get_mut().gmp_state
1100 }
1101
1102 fn gmp_state_and_config(
1103 &mut self,
1104 ) -> (&mut GmpState<Ipv4, IgmpTypeLayout, FakeBindingsCtx>, &mut IgmpConfig) {
1105 let shared = Rc::get_mut(&mut self.shared).unwrap().get_mut();
1106 (&mut shared.gmp_state, &mut shared.config)
1107 }
1108
1109 fn groups(
1110 &mut self,
1111 ) -> &mut MulticastGroupSet<Ipv4Addr, GmpGroupState<Ipv4, FakeBindingsCtx>> {
1112 &mut Rc::get_mut(&mut self.shared).unwrap().get_mut().groups
1113 }
1114 }
1115
1116 impl CounterContext<IgmpCounters> for FakeIgmpCtx {
1117 fn counters(&self) -> &IgmpCounters {
1118 &self.stack_wide_counters
1119 }
1120 }
1121
1122 impl ResourceCounterContext<FakeDeviceId, IgmpCounters> for FakeIgmpCtx {
1123 fn per_resource_counters<'a>(&'a self, _device_id: &'a FakeDeviceId) -> &'a IgmpCounters {
1124 &self.device_specific_counters
1125 }
1126 }
1127
1128 type FakeCtx = CtxPair<FakeCoreCtx, FakeBindingsCtx>;
1129
1130 type FakeCoreCtx = netstack3_base::testutil::FakeCoreCtx<
1131 FakeIgmpCtx,
1132 IgmpPacketMetadata<FakeDeviceId>,
1133 FakeDeviceId,
1134 >;
1135
1136 type FakeBindingsCtx = netstack3_base::testutil::FakeBindingsCtx<
1137 IgmpTimerId<FakeWeakDeviceId<FakeDeviceId>>,
1138 (),
1139 (),
1140 (),
1141 >;
1142
1143 impl IgmpContextMarker for FakeCoreCtx {}
1144
1145 impl IgmpStateContext<FakeBindingsCtx> for FakeCoreCtx {
1146 fn with_igmp_state<
1147 O,
1148 F: FnOnce(
1149 &MulticastGroupSet<Ipv4Addr, GmpGroupState<Ipv4, FakeBindingsCtx>>,
1150 &GmpState<Ipv4, IgmpTypeLayout, FakeBindingsCtx>,
1151 ) -> O,
1152 >(
1153 &mut self,
1154 &FakeDeviceId: &FakeDeviceId,
1155 cb: F,
1156 ) -> O {
1157 let state = self.state.shared.borrow();
1158 cb(&state.groups, &state.gmp_state)
1159 }
1160 }
1161
1162 impl IgmpContext<FakeBindingsCtx> for FakeCoreCtx {
1163 type SendContext<'a> = &'a mut Self;
1164 fn with_igmp_state_mut<
1165 O,
1166 F: for<'a> FnOnce(
1167 Self::SendContext<'a>,
1168 GmpStateRef<'a, Ipv4, IgmpTypeLayout, FakeBindingsCtx>,
1169 ) -> O,
1170 >(
1171 &mut self,
1172 &FakeDeviceId: &FakeDeviceId,
1173 cb: F,
1174 ) -> O {
1175 let FakeIgmpCtx { igmp_enabled, shared, .. } = &mut self.state;
1176 let enabled = *igmp_enabled;
1177 let shared = Rc::clone(shared);
1178 let mut shared = shared.borrow_mut();
1179 let Shared { gmp_state, groups, config } = &mut *shared;
1180 cb(self, GmpStateRef { enabled, groups, gmp: gmp_state, config })
1181 }
1182 }
1183
1184 impl IgmpSendContext<FakeBindingsCtx> for &mut FakeCoreCtx {
1185 fn get_ip_addr_subnet(
1186 &mut self,
1187 _device: &FakeDeviceId,
1188 ) -> Option<AddrSubnet<Ipv4Addr, Ipv4DeviceAddr>> {
1189 self.state.addr_subnet
1190 }
1191 }
1192
1193 impl IpDeviceMtuContext<Ipv4> for &mut FakeCoreCtx {
1194 fn get_mtu(&mut self, _device: &FakeDeviceId) -> Mtu {
1195 Mtu::new(1500)
1196 }
1197 }
1198
1199 impl IpLayerHandler<Ipv4, FakeBindingsCtx> for &mut FakeCoreCtx {
1200 fn send_ip_packet_from_device<S>(
1201 &mut self,
1202 _bindings_ctx: &mut FakeBindingsCtx,
1203 _meta: SendIpPacketMeta<
1204 Ipv4,
1205 &Self::DeviceId,
1206 Option<SpecifiedAddr<<Ipv4 as Ip>::Addr>>,
1207 >,
1208 _body: S,
1209 ) -> Result<(), IpSendFrameError<S>>
1210 where
1211 S: Serializer,
1212 S::Buffer: BufferMut,
1213 {
1214 unimplemented!();
1215 }
1216
1217 fn send_ip_frame<S>(
1218 &mut self,
1219 bindings_ctx: &mut FakeBindingsCtx,
1220 device: &Self::DeviceId,
1221 destination: IpPacketDestination<Ipv4, &Self::DeviceId>,
1222 body: S,
1223 ) -> Result<(), IpSendFrameError<S>>
1224 where
1225 S: FragmentableIpSerializer<Ipv4, Buffer: BufferMut> + netstack3_filter::IpPacket<Ipv4>,
1226 {
1227 let addr = match destination {
1228 IpPacketDestination::Multicast(addr) => addr,
1229 _ => panic!("destination is not multicast: {:?}", destination),
1230 };
1231
1232 (*self)
1233 .send_frame(bindings_ctx, IgmpPacketMetadata::new(device.clone(), addr), body)
1234 .map_err(|err| err.err_into())
1235 }
1236 }
1237
1238 impl CounterContext<IgmpCounters> for &mut FakeCoreCtx {
1239 fn counters(&self) -> &IgmpCounters {
1240 <FakeCoreCtx as CounterContext<IgmpCounters>>::counters(self)
1241 }
1242 }
1243
1244 impl ResourceCounterContext<FakeDeviceId, IgmpCounters> for &mut FakeCoreCtx {
1245 fn per_resource_counters<'a>(&'a self, device_id: &'a FakeDeviceId) -> &'a IgmpCounters {
1246 <
1247 FakeCoreCtx as ResourceCounterContext<FakeDeviceId, IgmpCounters>
1248 >::per_resource_counters(self, device_id)
1249 }
1250 }
1251
1252 type CounterExpectations = IgmpCounters<u64>;
1253
1254 impl CounterExpectations {
1255 #[track_caller]
1256 fn assert_counters<CC: ResourceCounterContext<FakeDeviceId, IgmpCounters>>(
1257 &self,
1258 core_ctx: &mut CC,
1259 ) {
1260 assert_eq!(self, &core_ctx.counters().cast(), "stack-wide counter mismatch");
1261 assert_eq!(
1262 self,
1263 &core_ctx.per_resource_counters(&FakeDeviceId).cast(),
1264 "device-specific counter mismatch"
1265 );
1266 }
1267 }
1268
1269 const MY_ADDR: SpecifiedAddr<Ipv4Addr> =
1270 unsafe { SpecifiedAddr::new_unchecked(Ipv4Addr::new([192, 168, 0, 2])) };
1271 const ROUTER_ADDR: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 1]);
1272 const OTHER_HOST_ADDR: Ipv4Addr = Ipv4Addr::new([192, 168, 0, 3]);
1273 const GROUP_ADDR: MulticastAddr<Ipv4Addr> = <Ipv4 as gmp::testutil::TestIpExt>::GROUP_ADDR1;
1274 const GROUP_ADDR_2: MulticastAddr<Ipv4Addr> = <Ipv4 as gmp::testutil::TestIpExt>::GROUP_ADDR2;
1275 const GMP_TIMER_ID: IgmpTimerId<FakeWeakDeviceId<FakeDeviceId>> =
1276 IgmpTimerId::new(FakeWeakDeviceId(FakeDeviceId));
1277
1278 fn new_recv_pkt_info() -> LocalDeliveryPacketInfo<Ipv4, FakeIpHeaderInfo> {
1279 LocalDeliveryPacketInfo {
1280 header_info: FakeIpHeaderInfo {
1281 router_alert: true,
1282 hop_limit: 1,
1283 ..Default::default()
1284 },
1285 ..Default::default()
1286 }
1287 }
1288
1289 fn receive_igmp_v1_query(core_ctx: &mut FakeCoreCtx, bindings_ctx: &mut FakeBindingsCtx) {
1290 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
1291 GROUP_ADDR.get(),
1292 Duration::ZERO.try_into().unwrap(),
1293 );
1294 let buff = ser.into_serializer().serialize_vec_outer().unwrap();
1295 core_ctx.receive_igmp_packet(
1296 bindings_ctx,
1297 &FakeDeviceId,
1298 Ipv4SourceAddr::new(ROUTER_ADDR).unwrap(),
1299 MY_ADDR,
1300 buff,
1301 &new_recv_pkt_info(),
1302 );
1303 }
1304
1305 fn receive_igmp_v2_query(
1306 core_ctx: &mut FakeCoreCtx,
1307 bindings_ctx: &mut FakeBindingsCtx,
1308 resp_time: NonZeroDuration,
1309 ) {
1310 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
1311 GROUP_ADDR.get(),
1312 resp_time.get().try_into().unwrap(),
1313 );
1314 let buff = ser.into_serializer().serialize_vec_outer().unwrap();
1315 core_ctx.receive_igmp_packet(
1316 bindings_ctx,
1317 &FakeDeviceId,
1318 Ipv4SourceAddr::new(ROUTER_ADDR).unwrap(),
1319 MY_ADDR,
1320 buff,
1321 &new_recv_pkt_info(),
1322 );
1323 }
1324
1325 fn receive_igmp_v2_general_query(
1326 core_ctx: &mut FakeCoreCtx,
1327 bindings_ctx: &mut FakeBindingsCtx,
1328 resp_time: NonZeroDuration,
1329 ) {
1330 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
1331 Ipv4Addr::new([0, 0, 0, 0]),
1332 resp_time.get().try_into().unwrap(),
1333 );
1334 let buff = ser.into_serializer().serialize_vec_outer().unwrap();
1335 core_ctx.receive_igmp_packet(
1336 bindings_ctx,
1337 &FakeDeviceId,
1338 Ipv4SourceAddr::new(ROUTER_ADDR).unwrap(),
1339 MY_ADDR,
1340 buff,
1341 &new_recv_pkt_info(),
1342 );
1343 }
1344
1345 fn receive_igmp_report(core_ctx: &mut FakeCoreCtx, bindings_ctx: &mut FakeBindingsCtx) {
1346 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipReportV2>::new(GROUP_ADDR.get());
1347 let buff = ser.into_serializer().serialize_vec_outer().unwrap();
1348 core_ctx.receive_igmp_packet(
1349 bindings_ctx,
1350 &FakeDeviceId,
1351 Ipv4SourceAddr::new(OTHER_HOST_ADDR).unwrap(),
1352 MY_ADDR,
1353 buff,
1354 &new_recv_pkt_info(),
1355 );
1356 }
1357
1358 fn setup_igmpv2_test_environment_with_addr_subnet(
1359 seed: u128,
1360 a: Option<AddrSubnet<Ipv4Addr, Ipv4DeviceAddr>>,
1361 ) -> FakeCtx {
1362 let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1363 let igmp_enabled = true;
1365 FakeCoreCtx::with_state(FakeIgmpCtx {
1366 shared: Rc::new(RefCell::new(Shared {
1367 groups: MulticastGroupSet::default(),
1368 gmp_state: GmpState::new_with_enabled_and_mode::<_, IntoCoreTimerCtx>(
1369 bindings_ctx,
1370 FakeWeakDeviceId(FakeDeviceId),
1371 igmp_enabled,
1372 IgmpMode::V2 { compat: false },
1373 ),
1374 config: Default::default(),
1375 })),
1376 igmp_enabled,
1377 addr_subnet: None,
1378 stack_wide_counters: Default::default(),
1379 device_specific_counters: Default::default(),
1380 })
1381 });
1382 ctx.bindings_ctx.seed_rng(seed);
1383 ctx.core_ctx.state.addr_subnet = a;
1384 ctx
1385 }
1386
1387 fn setup_igmpv2_test_environment(seed: u128) -> FakeCtx {
1388 setup_igmpv2_test_environment_with_addr_subnet(
1389 seed,
1390 Some(AddrSubnet::new(MY_ADDR.get(), 24).unwrap()),
1391 )
1392 }
1393
1394 fn ensure_ttl_ihl_rtr(core_ctx: &FakeCoreCtx) {
1395 for (_, frame) in core_ctx.frames() {
1396 assert_eq!(frame[8], IGMP_IP_TTL); assert_eq!(&frame[20..24], &[148, 4, 0, 0]); assert_eq!(frame[0], 0x46); }
1400 }
1401
1402 #[test_case(Some(MY_ADDR); "specified_src")]
1403 #[test_case(None; "unspecified_src")]
1404 fn test_igmp_simple_integration(src_ip: Option<SpecifiedAddr<Ipv4Addr>>) {
1405 let check_report = |core_ctx: &mut FakeCoreCtx| {
1406 let expected_src_ip = src_ip.map_or(Ipv4::UNSPECIFIED_ADDRESS, |a| a.get());
1407
1408 let frames = core_ctx.take_frames();
1409 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) = assert_matches!(
1410 &frames[..], [x] => x);
1411 assert_eq!(dst_ip, &GROUP_ADDR);
1412 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1413 assert_eq!(src_ip, expected_src_ip);
1414 assert_eq!(dst_ip, GROUP_ADDR.get());
1415 assert_eq!(proto, Ipv4Proto::Igmp);
1416 assert_eq!(ttl, IGMP_IP_TTL);
1417 let mut bv = &body[..];
1418 assert_matches!(
1419 IgmpPacket::parse(&mut bv, ()).unwrap(),
1420 IgmpPacket::MembershipReportV2(msg) => {
1421 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1422 }
1423 );
1424 };
1425
1426 let addr_subnet = src_ip.map(|a| AddrSubnet::new(a.get(), 16).unwrap());
1427 run_with_many_seeds(|seed| {
1428 let FakeCtx { mut core_ctx, mut bindings_ctx } =
1429 setup_igmpv2_test_environment_with_addr_subnet(seed, addr_subnet);
1430
1431 assert_eq!(
1433 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1434 GroupJoinResult::Joined(())
1435 );
1436 check_report(&mut core_ctx);
1437
1438 receive_igmp_v2_query(
1440 &mut core_ctx,
1441 &mut bindings_ctx,
1442 NonZeroDuration::from_secs(10).unwrap(),
1443 );
1444 core_ctx
1445 .state
1446 .gmp_state()
1447 .timers
1448 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1449 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1450 check_report(&mut core_ctx);
1451
1452 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 2, ..Default::default() }
1453 .assert_counters(&mut core_ctx);
1454 });
1455 }
1456
1457 #[test]
1458 fn test_igmp_integration_fallback_from_idle() {
1459 run_with_many_seeds(|seed| {
1460 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1461 assert_eq!(
1462 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1463 GroupJoinResult::Joined(())
1464 );
1465 assert_eq!(core_ctx.frames().len(), 1);
1466
1467 core_ctx
1468 .state
1469 .gmp_state()
1470 .timers
1471 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1472 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1473 assert_eq!(core_ctx.frames().len(), 2);
1474
1475 receive_igmp_v2_query(
1476 &mut core_ctx,
1477 &mut bindings_ctx,
1478 NonZeroDuration::from_secs(10).unwrap(),
1479 );
1480
1481 let group_state = core_ctx.state.groups().get(&GROUP_ADDR).unwrap().v1();
1484 match group_state.get_inner() {
1485 gmp::v1::MemberState::Delaying(_) => {}
1486 _ => panic!("Wrong State!"),
1487 }
1488
1489 core_ctx
1490 .state
1491 .gmp_state()
1492 .timers
1493 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1494 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1495 assert_eq!(core_ctx.frames().len(), 3);
1496 ensure_ttl_ihl_rtr(&core_ctx);
1497
1498 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 3, ..Default::default() }
1499 .assert_counters(&mut core_ctx);
1500 });
1501 }
1502
1503 #[test]
1504 fn test_igmpv2_integration_igmpv1_router_present() {
1505 run_with_many_seeds(|seed| {
1506 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1507
1508 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
1509 assert_eq!(
1510 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1511 GroupJoinResult::Joined(())
1512 );
1513 let now = bindings_ctx.now();
1514 core_ctx.state.gmp_state().timers.assert_range([(
1515 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1516 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1517 )]);
1518
1519 receive_igmp_v1_query(&mut core_ctx, &mut bindings_ctx);
1520 assert_eq!(core_ctx.frames().len(), 1);
1521
1522 let now = bindings_ctx.now();
1525 let until = now.panicking_add(DEFAULT_V1_ROUTER_PRESENT_TIMEOUT);
1526 assert_eq!(
1527 core_ctx.state.gmp_state().mode,
1528 IgmpMode::V1(IgmpV1Mode::V2Compat { until })
1529 );
1530 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), true);
1531 assert_eq!(core_ctx.frames().len(), 1);
1532 core_ctx.state.gmp_state().timers.assert_range([(
1533 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1534 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1535 )]);
1536 bindings_ctx.timers.assert_timers_installed_range([(
1537 GMP_TIMER_ID,
1538 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1539 )]);
1540
1541 core_ctx
1542 .state
1543 .gmp_state()
1544 .timers
1545 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1546 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1547 assert_eq!(core_ctx.frames().len(), 2);
1549 let (_, frame) = core_ctx.frames().last().unwrap();
1551 assert_eq!(frame[24], 0x12);
1554
1555 bindings_ctx.timers.instant.time = until;
1557
1558 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), false);
1560
1561 receive_igmp_v2_query(
1562 &mut core_ctx,
1563 &mut bindings_ctx,
1564 NonZeroDuration::from_secs(10).unwrap(),
1565 );
1566 core_ctx
1567 .state
1568 .gmp_state()
1569 .timers
1570 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1571 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1572 assert_eq!(core_ctx.frames().len(), 3);
1573 assert_eq!(core_ctx.frames().last().unwrap().1[24], 0x16);
1575 ensure_ttl_ihl_rtr(&core_ctx);
1576
1577 CounterExpectations {
1578 rx_igmpv1_query: 1,
1579 rx_igmpv2_query: 1,
1580 tx_igmpv2_report: 2,
1581 tx_igmpv1_report: 1,
1582 ..Default::default()
1583 }
1584 .assert_counters(&mut core_ctx);
1585 });
1586 }
1587
1588 #[test]
1589 fn test_igmp_integration_delay_reset_timer() {
1590 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(123456);
1592 assert_eq!(
1593 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1594 GroupJoinResult::Joined(())
1595 );
1596 let now = bindings_ctx.now();
1597 core_ctx.state.gmp_state().timers.assert_range([(
1598 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1599 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1600 )]);
1601 let instant1 = bindings_ctx.timers.timers()[0].0.clone();
1602 let start = bindings_ctx.now();
1603 let duration = Duration::from_micros(((instant1 - start).as_micros() / 2) as u64);
1604 assert!(duration.as_millis() > 100);
1605 receive_igmp_v2_query(
1606 &mut core_ctx,
1607 &mut bindings_ctx,
1608 NonZeroDuration::new(duration).unwrap(),
1609 );
1610 assert_eq!(core_ctx.frames().len(), 1);
1611 let now = bindings_ctx.now();
1612 core_ctx.state.gmp_state().timers.assert_range([(
1613 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1614 now..=(now + duration),
1615 )]);
1616 let instant2 = bindings_ctx.timers.timers()[0].0.clone();
1617 assert!(instant2 <= instant1);
1619 core_ctx
1620 .state
1621 .gmp_state()
1622 .timers
1623 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1624 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1625 assert!(bindings_ctx.now() - start <= duration);
1626 assert_eq!(core_ctx.frames().len(), 2);
1627 assert_eq!(core_ctx.frames().last().unwrap().1[24], 0x16);
1629 ensure_ttl_ihl_rtr(&core_ctx);
1630
1631 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 2, ..Default::default() }
1632 .assert_counters(&mut core_ctx);
1633 }
1634
1635 #[test]
1636 fn test_igmp_integration_last_send_leave() {
1637 run_with_many_seeds(|seed| {
1638 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1639 assert_eq!(
1640 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1641 GroupJoinResult::Joined(())
1642 );
1643 let now = bindings_ctx.now();
1644 core_ctx.state.gmp_state().timers.assert_range([(
1645 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1646 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1647 )]);
1648 assert_eq!(core_ctx.frames().len(), 1);
1650 core_ctx
1651 .state
1652 .gmp_state()
1653 .timers
1654 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1655 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1656 assert_eq!(core_ctx.frames().len(), 2);
1658 assert_eq!(
1659 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1660 GroupLeaveResult::Left(())
1661 );
1662 assert_eq!(core_ctx.frames().len(), 3);
1664
1665 let leave_frame = &core_ctx.frames().last().unwrap().1;
1666
1667 assert_eq!(leave_frame[24], 0x17);
1669 assert_eq!(leave_frame[16], 224);
1671 assert_eq!(leave_frame[17], 0);
1672 assert_eq!(leave_frame[18], 0);
1673 assert_eq!(leave_frame[19], 2);
1674 ensure_ttl_ihl_rtr(&core_ctx);
1675
1676 CounterExpectations { tx_igmpv2_report: 2, tx_leave_group: 1, ..Default::default() }
1677 .assert_counters(&mut core_ctx);
1678 });
1679 }
1680
1681 #[test]
1682 fn test_igmp_integration_always_idle_member() {
1683 run_with_many_seeds(|seed| {
1684 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1685 assert_eq!(
1686 core_ctx.gmp_join_group(
1687 &mut bindings_ctx,
1688 &FakeDeviceId,
1689 Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS
1690 ),
1691 GroupJoinResult::Joined(())
1692 );
1693 assert_eq!(core_ctx.frames().len(), 0);
1694 bindings_ctx.timers.assert_no_timers_installed();
1695
1696 CounterExpectations::default().assert_counters(&mut core_ctx);
1697 });
1698 }
1699
1700 #[test]
1701 fn test_igmp_integration_not_last_does_not_send_leave() {
1702 run_with_many_seeds(|seed| {
1703 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1704 assert_eq!(
1705 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1706 GroupJoinResult::Joined(())
1707 );
1708 let now = bindings_ctx.now();
1709 core_ctx.state.gmp_state().timers.assert_range([(
1710 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1711 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1712 )]);
1713 assert_eq!(core_ctx.frames().len(), 1);
1714 receive_igmp_report(&mut core_ctx, &mut bindings_ctx);
1715 bindings_ctx.timers.assert_no_timers_installed();
1716 assert_eq!(core_ctx.frames().len(), 1);
1719 assert_eq!(
1720 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1721 GroupLeaveResult::Left(())
1722 );
1723 assert_eq!(core_ctx.frames().len(), 1);
1725 ensure_ttl_ihl_rtr(&core_ctx);
1726
1727 CounterExpectations { tx_igmpv2_report: 1, rx_igmpv2_report: 1, ..Default::default() }
1728 .assert_counters(&mut core_ctx);
1729 });
1730 }
1731
1732 #[test]
1733 fn test_receive_general_query() {
1734 run_with_many_seeds(|seed| {
1735 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1736 assert_eq!(
1737 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1738 GroupJoinResult::Joined(())
1739 );
1740 assert_eq!(
1741 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR_2),
1742 GroupJoinResult::Joined(())
1743 );
1744 let now = bindings_ctx.now();
1745 let range = now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
1746 core_ctx.state.gmp_state().timers.assert_range([
1747 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), range.clone()),
1748 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR_2).into(), range),
1749 ]);
1750 assert_eq!(core_ctx.frames().len(), 2);
1752 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1753 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1754 assert_eq!(core_ctx.frames().len(), 4);
1755 const RESP_TIME: NonZeroDuration = NonZeroDuration::from_secs(10).unwrap();
1756 receive_igmp_v2_general_query(&mut core_ctx, &mut bindings_ctx, RESP_TIME);
1757 let now = bindings_ctx.now();
1759 let range = now..=(now + RESP_TIME.get());
1760 core_ctx.state.gmp_state().timers.assert_range([
1761 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), range.clone()),
1762 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR_2).into(), range),
1763 ]);
1764 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1765 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1766 assert_eq!(core_ctx.frames().len(), 6);
1768 ensure_ttl_ihl_rtr(&core_ctx);
1769
1770 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 6, ..Default::default() }
1771 .assert_counters(&mut core_ctx);
1772 });
1773 }
1774
1775 #[test]
1776 fn test_skip_igmp() {
1777 run_with_many_seeds(|seed| {
1778 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1781 bindings_ctx.seed_rng(seed);
1782 core_ctx.state.igmp_enabled = false;
1784 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1785
1786 let assert_no_effect = |core_ctx: &FakeCoreCtx, bindings_ctx: &FakeBindingsCtx| {
1788 bindings_ctx.timers.assert_no_timers_installed();
1789 assert_empty(core_ctx.frames());
1790 };
1791
1792 assert_eq!(
1793 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1794 GroupJoinResult::Joined(())
1795 );
1796 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1799 assert_no_effect(&core_ctx, &bindings_ctx);
1800
1801 receive_igmp_report(&mut core_ctx, &mut bindings_ctx);
1802 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1804 assert_no_effect(&core_ctx, &bindings_ctx);
1805
1806 receive_igmp_v2_query(
1807 &mut core_ctx,
1808 &mut bindings_ctx,
1809 NonZeroDuration::from_secs(10).unwrap(),
1810 );
1811 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1813 assert_no_effect(&core_ctx, &bindings_ctx);
1814
1815 assert_eq!(
1816 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1817 GroupLeaveResult::Left(())
1818 );
1819 assert!(core_ctx.state.groups().get(&GROUP_ADDR).is_none());
1821 assert_no_effect(&core_ctx, &bindings_ctx);
1822
1823 CounterExpectations { rx_igmpv2_report: 1, rx_igmpv2_query: 1, ..Default::default() }
1824 .assert_counters(&mut core_ctx);
1825 });
1826 }
1827
1828 #[test]
1829 fn test_igmp_integration_with_local_join_leave() {
1830 run_with_many_seeds(|seed| {
1831 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1835
1836 assert_eq!(
1837 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1838 GroupJoinResult::Joined(())
1839 );
1840 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1841 assert_eq!(core_ctx.frames().len(), 1);
1842 let now = bindings_ctx.now();
1843 let range = now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
1844 core_ctx.state.gmp_state().timers.assert_range([(
1845 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1846 range.clone(),
1847 )]);
1848 ensure_ttl_ihl_rtr(&core_ctx);
1849
1850 assert_eq!(
1851 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1852 GroupJoinResult::AlreadyMember
1853 );
1854 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1855 assert_eq!(core_ctx.frames().len(), 1);
1856 core_ctx.state.gmp_state().timers.assert_range([(
1857 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1858 range.clone(),
1859 )]);
1860
1861 assert_eq!(
1862 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1863 GroupLeaveResult::StillMember
1864 );
1865 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1866 assert_eq!(core_ctx.frames().len(), 1);
1867 core_ctx.state.gmp_state().timers.assert_range([(
1868 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1869 range,
1870 )]);
1871
1872 assert_eq!(
1873 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1874 GroupLeaveResult::Left(())
1875 );
1876 assert_eq!(core_ctx.frames().len(), 2);
1877 bindings_ctx.timers.assert_no_timers_installed();
1878 ensure_ttl_ihl_rtr(&core_ctx);
1879
1880 CounterExpectations { tx_igmpv2_report: 1, tx_leave_group: 1, ..Default::default() }
1881 .assert_counters(&mut core_ctx);
1882 });
1883 }
1884
1885 #[test]
1886 fn test_igmp_enable_disable() {
1887 run_with_many_seeds(|seed| {
1888 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1889 assert_eq!(core_ctx.take_frames(), []);
1890
1891 assert_eq!(
1892 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1893 GroupJoinResult::Joined(())
1894 );
1895 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1896 {
1897 let frames = core_ctx.take_frames();
1898 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1899 assert_matches!(&frames[..], [x] => x);
1900 assert_eq!(dst_ip, &GROUP_ADDR);
1901 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1902 assert_eq!(src_ip, MY_ADDR.get());
1903 assert_eq!(dst_ip, GROUP_ADDR.get());
1904 assert_eq!(proto, Ipv4Proto::Igmp);
1905 assert_eq!(ttl, IGMP_IP_TTL);
1906 let mut bv = &body[..];
1907 assert_matches!(
1908 IgmpPacket::parse(&mut bv, ()).unwrap(),
1909 IgmpPacket::MembershipReportV2(msg) => {
1910 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1911 }
1912 );
1913 }
1914
1915 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1917 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1918 assert_eq!(core_ctx.take_frames(), []);
1919
1920 core_ctx.state.igmp_enabled = false;
1922 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1923 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1924 {
1925 let frames = core_ctx.take_frames();
1926 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1927 assert_matches!(&frames[..], [x] => x);
1928 assert_eq!(dst_ip, &Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS);
1929 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1930 assert_eq!(src_ip, MY_ADDR.get());
1931 assert_eq!(dst_ip, Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS.get());
1932 assert_eq!(proto, Ipv4Proto::Igmp);
1933 assert_eq!(ttl, IGMP_IP_TTL);
1934 let mut bv = &body[..];
1935 assert_matches!(
1936 IgmpPacket::parse(&mut bv, ()).unwrap(),
1937 IgmpPacket::LeaveGroup(msg) => {
1938 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1939 }
1940 );
1941 }
1942
1943 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1945 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1946 assert_eq!(core_ctx.take_frames(), []);
1947
1948 core_ctx.state.igmp_enabled = true;
1950 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1951 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1952 {
1953 let frames = core_ctx.take_frames();
1954 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1955 assert_matches!(&frames[..], [x] => x);
1956 assert_eq!(dst_ip, &GROUP_ADDR);
1957 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1958 assert_eq!(src_ip, MY_ADDR.get());
1959 assert_eq!(dst_ip, GROUP_ADDR.get());
1960 assert_eq!(proto, Ipv4Proto::Igmp);
1961 assert_eq!(ttl, IGMP_IP_TTL);
1962 let mut bv = &body[..];
1963 assert_matches!(
1964 IgmpPacket::parse(&mut bv, ()).unwrap(),
1965 IgmpPacket::MembershipReportV2(msg) => {
1966 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1967 }
1968 );
1969 }
1970
1971 CounterExpectations { tx_igmpv2_report: 2, tx_leave_group: 1, ..Default::default() }
1972 .assert_counters(&mut core_ctx);
1973 });
1974 }
1975
1976 #[test]
1978 fn send_igmpv3_report() {
1979 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(0);
1980 let sent_report_addr = Ipv4::get_multicast_addr(130);
1981 let sent_report_mode = GroupRecordType::ModeIsExclude;
1982 let sent_report_sources = Vec::<Ipv4Addr>::new();
1983 core_ctx.with_gmp_state_mut_and_ctx(&FakeDeviceId, |mut core_ctx, _| {
1984 core_ctx.send_report_v2(
1985 &mut bindings_ctx,
1986 &FakeDeviceId,
1987 [gmp::v2::GroupRecord::new_with_sources(
1988 GmpEnabledGroup::new(sent_report_addr).unwrap(),
1989 sent_report_mode,
1990 sent_report_sources.iter(),
1991 )]
1992 .into_iter(),
1993 );
1994 });
1995
1996 let frames = core_ctx.take_frames();
1997 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1998 assert_matches!(&frames[..], [x] => x);
1999 assert_eq!(dst_ip, &ALL_IGMPV3_CAPABLE_ROUTERS);
2000 let mut buff = &frame[..];
2001 let ipv4 = buff.parse::<Ipv4Packet<_>>().expect("parse IPv4");
2002 assert_eq!(ipv4.ttl(), IGMP_IP_TTL);
2003 assert_eq!(ipv4.src_ip(), MY_ADDR.get());
2004 assert_eq!(ipv4.dst_ip(), ALL_IGMPV3_CAPABLE_ROUTERS.get());
2005 assert_eq!(ipv4.proto(), Ipv4Proto::Igmp);
2006 assert_eq!(ipv4.dscp_and_ecn(), IGMPV3_DSCP_AND_ECN);
2007 assert_eq!(
2008 ipv4.iter_options()
2009 .map(|o| {
2010 assert_matches!(o, Ipv4Option::RouterAlert { data: 0 });
2011 })
2012 .count(),
2013 1
2014 );
2015 let igmp = buff.parse::<IgmpPacket<_>>().expect("parse IGMP");
2016 let report = assert_matches!(
2017 igmp,
2018 IgmpPacket::MembershipReportV3(report) => report
2019 );
2020 let report = report
2021 .body()
2022 .iter()
2023 .map(|r| {
2024 (
2025 r.header().multicast_addr().clone(),
2026 r.header().record_type().unwrap(),
2027 r.sources().to_vec(),
2028 )
2029 })
2030 .collect::<Vec<_>>();
2031 assert_eq!(report, vec![(sent_report_addr.get(), sent_report_mode, sent_report_sources)]);
2032
2033 CounterExpectations { tx_igmpv3_report: 1, ..Default::default() }
2034 .assert_counters(&mut core_ctx);
2035 }
2036
2037 #[test]
2039 fn igmpv3_version_compat() {
2040 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(0);
2041 core_ctx.with_gmp_state_mut(&FakeDeviceId, |state| {
2042 gmp::enter_mode(&mut bindings_ctx, state, IgmpMode::V3);
2043 });
2044
2045 for _ in 0..2 {
2046 assert_eq!(
2047 gmp::v1::handle_query_message(
2048 &mut core_ctx,
2049 &mut bindings_ctx,
2050 &FakeDeviceId,
2051 &gmp::testutil::FakeV1Query {
2052 group_addr: Ipv4::UNSPECIFIED_ADDRESS,
2053 max_response_time: Duration::ZERO
2054 }
2055 ),
2056 Ok(())
2057 );
2058 let (gmp_state, config) = core_ctx.state.gmp_state_and_config();
2061 let until = bindings_ctx.now().panicking_add(
2062 gmp_state.v2_proto.older_version_querier_present_timeout(config).get(),
2063 );
2064 assert_eq!(gmp_state.mode, IgmpMode::V1(IgmpV1Mode::V3Compat { until }));
2065 assert_eq!(gmp_state.mode.should_send_v1(&mut bindings_ctx), true);
2066 bindings_ctx.timers.instant.sleep(Duration::from_secs(2));
2067 }
2068
2069 let (v2_deadline, ()) =
2070 core_ctx.state.gmp_state().timers.get(&gmp::TimerIdInner::V1Compat).unwrap();
2071 let prev_mode = core_ctx.state.gmp_state().mode;
2072 assert_eq!(
2075 gmp::v1::handle_query_message(
2076 &mut core_ctx,
2077 &mut bindings_ctx,
2078 &FakeDeviceId,
2079 &gmp::testutil::FakeV1Query {
2080 group_addr: Ipv4::UNSPECIFIED_ADDRESS,
2081 max_response_time: Duration::from_secs(10)
2082 }
2083 ),
2084 Ok(())
2085 );
2086 assert_eq!(core_ctx.state.gmp_state().mode, prev_mode);
2087 assert_ne!(
2088 core_ctx.state.gmp_state().timers.get(&gmp::TimerIdInner::V1Compat).unwrap(),
2089 (v2_deadline, &())
2090 );
2091 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), true);
2093
2094 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
2097 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V3);
2098 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), false);
2099 }
2100
2101 #[test]
2102 fn version_compat_clears_on_disable() {
2103 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(0);
2104 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2105 assert_eq!(
2106 gmp::v1::handle_query_message(
2107 &mut core_ctx,
2108 &mut bindings_ctx,
2109 &FakeDeviceId,
2110 &gmp::testutil::FakeV1Query {
2111 group_addr: Ipv4::UNSPECIFIED_ADDRESS,
2112 max_response_time: Duration::ZERO
2113 }
2114 ),
2115 Ok(())
2116 );
2117 assert_matches!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::V2Compat { .. }));
2118 core_ctx.state.igmp_enabled = false;
2119 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
2120 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2121 }
2122
2123 #[test]
2124 fn user_mode_change() {
2125 let mut ctx = setup_igmpv2_test_environment(0);
2126 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
2127 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V2);
2128 assert_eq!(
2129 core_ctx.gmp_join_group(bindings_ctx, &FakeDeviceId, GROUP_ADDR),
2130 GroupJoinResult::Joined(())
2131 );
2132 let _ = core_ctx.take_frames();
2134 assert_eq!(
2135 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2136 IgmpConfigMode::V2
2137 );
2138 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V3);
2139 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V3);
2140 assert_eq!(core_ctx.take_frames(), Vec::new());
2142
2143 receive_igmp_v1_query(core_ctx, bindings_ctx);
2146 assert_matches!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::V3Compat { .. }));
2147 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V3);
2148
2149 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(GMP_TIMER_ID));
2151 assert_eq!(core_ctx.take_frames().len(), 1);
2152
2153 assert_eq!(
2155 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2156 IgmpConfigMode::V3
2157 );
2158 assert_eq!(core_ctx.take_frames(), Vec::new());
2159 let until = assert_matches!(
2160 core_ctx.state.gmp_state().mode,
2161 IgmpMode::V1(IgmpV1Mode::V3Compat { until }) => until
2162 );
2163 assert_eq!(
2165 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V2),
2166 IgmpConfigMode::V3
2167 );
2168 assert_eq!(core_ctx.take_frames(), Vec::new());
2169 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::V2Compat { until }));
2170
2171 assert_eq!(
2173 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V1),
2174 IgmpConfigMode::V2
2175 );
2176 assert_eq!(core_ctx.take_frames(), Vec::new());
2177 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::Forced));
2178
2179 assert_eq!(
2181 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V2),
2182 IgmpConfigMode::V1
2183 );
2184 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2185 assert_eq!(
2187 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2188 IgmpConfigMode::V2
2189 );
2190 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V3);
2191 assert_eq!(core_ctx.take_frames(), Vec::new());
2192
2193 receive_igmp_v2_query(core_ctx, bindings_ctx, NonZeroDuration::from_secs(1).unwrap());
2195 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: true });
2196 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V3);
2197 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(GMP_TIMER_ID));
2199 assert_eq!(core_ctx.take_frames().len(), 1);
2200
2201 assert_eq!(
2203 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2204 IgmpConfigMode::V3
2205 );
2206 assert_eq!(core_ctx.take_frames(), Vec::new());
2207 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: true });
2208
2209 assert_eq!(
2211 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V2),
2212 IgmpConfigMode::V3
2213 );
2214 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2215 assert_eq!(core_ctx.take_frames(), Vec::new());
2216 core_ctx.state.gmp_state().timers.assert_timers([]);
2218 }
2219
2220 #[test]
2221 fn reject_bad_messages() {
2222 let mut ctx = setup_igmpv2_test_environment(0);
2223 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
2224
2225 let v1_query = {
2226 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
2227 Ipv4::UNSPECIFIED_ADDRESS,
2228 Duration::ZERO.try_into().unwrap(),
2229 );
2230 ser.into_serializer().serialize_vec_outer().unwrap()
2231 };
2232 let v2_query = {
2233 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
2234 Ipv4::UNSPECIFIED_ADDRESS,
2235 Duration::from_secs(10).try_into().unwrap(),
2236 );
2237 ser.into_serializer().serialize_vec_outer().unwrap()
2238 };
2239 let v3_query = {
2240 let ser = IgmpMembershipQueryV3Builder::new(
2241 IgmpResponseTimeV3::new_exact(Duration::from_secs(1)).unwrap(),
2242 None,
2243 false,
2244 Igmpv3QRV::new(2),
2245 Igmpv3QQIC::new_exact(Duration::from_secs(125)).unwrap(),
2246 core::iter::empty(),
2247 );
2248 ser.into_serializer().serialize_vec_outer().unwrap()
2249 };
2250
2251 let base_header_info = new_recv_pkt_info().header_info;
2252
2253 const BAD_TTL: u8 = 2;
2255 for q in [&v1_query, &v2_query, &v3_query] {
2256 assert_eq!(
2257 receive_igmp_packet(
2258 core_ctx,
2259 bindings_ctx,
2260 &FakeDeviceId,
2261 Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS.into(),
2262 q.clone(),
2263 &LocalDeliveryPacketInfo {
2264 header_info: FakeIpHeaderInfo {
2265 hop_limit: BAD_TTL,
2266 ..base_header_info.clone()
2267 },
2268 ..Default::default()
2269 }
2270 ),
2271 Err(IgmpError::BadTtl(BAD_TTL))
2272 );
2273 }
2274
2275 const BAD_DST_IP: MulticastAddr<Ipv4Addr> = GROUP_ADDR;
2277 for q in [&v1_query, &v2_query, &v3_query] {
2278 assert_eq!(
2279 receive_igmp_packet(
2280 core_ctx,
2281 bindings_ctx,
2282 &FakeDeviceId,
2283 BAD_DST_IP.into(),
2284 q.clone(),
2285 &new_recv_pkt_info(),
2286 ),
2287 Err(IgmpError::RejectedGeneralQuery { dst_ip: BAD_DST_IP.get() })
2288 );
2289 }
2290
2291 for q in [&v2_query, &v3_query] {
2292 assert_eq!(
2293 receive_igmp_packet(
2294 core_ctx,
2295 bindings_ctx,
2296 &FakeDeviceId,
2297 MY_ADDR,
2298 q.clone(),
2299 &LocalDeliveryPacketInfo {
2300 header_info: FakeIpHeaderInfo {
2301 router_alert: false,
2303 ..base_header_info.clone()
2304 },
2305 ..Default::default()
2306 },
2307 ),
2308 Err(IgmpError::MissingRouterAlertInQuery)
2309 );
2310 }
2311
2312 CounterExpectations {
2313 rx_igmpv1_query: 1,
2315 rx_igmpv2_query: 2,
2316 rx_igmpv3_query: 2,
2317 rx_err_rejected_general_query: 3,
2318 rx_err_missing_router_alert_in_query: 2,
2319 rx_err_bad_ttl: 3,
2320 ..Default::default()
2321 }
2322 .assert_counters(core_ctx);
2323 }
2324}