1use core::time::Duration;
12
13use log::{debug, error};
14use net_declare::net_ip_v6;
15use net_types::ip::{Ip, Ipv6, Ipv6Addr, Ipv6ReservedScope, Ipv6Scope, Ipv6SourceAddr};
16use net_types::{
17 LinkLocalAddress as _, LinkLocalUnicastAddr, MulticastAddr, ScopeableAddress, SpecifiedAddr,
18 Witness,
19};
20use netstack3_base::{
21 AnyDevice, Counter, DeviceIdContext, ErrorAndSerializer, HandleableTimer, Inspectable,
22 InspectableValue, Inspector, InspectorExt, ResourceCounterContext, WeakDeviceIdentifier,
23};
24use netstack3_filter::{self as filter, DynTransportSerializer};
25use packet::InnerPacketBuilder;
26use packet::serialize::{PacketBuilder, Serializer};
27use packet_formats::icmp::mld::{
28 MldPacket, Mldv1Body, Mldv1MessageBuilder, Mldv1MessageType, Mldv2QueryBody,
29 Mldv2ReportMessageBuilder, MulticastListenerDone, MulticastListenerReport,
30 MulticastListenerReportV2,
31};
32use packet_formats::icmp::{IcmpMessage, IcmpPacketBuilder, IcmpSenderZeroCode};
33use packet_formats::ip::Ipv6Proto;
34use packet_formats::ipv6::ext_hdrs::{
35 ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
36};
37use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
38use packet_formats::utils::NonZeroDuration;
39use thiserror::Error;
40use zerocopy::SplitByteSlice;
41
42use crate::internal::base::{IpDeviceMtuContext, IpLayerHandler, IpPacketDestination};
43use crate::internal::gmp::{
44 self, GmpBindingsContext, GmpBindingsTypes, GmpContext, GmpContextInner, GmpEnabledGroup,
45 GmpGroupState, GmpMode, GmpState, GmpStateContext, GmpStateRef, GmpTimerId, GmpTypeLayout,
46 IpExt, MulticastGroupSet, NotAMemberErr,
47};
48use crate::internal::local_delivery::IpHeaderInfo;
49
50const ALL_MLDV2_CAPABLE_ROUTERS: MulticastAddr<Ipv6Addr> =
57 unsafe { MulticastAddr::new_unchecked(net_ip_v6!("FF02::16")) };
58
59pub trait MldBindingsTypes: GmpBindingsTypes {}
61impl<BT> MldBindingsTypes for BT where BT: GmpBindingsTypes {}
62
63pub(crate) trait MldBindingsContext: GmpBindingsContext {}
65impl<BC> MldBindingsContext for BC where BC: GmpBindingsContext {}
66
67pub trait MldStateContext<BT: MldBindingsTypes>:
69 DeviceIdContext<AnyDevice> + MldContextMarker
70{
71 fn with_mld_state<
74 O,
75 F: FnOnce(
76 &MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, BT>>,
77 &GmpState<Ipv6, MldTypeLayout, BT>,
78 ) -> O,
79 >(
80 &mut self,
81 device: &Self::DeviceId,
82 cb: F,
83 ) -> O;
84}
85
86pub trait MldSendContext<BT: MldBindingsTypes>:
88 DeviceIdContext<AnyDevice>
89 + IpLayerHandler<Ipv6, BT>
90 + IpDeviceMtuContext<Ipv6>
91 + MldContextMarker
92 + ResourceCounterContext<Self::DeviceId, MldCounters>
93{
94 fn get_ipv6_link_local_addr(
96 &mut self,
97 device: &Self::DeviceId,
98 ) -> Option<LinkLocalUnicastAddr<Ipv6Addr>>;
99}
100
101pub trait MldContextMarker {}
103
104pub trait MldContext<BT: MldBindingsTypes>:
106 DeviceIdContext<AnyDevice> + MldContextMarker + ResourceCounterContext<Self::DeviceId, MldCounters>
107{
108 type SendContext<'a>: MldSendContext<BT, DeviceId = Self::DeviceId> + 'a;
110
111 fn with_mld_state_mut<
114 O,
115 F: FnOnce(Self::SendContext<'_>, GmpStateRef<'_, Ipv6, MldTypeLayout, BT>) -> O,
116 >(
117 &mut self,
118 device: &Self::DeviceId,
119 cb: F,
120 ) -> O;
121}
122
123pub trait MldPacketHandler<BC, DeviceId> {
127 fn receive_mld_packet<B: SplitByteSlice, H: IpHeaderInfo<Ipv6>>(
129 &mut self,
130 bindings_ctx: &mut BC,
131 device: &DeviceId,
132 src_ip: Ipv6SourceAddr,
133 dst_ip: SpecifiedAddr<Ipv6Addr>,
134 packet: MldPacket<B>,
135 header_info: &H,
136 );
137}
138
139fn receive_mld_packet<
140 B: SplitByteSlice,
141 H: IpHeaderInfo<Ipv6>,
142 CC: MldContext<BC>,
143 BC: MldBindingsContext,
144>(
145 core_ctx: &mut CC,
146 bindings_ctx: &mut BC,
147 device: &CC::DeviceId,
148 src_ip: Ipv6SourceAddr,
149 packet: MldPacket<B>,
150 header_info: &H,
151) -> Result<(), MldError> {
152 if header_info.hop_limit() != MLD_IP_HOP_LIMIT {
167 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_err_bad_hop_limit);
168 return Err(MldError::BadHopLimit { hop_limit: header_info.hop_limit() });
169 }
170
171 match packet {
172 MldPacket::MulticastListenerQuery(msg) => {
173 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv1_query);
174 if !src_ip.is_link_local() {
179 core_ctx
180 .increment_both(device, |counters: &MldCounters| &counters.rx_err_bad_src_addr);
181 return Err(MldError::BadSourceAddress { addr: src_ip.into_addr() });
182 }
183 gmp::v1::handle_query_message(core_ctx, bindings_ctx, device, msg.body())
184 .map_err(Into::into)
185 }
186 MldPacket::MulticastListenerQueryV2(msg) => {
187 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv2_query);
188 if !src_ip.is_link_local() {
195 core_ctx
196 .increment_both(device, |counters: &MldCounters| &counters.rx_err_bad_src_addr);
197 return Err(MldError::BadSourceAddress { addr: src_ip.into_addr() });
198 }
199
200 if !header_info.router_alert() {
207 core_ctx.increment_both(device, |counters: &MldCounters| {
208 &counters.rx_err_missing_router_alert
209 });
210 return Err(MldError::MissingRouterAlert);
211 }
212
213 gmp::v2::handle_query_message(core_ctx, bindings_ctx, device, msg.body())
214 .map_err(Into::into)
215 }
216 MldPacket::MulticastListenerReport(msg) => {
217 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv1_report);
218 match src_ip {
227 Ipv6SourceAddr::Unspecified => {}
228 Ipv6SourceAddr::Unicast(src_ip) => {
229 if !src_ip.is_link_local() {
230 core_ctx.increment_both(device, |counters: &MldCounters| {
231 &counters.rx_err_bad_src_addr
232 });
233 return Err(MldError::BadSourceAddress { addr: src_ip.into_addr() });
234 }
235 }
236 }
237 let addr = msg.body().group_addr;
238 MulticastAddr::new(msg.body().group_addr).map_or(
239 Err(MldError::NotAMember { addr }),
240 |group_addr| {
241 gmp::v1::handle_report_message(core_ctx, bindings_ctx, device, group_addr)
242 .map_err(Into::into)
243 },
244 )
245 }
246 MldPacket::MulticastListenerReportV2(_) => {
247 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv2_report);
248 debug!("Hosts are not interested in MLDv2 report messages");
249 Ok(())
250 }
251 MldPacket::MulticastListenerDone(_) => {
252 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_leave_group);
253 debug!("Hosts are not interested in Done messages");
254 Ok(())
255 }
256 }
257}
258
259impl<BC: MldBindingsContext, CC: MldContext<BC>> MldPacketHandler<BC, CC::DeviceId> for CC {
260 fn receive_mld_packet<B: SplitByteSlice, H: IpHeaderInfo<Ipv6>>(
261 &mut self,
262 bindings_ctx: &mut BC,
263 device: &CC::DeviceId,
264 src_ip: Ipv6SourceAddr,
265 _dst_ip: SpecifiedAddr<Ipv6Addr>,
266 packet: MldPacket<B>,
267 header_info: &H,
268 ) {
269 receive_mld_packet(self, bindings_ctx, device, src_ip, packet, header_info)
270 .unwrap_or_else(|e| debug!("Error occurred when handling MLD message: {}", e));
271 }
272}
273
274impl<B: SplitByteSlice> gmp::v1::QueryMessage<Ipv6> for Mldv1Body<B> {
275 fn group_addr(&self) -> Ipv6Addr {
276 self.group_addr
277 }
278
279 fn max_response_time(&self) -> Duration {
280 self.max_response_delay()
281 }
282}
283
284impl<B: SplitByteSlice> gmp::v2::QueryMessage<Ipv6> for Mldv2QueryBody<B> {
285 fn as_v1(&self) -> impl gmp::v1::QueryMessage<Ipv6> + '_ {
286 self.as_v1_query()
287 }
288
289 fn robustness_variable(&self) -> u8 {
290 self.header().querier_robustness_variable()
291 }
292
293 fn query_interval(&self) -> Duration {
294 self.header().querier_query_interval()
295 }
296
297 fn group_address(&self) -> Ipv6Addr {
298 self.header().group_address()
299 }
300
301 fn max_response_time(&self) -> Duration {
302 self.header().max_response_delay().into()
303 }
304
305 fn sources(&self) -> impl Iterator<Item = Ipv6Addr> + '_ {
306 self.sources().iter().copied()
307 }
308}
309
310#[derive(Debug, Eq, PartialEq, Copy, Clone)]
312#[allow(missing_docs)]
313pub enum MldConfigMode {
314 V1,
315 V2,
316}
317
318impl IpExt for Ipv6 {
319 type GmpProtoConfigMode = MldConfigMode;
320
321 fn should_perform_gmp(group_addr: MulticastAddr<Ipv6Addr>) -> bool {
322 group_addr != Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS
335 && ![Ipv6Scope::Reserved(Ipv6ReservedScope::Scope0), Ipv6Scope::InterfaceLocal]
336 .contains(&group_addr.scope())
337 }
338}
339
340#[derive(Debug, Eq, PartialEq, Copy, Clone)]
342pub struct MldMode(GmpMode);
343
344impl From<MldMode> for GmpMode {
345 fn from(MldMode(v): MldMode) -> Self {
346 v
347 }
348}
349
350impl Default for MldMode {
353 fn default() -> Self {
354 Self(GmpMode::V2)
355 }
356}
357
358impl InspectableValue for MldMode {
359 fn record<I: Inspector>(&self, name: &str, inspector: &mut I) {
360 let Self(gmp_mode) = self;
361 let v = match gmp_mode {
362 GmpMode::V1 { compat: false } => "MLDv1(compat)",
363 GmpMode::V1 { compat: true } => "MLDv1",
364 GmpMode::V2 => "MLDv2",
365 };
366 inspector.record_str(name, v);
367 }
368}
369
370pub enum MldTypeLayout {}
372
373impl<BT: MldBindingsTypes> GmpTypeLayout<Ipv6, BT> for MldTypeLayout {
374 type Config = MldConfig;
375 type ProtoMode = MldMode;
376}
377
378impl<BT: MldBindingsTypes, CC: MldStateContext<BT>> GmpStateContext<Ipv6, BT> for CC {
379 type TypeLayout = MldTypeLayout;
380
381 fn with_gmp_state<
382 O,
383 F: FnOnce(
384 &MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, BT>>,
385 &GmpState<Ipv6, MldTypeLayout, BT>,
386 ) -> O,
387 >(
388 &mut self,
389 device: &Self::DeviceId,
390 cb: F,
391 ) -> O {
392 self.with_mld_state(device, cb)
393 }
394}
395
396impl<BC: MldBindingsContext, CC: MldContext<BC>> GmpContext<Ipv6, BC> for CC {
397 type TypeLayout = MldTypeLayout;
398 type Inner<'a> = CC::SendContext<'a>;
399
400 fn with_gmp_state_mut_and_ctx<
401 O,
402 F: FnOnce(Self::Inner<'_>, GmpStateRef<'_, Ipv6, Self::TypeLayout, BC>) -> O,
403 >(
404 &mut self,
405 device: &Self::DeviceId,
406 cb: F,
407 ) -> O {
408 self.with_mld_state_mut(device, cb)
409 }
410}
411
412impl<BC: MldBindingsContext, CC: MldSendContext<BC>> GmpContextInner<Ipv6, BC> for CC {
413 type TypeLayout = MldTypeLayout;
414 fn send_message_v1(
415 &mut self,
416 bindings_ctx: &mut BC,
417 device: &Self::DeviceId,
418 _cur_mode: &MldMode,
419 group_addr: GmpEnabledGroup<Ipv6Addr>,
420 msg_type: gmp::v1::GmpMessageType,
421 ) {
422 let group_addr = group_addr.into_multicast_addr();
423 let result = match msg_type {
424 gmp::v1::GmpMessageType::Report => {
425 self.increment_both(device, |counters: &MldCounters| &counters.tx_mldv1_report);
426 send_mld_v1_packet::<_, _>(
427 self,
428 bindings_ctx,
429 device,
430 group_addr,
431 MldMessage::ListenerReport { group_addr },
432 )
433 }
434 gmp::v1::GmpMessageType::Leave => {
435 self.increment_both(device, |counters: &MldCounters| &counters.tx_leave_group);
436 send_mld_v1_packet::<_, _>(
437 self,
438 bindings_ctx,
439 device,
440 Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
441 MldMessage::ListenerDone { group_addr },
442 )
443 }
444 };
445
446 match result {
447 Ok(()) => {}
448 Err(err) => {
449 self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
450 debug!(
451 "error sending MLD message ({msg_type:?}) on device {device:?} for group \
452 {group_addr}: {err}",
453 )
454 }
455 }
456 }
457
458 fn send_report_v2(
459 &mut self,
460 bindings_ctx: &mut BC,
461 device: &Self::DeviceId,
462 groups: impl Iterator<Item: gmp::v2::VerifiedReportGroupRecord<Ipv6Addr> + Clone> + Clone,
463 ) {
464 let dst_ip = ALL_MLDV2_CAPABLE_ROUTERS;
465 let (ipv6, icmp) =
466 new_ip_and_icmp_builders(self, device, dst_ip, MulticastListenerReportV2);
467 let header = ipv6.constraints().header_len() + icmp.constraints().header_len();
468 let avail_len = usize::from(self.get_mtu(device)).saturating_sub(header);
469 let reports = match Mldv2ReportMessageBuilder::new(groups).with_len_limits(avail_len) {
470 Ok(msg) => msg,
471 Err(e) => {
472 self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
473 error!("MTU too small to send MLD reports: {e:?}");
480 return;
481 }
482 };
483 for report in reports {
484 self.increment_both(device, |counters: &MldCounters| &counters.tx_mldv2_report);
485 let mut report = report.into_serializer().wrap_in(icmp.clone());
486 let report = DynTransportSerializer::new(&mut report);
487 let destination = IpPacketDestination::Multicast(dst_ip);
488 let ip_frame = report.wrap_in(ipv6.clone());
489 IpLayerHandler::send_ip_frame(self, bindings_ctx, device, destination, ip_frame)
490 .unwrap_or_else(|ErrorAndSerializer { error, .. }| {
491 self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
492 debug!("failed to send MLDv2 report over {device:?}: {error:?}")
493 });
494 }
495 }
496
497 fn mode_update_from_v1_query<Q: gmp::v1::QueryMessage<Ipv6>>(
498 &mut self,
499 _bindings_ctx: &mut BC,
500 _query: &Q,
501 gmp_state: &GmpState<Ipv6, MldTypeLayout, BC>,
502 _config: &MldConfig,
503 ) -> MldMode {
504 let MldMode(gmp) = &gmp_state.mode;
505 MldMode(gmp.maybe_enter_v1_compat())
506 }
507
508 fn mode_to_config(MldMode(gmp_mode): &MldMode) -> MldConfigMode {
509 match gmp_mode {
510 GmpMode::V2 | GmpMode::V1 { compat: true } => MldConfigMode::V2,
511 GmpMode::V1 { compat: false } => MldConfigMode::V1,
512 }
513 }
514
515 fn config_to_mode(MldMode(cur_mode): &MldMode, config: MldConfigMode) -> MldMode {
516 MldMode(match config {
517 MldConfigMode::V1 => GmpMode::V1 { compat: false },
518 MldConfigMode::V2 => match cur_mode {
519 GmpMode::V1 { compat: true } => *cur_mode,
520 GmpMode::V1 { compat: false } | GmpMode::V2 => GmpMode::V2,
521 },
522 })
523 }
524
525 fn mode_on_disable(MldMode(cur_mode): &MldMode) -> MldMode {
526 MldMode(cur_mode.maybe_exit_v1_compat())
527 }
528
529 fn mode_on_exit_compat() -> MldMode {
530 MldMode(GmpMode::V2)
531 }
532}
533
534#[derive(Debug, Error, Eq, PartialEq)]
535pub(crate) enum MldError {
536 #[error("the host has not already been a member of the address: {}", addr)]
539 NotAMember { addr: Ipv6Addr },
540 #[error("failed to send out an MLD packet to address: {}", addr)]
542 SendFailure { addr: Ipv6Addr },
543 #[error("bad source address: {}", addr)]
545 BadSourceAddress { addr: Ipv6Addr },
546 #[error("router alert option not present")]
548 MissingRouterAlert,
549 #[error("message with incorrect hop limit: {hop_limit}")]
551 BadHopLimit { hop_limit: u8 },
552 #[error("MLD is disabled on interface")]
554 Disabled,
555}
556
557impl From<NotAMemberErr<Ipv6>> for MldError {
558 fn from(NotAMemberErr(addr): NotAMemberErr<Ipv6>) -> Self {
559 Self::NotAMember { addr }
560 }
561}
562
563impl From<gmp::v2::QueryError<Ipv6>> for MldError {
564 fn from(err: gmp::v2::QueryError<Ipv6>) -> Self {
565 match err {
566 gmp::v2::QueryError::NotAMember(addr) => Self::NotAMember { addr },
567 gmp::v2::QueryError::Disabled => Self::Disabled,
568 }
569 }
570}
571
572pub(crate) type MldResult<T> = Result<T, MldError>;
573
574#[derive(Debug)]
575pub struct MldConfig {
576 unsolicited_report_interval: Duration,
577 send_leave_anyway: bool,
578}
579
580pub const MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL: Duration = Duration::from_secs(10);
584
585impl Default for MldConfig {
586 fn default() -> Self {
587 MldConfig {
588 unsolicited_report_interval: MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL,
589 send_leave_anyway: false,
590 }
591 }
592}
593
594impl gmp::v1::ProtocolConfig for MldConfig {
595 fn unsolicited_report_interval(&self) -> Duration {
596 self.unsolicited_report_interval
597 }
598
599 fn send_leave_anyway(&self) -> bool {
600 self.send_leave_anyway
601 }
602
603 fn get_max_resp_time(&self, resp_time: Duration) -> Option<NonZeroDuration> {
604 NonZeroDuration::new(resp_time)
605 }
606}
607
608impl gmp::v2::ProtocolConfig for MldConfig {
609 fn query_response_interval(&self) -> NonZeroDuration {
610 gmp::v2::DEFAULT_QUERY_RESPONSE_INTERVAL
611 }
612
613 fn unsolicited_report_interval(&self) -> NonZeroDuration {
614 gmp::v2::DEFAULT_UNSOLICITED_REPORT_INTERVAL
615 }
616}
617
618#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
620pub struct MldTimerId<D: WeakDeviceIdentifier>(GmpTimerId<Ipv6, D>);
621
622impl<D: WeakDeviceIdentifier> MldTimerId<D> {
623 pub(crate) fn device_id(&self) -> &D {
624 let Self(this) = self;
625 this.device_id()
626 }
627
628 #[cfg(any(test, feature = "testutils"))]
630 pub fn new(device: D) -> Self {
631 Self(GmpTimerId { device, _marker: Default::default() })
632 }
633}
634
635impl<D: WeakDeviceIdentifier> From<GmpTimerId<Ipv6, D>> for MldTimerId<D> {
636 fn from(id: GmpTimerId<Ipv6, D>) -> MldTimerId<D> {
637 MldTimerId(id)
638 }
639}
640
641impl<BC: MldBindingsContext, CC: MldContext<BC>> HandleableTimer<CC, BC>
642 for MldTimerId<CC::WeakDeviceId>
643{
644 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
645 let Self(id) = self;
646 gmp::handle_timer(core_ctx, bindings_ctx, id);
647 }
648}
649
650#[derive(Debug, Clone, Default)]
663struct MldIpOptions(bool);
664
665impl Iterator for MldIpOptions {
666 type Item = HopByHopOption<'static>;
667
668 fn next(&mut self) -> Option<Self::Item> {
669 let Self(yielded) = self;
670 if core::mem::replace(yielded, true) {
671 None
672 } else {
673 Some(HopByHopOption {
674 action: ExtensionHeaderOptionAction::SkipAndContinue,
675 mutable: false,
676 data: HopByHopOptionData::RouterAlert { data: 0 },
677 })
678 }
679 }
680}
681
682const MLD_IP_HOP_LIMIT: u8 = 1;
691
692fn new_ip_and_icmp_builders<
693 BC: MldBindingsContext,
694 CC: MldSendContext<BC>,
695 M: IcmpMessage<Ipv6, Code = IcmpSenderZeroCode> + filter::IcmpMessage<Ipv6>,
696>(
697 core_ctx: &mut CC,
698 device: &CC::DeviceId,
699 dst_ip: MulticastAddr<Ipv6Addr>,
700 msg: M,
701) -> (Ipv6PacketBuilderWithHbhOptions<'static, MldIpOptions>, IcmpPacketBuilder<Ipv6, M>) {
702 let src_ip =
710 core_ctx.get_ipv6_link_local_addr(device).map_or(Ipv6::UNSPECIFIED_ADDRESS, |x| x.get());
711
712 let ipv6 = Ipv6PacketBuilderWithHbhOptions::new(
713 Ipv6PacketBuilder::new(src_ip, dst_ip.get(), MLD_IP_HOP_LIMIT, Ipv6Proto::Icmpv6),
714 MldIpOptions::default(),
715 )
716 .unwrap();
717 let icmp = IcmpPacketBuilder::new(src_ip, dst_ip.get(), IcmpSenderZeroCode, msg);
718 (ipv6, icmp)
719}
720
721enum MldMessage {
724 ListenerReport { group_addr: <MulticastListenerReport as Mldv1MessageType>::GroupAddr },
725 ListenerDone { group_addr: <MulticastListenerDone as Mldv1MessageType>::GroupAddr },
726}
727
728fn send_mld_v1_packet<BC: MldBindingsContext, CC: MldSendContext<BC>>(
733 core_ctx: &mut CC,
734 bindings_ctx: &mut BC,
735 device: &CC::DeviceId,
736 dst_ip: MulticastAddr<Ipv6Addr>,
737 msg: MldMessage,
738) -> MldResult<()> {
739 macro_rules! send {
740 ($type:ty, $struct:expr, $group_addr:expr) => {{
741 let (ipv6, icmp) = new_ip_and_icmp_builders(core_ctx, device, dst_ip, $struct);
742
743 let mut message =
744 Mldv1MessageBuilder::<$type>::new_with_max_resp_delay($group_addr, ())
745 .into_serializer()
746 .wrap_in(icmp);
747 let message = DynTransportSerializer::new(&mut message);
748 let ip_frame = message.wrap_in(ipv6);
749 let destination = IpPacketDestination::Multicast(dst_ip);
750 IpLayerHandler::send_ip_frame(core_ctx, bindings_ctx, &device, destination, ip_frame)
751 .map_err(|_| MldError::SendFailure { addr: $group_addr.into() })
752 }};
753 }
754
755 match msg {
756 MldMessage::ListenerReport { group_addr } => {
757 send!(MulticastListenerReport, MulticastListenerReport, group_addr)
758 }
759 MldMessage::ListenerDone { group_addr } => {
760 send!(MulticastListenerDone, MulticastListenerDone, group_addr)
761 }
762 }
763}
764
765#[derive(Default, Debug)]
769#[cfg_attr(
770 any(test, feature = "testutils"),
771 derive(PartialEq, netstack3_macros::CounterCollection)
772)]
773pub struct MldCounters<C = Counter> {
774 rx_mldv1_query: C,
776 rx_mldv2_query: C,
778 rx_mldv1_report: C,
780 rx_mldv2_report: C,
782 rx_leave_group: C,
784 rx_err_bad_src_addr: C,
786 rx_err_bad_hop_limit: C,
788 rx_err_missing_router_alert: C,
790 tx_mldv1_report: C,
792 tx_mldv2_report: C,
794 tx_leave_group: C,
796 tx_err: C,
798}
799
800impl Inspectable for MldCounters {
801 fn record<I: Inspector>(&self, inspector: &mut I) {
802 let Self {
803 rx_mldv1_query,
804 rx_mldv2_query,
805 rx_mldv1_report,
806 rx_mldv2_report,
807 rx_leave_group,
808 rx_err_bad_src_addr,
809 rx_err_bad_hop_limit,
810 rx_err_missing_router_alert,
811 tx_mldv1_report,
812 tx_mldv2_report,
813 tx_leave_group,
814 tx_err,
815 } = self;
816 inspector.record_child("Rx", |inspector| {
817 inspector.record_counter("MLDv1Query", rx_mldv1_query);
818 inspector.record_counter("MLDv2Query", rx_mldv2_query);
819 inspector.record_counter("MLDv1Report", rx_mldv1_report);
820 inspector.record_counter("MLDv2Report", rx_mldv2_report);
821 inspector.record_counter("LeaveGroup", rx_leave_group);
822 inspector.record_child("Errors", |inspector| {
823 inspector.record_counter("BadSourceAddress", rx_err_bad_src_addr);
824 inspector.record_counter("BadHopLimit", rx_err_bad_hop_limit);
825 inspector.record_counter("MissingRouterAlert", rx_err_missing_router_alert);
826 })
827 });
828 inspector.record_child("Tx", |inspector| {
829 inspector.record_counter("MLDv1Report", tx_mldv1_report);
830 inspector.record_counter("MLDv2Report", tx_mldv2_report);
831 inspector.record_counter("LeaveGroup", tx_leave_group);
832 inspector.record_child("Errors", |inspector| {
833 inspector.record_counter("SendFailed", tx_err);
834 });
835 });
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use alloc::rc::Rc;
842 use alloc::vec;
843 use alloc::vec::Vec;
844 use core::cell::RefCell;
845
846 use assert_matches::assert_matches;
847 use net_types::ethernet::Mac;
848 use net_types::ip::{Ip as _, IpVersionMarker, Mtu};
849 use netstack3_base::testutil::{
850 FakeDeviceId, FakeInstant, FakeTimerCtxExt, FakeWeakDeviceId, TestIpExt, assert_empty,
851 new_rng, run_with_many_seeds,
852 };
853 use netstack3_base::{
854 CounterCollection, CounterContext, CtxPair, InstantContext as _, IntoCoreTimerCtx,
855 SendFrameContext,
856 };
857 use packet::{Buf, BufferMut, ParseBuffer};
858 use packet_formats::gmp::GroupRecordType;
859 use packet_formats::icmp::mld::{
860 Mldv2QueryMessageBuilder, MulticastListenerQuery, MulticastListenerQueryV2,
861 };
862 use packet_formats::icmp::{IcmpParseArgs, Icmpv6MessageType, Icmpv6Packet};
863 use packet_formats::ip::IpPacket;
864 use packet_formats::ipv6::Ipv6Packet;
865 use packet_formats::ipv6::ext_hdrs::Ipv6ExtensionHeaderData;
866
867 use super::*;
868 use crate::internal::base::{IpPacketDestination, IpSendFrameError, SendIpPacketMeta};
869 use crate::internal::fragmentation::FragmentableIpSerializer;
870 use crate::internal::gmp::{
871 GmpEnabledGroup, GmpHandler as _, GmpState, GroupJoinResult, GroupLeaveResult,
872 };
873
874 #[derive(Debug, PartialEq)]
876 pub(crate) struct MldFrameMetadata<D> {
877 pub(crate) device: D,
878 pub(crate) dst_ip: MulticastAddr<Ipv6Addr>,
879 }
880
881 impl<D> MldFrameMetadata<D> {
882 fn new(device: D, dst_ip: MulticastAddr<Ipv6Addr>) -> MldFrameMetadata<D> {
883 MldFrameMetadata { device, dst_ip }
884 }
885 }
886
887 struct FakeMldCtx {
891 shared: Rc<RefCell<Shared>>,
892 mld_enabled: bool,
893 ipv6_link_local: Option<LinkLocalUnicastAddr<Ipv6Addr>>,
894 stack_wide_counters: MldCounters,
895 device_specific_counters: MldCounters,
896 }
897
898 impl FakeMldCtx {
899 fn gmp_state(&mut self) -> &mut GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl> {
900 &mut Rc::get_mut(&mut self.shared).unwrap().get_mut().gmp_state
901 }
902
903 fn groups(
904 &mut self,
905 ) -> &mut MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>> {
906 &mut Rc::get_mut(&mut self.shared).unwrap().get_mut().groups
907 }
908 }
909
910 impl CounterContext<MldCounters> for FakeMldCtx {
911 fn counters(&self) -> &MldCounters {
912 &self.stack_wide_counters
913 }
914 }
915
916 impl ResourceCounterContext<FakeDeviceId, MldCounters> for FakeMldCtx {
917 fn per_resource_counters<'a>(&'a self, _device_id: &'a FakeDeviceId) -> &'a MldCounters {
918 &self.device_specific_counters
919 }
920 }
921
922 struct Shared {
924 groups: MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>>,
925 gmp_state: GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
926 config: MldConfig,
927 }
928
929 fn new_mldv1_context() -> FakeCtxImpl {
934 FakeCtxImpl::with_default_bindings_ctx(|bindings_ctx| {
935 let mld_enabled = true;
937 FakeCoreCtxImpl::with_state(FakeMldCtx {
938 shared: Rc::new(RefCell::new(Shared {
939 groups: MulticastGroupSet::default(),
940 gmp_state: GmpState::new_with_enabled_and_mode::<_, IntoCoreTimerCtx>(
941 bindings_ctx,
942 FakeWeakDeviceId(FakeDeviceId),
943 mld_enabled,
944 MldMode(GmpMode::V1 { compat: false }),
945 ),
946 config: Default::default(),
947 })),
948 mld_enabled,
949 ipv6_link_local: None,
950 stack_wide_counters: Default::default(),
951 device_specific_counters: Default::default(),
952 })
953 })
954 }
955
956 type FakeCtxImpl = CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl>;
957 type FakeCoreCtxImpl = netstack3_base::testutil::FakeCoreCtx<
958 FakeMldCtx,
959 MldFrameMetadata<FakeDeviceId>,
960 FakeDeviceId,
961 >;
962 type FakeBindingsCtxImpl = netstack3_base::testutil::FakeBindingsCtx<
963 MldTimerId<FakeWeakDeviceId<FakeDeviceId>>,
964 (),
965 (),
966 (),
967 >;
968
969 impl MldContextMarker for FakeCoreCtxImpl {}
970 impl MldContextMarker for &'_ mut FakeCoreCtxImpl {}
971
972 impl MldStateContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
973 fn with_mld_state<
974 O,
975 F: FnOnce(
976 &MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>>,
977 &GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
978 ) -> O,
979 >(
980 &mut self,
981 &FakeDeviceId: &FakeDeviceId,
982 cb: F,
983 ) -> O {
984 let state = self.state.shared.borrow();
985 cb(&state.groups, &state.gmp_state)
986 }
987 }
988
989 impl MldContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
990 type SendContext<'a> = &'a mut Self;
991 fn with_mld_state_mut<
992 O,
993 F: FnOnce(
994 Self::SendContext<'_>,
995 GmpStateRef<'_, Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
996 ) -> O,
997 >(
998 &mut self,
999 &FakeDeviceId: &FakeDeviceId,
1000 cb: F,
1001 ) -> O {
1002 let FakeMldCtx { mld_enabled, shared, .. } = &mut self.state;
1003 let enabled = *mld_enabled;
1004 let shared = Rc::clone(shared);
1005 let mut shared = shared.borrow_mut();
1006 let Shared { gmp_state, groups, config } = &mut *shared;
1007 cb(self, GmpStateRef { enabled, groups, gmp: gmp_state, config })
1008 }
1009 }
1010
1011 impl IpDeviceMtuContext<Ipv6> for &mut FakeCoreCtxImpl {
1012 fn get_mtu(&mut self, _device: &FakeDeviceId) -> Mtu {
1013 Ipv6::MINIMUM_LINK_MTU
1014 }
1015 }
1016
1017 impl MldSendContext<FakeBindingsCtxImpl> for &mut FakeCoreCtxImpl {
1018 fn get_ipv6_link_local_addr(
1019 &mut self,
1020 _device: &FakeDeviceId,
1021 ) -> Option<LinkLocalUnicastAddr<Ipv6Addr>> {
1022 self.state.ipv6_link_local
1023 }
1024 }
1025
1026 impl IpLayerHandler<Ipv6, FakeBindingsCtxImpl> for &mut FakeCoreCtxImpl {
1027 fn send_ip_packet_from_device<S>(
1028 &mut self,
1029 _bindings_ctx: &mut FakeBindingsCtxImpl,
1030 _meta: SendIpPacketMeta<
1031 Ipv6,
1032 &Self::DeviceId,
1033 Option<SpecifiedAddr<<Ipv6 as Ip>::Addr>>,
1034 >,
1035 _body: S,
1036 ) -> Result<(), IpSendFrameError<S>>
1037 where
1038 S: Serializer,
1039 S::Buffer: BufferMut,
1040 {
1041 unimplemented!();
1042 }
1043
1044 fn send_ip_frame<S>(
1045 &mut self,
1046 bindings_ctx: &mut FakeBindingsCtxImpl,
1047 device: &Self::DeviceId,
1048 destination: IpPacketDestination<Ipv6, &Self::DeviceId>,
1049 body: S,
1050 ) -> Result<(), IpSendFrameError<S>>
1051 where
1052 S: FragmentableIpSerializer<Ipv6, Buffer: BufferMut> + netstack3_filter::IpPacket<Ipv6>,
1053 {
1054 let addr = match destination {
1055 IpPacketDestination::Multicast(addr) => addr,
1056 _ => panic!("destination is not multicast: {:?}", destination),
1057 };
1058 (*self)
1059 .send_frame(bindings_ctx, MldFrameMetadata::new(device.clone(), addr), body)
1060 .map_err(|e| e.err_into())
1061 }
1062 }
1063
1064 impl CounterContext<MldCounters> for &mut FakeCoreCtxImpl {
1065 fn counters(&self) -> &MldCounters {
1066 <FakeCoreCtxImpl as CounterContext<MldCounters>>::counters(self)
1067 }
1068 }
1069
1070 impl ResourceCounterContext<FakeDeviceId, MldCounters> for &mut FakeCoreCtxImpl {
1071 fn per_resource_counters<'a>(&'a self, device_id: &'a FakeDeviceId) -> &'a MldCounters {
1072 <
1073 FakeCoreCtxImpl as ResourceCounterContext<FakeDeviceId, MldCounters>
1074 >::per_resource_counters(self, device_id)
1075 }
1076 }
1077
1078 type CounterExpectations = MldCounters<u64>;
1079
1080 impl CounterExpectations {
1081 #[track_caller]
1082 fn assert_counters<CC: ResourceCounterContext<FakeDeviceId, MldCounters>>(
1083 &self,
1084 core_ctx: &mut CC,
1085 ) {
1086 assert_eq!(self, &core_ctx.counters().cast(), "stack-wide counter mismatch");
1087 assert_eq!(
1088 self,
1089 &core_ctx.per_resource_counters(&FakeDeviceId).cast(),
1090 "device-specific counter mismatch"
1091 );
1092 }
1093 }
1094
1095 #[test]
1096 fn test_mld_immediate_report() {
1097 run_with_many_seeds(|seed| {
1098 let mut rng = new_rng(seed);
1105 let cfg = MldConfig::default();
1106 let (mut s, _actions) =
1107 gmp::v1::GmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
1108 assert_eq!(
1109 s.query_received(&mut rng, Duration::from_secs(0), FakeInstant::default(), &cfg),
1110 gmp::v1::QueryReceivedActions::StopTimerAndSendReport,
1111 );
1112 });
1113 }
1114
1115 const MY_IP: SpecifiedAddr<Ipv6Addr> = unsafe {
1116 SpecifiedAddr::new_unchecked(Ipv6Addr::from_bytes([
1117 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 3,
1118 ]))
1119 };
1120 const MY_MAC: Mac = Mac::new([1, 2, 3, 4, 5, 6]);
1121 const ROUTER_MAC: Mac = Mac::new([6, 5, 4, 3, 2, 1]);
1122 const GROUP_ADDR: MulticastAddr<Ipv6Addr> = <Ipv6 as gmp::testutil::TestIpExt>::GROUP_ADDR1;
1123 const TIMER_ID: MldTimerId<FakeWeakDeviceId<FakeDeviceId>> = MldTimerId(GmpTimerId {
1124 device: FakeWeakDeviceId(FakeDeviceId),
1125 _marker: IpVersionMarker::new(),
1126 });
1127
1128 struct FakeHeaderInfo {
1129 hop_limit: u8,
1130 router_alert: bool,
1131 }
1132
1133 impl IpHeaderInfo<Ipv6> for FakeHeaderInfo {
1134 fn dscp_and_ecn(&self) -> packet_formats::ip::DscpAndEcn {
1135 unimplemented!()
1136 }
1137 fn hop_limit(&self) -> u8 {
1138 self.hop_limit
1139 }
1140 fn router_alert(&self) -> bool {
1141 self.router_alert
1142 }
1143 fn as_bytes(&self) -> [&[u8]; 2] {
1144 unimplemented!()
1145 }
1146 }
1147
1148 const DEFAULT_HEADER_INFO: FakeHeaderInfo =
1149 FakeHeaderInfo { hop_limit: MLD_IP_HOP_LIMIT, router_alert: true };
1150
1151 fn new_v1_query(resp_time: Duration, group_addr: MulticastAddr<Ipv6Addr>) -> Buf<Vec<u8>> {
1152 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1153 IcmpPacketBuilder::<_, _>::new(
1154 router_addr,
1155 MY_IP,
1156 IcmpSenderZeroCode,
1157 MulticastListenerQuery,
1158 )
1159 .wrap_body(
1160 Mldv1MessageBuilder::<MulticastListenerQuery>::new_with_max_resp_delay(
1161 group_addr.get(),
1162 resp_time.try_into().unwrap(),
1163 )
1164 .into_serializer(),
1165 )
1166 .serialize_vec_outer()
1167 .unwrap()
1168 .unwrap_b()
1169 }
1170
1171 fn new_v1_report(group_addr: MulticastAddr<Ipv6Addr>) -> Buf<Vec<u8>> {
1172 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1173 IcmpPacketBuilder::<_, _>::new(
1174 router_addr,
1175 MY_IP,
1176 IcmpSenderZeroCode,
1177 MulticastListenerReport,
1178 )
1179 .wrap_body(
1180 Mldv1MessageBuilder::<MulticastListenerReport>::new(group_addr).into_serializer(),
1181 )
1182 .serialize_vec_outer()
1183 .unwrap()
1184 .unwrap_b()
1185 }
1186
1187 fn new_v2_general_query() -> Buf<Vec<u8>> {
1188 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1189 IcmpPacketBuilder::<_, _>::new(
1190 router_addr,
1191 MY_IP,
1192 IcmpSenderZeroCode,
1193 MulticastListenerQueryV2,
1194 )
1195 .wrap_body(
1196 Mldv2QueryMessageBuilder::new(
1197 Default::default(),
1198 None,
1199 false,
1200 Default::default(),
1201 Default::default(),
1202 core::iter::empty::<Ipv6Addr>(),
1203 )
1204 .into_serializer(),
1205 )
1206 .serialize_vec_outer()
1207 .unwrap()
1208 .unwrap_b()
1209 }
1210
1211 fn parse_mld_packet<B: ParseBuffer>(buffer: &mut B) -> MldPacket<&[u8]> {
1212 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1213 match buffer
1214 .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(router_addr, MY_IP))
1215 .unwrap()
1216 {
1217 Icmpv6Packet::Mld(packet) => packet,
1218 _ => panic!("serialized icmpv6 message is not an mld message"),
1219 }
1220 }
1221
1222 fn receive_mldv1_query(
1223 core_ctx: &mut FakeCoreCtxImpl,
1224 bindings_ctx: &mut FakeBindingsCtxImpl,
1225 resp_time: Duration,
1226 group_addr: MulticastAddr<Ipv6Addr>,
1227 ) {
1228 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1229 let mut buffer = new_v1_query(resp_time, group_addr);
1230 let packet = parse_mld_packet(&mut buffer);
1231 core_ctx.receive_mld_packet(
1232 bindings_ctx,
1233 &FakeDeviceId,
1234 router_addr.try_into().unwrap(),
1235 MY_IP,
1236 packet,
1237 &DEFAULT_HEADER_INFO,
1238 )
1239 }
1240
1241 fn receive_mldv1_report(
1242 core_ctx: &mut FakeCoreCtxImpl,
1243 bindings_ctx: &mut FakeBindingsCtxImpl,
1244 group_addr: MulticastAddr<Ipv6Addr>,
1245 ) {
1246 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1247 let mut buffer = new_v1_report(group_addr);
1248 let packet = parse_mld_packet(&mut buffer);
1249 core_ctx.receive_mld_packet(
1250 bindings_ctx,
1251 &FakeDeviceId,
1252 router_addr.try_into().unwrap(),
1253 MY_IP,
1254 packet,
1255 &DEFAULT_HEADER_INFO,
1256 )
1257 }
1258
1259 fn ensure_ttl(frame: &[u8]) {
1261 assert_eq!(frame[7], MLD_IP_HOP_LIMIT);
1262 }
1263
1264 fn ensure_slice_addr(frame: &[u8], start: usize, end: usize, ip: Ipv6Addr) {
1265 let mut bytes = [0u8; 16];
1266 bytes.copy_from_slice(&frame[start..end]);
1267 assert_eq!(Ipv6Addr::from_bytes(bytes), ip);
1268 }
1269
1270 fn ensure_dst_addr(frame: &[u8], ip: Ipv6Addr) {
1272 ensure_slice_addr(frame, 24, 40, ip);
1273 }
1274
1275 fn ensure_multicast_addr(frame: &[u8], ip: Ipv6Addr) {
1277 ensure_slice_addr(frame, 56, 72, ip);
1278 }
1279
1280 fn ensure_frame(
1282 frame: &[u8],
1283 op: u8,
1284 dst: MulticastAddr<Ipv6Addr>,
1285 multicast: MulticastAddr<Ipv6Addr>,
1286 ) {
1287 ensure_ttl(frame);
1288 assert_eq!(frame[48], op);
1289 assert_eq!(frame[5], 32);
1291 assert_eq!(frame[6], 0);
1293 assert_eq!(&frame[40..48], &[58, 0, 5, 2, 0, 0, 1, 0]);
1295 ensure_ttl(&frame[..]);
1296 ensure_dst_addr(&frame[..], dst.get());
1297 ensure_multicast_addr(&frame[..], multicast.get());
1298 }
1299
1300 #[test]
1301 fn test_mld_simple_integration() {
1302 run_with_many_seeds(|seed| {
1303 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1304 bindings_ctx.seed_rng(seed);
1305
1306 assert_eq!(
1307 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1308 GroupJoinResult::Joined(())
1309 );
1310
1311 receive_mldv1_query(
1312 &mut core_ctx,
1313 &mut bindings_ctx,
1314 Duration::from_secs(10),
1315 GROUP_ADDR,
1316 );
1317 core_ctx
1318 .state
1319 .gmp_state()
1320 .timers
1321 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1322 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1323
1324 assert_eq!(core_ctx.frames().len(), 2);
1328 for (_, frame) in core_ctx.frames() {
1330 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1331 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1332 }
1333
1334 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 2, ..Default::default() }
1335 .assert_counters(&mut core_ctx);
1336 });
1337 }
1338
1339 #[test]
1340 fn test_mld_immediate_query() {
1341 run_with_many_seeds(|seed| {
1342 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1343 bindings_ctx.seed_rng(seed);
1344
1345 assert_eq!(
1346 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1347 GroupJoinResult::Joined(())
1348 );
1349 assert_eq!(core_ctx.frames().len(), 1);
1350
1351 receive_mldv1_query(
1352 &mut core_ctx,
1353 &mut bindings_ctx,
1354 Duration::from_secs(0),
1355 GROUP_ADDR,
1356 );
1357 assert_eq!(core_ctx.frames().len(), 2);
1359 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1361 for (_, frame) in core_ctx.frames() {
1363 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1364 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1365 }
1366
1367 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 2, ..Default::default() }
1368 .assert_counters(&mut core_ctx);
1369 });
1370 }
1371
1372 #[test]
1373 fn test_mld_integration_fallback_from_idle() {
1374 run_with_many_seeds(|seed| {
1375 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1376 bindings_ctx.seed_rng(seed);
1377
1378 assert_eq!(
1379 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1380 GroupJoinResult::Joined(())
1381 );
1382 assert_eq!(core_ctx.frames().len(), 1);
1383
1384 core_ctx
1385 .state
1386 .gmp_state()
1387 .timers
1388 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1389 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1390 assert_eq!(core_ctx.frames().len(), 2);
1391
1392 receive_mldv1_query(
1393 &mut core_ctx,
1394 &mut bindings_ctx,
1395 Duration::from_secs(10),
1396 GROUP_ADDR,
1397 );
1398
1399 let group_state = core_ctx.state.groups().get(&GROUP_ADDR).unwrap();
1402 match group_state.v1().get_inner() {
1403 gmp::v1::MemberState::Delaying(_) => {}
1404 _ => panic!("Wrong State!"),
1405 }
1406
1407 core_ctx
1408 .state
1409 .gmp_state()
1410 .timers
1411 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1412 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1413 assert_eq!(core_ctx.frames().len(), 3);
1414 for (_, frame) in core_ctx.frames() {
1416 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1417 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1418 }
1419
1420 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 3, ..Default::default() }
1421 .assert_counters(&mut core_ctx);
1422 });
1423 }
1424
1425 #[test]
1426 fn test_mld_integration_immediate_query_wont_fallback() {
1427 run_with_many_seeds(|seed| {
1428 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1429 bindings_ctx.seed_rng(seed);
1430
1431 assert_eq!(
1432 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1433 GroupJoinResult::Joined(())
1434 );
1435 assert_eq!(core_ctx.frames().len(), 1);
1436
1437 core_ctx
1438 .state
1439 .gmp_state()
1440 .timers
1441 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1442 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1443 assert_eq!(core_ctx.frames().len(), 2);
1444
1445 receive_mldv1_query(
1446 &mut core_ctx,
1447 &mut bindings_ctx,
1448 Duration::from_secs(0),
1449 GROUP_ADDR,
1450 );
1451
1452 let group_state = core_ctx.state.groups().get(&GROUP_ADDR).unwrap();
1455 match group_state.v1().get_inner() {
1456 gmp::v1::MemberState::Idle(_) => {}
1457 _ => panic!("Wrong State!"),
1458 }
1459
1460 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1462 assert_eq!(core_ctx.frames().len(), 3);
1463 for (_, frame) in core_ctx.frames() {
1465 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1466 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1467 }
1468
1469 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 3, ..Default::default() }
1470 .assert_counters(&mut core_ctx);
1471 });
1472 }
1473
1474 #[test]
1475 fn test_mld_integration_delay_reset_timer() {
1476 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1477 bindings_ctx.seed_rng(123456);
1480 assert_eq!(
1481 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1482 GroupJoinResult::Joined(())
1483 );
1484
1485 core_ctx.state.gmp_state().timers.assert_timers([(
1486 gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1487 (),
1488 FakeInstant::from(Duration::from_micros(590_354)),
1489 )]);
1490 let instant1 = bindings_ctx.timers.timers()[0].0.clone();
1491 let start = bindings_ctx.now();
1492 let duration = instant1 - start;
1493
1494 receive_mldv1_query(&mut core_ctx, &mut bindings_ctx, duration, GROUP_ADDR);
1495 assert_eq!(core_ctx.frames().len(), 1);
1496 core_ctx.state.gmp_state().timers.assert_timers([(
1497 gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1498 (),
1499 FakeInstant::from(Duration::from_micros(34_831)),
1500 )]);
1501 let instant2 = bindings_ctx.timers.timers()[0].0.clone();
1502 assert!(instant2 <= instant1);
1504 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1505 assert!(bindings_ctx.now() - start <= duration);
1506 assert_eq!(core_ctx.frames().len(), 2);
1507 for (_, frame) in core_ctx.frames() {
1509 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1510 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1511 }
1512
1513 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 2, ..Default::default() }
1514 .assert_counters(&mut core_ctx);
1515 }
1516
1517 #[test]
1518 fn test_mld_integration_last_send_leave() {
1519 run_with_many_seeds(|seed| {
1520 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1521 bindings_ctx.seed_rng(seed);
1522
1523 assert_eq!(
1524 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1525 GroupJoinResult::Joined(())
1526 );
1527 let now = bindings_ctx.now();
1528
1529 core_ctx.state.gmp_state().timers.assert_range([(
1530 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1531 now..=(now + MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1532 )]);
1533 assert_eq!(core_ctx.frames().len(), 1);
1535 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1536 assert_eq!(core_ctx.frames().len(), 2);
1538 assert_eq!(
1539 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1540 GroupLeaveResult::Left(())
1541 );
1542 assert_eq!(core_ctx.frames().len(), 3);
1544 ensure_frame(&core_ctx.frames()[0].1, 131, GROUP_ADDR, GROUP_ADDR);
1546 ensure_slice_addr(&core_ctx.frames()[0].1, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1547 ensure_frame(&core_ctx.frames()[1].1, 131, GROUP_ADDR, GROUP_ADDR);
1548 ensure_slice_addr(&core_ctx.frames()[1].1, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1549 ensure_frame(
1552 &core_ctx.frames()[2].1,
1553 132,
1554 Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
1555 GROUP_ADDR,
1556 );
1557 ensure_slice_addr(&core_ctx.frames()[2].1, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1558
1559 CounterExpectations { tx_mldv1_report: 2, tx_leave_group: 1, ..Default::default() }
1560 .assert_counters(&mut core_ctx);
1561 });
1562 }
1563
1564 #[test]
1565 fn test_mld_integration_not_last_does_not_send_leave() {
1566 run_with_many_seeds(|seed| {
1567 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1568 bindings_ctx.seed_rng(seed);
1569
1570 assert_eq!(
1571 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1572 GroupJoinResult::Joined(())
1573 );
1574 let now = bindings_ctx.now();
1575 core_ctx.state.gmp_state().timers.assert_range([(
1576 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1577 now..=(now + MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1578 )]);
1579 assert_eq!(core_ctx.frames().len(), 1);
1580 receive_mldv1_report(&mut core_ctx, &mut bindings_ctx, GROUP_ADDR);
1581 bindings_ctx.timers.assert_no_timers_installed();
1582 assert_eq!(core_ctx.frames().len(), 1);
1585 assert_eq!(
1586 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1587 GroupLeaveResult::Left(())
1588 );
1589 assert_eq!(core_ctx.frames().len(), 1);
1591 for (_, frame) in core_ctx.frames() {
1593 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1594 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1595 }
1596
1597 CounterExpectations { rx_mldv1_report: 1, tx_mldv1_report: 1, ..Default::default() }
1598 .assert_counters(&mut core_ctx);
1599 });
1600 }
1601
1602 #[test]
1603 fn test_mld_with_link_local() {
1604 run_with_many_seeds(|seed| {
1605 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1606 bindings_ctx.seed_rng(seed);
1607
1608 core_ctx.state.ipv6_link_local = Some(MY_MAC.to_ipv6_link_local().addr());
1609 assert_eq!(
1610 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1611 GroupJoinResult::Joined(())
1612 );
1613 core_ctx
1614 .state
1615 .gmp_state()
1616 .timers
1617 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1618 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1619 for (_, frame) in core_ctx.frames() {
1620 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1621 ensure_slice_addr(&frame, 8, 24, MY_MAC.to_ipv6_link_local().addr().get());
1622 }
1623 });
1624 }
1625
1626 #[test]
1627 fn test_skip_mld() {
1628 run_with_many_seeds(|seed| {
1629 let test = |FakeCtxImpl { mut core_ctx, mut bindings_ctx }, group| {
1632 core_ctx.state.ipv6_link_local = Some(MY_MAC.to_ipv6_link_local().addr());
1633
1634 let assert_no_effect =
1636 |core_ctx: &FakeCoreCtxImpl, bindings_ctx: &FakeBindingsCtxImpl| {
1637 bindings_ctx.timers.assert_no_timers_installed();
1638 assert_empty(core_ctx.frames());
1639 };
1640
1641 assert_eq!(
1642 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, group),
1643 GroupJoinResult::Joined(())
1644 );
1645 assert_gmp_state!(core_ctx, &group, NonMember);
1648 assert_no_effect(&core_ctx, &bindings_ctx);
1649
1650 receive_mldv1_report(&mut core_ctx, &mut bindings_ctx, group);
1651 assert_gmp_state!(core_ctx, &group, NonMember);
1653 assert_no_effect(&core_ctx, &bindings_ctx);
1654
1655 receive_mldv1_query(
1656 &mut core_ctx,
1657 &mut bindings_ctx,
1658 Duration::from_secs(10),
1659 group,
1660 );
1661 assert_gmp_state!(core_ctx, &group, NonMember);
1663 assert_no_effect(&core_ctx, &bindings_ctx);
1664
1665 assert_eq!(
1666 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, group),
1667 GroupLeaveResult::Left(())
1668 );
1669 assert!(core_ctx.state.groups().get(&group).is_none());
1671 assert_no_effect(&core_ctx, &bindings_ctx);
1672
1673 CounterExpectations { rx_mldv1_report: 1, rx_mldv1_query: 1, ..Default::default() }
1674 .assert_counters(&mut core_ctx);
1675 };
1676
1677 let new_ctx = || {
1678 let mut ctx = new_mldv1_context();
1679 ctx.bindings_ctx.seed_rng(seed);
1680 ctx
1681 };
1682
1683 test(new_ctx(), Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS);
1686 let mut bytes = Ipv6::MULTICAST_SUBNET.network().ipv6_bytes();
1687 bytes[1] = bytes[1] & 0xF0;
1689 let reserved0 = MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap();
1690 bytes[1] = (bytes[1] & 0xF0) | 1;
1692 let iface_local = MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap();
1693 test(new_ctx(), reserved0);
1694 test(new_ctx(), iface_local);
1695
1696 let mut ctx = new_ctx();
1699 ctx.core_ctx.state.mld_enabled = false;
1700 ctx.core_ctx.gmp_handle_disabled(&mut ctx.bindings_ctx, &FakeDeviceId);
1701 test(ctx, GROUP_ADDR);
1702 });
1703 }
1704
1705 #[test]
1706 fn test_mld_integration_with_local_join_leave() {
1707 run_with_many_seeds(|seed| {
1708 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1711 bindings_ctx.seed_rng(seed);
1712
1713 assert_eq!(
1714 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1715 GroupJoinResult::Joined(())
1716 );
1717 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1718 assert_eq!(core_ctx.frames().len(), 1);
1719 let now = bindings_ctx.now();
1720 let range = now..=(now + MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
1721
1722 core_ctx.state.gmp_state().timers.assert_range([(
1723 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1724 range.clone(),
1725 )]);
1726 let frame = &core_ctx.frames().last().unwrap().1;
1727 ensure_frame(frame, 131, GROUP_ADDR, GROUP_ADDR);
1728 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1729
1730 assert_eq!(
1731 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1732 GroupJoinResult::AlreadyMember
1733 );
1734 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1735 assert_eq!(core_ctx.frames().len(), 1);
1736 core_ctx.state.gmp_state().timers.assert_range([(
1737 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1738 range.clone(),
1739 )]);
1740
1741 assert_eq!(
1742 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1743 GroupLeaveResult::StillMember
1744 );
1745 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1746 assert_eq!(core_ctx.frames().len(), 1);
1747
1748 core_ctx.state.gmp_state().timers.assert_range([(
1749 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1750 range,
1751 )]);
1752
1753 assert_eq!(
1754 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1755 GroupLeaveResult::Left(())
1756 );
1757 assert_eq!(core_ctx.frames().len(), 2);
1758 bindings_ctx.timers.assert_no_timers_installed();
1759 let frame = &core_ctx.frames().last().unwrap().1;
1760 ensure_frame(frame, 132, Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS, GROUP_ADDR);
1761 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1762
1763 CounterExpectations { tx_mldv1_report: 1, tx_leave_group: 1, ..Default::default() }
1764 .assert_counters(&mut core_ctx);
1765 });
1766 }
1767
1768 #[test]
1769 fn test_mld_enable_disable() {
1770 run_with_many_seeds(|seed| {
1771 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1772 bindings_ctx.seed_rng(seed);
1773 assert_eq!(core_ctx.take_frames(), []);
1774
1775 assert_eq!(
1783 core_ctx.gmp_join_group(
1784 &mut bindings_ctx,
1785 &FakeDeviceId,
1786 Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS
1787 ),
1788 GroupJoinResult::Joined(())
1789 );
1790 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1791 assert_eq!(
1792 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1793 GroupJoinResult::Joined(())
1794 );
1795 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1796 {
1797 let frames = core_ctx.take_frames();
1798 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1799 assert_matches!(&frames[..], [x] => x);
1800 assert_eq!(dst_ip, &GROUP_ADDR);
1801 ensure_frame(
1802 frame,
1803 Icmpv6MessageType::MulticastListenerReport.into(),
1804 GROUP_ADDR,
1805 GROUP_ADDR,
1806 );
1807 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1808 }
1809
1810 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1812 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1813 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1814 assert_eq!(core_ctx.take_frames(), []);
1815
1816 core_ctx.state.mld_enabled = false;
1818 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1819 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1820 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1821 {
1822 let frames = core_ctx.take_frames();
1823 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1824 assert_matches!(&frames[..], [x] => x);
1825 assert_eq!(dst_ip, &Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS);
1826 ensure_frame(
1827 frame,
1828 Icmpv6MessageType::MulticastListenerDone.into(),
1829 Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
1830 GROUP_ADDR,
1831 );
1832 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1833 }
1834
1835 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1837 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1838 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1839 assert_eq!(core_ctx.take_frames(), []);
1840
1841 core_ctx.state.mld_enabled = true;
1843 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1844 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1845 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1846 let frames = core_ctx.take_frames();
1847 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1848 assert_matches!(&frames[..], [x] => x);
1849 assert_eq!(dst_ip, &GROUP_ADDR);
1850 ensure_frame(
1851 frame,
1852 Icmpv6MessageType::MulticastListenerReport.into(),
1853 GROUP_ADDR,
1854 GROUP_ADDR,
1855 );
1856 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1857
1858 CounterExpectations { tx_mldv1_report: 2, tx_leave_group: 1, ..Default::default() }
1859 .assert_counters(&mut core_ctx);
1860 });
1861 }
1862
1863 #[test]
1865 fn send_gmpv2_report() {
1866 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1867 let sent_report_addr = Ipv6::get_multicast_addr(130);
1868 let sent_report_mode = GroupRecordType::ModeIsExclude;
1869 let sent_report_sources = Vec::<Ipv6Addr>::new();
1870 (&mut core_ctx).send_report_v2(
1871 &mut bindings_ctx,
1872 &FakeDeviceId,
1873 [gmp::v2::GroupRecord::new_with_sources(
1874 GmpEnabledGroup::new(sent_report_addr).unwrap(),
1875 sent_report_mode,
1876 sent_report_sources.iter(),
1877 )]
1878 .into_iter(),
1879 );
1880 let frames = core_ctx.take_frames();
1881 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1882 assert_matches!(&frames[..], [x] => x);
1883 assert_eq!(dst_ip, &ALL_MLDV2_CAPABLE_ROUTERS);
1884 let mut buff = &frame[..];
1885 let ipv6 = buff.parse::<Ipv6Packet<_>>().expect("parse IPv6");
1886 assert_eq!(ipv6.ttl(), MLD_IP_HOP_LIMIT);
1887 assert_eq!(ipv6.src_ip(), Ipv6::UNSPECIFIED_ADDRESS);
1888 assert_eq!(ipv6.dst_ip(), ALL_MLDV2_CAPABLE_ROUTERS.get());
1889 assert_eq!(ipv6.proto(), Ipv6Proto::Icmpv6);
1890 assert_eq!(
1891 ipv6.iter_extension_hdrs()
1892 .map(|h| {
1893 let options = assert_matches!(
1894 h.data(),
1895 Ipv6ExtensionHeaderData::HopByHopOptions { options } => options
1896 );
1897 assert_eq!(
1898 options
1899 .iter()
1900 .map(|o| {
1901 assert_matches!(
1902 o.data,
1903 HopByHopOptionData::RouterAlert { data: 0 }
1904 );
1905 })
1906 .count(),
1907 1
1908 );
1909 })
1910 .count(),
1911 1
1912 );
1913 let args = IcmpParseArgs::new(ipv6.src_ip(), ipv6.dst_ip());
1914 let icmp = buff.parse_with::<_, Icmpv6Packet<_>>(args).expect("parse ICMPv6");
1915 let report = assert_matches!(
1916 icmp,
1917 Icmpv6Packet::Mld(MldPacket::MulticastListenerReportV2(report)) => report
1918 );
1919 let report = report
1920 .body()
1921 .iter_multicast_records()
1922 .map(|r| {
1923 (
1924 r.header().multicast_addr().clone(),
1925 r.header().record_type().unwrap(),
1926 r.sources().to_vec(),
1927 )
1928 })
1929 .collect::<Vec<_>>();
1930 assert_eq!(report, vec![(sent_report_addr.get(), sent_report_mode, sent_report_sources)]);
1931
1932 CounterExpectations { tx_mldv2_report: 1, ..Default::default() }
1933 .assert_counters(&mut core_ctx);
1934 }
1935
1936 #[test]
1937 fn v1_query_reject_bad_ipv6_source_addr() {
1938 let mut ctx = new_mldv1_context();
1939 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
1940
1941 let buffer = new_v1_query(Duration::from_secs(1), GROUP_ADDR).into_inner();
1942 for addr in
1943 [Ipv6SourceAddr::Unspecified, Ipv6SourceAddr::new(net_ip_v6!("2001::1")).unwrap()]
1944 {
1945 let mut buffer = &buffer[..];
1946 let packet = parse_mld_packet(&mut buffer);
1947 assert_eq!(
1948 receive_mld_packet(
1949 core_ctx,
1950 bindings_ctx,
1951 &FakeDeviceId,
1952 addr,
1953 packet,
1954 &DEFAULT_HEADER_INFO,
1955 ),
1956 Err(MldError::BadSourceAddress { addr: addr.into_addr() })
1957 );
1958 }
1959 CounterExpectations { rx_mldv1_query: 2, rx_err_bad_src_addr: 2, ..Default::default() }
1960 .assert_counters(core_ctx);
1961 }
1962
1963 #[test]
1964 fn v2_query_reject_bad_ipv6_source_addr() {
1965 let mut ctx = new_mldv1_context();
1966 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
1967
1968 let buffer = new_v2_general_query().into_inner();
1969 for addr in
1970 [Ipv6SourceAddr::Unspecified, Ipv6SourceAddr::new(net_ip_v6!("2001::1")).unwrap()]
1971 {
1972 let mut buffer = &buffer[..];
1973 let packet = parse_mld_packet(&mut buffer);
1974 assert_eq!(
1975 receive_mld_packet(
1976 core_ctx,
1977 bindings_ctx,
1978 &FakeDeviceId,
1979 addr,
1980 packet,
1981 &DEFAULT_HEADER_INFO,
1982 ),
1983 Err(MldError::BadSourceAddress { addr: addr.into_addr() })
1984 );
1985 }
1986
1987 CounterExpectations { rx_mldv2_query: 2, rx_err_bad_src_addr: 2, ..Default::default() }
1988 .assert_counters(core_ctx);
1989 }
1990
1991 #[test]
1992 fn v1_report_reject_bad_ipv6_source_addr() {
1993 let mut ctx = new_mldv1_context();
1994 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
1995
1996 assert_eq!(
1997 core_ctx.gmp_join_group(bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1998 GroupJoinResult::Joined(())
1999 );
2000
2001 let buffer = new_v1_report(GROUP_ADDR).into_inner();
2002 let addr = Ipv6SourceAddr::new(net_ip_v6!("2001::1")).unwrap();
2003 let mut buffer = &buffer[..];
2004 let packet = parse_mld_packet(&mut buffer);
2005 assert_eq!(
2006 receive_mld_packet(
2007 core_ctx,
2008 bindings_ctx,
2009 &FakeDeviceId,
2010 addr,
2011 packet,
2012 &DEFAULT_HEADER_INFO,
2013 ),
2014 Err(MldError::BadSourceAddress { addr: addr.into_addr() })
2015 );
2016
2017 let buffer = new_v1_report(GROUP_ADDR).into_inner();
2019 let addr = Ipv6SourceAddr::Unspecified;
2020 let mut buffer = &buffer[..];
2021 let packet = parse_mld_packet(&mut buffer);
2022 assert_eq!(
2023 receive_mld_packet(
2024 core_ctx,
2025 bindings_ctx,
2026 &FakeDeviceId,
2027 addr,
2028 packet,
2029 &DEFAULT_HEADER_INFO,
2030 ),
2031 Ok(())
2032 );
2033
2034 CounterExpectations {
2035 rx_mldv1_report: 2,
2036 rx_err_bad_src_addr: 1,
2037 tx_mldv1_report: 1,
2038 ..Default::default()
2039 }
2040 .assert_counters(core_ctx);
2041 }
2042
2043 #[test]
2044 fn reject_bad_hop_limit() {
2045 let mut ctx = new_mldv1_context();
2046 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2047 let src_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
2048 let src_addr: Ipv6SourceAddr = src_addr.try_into().unwrap();
2049
2050 let messages = [
2051 new_v1_query(Duration::from_secs(1), GROUP_ADDR).into_inner(),
2052 new_v2_general_query().into_inner(),
2053 new_v1_report(GROUP_ADDR).into_inner(),
2054 ];
2055 for buffer in messages {
2056 for hop_limit in [0, 2] {
2057 let header_info = FakeHeaderInfo { hop_limit, router_alert: true };
2058 let mut buffer = &buffer[..];
2059 let packet = parse_mld_packet(&mut buffer);
2060 assert_eq!(
2061 receive_mld_packet(
2062 core_ctx,
2063 bindings_ctx,
2064 &FakeDeviceId,
2065 src_addr,
2066 packet,
2067 &header_info,
2068 ),
2069 Err(MldError::BadHopLimit { hop_limit })
2070 );
2071 }
2072 }
2073 CounterExpectations { rx_err_bad_hop_limit: 6, ..Default::default() }
2074 .assert_counters(core_ctx);
2075 }
2076
2077 #[test]
2078 fn v2_query_reject_missing_router_alert() {
2079 let mut ctx = new_mldv1_context();
2080 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2081 let src_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
2082 let src_addr: Ipv6SourceAddr = src_addr.try_into().unwrap();
2083
2084 let buffer = new_v2_general_query().into_inner();
2085 let header_info = FakeHeaderInfo { hop_limit: MLD_IP_HOP_LIMIT, router_alert: false };
2086 let mut buffer = &buffer[..];
2087 let packet = parse_mld_packet(&mut buffer);
2088 assert_eq!(
2089 receive_mld_packet(
2090 core_ctx,
2091 bindings_ctx,
2092 &FakeDeviceId,
2093 src_addr,
2094 packet,
2095 &header_info,
2096 ),
2097 Err(MldError::MissingRouterAlert),
2098 );
2099 CounterExpectations {
2100 rx_mldv2_query: 1,
2101 rx_err_missing_router_alert: 1,
2102 ..Default::default()
2103 }
2104 .assert_counters(core_ctx);
2105 }
2106
2107 #[test]
2108 fn user_mode_change() {
2109 let mut ctx = new_mldv1_context();
2110 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2111 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V1);
2112 assert_eq!(
2113 core_ctx.gmp_join_group(bindings_ctx, &FakeDeviceId, GROUP_ADDR),
2114 GroupJoinResult::Joined(())
2115 );
2116 let _ = core_ctx.take_frames();
2118 assert_eq!(
2119 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, MldConfigMode::V2),
2120 MldConfigMode::V1
2121 );
2122 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V2);
2123 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V2));
2124 assert_eq!(core_ctx.take_frames(), Vec::new());
2126
2127 receive_mldv1_query(core_ctx, bindings_ctx, Duration::from_secs(0), GROUP_ADDR);
2130 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V1 { compat: true }));
2131 assert_eq!(core_ctx.take_frames().len(), 1);
2133 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V2);
2134
2135 assert_eq!(
2137 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, MldConfigMode::V2),
2138 MldConfigMode::V2
2139 );
2140 assert_eq!(core_ctx.take_frames(), Vec::new());
2141 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V1 { compat: true }));
2142
2143 assert_eq!(
2145 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, MldConfigMode::V1),
2146 MldConfigMode::V2
2147 );
2148 assert_eq!(core_ctx.take_frames(), Vec::new());
2149 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V1 { compat: false }));
2150 }
2151}