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