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 as filter;
25use packet::serialize::{PacketBuilder, Serializer};
26use packet::InnerPacketBuilder;
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 destination = IpPacketDestination::Multicast(dst_ip);
486 let ip_frame =
487 report.into_serializer().encapsulate(icmp.clone()).encapsulate(ipv6.clone());
488 IpLayerHandler::send_ip_frame(self, bindings_ctx, device, destination, ip_frame)
489 .unwrap_or_else(|ErrorAndSerializer { error, .. }| {
490 self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
491 debug!("failed to send MLDv2 report over {device:?}: {error:?}")
492 });
493 }
494 }
495
496 fn mode_update_from_v1_query<Q: gmp::v1::QueryMessage<Ipv6>>(
497 &mut self,
498 _bindings_ctx: &mut BC,
499 _query: &Q,
500 gmp_state: &GmpState<Ipv6, MldTypeLayout, BC>,
501 _config: &MldConfig,
502 ) -> MldMode {
503 let MldMode(gmp) = &gmp_state.mode;
504 MldMode(gmp.maybe_enter_v1_compat())
505 }
506
507 fn mode_to_config(MldMode(gmp_mode): &MldMode) -> MldConfigMode {
508 match gmp_mode {
509 GmpMode::V2 | GmpMode::V1 { compat: true } => MldConfigMode::V2,
510 GmpMode::V1 { compat: false } => MldConfigMode::V1,
511 }
512 }
513
514 fn config_to_mode(MldMode(cur_mode): &MldMode, config: MldConfigMode) -> MldMode {
515 MldMode(match config {
516 MldConfigMode::V1 => GmpMode::V1 { compat: false },
517 MldConfigMode::V2 => match cur_mode {
518 GmpMode::V1 { compat: true } => *cur_mode,
519 GmpMode::V1 { compat: false } | GmpMode::V2 => GmpMode::V2,
520 },
521 })
522 }
523
524 fn mode_on_disable(MldMode(cur_mode): &MldMode) -> MldMode {
525 MldMode(cur_mode.maybe_exit_v1_compat())
526 }
527
528 fn mode_on_exit_compat() -> MldMode {
529 MldMode(GmpMode::V2)
530 }
531}
532
533#[derive(Debug, Error, Eq, PartialEq)]
534pub(crate) enum MldError {
535 #[error("the host has not already been a member of the address: {}", addr)]
538 NotAMember { addr: Ipv6Addr },
539 #[error("failed to send out an MLD packet to address: {}", addr)]
541 SendFailure { addr: Ipv6Addr },
542 #[error("bad source address: {}", addr)]
544 BadSourceAddress { addr: Ipv6Addr },
545 #[error("router alert option not present")]
547 MissingRouterAlert,
548 #[error("message with incorrect hop limit: {hop_limit}")]
550 BadHopLimit { hop_limit: u8 },
551 #[error("MLD is disabled on interface")]
553 Disabled,
554}
555
556impl From<NotAMemberErr<Ipv6>> for MldError {
557 fn from(NotAMemberErr(addr): NotAMemberErr<Ipv6>) -> Self {
558 Self::NotAMember { addr }
559 }
560}
561
562impl From<gmp::v2::QueryError<Ipv6>> for MldError {
563 fn from(err: gmp::v2::QueryError<Ipv6>) -> Self {
564 match err {
565 gmp::v2::QueryError::NotAMember(addr) => Self::NotAMember { addr },
566 gmp::v2::QueryError::Disabled => Self::Disabled,
567 }
568 }
569}
570
571pub(crate) type MldResult<T> = Result<T, MldError>;
572
573#[derive(Debug)]
574pub struct MldConfig {
575 unsolicited_report_interval: Duration,
576 send_leave_anyway: bool,
577}
578
579pub const MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL: Duration = Duration::from_secs(10);
583
584impl Default for MldConfig {
585 fn default() -> Self {
586 MldConfig {
587 unsolicited_report_interval: MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL,
588 send_leave_anyway: false,
589 }
590 }
591}
592
593impl gmp::v1::ProtocolConfig for MldConfig {
594 fn unsolicited_report_interval(&self) -> Duration {
595 self.unsolicited_report_interval
596 }
597
598 fn send_leave_anyway(&self) -> bool {
599 self.send_leave_anyway
600 }
601
602 fn get_max_resp_time(&self, resp_time: Duration) -> Option<NonZeroDuration> {
603 NonZeroDuration::new(resp_time)
604 }
605}
606
607impl gmp::v2::ProtocolConfig for MldConfig {
608 fn query_response_interval(&self) -> NonZeroDuration {
609 gmp::v2::DEFAULT_QUERY_RESPONSE_INTERVAL
610 }
611
612 fn unsolicited_report_interval(&self) -> NonZeroDuration {
613 gmp::v2::DEFAULT_UNSOLICITED_REPORT_INTERVAL
614 }
615}
616
617#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
619pub struct MldTimerId<D: WeakDeviceIdentifier>(GmpTimerId<Ipv6, D>);
620
621impl<D: WeakDeviceIdentifier> MldTimerId<D> {
622 pub(crate) fn device_id(&self) -> &D {
623 let Self(this) = self;
624 this.device_id()
625 }
626
627 #[cfg(any(test, feature = "testutils"))]
629 pub fn new(device: D) -> Self {
630 Self(GmpTimerId { device, _marker: Default::default() })
631 }
632}
633
634impl<D: WeakDeviceIdentifier> From<GmpTimerId<Ipv6, D>> for MldTimerId<D> {
635 fn from(id: GmpTimerId<Ipv6, D>) -> MldTimerId<D> {
636 MldTimerId(id)
637 }
638}
639
640impl<BC: MldBindingsContext, CC: MldContext<BC>> HandleableTimer<CC, BC>
641 for MldTimerId<CC::WeakDeviceId>
642{
643 fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
644 let Self(id) = self;
645 gmp::handle_timer(core_ctx, bindings_ctx, id);
646 }
647}
648
649#[derive(Debug, Clone, Default)]
662struct MldIpOptions(bool);
663
664impl Iterator for MldIpOptions {
665 type Item = HopByHopOption<'static>;
666
667 fn next(&mut self) -> Option<Self::Item> {
668 let Self(yielded) = self;
669 if core::mem::replace(yielded, true) {
670 None
671 } else {
672 Some(HopByHopOption {
673 action: ExtensionHeaderOptionAction::SkipAndContinue,
674 mutable: false,
675 data: HopByHopOptionData::RouterAlert { data: 0 },
676 })
677 }
678 }
679}
680
681const MLD_IP_HOP_LIMIT: u8 = 1;
690
691fn new_ip_and_icmp_builders<
692 BC: MldBindingsContext,
693 CC: MldSendContext<BC>,
694 M: IcmpMessage<Ipv6, Code = IcmpSenderZeroCode> + filter::IcmpMessage<Ipv6>,
695>(
696 core_ctx: &mut CC,
697 device: &CC::DeviceId,
698 dst_ip: MulticastAddr<Ipv6Addr>,
699 msg: M,
700) -> (Ipv6PacketBuilderWithHbhOptions<'static, MldIpOptions>, IcmpPacketBuilder<Ipv6, M>) {
701 let src_ip =
709 core_ctx.get_ipv6_link_local_addr(device).map_or(Ipv6::UNSPECIFIED_ADDRESS, |x| x.get());
710
711 let ipv6 = Ipv6PacketBuilderWithHbhOptions::new(
712 Ipv6PacketBuilder::new(src_ip, dst_ip.get(), MLD_IP_HOP_LIMIT, Ipv6Proto::Icmpv6),
713 MldIpOptions::default(),
714 )
715 .unwrap();
716 let icmp = IcmpPacketBuilder::new(src_ip, dst_ip.get(), IcmpSenderZeroCode, msg);
717 (ipv6, icmp)
718}
719
720enum MldMessage {
723 ListenerReport { group_addr: <MulticastListenerReport as Mldv1MessageType>::GroupAddr },
724 ListenerDone { group_addr: <MulticastListenerDone as Mldv1MessageType>::GroupAddr },
725}
726
727fn send_mld_v1_packet<BC: MldBindingsContext, CC: MldSendContext<BC>>(
732 core_ctx: &mut CC,
733 bindings_ctx: &mut BC,
734 device: &CC::DeviceId,
735 dst_ip: MulticastAddr<Ipv6Addr>,
736 msg: MldMessage,
737) -> MldResult<()> {
738 macro_rules! send {
739 ($type:ty, $struct:expr, $group_addr:expr) => {{
740 let (ipv6, icmp) = new_ip_and_icmp_builders(core_ctx, device, dst_ip, $struct);
741
742 let body = Mldv1MessageBuilder::<$type>::new_with_max_resp_delay($group_addr, ())
743 .into_serializer()
744 .encapsulate(icmp)
745 .encapsulate(ipv6);
746
747 let destination = IpPacketDestination::Multicast(dst_ip);
748 IpLayerHandler::send_ip_frame(core_ctx, bindings_ctx, &device, destination, body)
749 .map_err(|_| MldError::SendFailure { addr: $group_addr.into() })
750 }};
751 }
752
753 match msg {
754 MldMessage::ListenerReport { group_addr } => {
755 send!(MulticastListenerReport, MulticastListenerReport, group_addr)
756 }
757 MldMessage::ListenerDone { group_addr } => {
758 send!(MulticastListenerDone, MulticastListenerDone, group_addr)
759 }
760 }
761}
762
763#[derive(Debug, Default)]
767#[cfg_attr(test, derive(PartialEq))]
768pub struct MldCounters<C = Counter> {
769 rx_mldv1_query: C,
771 rx_mldv2_query: C,
773 rx_mldv1_report: C,
775 rx_mldv2_report: C,
777 rx_leave_group: C,
779 rx_err_bad_src_addr: C,
781 rx_err_bad_hop_limit: C,
783 rx_err_missing_router_alert: C,
785 tx_mldv1_report: C,
787 tx_mldv2_report: C,
789 tx_leave_group: C,
791 tx_err: C,
793}
794
795impl Inspectable for MldCounters {
796 fn record<I: Inspector>(&self, inspector: &mut I) {
797 let Self {
798 rx_mldv1_query,
799 rx_mldv2_query,
800 rx_mldv1_report,
801 rx_mldv2_report,
802 rx_leave_group,
803 rx_err_bad_src_addr,
804 rx_err_bad_hop_limit,
805 rx_err_missing_router_alert,
806 tx_mldv1_report,
807 tx_mldv2_report,
808 tx_leave_group,
809 tx_err,
810 } = self;
811 inspector.record_child("Rx", |inspector| {
812 inspector.record_counter("MLDv1Query", rx_mldv1_query);
813 inspector.record_counter("MLDv2Query", rx_mldv2_query);
814 inspector.record_counter("MLDv1Report", rx_mldv1_report);
815 inspector.record_counter("MLDv2Report", rx_mldv2_report);
816 inspector.record_counter("LeaveGroup", rx_leave_group);
817 inspector.record_child("Errors", |inspector| {
818 inspector.record_counter("BadSourceAddress", rx_err_bad_src_addr);
819 inspector.record_counter("BadHopLimit", rx_err_bad_hop_limit);
820 inspector.record_counter("MissingRouterAlert", rx_err_missing_router_alert);
821 })
822 });
823 inspector.record_child("Tx", |inspector| {
824 inspector.record_counter("MLDv1Report", tx_mldv1_report);
825 inspector.record_counter("MLDv2Report", tx_mldv2_report);
826 inspector.record_counter("LeaveGroup", tx_leave_group);
827 inspector.record_child("Errors", |inspector| {
828 inspector.record_counter("SendFailed", tx_err);
829 });
830 });
831 }
832}
833
834#[cfg(test)]
835mod tests {
836 use alloc::rc::Rc;
837 use alloc::vec;
838 use alloc::vec::Vec;
839 use core::cell::RefCell;
840
841 use assert_matches::assert_matches;
842 use net_types::ethernet::Mac;
843 use net_types::ip::{Ip as _, IpVersionMarker, Mtu};
844 use netstack3_base::testutil::{
845 assert_empty, new_rng, run_with_many_seeds, FakeDeviceId, FakeInstant, FakeTimerCtxExt,
846 FakeWeakDeviceId, TestIpExt,
847 };
848 use netstack3_base::{
849 CounterContext, CtxPair, InstantContext as _, IntoCoreTimerCtx, SendFrameContext,
850 };
851 use packet::{Buf, BufferMut, ParseBuffer};
852 use packet_formats::gmp::GroupRecordType;
853 use packet_formats::icmp::mld::{
854 Mldv2QueryMessageBuilder, MulticastListenerQuery, MulticastListenerQueryV2,
855 };
856 use packet_formats::icmp::{IcmpParseArgs, Icmpv6MessageType, Icmpv6Packet};
857 use packet_formats::ip::IpPacket;
858 use packet_formats::ipv6::ext_hdrs::Ipv6ExtensionHeaderData;
859 use packet_formats::ipv6::Ipv6Packet;
860
861 use super::*;
862 use crate::internal::base::{IpPacketDestination, IpSendFrameError, SendIpPacketMeta};
863 use crate::internal::fragmentation::FragmentableIpSerializer;
864 use crate::internal::gmp::{
865 GmpEnabledGroup, GmpHandler as _, GmpState, GroupJoinResult, GroupLeaveResult,
866 };
867
868 #[derive(Debug, PartialEq)]
870 pub(crate) struct MldFrameMetadata<D> {
871 pub(crate) device: D,
872 pub(crate) dst_ip: MulticastAddr<Ipv6Addr>,
873 }
874
875 impl<D> MldFrameMetadata<D> {
876 fn new(device: D, dst_ip: MulticastAddr<Ipv6Addr>) -> MldFrameMetadata<D> {
877 MldFrameMetadata { device, dst_ip }
878 }
879 }
880
881 struct FakeMldCtx {
885 shared: Rc<RefCell<Shared>>,
886 mld_enabled: bool,
887 ipv6_link_local: Option<LinkLocalUnicastAddr<Ipv6Addr>>,
888 stack_wide_counters: MldCounters,
889 device_specific_counters: MldCounters,
890 }
891
892 impl FakeMldCtx {
893 fn gmp_state(&mut self) -> &mut GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl> {
894 &mut Rc::get_mut(&mut self.shared).unwrap().get_mut().gmp_state
895 }
896
897 fn groups(
898 &mut self,
899 ) -> &mut MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>> {
900 &mut Rc::get_mut(&mut self.shared).unwrap().get_mut().groups
901 }
902 }
903
904 impl CounterContext<MldCounters> for FakeMldCtx {
905 fn counters(&self) -> &MldCounters {
906 &self.stack_wide_counters
907 }
908 }
909
910 impl ResourceCounterContext<FakeDeviceId, MldCounters> for FakeMldCtx {
911 fn per_resource_counters<'a>(&'a self, _device_id: &'a FakeDeviceId) -> &'a MldCounters {
912 &self.device_specific_counters
913 }
914 }
915
916 struct Shared {
918 groups: MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>>,
919 gmp_state: GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
920 config: MldConfig,
921 }
922
923 fn new_mldv1_context() -> FakeCtxImpl {
928 FakeCtxImpl::with_default_bindings_ctx(|bindings_ctx| {
929 let mld_enabled = true;
931 FakeCoreCtxImpl::with_state(FakeMldCtx {
932 shared: Rc::new(RefCell::new(Shared {
933 groups: MulticastGroupSet::default(),
934 gmp_state: GmpState::new_with_enabled_and_mode::<_, IntoCoreTimerCtx>(
935 bindings_ctx,
936 FakeWeakDeviceId(FakeDeviceId),
937 mld_enabled,
938 MldMode(GmpMode::V1 { compat: false }),
939 ),
940 config: Default::default(),
941 })),
942 mld_enabled,
943 ipv6_link_local: None,
944 stack_wide_counters: Default::default(),
945 device_specific_counters: Default::default(),
946 })
947 })
948 }
949
950 type FakeCtxImpl = CtxPair<FakeCoreCtxImpl, FakeBindingsCtxImpl>;
951 type FakeCoreCtxImpl = netstack3_base::testutil::FakeCoreCtx<
952 FakeMldCtx,
953 MldFrameMetadata<FakeDeviceId>,
954 FakeDeviceId,
955 >;
956 type FakeBindingsCtxImpl = netstack3_base::testutil::FakeBindingsCtx<
957 MldTimerId<FakeWeakDeviceId<FakeDeviceId>>,
958 (),
959 (),
960 (),
961 >;
962
963 impl MldContextMarker for FakeCoreCtxImpl {}
964 impl MldContextMarker for &'_ mut FakeCoreCtxImpl {}
965
966 impl MldStateContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
967 fn with_mld_state<
968 O,
969 F: FnOnce(
970 &MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>>,
971 &GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
972 ) -> O,
973 >(
974 &mut self,
975 &FakeDeviceId: &FakeDeviceId,
976 cb: F,
977 ) -> O {
978 let state = self.state.shared.borrow();
979 cb(&state.groups, &state.gmp_state)
980 }
981 }
982
983 impl MldContext<FakeBindingsCtxImpl> for FakeCoreCtxImpl {
984 type SendContext<'a> = &'a mut Self;
985 fn with_mld_state_mut<
986 O,
987 F: FnOnce(
988 Self::SendContext<'_>,
989 GmpStateRef<'_, Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
990 ) -> O,
991 >(
992 &mut self,
993 &FakeDeviceId: &FakeDeviceId,
994 cb: F,
995 ) -> O {
996 let FakeMldCtx { mld_enabled, shared, .. } = &mut self.state;
997 let enabled = *mld_enabled;
998 let shared = Rc::clone(shared);
999 let mut shared = shared.borrow_mut();
1000 let Shared { gmp_state, groups, config } = &mut *shared;
1001 cb(self, GmpStateRef { enabled, groups, gmp: gmp_state, config })
1002 }
1003 }
1004
1005 impl IpDeviceMtuContext<Ipv6> for &mut FakeCoreCtxImpl {
1006 fn get_mtu(&mut self, _device: &FakeDeviceId) -> Mtu {
1007 Ipv6::MINIMUM_LINK_MTU
1008 }
1009 }
1010
1011 impl MldSendContext<FakeBindingsCtxImpl> for &mut FakeCoreCtxImpl {
1012 fn get_ipv6_link_local_addr(
1013 &mut self,
1014 _device: &FakeDeviceId,
1015 ) -> Option<LinkLocalUnicastAddr<Ipv6Addr>> {
1016 self.state.ipv6_link_local
1017 }
1018 }
1019
1020 impl IpLayerHandler<Ipv6, FakeBindingsCtxImpl> for &mut FakeCoreCtxImpl {
1021 fn send_ip_packet_from_device<S>(
1022 &mut self,
1023 _bindings_ctx: &mut FakeBindingsCtxImpl,
1024 _meta: SendIpPacketMeta<
1025 Ipv6,
1026 &Self::DeviceId,
1027 Option<SpecifiedAddr<<Ipv6 as Ip>::Addr>>,
1028 >,
1029 _body: S,
1030 ) -> Result<(), IpSendFrameError<S>>
1031 where
1032 S: Serializer,
1033 S::Buffer: BufferMut,
1034 {
1035 unimplemented!();
1036 }
1037
1038 fn send_ip_frame<S>(
1039 &mut self,
1040 bindings_ctx: &mut FakeBindingsCtxImpl,
1041 device: &Self::DeviceId,
1042 destination: IpPacketDestination<Ipv6, &Self::DeviceId>,
1043 body: S,
1044 ) -> Result<(), IpSendFrameError<S>>
1045 where
1046 S: FragmentableIpSerializer<Ipv6, Buffer: BufferMut> + netstack3_filter::IpPacket<Ipv6>,
1047 {
1048 let addr = match destination {
1049 IpPacketDestination::Multicast(addr) => addr,
1050 _ => panic!("destination is not multicast: {:?}", destination),
1051 };
1052 (*self)
1053 .send_frame(bindings_ctx, MldFrameMetadata::new(device.clone(), addr), body)
1054 .map_err(|e| e.err_into())
1055 }
1056 }
1057
1058 impl CounterContext<MldCounters> for &mut FakeCoreCtxImpl {
1059 fn counters(&self) -> &MldCounters {
1060 <FakeCoreCtxImpl as CounterContext<MldCounters>>::counters(self)
1061 }
1062 }
1063
1064 impl ResourceCounterContext<FakeDeviceId, MldCounters> for &mut FakeCoreCtxImpl {
1065 fn per_resource_counters<'a>(&'a self, device_id: &'a FakeDeviceId) -> &'a MldCounters {
1066 <
1067 FakeCoreCtxImpl as ResourceCounterContext<FakeDeviceId, MldCounters>
1068 >::per_resource_counters(self, device_id)
1069 }
1070 }
1071
1072 type CounterExpectations = MldCounters<u64>;
1073
1074 impl CounterExpectations {
1075 #[track_caller]
1076 fn assert_counters<CC: ResourceCounterContext<FakeDeviceId, MldCounters>>(
1077 &self,
1078 core_ctx: &mut CC,
1079 ) {
1080 assert_eq!(
1081 self,
1082 &CounterExpectations::from(core_ctx.counters()),
1083 "stack-wide counter mismatch"
1084 );
1085 assert_eq!(
1086 self,
1087 &CounterExpectations::from(core_ctx.per_resource_counters(&FakeDeviceId)),
1088 "device-specific counter mismatch"
1089 );
1090 }
1091 }
1092
1093 impl From<&MldCounters> for CounterExpectations {
1094 fn from(mld_counters: &MldCounters) -> CounterExpectations {
1095 let MldCounters {
1096 rx_mldv1_query,
1097 rx_mldv2_query,
1098 rx_mldv1_report,
1099 rx_mldv2_report,
1100 rx_leave_group,
1101 rx_err_missing_router_alert,
1102 rx_err_bad_src_addr,
1103 rx_err_bad_hop_limit,
1104 tx_mldv1_report,
1105 tx_mldv2_report,
1106 tx_leave_group,
1107 tx_err,
1108 } = mld_counters;
1109 CounterExpectations {
1110 rx_mldv1_query: rx_mldv1_query.get(),
1111 rx_mldv2_query: rx_mldv2_query.get(),
1112 rx_mldv1_report: rx_mldv1_report.get(),
1113 rx_mldv2_report: rx_mldv2_report.get(),
1114 rx_leave_group: rx_leave_group.get(),
1115 rx_err_missing_router_alert: rx_err_missing_router_alert.get(),
1116 rx_err_bad_src_addr: rx_err_bad_src_addr.get(),
1117 rx_err_bad_hop_limit: rx_err_bad_hop_limit.get(),
1118 tx_mldv1_report: tx_mldv1_report.get(),
1119 tx_mldv2_report: tx_mldv2_report.get(),
1120 tx_leave_group: tx_leave_group.get(),
1121 tx_err: tx_err.get(),
1122 }
1123 }
1124 }
1125
1126 #[test]
1127 fn test_mld_immediate_report() {
1128 run_with_many_seeds(|seed| {
1129 let mut rng = new_rng(seed);
1136 let cfg = MldConfig::default();
1137 let (mut s, _actions) =
1138 gmp::v1::GmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
1139 assert_eq!(
1140 s.query_received(&mut rng, Duration::from_secs(0), FakeInstant::default(), &cfg),
1141 gmp::v1::QueryReceivedActions::StopTimerAndSendReport,
1142 );
1143 });
1144 }
1145
1146 const MY_IP: SpecifiedAddr<Ipv6Addr> = unsafe {
1147 SpecifiedAddr::new_unchecked(Ipv6Addr::from_bytes([
1148 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 168, 0, 3,
1149 ]))
1150 };
1151 const MY_MAC: Mac = Mac::new([1, 2, 3, 4, 5, 6]);
1152 const ROUTER_MAC: Mac = Mac::new([6, 5, 4, 3, 2, 1]);
1153 const GROUP_ADDR: MulticastAddr<Ipv6Addr> = <Ipv6 as gmp::testutil::TestIpExt>::GROUP_ADDR1;
1154 const TIMER_ID: MldTimerId<FakeWeakDeviceId<FakeDeviceId>> = MldTimerId(GmpTimerId {
1155 device: FakeWeakDeviceId(FakeDeviceId),
1156 _marker: IpVersionMarker::new(),
1157 });
1158
1159 struct FakeHeaderInfo {
1160 hop_limit: u8,
1161 router_alert: bool,
1162 }
1163
1164 impl IpHeaderInfo<Ipv6> for FakeHeaderInfo {
1165 fn dscp_and_ecn(&self) -> packet_formats::ip::DscpAndEcn {
1166 unimplemented!()
1167 }
1168 fn hop_limit(&self) -> u8 {
1169 self.hop_limit
1170 }
1171 fn router_alert(&self) -> bool {
1172 self.router_alert
1173 }
1174 }
1175
1176 const DEFAULT_HEADER_INFO: FakeHeaderInfo =
1177 FakeHeaderInfo { hop_limit: MLD_IP_HOP_LIMIT, router_alert: true };
1178
1179 fn new_v1_query(resp_time: Duration, group_addr: MulticastAddr<Ipv6Addr>) -> Buf<Vec<u8>> {
1180 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1181 Mldv1MessageBuilder::<MulticastListenerQuery>::new_with_max_resp_delay(
1182 group_addr.get(),
1183 resp_time.try_into().unwrap(),
1184 )
1185 .into_serializer()
1186 .encapsulate(IcmpPacketBuilder::<_, _>::new(
1187 router_addr,
1188 MY_IP,
1189 IcmpSenderZeroCode,
1190 MulticastListenerQuery,
1191 ))
1192 .serialize_vec_outer()
1193 .unwrap()
1194 .unwrap_b()
1195 }
1196
1197 fn new_v1_report(group_addr: MulticastAddr<Ipv6Addr>) -> Buf<Vec<u8>> {
1198 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1199 Mldv1MessageBuilder::<MulticastListenerReport>::new(group_addr)
1200 .into_serializer()
1201 .encapsulate(IcmpPacketBuilder::<_, _>::new(
1202 router_addr,
1203 MY_IP,
1204 IcmpSenderZeroCode,
1205 MulticastListenerReport,
1206 ))
1207 .serialize_vec_outer()
1208 .unwrap()
1209 .unwrap_b()
1210 }
1211
1212 fn new_v2_general_query() -> Buf<Vec<u8>> {
1213 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1214 Mldv2QueryMessageBuilder::new(
1215 Default::default(),
1216 None,
1217 false,
1218 Default::default(),
1219 Default::default(),
1220 core::iter::empty::<Ipv6Addr>(),
1221 )
1222 .into_serializer()
1223 .encapsulate(IcmpPacketBuilder::<_, _>::new(
1224 router_addr,
1225 MY_IP,
1226 IcmpSenderZeroCode,
1227 MulticastListenerQueryV2,
1228 ))
1229 .serialize_vec_outer()
1230 .unwrap()
1231 .unwrap_b()
1232 }
1233
1234 fn parse_mld_packet<B: ParseBuffer>(buffer: &mut B) -> MldPacket<&[u8]> {
1235 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1236 match buffer
1237 .parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(router_addr, MY_IP))
1238 .unwrap()
1239 {
1240 Icmpv6Packet::Mld(packet) => packet,
1241 _ => panic!("serialized icmpv6 message is not an mld message"),
1242 }
1243 }
1244
1245 fn receive_mldv1_query(
1246 core_ctx: &mut FakeCoreCtxImpl,
1247 bindings_ctx: &mut FakeBindingsCtxImpl,
1248 resp_time: Duration,
1249 group_addr: MulticastAddr<Ipv6Addr>,
1250 ) {
1251 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1252 let mut buffer = new_v1_query(resp_time, group_addr);
1253 let packet = parse_mld_packet(&mut buffer);
1254 core_ctx.receive_mld_packet(
1255 bindings_ctx,
1256 &FakeDeviceId,
1257 router_addr.try_into().unwrap(),
1258 MY_IP,
1259 packet,
1260 &DEFAULT_HEADER_INFO,
1261 )
1262 }
1263
1264 fn receive_mldv1_report(
1265 core_ctx: &mut FakeCoreCtxImpl,
1266 bindings_ctx: &mut FakeBindingsCtxImpl,
1267 group_addr: MulticastAddr<Ipv6Addr>,
1268 ) {
1269 let router_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
1270 let mut buffer = new_v1_report(group_addr);
1271 let packet = parse_mld_packet(&mut buffer);
1272 core_ctx.receive_mld_packet(
1273 bindings_ctx,
1274 &FakeDeviceId,
1275 router_addr.try_into().unwrap(),
1276 MY_IP,
1277 packet,
1278 &DEFAULT_HEADER_INFO,
1279 )
1280 }
1281
1282 fn ensure_ttl(frame: &[u8]) {
1284 assert_eq!(frame[7], MLD_IP_HOP_LIMIT);
1285 }
1286
1287 fn ensure_slice_addr(frame: &[u8], start: usize, end: usize, ip: Ipv6Addr) {
1288 let mut bytes = [0u8; 16];
1289 bytes.copy_from_slice(&frame[start..end]);
1290 assert_eq!(Ipv6Addr::from_bytes(bytes), ip);
1291 }
1292
1293 fn ensure_dst_addr(frame: &[u8], ip: Ipv6Addr) {
1295 ensure_slice_addr(frame, 24, 40, ip);
1296 }
1297
1298 fn ensure_multicast_addr(frame: &[u8], ip: Ipv6Addr) {
1300 ensure_slice_addr(frame, 56, 72, ip);
1301 }
1302
1303 fn ensure_frame(
1305 frame: &[u8],
1306 op: u8,
1307 dst: MulticastAddr<Ipv6Addr>,
1308 multicast: MulticastAddr<Ipv6Addr>,
1309 ) {
1310 ensure_ttl(frame);
1311 assert_eq!(frame[48], op);
1312 assert_eq!(frame[5], 32);
1314 assert_eq!(frame[6], 0);
1316 assert_eq!(&frame[40..48], &[58, 0, 5, 2, 0, 0, 1, 0]);
1318 ensure_ttl(&frame[..]);
1319 ensure_dst_addr(&frame[..], dst.get());
1320 ensure_multicast_addr(&frame[..], multicast.get());
1321 }
1322
1323 #[test]
1324 fn test_mld_simple_integration() {
1325 run_with_many_seeds(|seed| {
1326 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1327 bindings_ctx.seed_rng(seed);
1328
1329 assert_eq!(
1330 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1331 GroupJoinResult::Joined(())
1332 );
1333
1334 receive_mldv1_query(
1335 &mut core_ctx,
1336 &mut bindings_ctx,
1337 Duration::from_secs(10),
1338 GROUP_ADDR,
1339 );
1340 core_ctx
1341 .state
1342 .gmp_state()
1343 .timers
1344 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1345 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1346
1347 assert_eq!(core_ctx.frames().len(), 2);
1351 for (_, frame) in core_ctx.frames() {
1353 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1354 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1355 }
1356
1357 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 2, ..Default::default() }
1358 .assert_counters(&mut core_ctx);
1359 });
1360 }
1361
1362 #[test]
1363 fn test_mld_immediate_query() {
1364 run_with_many_seeds(|seed| {
1365 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1366 bindings_ctx.seed_rng(seed);
1367
1368 assert_eq!(
1369 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1370 GroupJoinResult::Joined(())
1371 );
1372 assert_eq!(core_ctx.frames().len(), 1);
1373
1374 receive_mldv1_query(
1375 &mut core_ctx,
1376 &mut bindings_ctx,
1377 Duration::from_secs(0),
1378 GROUP_ADDR,
1379 );
1380 assert_eq!(core_ctx.frames().len(), 2);
1382 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1384 for (_, frame) in core_ctx.frames() {
1386 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1387 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1388 }
1389
1390 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 2, ..Default::default() }
1391 .assert_counters(&mut core_ctx);
1392 });
1393 }
1394
1395 #[test]
1396 fn test_mld_integration_fallback_from_idle() {
1397 run_with_many_seeds(|seed| {
1398 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1399 bindings_ctx.seed_rng(seed);
1400
1401 assert_eq!(
1402 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1403 GroupJoinResult::Joined(())
1404 );
1405 assert_eq!(core_ctx.frames().len(), 1);
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(), 2);
1414
1415 receive_mldv1_query(
1416 &mut core_ctx,
1417 &mut bindings_ctx,
1418 Duration::from_secs(10),
1419 GROUP_ADDR,
1420 );
1421
1422 let group_state = core_ctx.state.groups().get(&GROUP_ADDR).unwrap();
1425 match group_state.v1().get_inner() {
1426 gmp::v1::MemberState::Delaying(_) => {}
1427 _ => panic!("Wrong State!"),
1428 }
1429
1430 core_ctx
1431 .state
1432 .gmp_state()
1433 .timers
1434 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1435 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1436 assert_eq!(core_ctx.frames().len(), 3);
1437 for (_, frame) in core_ctx.frames() {
1439 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1440 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1441 }
1442
1443 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 3, ..Default::default() }
1444 .assert_counters(&mut core_ctx);
1445 });
1446 }
1447
1448 #[test]
1449 fn test_mld_integration_immediate_query_wont_fallback() {
1450 run_with_many_seeds(|seed| {
1451 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1452 bindings_ctx.seed_rng(seed);
1453
1454 assert_eq!(
1455 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1456 GroupJoinResult::Joined(())
1457 );
1458 assert_eq!(core_ctx.frames().len(), 1);
1459
1460 core_ctx
1461 .state
1462 .gmp_state()
1463 .timers
1464 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1465 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1466 assert_eq!(core_ctx.frames().len(), 2);
1467
1468 receive_mldv1_query(
1469 &mut core_ctx,
1470 &mut bindings_ctx,
1471 Duration::from_secs(0),
1472 GROUP_ADDR,
1473 );
1474
1475 let group_state = core_ctx.state.groups().get(&GROUP_ADDR).unwrap();
1478 match group_state.v1().get_inner() {
1479 gmp::v1::MemberState::Idle(_) => {}
1480 _ => panic!("Wrong State!"),
1481 }
1482
1483 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1485 assert_eq!(core_ctx.frames().len(), 3);
1486 for (_, frame) in core_ctx.frames() {
1488 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1489 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1490 }
1491
1492 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 3, ..Default::default() }
1493 .assert_counters(&mut core_ctx);
1494 });
1495 }
1496
1497 #[test]
1498 fn test_mld_integration_delay_reset_timer() {
1499 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1500 bindings_ctx.seed_rng(123456);
1503 assert_eq!(
1504 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1505 GroupJoinResult::Joined(())
1506 );
1507
1508 core_ctx.state.gmp_state().timers.assert_timers([(
1509 gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1510 (),
1511 FakeInstant::from(Duration::from_micros(590_354)),
1512 )]);
1513 let instant1 = bindings_ctx.timers.timers()[0].0.clone();
1514 let start = bindings_ctx.now();
1515 let duration = instant1 - start;
1516
1517 receive_mldv1_query(&mut core_ctx, &mut bindings_ctx, duration, GROUP_ADDR);
1518 assert_eq!(core_ctx.frames().len(), 1);
1519 core_ctx.state.gmp_state().timers.assert_timers([(
1520 gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1521 (),
1522 FakeInstant::from(Duration::from_micros(34_751)),
1523 )]);
1524 let instant2 = bindings_ctx.timers.timers()[0].0.clone();
1525 assert!(instant2 <= instant1);
1527 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1528 assert!(bindings_ctx.now() - start <= duration);
1529 assert_eq!(core_ctx.frames().len(), 2);
1530 for (_, frame) in core_ctx.frames() {
1532 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1533 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1534 }
1535
1536 CounterExpectations { rx_mldv1_query: 1, tx_mldv1_report: 2, ..Default::default() }
1537 .assert_counters(&mut core_ctx);
1538 }
1539
1540 #[test]
1541 fn test_mld_integration_last_send_leave() {
1542 run_with_many_seeds(|seed| {
1543 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1544 bindings_ctx.seed_rng(seed);
1545
1546 assert_eq!(
1547 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1548 GroupJoinResult::Joined(())
1549 );
1550 let now = bindings_ctx.now();
1551
1552 core_ctx.state.gmp_state().timers.assert_range([(
1553 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1554 now..=(now + MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1555 )]);
1556 assert_eq!(core_ctx.frames().len(), 1);
1558 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1559 assert_eq!(core_ctx.frames().len(), 2);
1561 assert_eq!(
1562 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1563 GroupLeaveResult::Left(())
1564 );
1565 assert_eq!(core_ctx.frames().len(), 3);
1567 ensure_frame(&core_ctx.frames()[0].1, 131, GROUP_ADDR, GROUP_ADDR);
1569 ensure_slice_addr(&core_ctx.frames()[0].1, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1570 ensure_frame(&core_ctx.frames()[1].1, 131, GROUP_ADDR, GROUP_ADDR);
1571 ensure_slice_addr(&core_ctx.frames()[1].1, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1572 ensure_frame(
1575 &core_ctx.frames()[2].1,
1576 132,
1577 Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
1578 GROUP_ADDR,
1579 );
1580 ensure_slice_addr(&core_ctx.frames()[2].1, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1581
1582 CounterExpectations { tx_mldv1_report: 2, tx_leave_group: 1, ..Default::default() }
1583 .assert_counters(&mut core_ctx);
1584 });
1585 }
1586
1587 #[test]
1588 fn test_mld_integration_not_last_does_not_send_leave() {
1589 run_with_many_seeds(|seed| {
1590 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1591 bindings_ctx.seed_rng(seed);
1592
1593 assert_eq!(
1594 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1595 GroupJoinResult::Joined(())
1596 );
1597 let now = bindings_ctx.now();
1598 core_ctx.state.gmp_state().timers.assert_range([(
1599 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1600 now..=(now + MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL),
1601 )]);
1602 assert_eq!(core_ctx.frames().len(), 1);
1603 receive_mldv1_report(&mut core_ctx, &mut bindings_ctx, GROUP_ADDR);
1604 bindings_ctx.timers.assert_no_timers_installed();
1605 assert_eq!(core_ctx.frames().len(), 1);
1608 assert_eq!(
1609 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1610 GroupLeaveResult::Left(())
1611 );
1612 assert_eq!(core_ctx.frames().len(), 1);
1614 for (_, frame) in core_ctx.frames() {
1616 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1617 ensure_slice_addr(&frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1618 }
1619
1620 CounterExpectations { rx_mldv1_report: 1, tx_mldv1_report: 1, ..Default::default() }
1621 .assert_counters(&mut core_ctx);
1622 });
1623 }
1624
1625 #[test]
1626 fn test_mld_with_link_local() {
1627 run_with_many_seeds(|seed| {
1628 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1629 bindings_ctx.seed_rng(seed);
1630
1631 core_ctx.state.ipv6_link_local = Some(MY_MAC.to_ipv6_link_local().addr());
1632 assert_eq!(
1633 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1634 GroupJoinResult::Joined(())
1635 );
1636 core_ctx
1637 .state
1638 .gmp_state()
1639 .timers
1640 .assert_top(&gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(), &());
1641 assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1642 for (_, frame) in core_ctx.frames() {
1643 ensure_frame(&frame, 131, GROUP_ADDR, GROUP_ADDR);
1644 ensure_slice_addr(&frame, 8, 24, MY_MAC.to_ipv6_link_local().addr().get());
1645 }
1646 });
1647 }
1648
1649 #[test]
1650 fn test_skip_mld() {
1651 run_with_many_seeds(|seed| {
1652 let test = |FakeCtxImpl { mut core_ctx, mut bindings_ctx }, group| {
1655 core_ctx.state.ipv6_link_local = Some(MY_MAC.to_ipv6_link_local().addr());
1656
1657 let assert_no_effect =
1659 |core_ctx: &FakeCoreCtxImpl, bindings_ctx: &FakeBindingsCtxImpl| {
1660 bindings_ctx.timers.assert_no_timers_installed();
1661 assert_empty(core_ctx.frames());
1662 };
1663
1664 assert_eq!(
1665 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, group),
1666 GroupJoinResult::Joined(())
1667 );
1668 assert_gmp_state!(core_ctx, &group, NonMember);
1671 assert_no_effect(&core_ctx, &bindings_ctx);
1672
1673 receive_mldv1_report(&mut core_ctx, &mut bindings_ctx, group);
1674 assert_gmp_state!(core_ctx, &group, NonMember);
1676 assert_no_effect(&core_ctx, &bindings_ctx);
1677
1678 receive_mldv1_query(
1679 &mut core_ctx,
1680 &mut bindings_ctx,
1681 Duration::from_secs(10),
1682 group,
1683 );
1684 assert_gmp_state!(core_ctx, &group, NonMember);
1686 assert_no_effect(&core_ctx, &bindings_ctx);
1687
1688 assert_eq!(
1689 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, group),
1690 GroupLeaveResult::Left(())
1691 );
1692 assert!(core_ctx.state.groups().get(&group).is_none());
1694 assert_no_effect(&core_ctx, &bindings_ctx);
1695
1696 CounterExpectations { rx_mldv1_report: 1, rx_mldv1_query: 1, ..Default::default() }
1697 .assert_counters(&mut core_ctx);
1698 };
1699
1700 let new_ctx = || {
1701 let mut ctx = new_mldv1_context();
1702 ctx.bindings_ctx.seed_rng(seed);
1703 ctx
1704 };
1705
1706 test(new_ctx(), Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS);
1709 let mut bytes = Ipv6::MULTICAST_SUBNET.network().ipv6_bytes();
1710 bytes[1] = bytes[1] & 0xF0;
1712 let reserved0 = MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap();
1713 bytes[1] = (bytes[1] & 0xF0) | 1;
1715 let iface_local = MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap();
1716 test(new_ctx(), reserved0);
1717 test(new_ctx(), iface_local);
1718
1719 let mut ctx = new_ctx();
1722 ctx.core_ctx.state.mld_enabled = false;
1723 ctx.core_ctx.gmp_handle_disabled(&mut ctx.bindings_ctx, &FakeDeviceId);
1724 test(ctx, GROUP_ADDR);
1725 });
1726 }
1727
1728 #[test]
1729 fn test_mld_integration_with_local_join_leave() {
1730 run_with_many_seeds(|seed| {
1731 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1734 bindings_ctx.seed_rng(seed);
1735
1736 assert_eq!(
1737 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1738 GroupJoinResult::Joined(())
1739 );
1740 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1741 assert_eq!(core_ctx.frames().len(), 1);
1742 let now = bindings_ctx.now();
1743 let range = now..=(now + MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL);
1744
1745 core_ctx.state.gmp_state().timers.assert_range([(
1746 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1747 range.clone(),
1748 )]);
1749 let frame = &core_ctx.frames().last().unwrap().1;
1750 ensure_frame(frame, 131, GROUP_ADDR, GROUP_ADDR);
1751 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1752
1753 assert_eq!(
1754 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1755 GroupJoinResult::AlreadyMember
1756 );
1757 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1758 assert_eq!(core_ctx.frames().len(), 1);
1759 core_ctx.state.gmp_state().timers.assert_range([(
1760 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1761 range.clone(),
1762 )]);
1763
1764 assert_eq!(
1765 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1766 GroupLeaveResult::StillMember
1767 );
1768 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1769 assert_eq!(core_ctx.frames().len(), 1);
1770
1771 core_ctx.state.gmp_state().timers.assert_range([(
1772 &gmp::v1::DelayedReportTimerId::new_multicast(GROUP_ADDR).into(),
1773 range,
1774 )]);
1775
1776 assert_eq!(
1777 core_ctx.gmp_leave_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1778 GroupLeaveResult::Left(())
1779 );
1780 assert_eq!(core_ctx.frames().len(), 2);
1781 bindings_ctx.timers.assert_no_timers_installed();
1782 let frame = &core_ctx.frames().last().unwrap().1;
1783 ensure_frame(frame, 132, Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS, GROUP_ADDR);
1784 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1785
1786 CounterExpectations { tx_mldv1_report: 1, tx_leave_group: 1, ..Default::default() }
1787 .assert_counters(&mut core_ctx);
1788 });
1789 }
1790
1791 #[test]
1792 fn test_mld_enable_disable() {
1793 run_with_many_seeds(|seed| {
1794 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1795 bindings_ctx.seed_rng(seed);
1796 assert_eq!(core_ctx.take_frames(), []);
1797
1798 assert_eq!(
1806 core_ctx.gmp_join_group(
1807 &mut bindings_ctx,
1808 &FakeDeviceId,
1809 Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS
1810 ),
1811 GroupJoinResult::Joined(())
1812 );
1813 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1814 assert_eq!(
1815 core_ctx.gmp_join_group(&mut bindings_ctx, &FakeDeviceId, GROUP_ADDR),
1816 GroupJoinResult::Joined(())
1817 );
1818 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1819 {
1820 let frames = core_ctx.take_frames();
1821 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1822 assert_matches!(&frames[..], [x] => x);
1823 assert_eq!(dst_ip, &GROUP_ADDR);
1824 ensure_frame(
1825 frame,
1826 Icmpv6MessageType::MulticastListenerReport.into(),
1827 GROUP_ADDR,
1828 GROUP_ADDR,
1829 );
1830 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1831 }
1832
1833 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1835 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1836 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1837 assert_eq!(core_ctx.take_frames(), []);
1838
1839 core_ctx.state.mld_enabled = false;
1841 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1842 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1843 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1844 {
1845 let frames = core_ctx.take_frames();
1846 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1847 assert_matches!(&frames[..], [x] => x);
1848 assert_eq!(dst_ip, &Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS);
1849 ensure_frame(
1850 frame,
1851 Icmpv6MessageType::MulticastListenerDone.into(),
1852 Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
1853 GROUP_ADDR,
1854 );
1855 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1856 }
1857
1858 core_ctx.gmp_handle_disabled(&mut bindings_ctx, &FakeDeviceId);
1860 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1861 assert_gmp_state!(core_ctx, &GROUP_ADDR, NonMember);
1862 assert_eq!(core_ctx.take_frames(), []);
1863
1864 core_ctx.state.mld_enabled = true;
1866 core_ctx.gmp_handle_maybe_enabled(&mut bindings_ctx, &FakeDeviceId);
1867 assert_gmp_state!(core_ctx, &Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS, NonMember);
1868 assert_gmp_state!(core_ctx, &GROUP_ADDR, Delaying);
1869 let frames = core_ctx.take_frames();
1870 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1871 assert_matches!(&frames[..], [x] => x);
1872 assert_eq!(dst_ip, &GROUP_ADDR);
1873 ensure_frame(
1874 frame,
1875 Icmpv6MessageType::MulticastListenerReport.into(),
1876 GROUP_ADDR,
1877 GROUP_ADDR,
1878 );
1879 ensure_slice_addr(frame, 8, 24, Ipv6::UNSPECIFIED_ADDRESS);
1880
1881 CounterExpectations { tx_mldv1_report: 2, tx_leave_group: 1, ..Default::default() }
1882 .assert_counters(&mut core_ctx);
1883 });
1884 }
1885
1886 #[test]
1888 fn send_gmpv2_report() {
1889 let FakeCtxImpl { mut core_ctx, mut bindings_ctx } = new_mldv1_context();
1890 let sent_report_addr = Ipv6::get_multicast_addr(130);
1891 let sent_report_mode = GroupRecordType::ModeIsExclude;
1892 let sent_report_sources = Vec::<Ipv6Addr>::new();
1893 (&mut core_ctx).send_report_v2(
1894 &mut bindings_ctx,
1895 &FakeDeviceId,
1896 [gmp::v2::GroupRecord::new_with_sources(
1897 GmpEnabledGroup::new(sent_report_addr).unwrap(),
1898 sent_report_mode,
1899 sent_report_sources.iter(),
1900 )]
1901 .into_iter(),
1902 );
1903 let frames = core_ctx.take_frames();
1904 let (MldFrameMetadata { device: FakeDeviceId, dst_ip }, frame) =
1905 assert_matches!(&frames[..], [x] => x);
1906 assert_eq!(dst_ip, &ALL_MLDV2_CAPABLE_ROUTERS);
1907 let mut buff = &frame[..];
1908 let ipv6 = buff.parse::<Ipv6Packet<_>>().expect("parse IPv6");
1909 assert_eq!(ipv6.ttl(), MLD_IP_HOP_LIMIT);
1910 assert_eq!(ipv6.src_ip(), Ipv6::UNSPECIFIED_ADDRESS);
1911 assert_eq!(ipv6.dst_ip(), ALL_MLDV2_CAPABLE_ROUTERS.get());
1912 assert_eq!(ipv6.proto(), Ipv6Proto::Icmpv6);
1913 assert_eq!(
1914 ipv6.iter_extension_hdrs()
1915 .map(|h| {
1916 let options = assert_matches!(
1917 h.data(),
1918 Ipv6ExtensionHeaderData::HopByHopOptions { options } => options
1919 );
1920 assert_eq!(
1921 options
1922 .iter()
1923 .map(|o| {
1924 assert_matches!(
1925 o.data,
1926 HopByHopOptionData::RouterAlert { data: 0 }
1927 );
1928 })
1929 .count(),
1930 1
1931 );
1932 })
1933 .count(),
1934 1
1935 );
1936 let args = IcmpParseArgs::new(ipv6.src_ip(), ipv6.dst_ip());
1937 let icmp = buff.parse_with::<_, Icmpv6Packet<_>>(args).expect("parse ICMPv6");
1938 let report = assert_matches!(
1939 icmp,
1940 Icmpv6Packet::Mld(MldPacket::MulticastListenerReportV2(report)) => report
1941 );
1942 let report = report
1943 .body()
1944 .iter_multicast_records()
1945 .map(|r| {
1946 (
1947 r.header().multicast_addr().clone(),
1948 r.header().record_type().unwrap(),
1949 r.sources().to_vec(),
1950 )
1951 })
1952 .collect::<Vec<_>>();
1953 assert_eq!(report, vec![(sent_report_addr.get(), sent_report_mode, sent_report_sources)]);
1954
1955 CounterExpectations { tx_mldv2_report: 1, ..Default::default() }
1956 .assert_counters(&mut core_ctx);
1957 }
1958
1959 #[test]
1960 fn v1_query_reject_bad_ipv6_source_addr() {
1961 let mut ctx = new_mldv1_context();
1962 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
1963
1964 let buffer = new_v1_query(Duration::from_secs(1), GROUP_ADDR).into_inner();
1965 for addr in
1966 [Ipv6SourceAddr::Unspecified, Ipv6SourceAddr::new(net_ip_v6!("2001::1")).unwrap()]
1967 {
1968 let mut buffer = &buffer[..];
1969 let packet = parse_mld_packet(&mut buffer);
1970 assert_eq!(
1971 receive_mld_packet(
1972 core_ctx,
1973 bindings_ctx,
1974 &FakeDeviceId,
1975 addr,
1976 packet,
1977 &DEFAULT_HEADER_INFO,
1978 ),
1979 Err(MldError::BadSourceAddress { addr: addr.into_addr() })
1980 );
1981 }
1982 CounterExpectations { rx_mldv1_query: 2, rx_err_bad_src_addr: 2, ..Default::default() }
1983 .assert_counters(core_ctx);
1984 }
1985
1986 #[test]
1987 fn v2_query_reject_bad_ipv6_source_addr() {
1988 let mut ctx = new_mldv1_context();
1989 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
1990
1991 let buffer = new_v2_general_query().into_inner();
1992 for addr in
1993 [Ipv6SourceAddr::Unspecified, Ipv6SourceAddr::new(net_ip_v6!("2001::1")).unwrap()]
1994 {
1995 let mut buffer = &buffer[..];
1996 let packet = parse_mld_packet(&mut buffer);
1997 assert_eq!(
1998 receive_mld_packet(
1999 core_ctx,
2000 bindings_ctx,
2001 &FakeDeviceId,
2002 addr,
2003 packet,
2004 &DEFAULT_HEADER_INFO,
2005 ),
2006 Err(MldError::BadSourceAddress { addr: addr.into_addr() })
2007 );
2008 }
2009
2010 CounterExpectations { rx_mldv2_query: 2, rx_err_bad_src_addr: 2, ..Default::default() }
2011 .assert_counters(core_ctx);
2012 }
2013
2014 #[test]
2015 fn v1_report_reject_bad_ipv6_source_addr() {
2016 let mut ctx = new_mldv1_context();
2017 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2018
2019 assert_eq!(
2020 core_ctx.gmp_join_group(bindings_ctx, &FakeDeviceId, GROUP_ADDR),
2021 GroupJoinResult::Joined(())
2022 );
2023
2024 let buffer = new_v1_report(GROUP_ADDR).into_inner();
2025 let addr = Ipv6SourceAddr::new(net_ip_v6!("2001::1")).unwrap();
2026 let mut buffer = &buffer[..];
2027 let packet = parse_mld_packet(&mut buffer);
2028 assert_eq!(
2029 receive_mld_packet(
2030 core_ctx,
2031 bindings_ctx,
2032 &FakeDeviceId,
2033 addr,
2034 packet,
2035 &DEFAULT_HEADER_INFO,
2036 ),
2037 Err(MldError::BadSourceAddress { addr: addr.into_addr() })
2038 );
2039
2040 let buffer = new_v1_report(GROUP_ADDR).into_inner();
2042 let addr = Ipv6SourceAddr::Unspecified;
2043 let mut buffer = &buffer[..];
2044 let packet = parse_mld_packet(&mut buffer);
2045 assert_eq!(
2046 receive_mld_packet(
2047 core_ctx,
2048 bindings_ctx,
2049 &FakeDeviceId,
2050 addr,
2051 packet,
2052 &DEFAULT_HEADER_INFO,
2053 ),
2054 Ok(())
2055 );
2056
2057 CounterExpectations {
2058 rx_mldv1_report: 2,
2059 rx_err_bad_src_addr: 1,
2060 tx_mldv1_report: 1,
2061 ..Default::default()
2062 }
2063 .assert_counters(core_ctx);
2064 }
2065
2066 #[test]
2067 fn reject_bad_hop_limit() {
2068 let mut ctx = new_mldv1_context();
2069 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2070 let src_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
2071 let src_addr: Ipv6SourceAddr = src_addr.try_into().unwrap();
2072
2073 let messages = [
2074 new_v1_query(Duration::from_secs(1), GROUP_ADDR).into_inner(),
2075 new_v2_general_query().into_inner(),
2076 new_v1_report(GROUP_ADDR).into_inner(),
2077 ];
2078 for buffer in messages {
2079 for hop_limit in [0, 2] {
2080 let header_info = FakeHeaderInfo { hop_limit, router_alert: true };
2081 let mut buffer = &buffer[..];
2082 let packet = parse_mld_packet(&mut buffer);
2083 assert_eq!(
2084 receive_mld_packet(
2085 core_ctx,
2086 bindings_ctx,
2087 &FakeDeviceId,
2088 src_addr,
2089 packet,
2090 &header_info,
2091 ),
2092 Err(MldError::BadHopLimit { hop_limit })
2093 );
2094 }
2095 }
2096 CounterExpectations { rx_err_bad_hop_limit: 6, ..Default::default() }
2097 .assert_counters(core_ctx);
2098 }
2099
2100 #[test]
2101 fn v2_query_reject_missing_router_alert() {
2102 let mut ctx = new_mldv1_context();
2103 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2104 let src_addr: Ipv6Addr = ROUTER_MAC.to_ipv6_link_local().addr().get();
2105 let src_addr: Ipv6SourceAddr = src_addr.try_into().unwrap();
2106
2107 let buffer = new_v2_general_query().into_inner();
2108 let header_info = FakeHeaderInfo { hop_limit: MLD_IP_HOP_LIMIT, router_alert: false };
2109 let mut buffer = &buffer[..];
2110 let packet = parse_mld_packet(&mut buffer);
2111 assert_eq!(
2112 receive_mld_packet(
2113 core_ctx,
2114 bindings_ctx,
2115 &FakeDeviceId,
2116 src_addr,
2117 packet,
2118 &header_info,
2119 ),
2120 Err(MldError::MissingRouterAlert),
2121 );
2122 CounterExpectations {
2123 rx_mldv2_query: 1,
2124 rx_err_missing_router_alert: 1,
2125 ..Default::default()
2126 }
2127 .assert_counters(core_ctx);
2128 }
2129
2130 #[test]
2131 fn user_mode_change() {
2132 let mut ctx = new_mldv1_context();
2133 let FakeCtxImpl { core_ctx, bindings_ctx } = &mut ctx;
2134 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V1);
2135 assert_eq!(
2136 core_ctx.gmp_join_group(bindings_ctx, &FakeDeviceId, GROUP_ADDR),
2137 GroupJoinResult::Joined(())
2138 );
2139 let _ = core_ctx.take_frames();
2141 assert_eq!(
2142 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, MldConfigMode::V2),
2143 MldConfigMode::V1
2144 );
2145 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V2);
2146 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V2));
2147 assert_eq!(core_ctx.take_frames(), Vec::new());
2149
2150 receive_mldv1_query(core_ctx, bindings_ctx, Duration::from_secs(0), GROUP_ADDR);
2153 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V1 { compat: true }));
2154 assert_eq!(core_ctx.take_frames().len(), 1);
2156 assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V2);
2157
2158 assert_eq!(
2160 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, MldConfigMode::V2),
2161 MldConfigMode::V2
2162 );
2163 assert_eq!(core_ctx.take_frames(), Vec::new());
2164 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V1 { compat: true }));
2165
2166 assert_eq!(
2168 core_ctx.gmp_set_mode(bindings_ctx, &FakeDeviceId, MldConfigMode::V1),
2169 MldConfigMode::V2
2170 );
2171 assert_eq!(core_ctx.take_frames(), Vec::new());
2172 assert_eq!(core_ctx.state.gmp_state().mode, MldMode(GmpMode::V1 { compat: false }));
2173 }
2174}