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