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: _ } = 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 NetworkSerializationContext, NetworkSerializer, 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: NetworkSerializer,
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
1295 .into_serializer()
1296 .serialize_vec_outer(&mut NetworkSerializationContext::default())
1297 .unwrap();
1298 core_ctx.receive_igmp_packet(
1299 bindings_ctx,
1300 &FakeDeviceId,
1301 Ipv4SourceAddr::new(ROUTER_ADDR).unwrap(),
1302 MY_ADDR,
1303 buff,
1304 &new_recv_pkt_info(),
1305 );
1306 }
1307
1308 fn receive_igmp_v2_query(
1309 core_ctx: &mut FakeCoreCtx,
1310 bindings_ctx: &mut FakeBindingsCtx,
1311 resp_time: NonZeroDuration,
1312 ) {
1313 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
1314 GROUP_ADDR.get(),
1315 resp_time.get().try_into().unwrap(),
1316 );
1317 let buff = ser
1318 .into_serializer()
1319 .serialize_vec_outer(&mut NetworkSerializationContext::default())
1320 .unwrap();
1321 core_ctx.receive_igmp_packet(
1322 bindings_ctx,
1323 &FakeDeviceId,
1324 Ipv4SourceAddr::new(ROUTER_ADDR).unwrap(),
1325 MY_ADDR,
1326 buff,
1327 &new_recv_pkt_info(),
1328 );
1329 }
1330
1331 fn receive_igmp_v2_general_query(
1332 core_ctx: &mut FakeCoreCtx,
1333 bindings_ctx: &mut FakeBindingsCtx,
1334 resp_time: NonZeroDuration,
1335 ) {
1336 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
1337 Ipv4Addr::new([0, 0, 0, 0]),
1338 resp_time.get().try_into().unwrap(),
1339 );
1340 let buff = ser
1341 .into_serializer()
1342 .serialize_vec_outer(&mut NetworkSerializationContext::default())
1343 .unwrap();
1344 core_ctx.receive_igmp_packet(
1345 bindings_ctx,
1346 &FakeDeviceId,
1347 Ipv4SourceAddr::new(ROUTER_ADDR).unwrap(),
1348 MY_ADDR,
1349 buff,
1350 &new_recv_pkt_info(),
1351 );
1352 }
1353
1354 fn receive_igmp_report(core_ctx: &mut FakeCoreCtx, bindings_ctx: &mut FakeBindingsCtx) {
1355 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipReportV2>::new(GROUP_ADDR.get());
1356 let buff = ser
1357 .into_serializer()
1358 .serialize_vec_outer(&mut NetworkSerializationContext::default())
1359 .unwrap();
1360 core_ctx.receive_igmp_packet(
1361 bindings_ctx,
1362 &FakeDeviceId,
1363 Ipv4SourceAddr::new(OTHER_HOST_ADDR).unwrap(),
1364 MY_ADDR,
1365 buff,
1366 &new_recv_pkt_info(),
1367 );
1368 }
1369
1370 fn setup_igmpv2_test_environment_with_addr_subnet(
1371 seed: u128,
1372 a: Option<AddrSubnet<Ipv4Addr, Ipv4DeviceAddr>>,
1373 ) -> FakeCtx {
1374 let mut ctx = FakeCtx::with_default_bindings_ctx(|bindings_ctx| {
1375 let igmp_enabled = true;
1377 FakeCoreCtx::with_state(FakeIgmpCtx {
1378 shared: Rc::new(RefCell::new(Shared {
1379 groups: MulticastGroupSet::default(),
1380 gmp_state: GmpState::new_with_enabled_and_mode::<_, IntoCoreTimerCtx>(
1381 bindings_ctx,
1382 FakeWeakDeviceId(FakeDeviceId),
1383 igmp_enabled,
1384 IgmpMode::V2 { compat: false },
1385 ),
1386 config: Default::default(),
1387 })),
1388 igmp_enabled,
1389 addr_subnet: None,
1390 stack_wide_counters: Default::default(),
1391 device_specific_counters: Default::default(),
1392 })
1393 });
1394 ctx.bindings_ctx.seed_rng(seed);
1395 ctx.core_ctx.state.addr_subnet = a;
1396 ctx
1397 }
1398
1399 fn setup_igmpv2_test_environment(seed: u128) -> FakeCtx {
1400 setup_igmpv2_test_environment_with_addr_subnet(
1401 seed,
1402 Some(AddrSubnet::new(MY_ADDR.get(), 24).unwrap()),
1403 )
1404 }
1405
1406 fn ensure_ttl_ihl_rtr(core_ctx: &FakeCoreCtx) {
1407 for (_, frame) in core_ctx.frames() {
1408 assert_eq!(frame[8], IGMP_IP_TTL); assert_eq!(&frame[20..24], &[148, 4, 0, 0]); assert_eq!(frame[0], 0x46); }
1412 }
1413
1414 #[test_case(Some(MY_ADDR); "specified_src")]
1415 #[test_case(None; "unspecified_src")]
1416 fn test_igmp_simple_integration(src_ip: Option<SpecifiedAddr<Ipv4Addr>>) {
1417 let check_report = |core_ctx: &mut FakeCoreCtx| {
1418 let expected_src_ip = src_ip.map_or(Ipv4::UNSPECIFIED_ADDRESS, |a| a.get());
1419
1420 let frames = core_ctx.take_frames();
1421 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) = assert_matches!(
1422 &frames[..], [x] => x);
1423 assert_eq!(dst_ip, &GROUP_ADDR);
1424 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1425 assert_eq!(src_ip, expected_src_ip);
1426 assert_eq!(dst_ip, GROUP_ADDR.get());
1427 assert_eq!(proto, Ipv4Proto::Igmp);
1428 assert_eq!(ttl, IGMP_IP_TTL);
1429 let mut bv = &body[..];
1430 assert_matches!(
1431 IgmpPacket::parse(&mut bv, ()).unwrap(),
1432 IgmpPacket::MembershipReportV2(msg) => {
1433 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1434 }
1435 );
1436 };
1437
1438 let addr_subnet = src_ip.map(|a| AddrSubnet::new(a.get(), 16).unwrap());
1439 run_with_many_seeds(|seed| {
1440 let FakeCtx { mut core_ctx, mut bindings_ctx } =
1441 setup_igmpv2_test_environment_with_addr_subnet(seed, addr_subnet);
1442
1443 assert_eq!(
1445 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1446 GroupJoinResult::Joined(())
1447 );
1448 check_report(&mut core_ctx);
1449
1450 receive_igmp_v2_query(
1452 &mut core_ctx,
1453 &mut bindings_ctx,
1454 NonZeroDuration::from_secs(10).unwrap(),
1455 );
1456 core_ctx
1457 .state
1458 .gmp_state()
1459 .timers
1460 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1461 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1462 check_report(&mut core_ctx);
1463
1464 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 2, ..Default::default() }
1465 .assert_counters(&mut core_ctx);
1466 });
1467 }
1468
1469 #[test]
1470 fn test_igmp_integration_fallback_from_idle() {
1471 run_with_many_seeds(|seed| {
1472 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1473 assert_eq!(
1474 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1475 GroupJoinResult::Joined(())
1476 );
1477 assert_eq!(core_ctx.frames().len(), 1);
1478
1479 core_ctx
1480 .state
1481 .gmp_state()
1482 .timers
1483 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1484 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1485 assert_eq!(core_ctx.frames().len(), 2);
1486
1487 receive_igmp_v2_query(
1488 &mut core_ctx,
1489 &mut bindings_ctx,
1490 NonZeroDuration::from_secs(10).unwrap(),
1491 );
1492
1493 let group_state = core_ctx.state.groups().get(&GROUP_ADDR).unwrap().v1();
1496 match group_state.get_inner() {
1497 gmp::v1::MemberState::Delaying(_) => {}
1498 _ => panic!("Wrong State!"),
1499 }
1500
1501 core_ctx
1502 .state
1503 .gmp_state()
1504 .timers
1505 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1506 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1507 assert_eq!(core_ctx.frames().len(), 3);
1508 ensure_ttl_ihl_rtr(&core_ctx);
1509
1510 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 3, ..Default::default() }
1511 .assert_counters(&mut core_ctx);
1512 });
1513 }
1514
1515 #[test]
1516 fn test_igmpv2_integration_igmpv1_router_present() {
1517 run_with_many_seeds(|seed| {
1518 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1519
1520 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
1521 assert_eq!(
1522 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1523 GroupJoinResult::Joined(())
1524 );
1525 let now = bindings_ctx.now();
1526 core_ctx.state.gmp_state().timers.assert_range([(
1527 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1528 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1529 )]);
1530
1531 receive_igmp_v1_query(&mut core_ctx, &mut bindings_ctx);
1532 assert_eq!(core_ctx.frames().len(), 1);
1533
1534 let now = bindings_ctx.now();
1537 let until = now.panicking_add(DEFAULT_V1_ROUTER_PRESENT_TIMEOUT);
1538 assert_eq!(
1539 core_ctx.state.gmp_state().mode,
1540 IgmpMode::V1(IgmpV1Mode::V2Compat { until })
1541 );
1542 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), true);
1543 assert_eq!(core_ctx.frames().len(), 1);
1544 core_ctx.state.gmp_state().timers.assert_range([(
1545 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1546 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1547 )]);
1548 bindings_ctx.timers.assert_timers_installed_range([(
1549 GMP_TIMER_ID,
1550 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1551 )]);
1552
1553 core_ctx
1554 .state
1555 .gmp_state()
1556 .timers
1557 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1558 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1559 assert_eq!(core_ctx.frames().len(), 2);
1561 let (_, frame) = core_ctx.frames().last().unwrap();
1563 assert_eq!(frame[24], 0x12);
1566
1567 bindings_ctx.timers.instant.time = until;
1569
1570 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), false);
1572
1573 receive_igmp_v2_query(
1574 &mut core_ctx,
1575 &mut bindings_ctx,
1576 NonZeroDuration::from_secs(10).unwrap(),
1577 );
1578 core_ctx
1579 .state
1580 .gmp_state()
1581 .timers
1582 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1583 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1584 assert_eq!(core_ctx.frames().len(), 3);
1585 assert_eq!(core_ctx.frames().last().unwrap().1[24], 0x16);
1587 ensure_ttl_ihl_rtr(&core_ctx);
1588
1589 CounterExpectations {
1590 rx_igmpv1_query: 1,
1591 rx_igmpv2_query: 1,
1592 tx_igmpv2_report: 2,
1593 tx_igmpv1_report: 1,
1594 ..Default::default()
1595 }
1596 .assert_counters(&mut core_ctx);
1597 });
1598 }
1599
1600 #[test]
1601 fn test_igmp_integration_delay_reset_timer() {
1602 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(123456);
1604 assert_eq!(
1605 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1606 GroupJoinResult::Joined(())
1607 );
1608 let now = bindings_ctx.now();
1609 core_ctx.state.gmp_state().timers.assert_range([(
1610 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1611 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1612 )]);
1613 let instant1 = bindings_ctx.timers.timers()[0].0.clone();
1614 let start = bindings_ctx.now();
1615 let duration = Duration::from_micros(((instant1 - start).as_micros() / 2) as u64);
1616 assert!(duration.as_millis() > 100);
1617 receive_igmp_v2_query(
1618 &mut core_ctx,
1619 &mut bindings_ctx,
1620 NonZeroDuration::new(duration).unwrap(),
1621 );
1622 assert_eq!(core_ctx.frames().len(), 1);
1623 let now = bindings_ctx.now();
1624 core_ctx.state.gmp_state().timers.assert_range([(
1625 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1626 now..=(now + duration),
1627 )]);
1628 let instant2 = bindings_ctx.timers.timers()[0].0.clone();
1629 assert!(instant2 <= instant1);
1631 core_ctx
1632 .state
1633 .gmp_state()
1634 .timers
1635 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1636 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1637 assert!(bindings_ctx.now() - start <= duration);
1638 assert_eq!(core_ctx.frames().len(), 2);
1639 assert_eq!(core_ctx.frames().last().unwrap().1[24], 0x16);
1641 ensure_ttl_ihl_rtr(&core_ctx);
1642
1643 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 2, ..Default::default() }
1644 .assert_counters(&mut core_ctx);
1645 }
1646
1647 #[test]
1648 fn test_igmp_integration_last_send_leave() {
1649 run_with_many_seeds(|seed| {
1650 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1651 assert_eq!(
1652 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1653 GroupJoinResult::Joined(())
1654 );
1655 let now = bindings_ctx.now();
1656 core_ctx.state.gmp_state().timers.assert_range([(
1657 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1658 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1659 )]);
1660 assert_eq!(core_ctx.frames().len(), 1);
1662 core_ctx
1663 .state
1664 .gmp_state()
1665 .timers
1666 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1667 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1668 assert_eq!(core_ctx.frames().len(), 2);
1670 assert_eq!(
1671 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1672 GroupLeaveResult::Left(())
1673 );
1674 assert_eq!(core_ctx.frames().len(), 3);
1676
1677 let leave_frame = &core_ctx.frames().last().unwrap().1;
1678
1679 assert_eq!(leave_frame[24], 0x17);
1681 assert_eq!(leave_frame[16], 224);
1683 assert_eq!(leave_frame[17], 0);
1684 assert_eq!(leave_frame[18], 0);
1685 assert_eq!(leave_frame[19], 2);
1686 ensure_ttl_ihl_rtr(&core_ctx);
1687
1688 CounterExpectations { tx_igmpv2_report: 2, tx_leave_group: 1, ..Default::default() }
1689 .assert_counters(&mut core_ctx);
1690 });
1691 }
1692
1693 #[test]
1694 fn test_igmp_integration_always_idle_member() {
1695 run_with_many_seeds(|seed| {
1696 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1697 assert_eq!(
1698 core_ctx.gmp_join_group(
1699 &mut bindings_ctx,
1700 &FakeDeviceId,
1701 Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS
1702 ),
1703 GroupJoinResult::Joined(())
1704 );
1705 assert_eq!(core_ctx.frames().len(), 0);
1706 bindings_ctx.timers.assert_no_timers_installed();
1707
1708 CounterExpectations::default().assert_counters(&mut core_ctx);
1709 });
1710 }
1711
1712 #[test]
1713 fn test_igmp_integration_not_last_does_not_send_leave() {
1714 run_with_many_seeds(|seed| {
1715 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1716 assert_eq!(
1717 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1718 GroupJoinResult::Joined(())
1719 );
1720 let now = bindings_ctx.now();
1721 core_ctx.state.gmp_state().timers.assert_range([(
1722 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1723 now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1724 )]);
1725 assert_eq!(core_ctx.frames().len(), 1);
1726 receive_igmp_report(&mut core_ctx, &mut bindings_ctx);
1727 bindings_ctx.timers.assert_no_timers_installed();
1728 assert_eq!(core_ctx.frames().len(), 1);
1731 assert_eq!(
1732 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1733 GroupLeaveResult::Left(())
1734 );
1735 assert_eq!(core_ctx.frames().len(), 1);
1737 ensure_ttl_ihl_rtr(&core_ctx);
1738
1739 CounterExpectations { tx_igmpv2_report: 1, rx_igmpv2_report: 1, ..Default::default() }
1740 .assert_counters(&mut core_ctx);
1741 });
1742 }
1743
1744 #[test]
1745 fn test_receive_general_query() {
1746 run_with_many_seeds(|seed| {
1747 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1748 assert_eq!(
1749 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1750 GroupJoinResult::Joined(())
1751 );
1752 assert_eq!(
1753 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR_2),
1754 GroupJoinResult::Joined(())
1755 );
1756 let now = bindings_ctx.now();
1757 let range = now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
1758 core_ctx.state.gmp_state().timers.assert_range([
1759 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), range.clone()),
1760 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR_2).into(), range),
1761 ]);
1762 assert_eq!(core_ctx.frames().len(), 2);
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(), 4);
1767 const RESP_TIME: NonZeroDuration = NonZeroDuration::from_secs(10).unwrap();
1768 receive_igmp_v2_general_query(&mut core_ctx, &mut bindings_ctx, RESP_TIME);
1769 let now = bindings_ctx.now();
1771 let range = now..=(now + RESP_TIME.get());
1772 core_ctx.state.gmp_state().timers.assert_range([
1773 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), range.clone()),
1774 (&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR_2).into(), range),
1775 ]);
1776 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1777 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
1778 assert_eq!(core_ctx.frames().len(), 6);
1780 ensure_ttl_ihl_rtr(&core_ctx);
1781
1782 CounterExpectations { rx_igmpv2_query: 1, tx_igmpv2_report: 6, ..Default::default() }
1783 .assert_counters(&mut core_ctx);
1784 });
1785 }
1786
1787 #[test]
1788 fn test_skip_igmp() {
1789 run_with_many_seeds(|seed| {
1790 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1793 bindings_ctx.seed_rng(seed);
1794 core_ctx.state.igmp_enabled = false;
1796 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1797
1798 let assert_no_effect = |core_ctx: &FakeCoreCtx, bindings_ctx: &FakeBindingsCtx| {
1800 bindings_ctx.timers.assert_no_timers_installed();
1801 assert_empty(core_ctx.frames());
1802 };
1803
1804 assert_eq!(
1805 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1806 GroupJoinResult::Joined(())
1807 );
1808 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1811 assert_no_effect(&core_ctx, &bindings_ctx);
1812
1813 receive_igmp_report(&mut core_ctx, &mut bindings_ctx);
1814 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1816 assert_no_effect(&core_ctx, &bindings_ctx);
1817
1818 receive_igmp_v2_query(
1819 &mut core_ctx,
1820 &mut bindings_ctx,
1821 NonZeroDuration::from_secs(10).unwrap(),
1822 );
1823 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1825 assert_no_effect(&core_ctx, &bindings_ctx);
1826
1827 assert_eq!(
1828 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1829 GroupLeaveResult::Left(())
1830 );
1831 assert!(core_ctx.state.groups().get(&GROUP_ADDR).is_none());
1833 assert_no_effect(&core_ctx, &bindings_ctx);
1834
1835 CounterExpectations { rx_igmpv2_report: 1, rx_igmpv2_query: 1, ..Default::default() }
1836 .assert_counters(&mut core_ctx);
1837 });
1838 }
1839
1840 #[test]
1841 fn test_igmp_integration_with_local_join_leave() {
1842 run_with_many_seeds(|seed| {
1843 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1847
1848 assert_eq!(
1849 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1850 GroupJoinResult::Joined(())
1851 );
1852 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1853 assert_eq!(core_ctx.frames().len(), 1);
1854 let now = bindings_ctx.now();
1855 let range = now..=(now + IGMP_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
1856 core_ctx.state.gmp_state().timers.assert_range([(
1857 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1858 range.clone(),
1859 )]);
1860 ensure_ttl_ihl_rtr(&core_ctx);
1861
1862 assert_eq!(
1863 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1864 GroupJoinResult::AlreadyMember
1865 );
1866 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1867 assert_eq!(core_ctx.frames().len(), 1);
1868 core_ctx.state.gmp_state().timers.assert_range([(
1869 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1870 range.clone(),
1871 )]);
1872
1873 assert_eq!(
1874 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1875 GroupLeaveResult::StillMember
1876 );
1877 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1878 assert_eq!(core_ctx.frames().len(), 1);
1879 core_ctx.state.gmp_state().timers.assert_range([(
1880 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1881 range,
1882 )]);
1883
1884 assert_eq!(
1885 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1886 GroupLeaveResult::Left(())
1887 );
1888 assert_eq!(core_ctx.frames().len(), 2);
1889 bindings_ctx.timers.assert_no_timers_installed();
1890 ensure_ttl_ihl_rtr(&core_ctx);
1891
1892 CounterExpectations { tx_igmpv2_report: 1, tx_leave_group: 1, ..Default::default() }
1893 .assert_counters(&mut core_ctx);
1894 });
1895 }
1896
1897 #[test]
1898 fn test_igmp_enable_disable() {
1899 run_with_many_seeds(|seed| {
1900 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(seed);
1901 assert_eq!(core_ctx.take_frames(), []);
1902
1903 assert_eq!(
1904 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1905 GroupJoinResult::Joined(())
1906 );
1907 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1908 {
1909 let frames = core_ctx.take_frames();
1910 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1911 assert_matches!(&frames[..], [x] => x);
1912 assert_eq!(dst_ip, &GROUP_ADDR);
1913 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1914 assert_eq!(src_ip, MY_ADDR.get());
1915 assert_eq!(dst_ip, GROUP_ADDR.get());
1916 assert_eq!(proto, Ipv4Proto::Igmp);
1917 assert_eq!(ttl, IGMP_IP_TTL);
1918 let mut bv = &body[..];
1919 assert_matches!(
1920 IgmpPacket::parse(&mut bv, ()).unwrap(),
1921 IgmpPacket::MembershipReportV2(msg) => {
1922 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1923 }
1924 );
1925 }
1926
1927 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1929 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1930 assert_eq!(core_ctx.take_frames(), []);
1931
1932 core_ctx.state.igmp_enabled = false;
1934 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1935 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1936 {
1937 let frames = core_ctx.take_frames();
1938 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1939 assert_matches!(&frames[..], [x] => x);
1940 assert_eq!(dst_ip, &Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS);
1941 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1942 assert_eq!(src_ip, MY_ADDR.get());
1943 assert_eq!(dst_ip, Ipv4::ALL_ROUTERS_MULTICAST_ADDRESS.get());
1944 assert_eq!(proto, Ipv4Proto::Igmp);
1945 assert_eq!(ttl, IGMP_IP_TTL);
1946 let mut bv = &body[..];
1947 assert_matches!(
1948 IgmpPacket::parse(&mut bv, ()).unwrap(),
1949 IgmpPacket::LeaveGroup(msg) => {
1950 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1951 }
1952 );
1953 }
1954
1955 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1957 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1958 assert_eq!(core_ctx.take_frames(), []);
1959
1960 core_ctx.state.igmp_enabled = true;
1962 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1963 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1964 {
1965 let frames = core_ctx.take_frames();
1966 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
1967 assert_matches!(&frames[..], [x] => x);
1968 assert_eq!(dst_ip, &GROUP_ADDR);
1969 let (body, src_ip, dst_ip, proto, ttl) = parse_ip_packet::<Ipv4>(frame).unwrap();
1970 assert_eq!(src_ip, MY_ADDR.get());
1971 assert_eq!(dst_ip, GROUP_ADDR.get());
1972 assert_eq!(proto, Ipv4Proto::Igmp);
1973 assert_eq!(ttl, IGMP_IP_TTL);
1974 let mut bv = &body[..];
1975 assert_matches!(
1976 IgmpPacket::parse(&mut bv, ()).unwrap(),
1977 IgmpPacket::MembershipReportV2(msg) => {
1978 assert_eq!(msg.group_addr(), GROUP_ADDR.get());
1979 }
1980 );
1981 }
1982
1983 CounterExpectations { tx_igmpv2_report: 2, tx_leave_group: 1, ..Default::default() }
1984 .assert_counters(&mut core_ctx);
1985 });
1986 }
1987
1988 #[test]
1990 fn send_igmpv3_report() {
1991 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(0);
1992 let sent_report_addr = Ipv4::get_multicast_addr(130);
1993 let sent_report_mode = GroupRecordType::ModeIsExclude;
1994 let sent_report_sources = Vec::<Ipv4Addr>::new();
1995 core_ctx.with_gmp_state_mut_and_ctx(&FakeDeviceId, |mut core_ctx, _| {
1996 core_ctx.send_report_v2(
1997 &mut bindings_ctx,
1998 &FakeDeviceId,
1999 [gmp::v2::GroupRecord::new_with_sources(
2000 GmpEnabledGroup::new(sent_report_addr).unwrap(),
2001 sent_report_mode,
2002 sent_report_sources.iter(),
2003 )]
2004 .into_iter(),
2005 );
2006 });
2007
2008 let frames = core_ctx.take_frames();
2009 let (IgmpPacketMetadata { device: FakeDeviceId, dst_ip }, frame) =
2010 assert_matches!(&frames[..], [x] => x);
2011 assert_eq!(dst_ip, &ALL_IGMPV3_CAPABLE_ROUTERS);
2012 let mut buff = &frame[..];
2013 let ipv4 = buff.parse::<Ipv4Packet<_>>().expect("parse IPv4");
2014 assert_eq!(ipv4.ttl(), IGMP_IP_TTL);
2015 assert_eq!(ipv4.src_ip(), MY_ADDR.get());
2016 assert_eq!(ipv4.dst_ip(), ALL_IGMPV3_CAPABLE_ROUTERS.get());
2017 assert_eq!(ipv4.proto(), Ipv4Proto::Igmp);
2018 assert_eq!(ipv4.dscp_and_ecn(), IGMPV3_DSCP_AND_ECN);
2019 assert_eq!(
2020 ipv4.iter_options()
2021 .map(|o| {
2022 assert_matches!(o, Ipv4Option::RouterAlert { data: 0 });
2023 })
2024 .count(),
2025 1
2026 );
2027 let igmp = buff.parse::<IgmpPacket<_>>().expect("parse IGMP");
2028 let report = assert_matches!(
2029 igmp,
2030 IgmpPacket::MembershipReportV3(report) => report
2031 );
2032 let report = report
2033 .body()
2034 .iter()
2035 .map(|r| {
2036 (
2037 r.header().multicast_addr().clone(),
2038 r.header().record_type().unwrap(),
2039 r.sources().to_vec(),
2040 )
2041 })
2042 .collect::<Vec<_>>();
2043 assert_eq!(report, vec![(sent_report_addr.get(), sent_report_mode, sent_report_sources)]);
2044
2045 CounterExpectations { tx_igmpv3_report: 1, ..Default::default() }
2046 .assert_counters(&mut core_ctx);
2047 }
2048
2049 #[test]
2051 fn igmpv3_version_compat() {
2052 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(0);
2053 core_ctx.with_gmp_state_mut(&FakeDeviceId, |state| {
2054 gmp::enter_mode(&mut bindings_ctx, state, IgmpMode::V3);
2055 });
2056
2057 for _ in 0..2 {
2058 assert_eq!(
2059 gmp::v1::handle_query_message(
2060 &mut core_ctx,
2061 &mut bindings_ctx,
2062 &FakeDeviceId,
2063 &gmp::testutil::FakeV1Query {
2064 group_addr: Ipv4::UNSPECIFIED_ADDRESS,
2065 max_response_time: Duration::ZERO
2066 }
2067 ),
2068 Ok(())
2069 );
2070 let (gmp_state, config) = core_ctx.state.gmp_state_and_config();
2073 let until = bindings_ctx.now().panicking_add(
2074 gmp_state.v2_proto.older_version_querier_present_timeout(config).get(),
2075 );
2076 assert_eq!(gmp_state.mode, IgmpMode::V1(IgmpV1Mode::V3Compat { until }));
2077 assert_eq!(gmp_state.mode.should_send_v1(&mut bindings_ctx), true);
2078 bindings_ctx.timers.instant.sleep(Duration::from_secs(2));
2079 }
2080
2081 let (v2_deadline, ()) =
2082 core_ctx.state.gmp_state().timers.get(&gmp::TimerIdInner::V1Compat).unwrap();
2083 let prev_mode = core_ctx.state.gmp_state().mode;
2084 assert_eq!(
2087 gmp::v1::handle_query_message(
2088 &mut core_ctx,
2089 &mut bindings_ctx,
2090 &FakeDeviceId,
2091 &gmp::testutil::FakeV1Query {
2092 group_addr: Ipv4::UNSPECIFIED_ADDRESS,
2093 max_response_time: Duration::from_secs(10)
2094 }
2095 ),
2096 Ok(())
2097 );
2098 assert_eq!(core_ctx.state.gmp_state().mode, prev_mode);
2099 assert_ne!(
2100 core_ctx.state.gmp_state().timers.get(&gmp::TimerIdInner::V1Compat).unwrap(),
2101 (v2_deadline, &())
2102 );
2103 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), true);
2105
2106 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(GMP_TIMER_ID));
2109 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V3);
2110 assert_eq!(core_ctx.state.gmp_state().mode.should_send_v1(&mut bindings_ctx), false);
2111 }
2112
2113 #[test]
2114 fn version_compat_clears_on_disable() {
2115 let FakeCtx { mut core_ctx, mut bindings_ctx } = setup_igmpv2_test_environment(0);
2116 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2117 assert_eq!(
2118 gmp::v1::handle_query_message(
2119 &mut core_ctx,
2120 &mut bindings_ctx,
2121 &FakeDeviceId,
2122 &gmp::testutil::FakeV1Query {
2123 group_addr: Ipv4::UNSPECIFIED_ADDRESS,
2124 max_response_time: Duration::ZERO
2125 }
2126 ),
2127 Ok(())
2128 );
2129 assert_matches!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::V2Compat { .. }));
2130 core_ctx.state.igmp_enabled = false;
2131 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
2132 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2133 }
2134
2135 #[test]
2136 fn user_mode_change() {
2137 let mut ctx = setup_igmpv2_test_environment(0);
2138 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
2139 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V2);
2140 assert_eq!(
2141 core_ctx.gmp_join_group(bindings_ctx, &FakeDeviceId, GROUP_ADDR),
2142 GroupJoinResult::Joined(())
2143 );
2144 let _ = core_ctx.take_frames();
2146 assert_eq!(
2147 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2148 IgmpConfigMode::V2
2149 );
2150 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V3);
2151 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V3);
2152 assert_eq!(core_ctx.take_frames(), Vec::new());
2154
2155 receive_igmp_v1_query(core_ctx, bindings_ctx);
2158 assert_matches!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::V3Compat { .. }));
2159 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V3);
2160
2161 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(GMP_TIMER_ID));
2163 assert_eq!(core_ctx.take_frames().len(), 1);
2164
2165 assert_eq!(
2167 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2168 IgmpConfigMode::V3
2169 );
2170 assert_eq!(core_ctx.take_frames(), Vec::new());
2171 let until = assert_matches!(
2172 core_ctx.state.gmp_state().mode,
2173 IgmpMode::V1(IgmpV1Mode::V3Compat { until }) => until
2174 );
2175 assert_eq!(
2177 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V2),
2178 IgmpConfigMode::V3
2179 );
2180 assert_eq!(core_ctx.take_frames(), Vec::new());
2181 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::V2Compat { until }));
2182
2183 assert_eq!(
2185 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V1),
2186 IgmpConfigMode::V2
2187 );
2188 assert_eq!(core_ctx.take_frames(), Vec::new());
2189 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V1(IgmpV1Mode::Forced));
2190
2191 assert_eq!(
2193 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V2),
2194 IgmpConfigMode::V1
2195 );
2196 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2197 assert_eq!(
2199 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2200 IgmpConfigMode::V2
2201 );
2202 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V3);
2203 assert_eq!(core_ctx.take_frames(), Vec::new());
2204
2205 receive_igmp_v2_query(core_ctx, bindings_ctx, NonZeroDuration::from_secs(1).unwrap());
2207 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: true });
2208 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), IgmpConfigMode::V3);
2209 assert_eq!(bindings_ctx.trigger_next_timer(core_ctx), Some(GMP_TIMER_ID));
2211 assert_eq!(core_ctx.take_frames().len(), 1);
2212
2213 assert_eq!(
2215 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V3),
2216 IgmpConfigMode::V3
2217 );
2218 assert_eq!(core_ctx.take_frames(), Vec::new());
2219 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: true });
2220
2221 assert_eq!(
2223 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, IgmpConfigMode::V2),
2224 IgmpConfigMode::V3
2225 );
2226 assert_eq!(core_ctx.state.gmp_state().mode, IgmpMode::V2 { compat: false });
2227 assert_eq!(core_ctx.take_frames(), Vec::new());
2228 core_ctx.state.gmp_state().timers.assert_timers([]);
2230 }
2231
2232 #[test]
2233 fn reject_bad_messages() {
2234 let mut ctx = setup_igmpv2_test_environment(0);
2235 let FakeCtx { core_ctx, bindings_ctx } = &mut ctx;
2236
2237 let v1_query = {
2238 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
2239 Ipv4::UNSPECIFIED_ADDRESS,
2240 Duration::ZERO.try_into().unwrap(),
2241 );
2242 ser.into_serializer()
2243 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2244 .unwrap()
2245 };
2246 let v2_query = {
2247 let ser = IgmpPacketBuilder::<Buf<Vec<u8>>, IgmpMembershipQueryV2>::new_with_resp_time(
2248 Ipv4::UNSPECIFIED_ADDRESS,
2249 Duration::from_secs(10).try_into().unwrap(),
2250 );
2251 ser.into_serializer()
2252 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2253 .unwrap()
2254 };
2255 let v3_query = {
2256 let ser = IgmpMembershipQueryV3Builder::new(
2257 IgmpResponseTimeV3::new_exact(Duration::from_secs(1)).unwrap(),
2258 None,
2259 false,
2260 Igmpv3QRV::new(2),
2261 Igmpv3QQIC::new_exact(Duration::from_secs(125)).unwrap(),
2262 core::iter::empty(),
2263 );
2264 ser.into_serializer()
2265 .serialize_vec_outer(&mut NetworkSerializationContext::default())
2266 .unwrap()
2267 };
2268
2269 let base_header_info = new_recv_pkt_info().header_info;
2270
2271 const BAD_TTL: u8 = 2;
2273 for q in [&v1_query, &v2_query, &v3_query] {
2274 assert_eq!(
2275 receive_igmp_packet(
2276 core_ctx,
2277 bindings_ctx,
2278 &FakeDeviceId,
2279 Ipv4::ALL_SYSTEMS_MULTICAST_ADDRESS.into(),
2280 q.clone(),
2281 &LocalDeliveryPacketInfo {
2282 header_info: FakeIpHeaderInfo {
2283 hop_limit: BAD_TTL,
2284 ..base_header_info.clone()
2285 },
2286 ..Default::default()
2287 }
2288 ),
2289 Err(IgmpError::BadTtl(BAD_TTL))
2290 );
2291 }
2292
2293 const BAD_DST_IP: MulticastAddr<Ipv4Addr> = GROUP_ADDR;
2295 for q in [&v1_query, &v2_query, &v3_query] {
2296 assert_eq!(
2297 receive_igmp_packet(
2298 core_ctx,
2299 bindings_ctx,
2300 &FakeDeviceId,
2301 BAD_DST_IP.into(),
2302 q.clone(),
2303 &new_recv_pkt_info(),
2304 ),
2305 Err(IgmpError::RejectedGeneralQuery { dst_ip: BAD_DST_IP.get() })
2306 );
2307 }
2308
2309 for q in [&v2_query, &v3_query] {
2310 assert_eq!(
2311 receive_igmp_packet(
2312 core_ctx,
2313 bindings_ctx,
2314 &FakeDeviceId,
2315 MY_ADDR,
2316 q.clone(),
2317 &LocalDeliveryPacketInfo {
2318 header_info: FakeIpHeaderInfo {
2319 router_alert: false,
2321 ..base_header_info.clone()
2322 },
2323 ..Default::default()
2324 },
2325 ),
2326 Err(IgmpError::MissingRouterAlertInQuery)
2327 );
2328 }
2329
2330 CounterExpectations {
2331 rx_igmpv1_query: 1,
2333 rx_igmpv2_query: 2,
2334 rx_igmpv3_query: 2,
2335 rx_err_rejected_general_query: 3,
2336 rx_err_missing_router_alert_in_query: 2,
2337 rx_err_bad_ttl: 3,
2338 ..Default::default()
2339 }
2340 .assert_counters(core_ctx);
2341 }
2342}