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, NestablePacketBuilder as _, NestableSerializer as _};
26use packet_formats::icmp::mld::{
27 MldPacket, Mldv1Body, Mldv1MessageBuilder, Mldv1MessageType, Mldv2QueryBody,
28 Mldv2ReportMessageBuilder, MulticastListenerDone, MulticastListenerReport,
29 MulticastListenerReportV2,
30};
31use packet_formats::icmp::{IcmpMessage, IcmpPacketBuilder, IcmpSenderZeroCode};
32use packet_formats::ip::Ipv6Proto;
33use packet_formats::ipv6::ext_hdrs::{
34 ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
35};
36use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
37use packet_formats::utils::NonZeroDuration;
38use thiserror::Error;
39use zerocopy::SplitByteSlice;
40
41use crate::internal::base::{IpDeviceMtuContext, IpLayerHandler, IpPacketDestination};
42use crate::internal::gmp::{
43 self, GmpBindingsContext, GmpBindingsTypes, GmpContext, GmpContextInner, GmpEnabledGroup,
44 GmpGroupState, GmpMode, GmpState, GmpStateContext, GmpStateRef, GmpTimerId, GmpTypeLayout,
45 IpExt, MulticastGroupSet, NotAMemberErr,
46};
47use crate::internal::local_delivery::IpHeaderInfo;
48
49const ALL_MLDV2_CAPABLE_ROUTERS: MulticastAddr<Ipv6Addr> =
56 unsafe { MulticastAddr::new_unchecked(net_ip_v6!("FF02::16")) };
57
58pub trait MldBindingsTypes: GmpBindingsTypes {}
60impl<BT> MldBindingsTypes for BT where BT: GmpBindingsTypes {}
61
62pub(crate) trait MldBindingsContext: GmpBindingsContext {}
64impl<BC> MldBindingsContext for BC where BC: GmpBindingsContext {}
65
66pub trait MldStateContext<BT: MldBindingsTypes>:
68 DeviceIdContext<AnyDevice> + MldContextMarker
69{
70 fn with_mld_state<
73 O,
74 F: FnOnce(
75 &MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, BT>>,
76 &GmpState<Ipv6, MldTypeLayout, BT>,
77 ) -> O,
78 >(
79 &mut self,
80 device: &Self::DeviceId,
81 cb: F,
82 ) -> O;
83}
84
85pub trait MldSendContext<BT: MldBindingsTypes>:
87 DeviceIdContext<AnyDevice>
88 + IpLayerHandler<Ipv6, BT>
89 + IpDeviceMtuContext<Ipv6>
90 + MldContextMarker
91 + ResourceCounterContext<Self::DeviceId, MldCounters>
92{
93 fn get_ipv6_link_local_addr(
95 &mut self,
96 device: &Self::DeviceId,
97 ) -> Option<LinkLocalUnicastAddr<Ipv6Addr>>;
98}
99
100pub trait MldContextMarker {}
102
103pub trait MldContext<BT: MldBindingsTypes>:
105 DeviceIdContext<AnyDevice> + MldContextMarker + ResourceCounterContext<Self::DeviceId, MldCounters>
106{
107 type SendContext<'a>: MldSendContext<BT, DeviceId = Self::DeviceId> + 'a;
109
110 fn with_mld_state_mut<
113 O,
114 F: FnOnce(Self::SendContext<'_>, GmpStateRef<'_, Ipv6, MldTypeLayout, BT>) -> O,
115 >(
116 &mut self,
117 device: &Self::DeviceId,
118 cb: F,
119 ) -> O;
120}
121
122pub trait MldPacketHandler<BC, DeviceId> {
126 fn receive_mld_packet<B: SplitByteSlice, H: IpHeaderInfo<Ipv6>>(
128 &mut self,
129 bindings_ctx: &mut BC,
130 device: &DeviceId,
131 src_ip: Ipv6SourceAddr,
132 dst_ip: SpecifiedAddr<Ipv6Addr>,
133 packet: MldPacket<B>,
134 header_info: &H,
135 );
136}
137
138fn receive_mld_packet<
139 B: SplitByteSlice,
140 H: IpHeaderInfo<Ipv6>,
141 CC: MldContext<BC>,
142 BC: MldBindingsContext,
143>(
144 core_ctx: &mut CC,
145 bindings_ctx: &mut BC,
146 device: &CC::DeviceId,
147 src_ip: Ipv6SourceAddr,
148 packet: MldPacket<B>,
149 header_info: &H,
150) -> Result<(), MldError> {
151 if header_info.hop_limit() != MLD_IP_HOP_LIMIT {
166 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_err_bad_hop_limit);
167 return Err(MldError::BadHopLimit { hop_limit: header_info.hop_limit() });
168 }
169
170 match packet {
171 MldPacket::MulticastListenerQuery(msg) => {
172 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv1_query);
173 if !src_ip.is_link_local() {
178 core_ctx
179 .increment_both(device, |counters: &MldCounters| &counters.rx_err_bad_src_addr);
180 return Err(MldError::BadSourceAddress { addr: src_ip.into_addr() });
181 }
182 gmp::v1::handle_query_message(core_ctx, bindings_ctx, device, msg.body())
183 .map_err(Into::into)
184 }
185 MldPacket::MulticastListenerQueryV2(msg) => {
186 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv2_query);
187 if !src_ip.is_link_local() {
194 core_ctx
195 .increment_both(device, |counters: &MldCounters| &counters.rx_err_bad_src_addr);
196 return Err(MldError::BadSourceAddress { addr: src_ip.into_addr() });
197 }
198
199 if !header_info.router_alert() {
206 core_ctx.increment_both(device, |counters: &MldCounters| {
207 &counters.rx_err_missing_router_alert
208 });
209 return Err(MldError::MissingRouterAlert);
210 }
211
212 gmp::v2::handle_query_message(core_ctx, bindings_ctx, device, msg.body())
213 .map_err(Into::into)
214 }
215 MldPacket::MulticastListenerReport(msg) => {
216 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv1_report);
217 match src_ip {
226 Ipv6SourceAddr::Unspecified => {}
227 Ipv6SourceAddr::Unicast(src_ip) => {
228 if !src_ip.is_link_local() {
229 core_ctx.increment_both(device, |counters: &MldCounters| {
230 &counters.rx_err_bad_src_addr
231 });
232 return Err(MldError::BadSourceAddress { addr: src_ip.into_addr() });
233 }
234 }
235 }
236 let addr = msg.body().group_addr;
237 MulticastAddr::new(msg.body().group_addr).map_or(
238 Err(MldError::NotAMember { addr }),
239 |group_addr| {
240 gmp::v1::handle_report_message(core_ctx, bindings_ctx, device, group_addr)
241 .map_err(Into::into)
242 },
243 )
244 }
245 MldPacket::MulticastListenerReportV2(_) => {
246 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_mldv2_report);
247 debug!("Hosts are not interested in MLDv2 report messages");
248 Ok(())
249 }
250 MldPacket::MulticastListenerDone(_) => {
251 core_ctx.increment_both(device, |counters: &MldCounters| &counters.rx_leave_group);
252 debug!("Hosts are not interested in Done messages");
253 Ok(())
254 }
255 }
256}
257
258impl<BC: MldBindingsContext, CC: MldContext<BC>> MldPacketHandler<BC, CC::DeviceId> for CC {
259 fn receive_mld_packet<B: SplitByteSlice, H: IpHeaderInfo<Ipv6>>(
260 &mut self,
261 bindings_ctx: &mut BC,
262 device: &CC::DeviceId,
263 src_ip: Ipv6SourceAddr,
264 _dst_ip: SpecifiedAddr<Ipv6Addr>,
265 packet: MldPacket<B>,
266 header_info: &H,
267 ) {
268 receive_mld_packet(self, bindings_ctx, device, src_ip, packet, header_info)
269 .unwrap_or_else(|e| debug!("Error occurred when handling MLD message: {}", e));
270 }
271}
272
273impl<B: SplitByteSlice> gmp::v1::QueryMessage<Ipv6> for Mldv1Body<B> {
274 fn group_addr(&self) -> Ipv6Addr {
275 self.group_addr
276 }
277
278 fn max_response_time(&self) -> Duration {
279 self.max_response_delay()
280 }
281}
282
283impl<B: SplitByteSlice> gmp::v2::QueryMessage<Ipv6> for Mldv2QueryBody<B> {
284 fn as_v1(&self) -> impl gmp::v1::QueryMessage<Ipv6> + '_ {
285 self.as_v1_query()
286 }
287
288 fn robustness_variable(&self) -> u8 {
289 self.header().querier_robustness_variable()
290 }
291
292 fn query_interval(&self) -> Duration {
293 self.header().querier_query_interval()
294 }
295
296 fn group_address(&self) -> Ipv6Addr {
297 self.header().group_address()
298 }
299
300 fn max_response_time(&self) -> Duration {
301 self.header().max_response_delay().into()
302 }
303
304 fn sources(&self) -> impl Iterator<Item = Ipv6Addr> + '_ {
305 self.sources().iter().copied()
306 }
307}
308
309#[derive(Debug, Eq, PartialEq, Copy, Clone)]
311#[allow(missing_docs)]
312pub enum MldConfigMode {
313 V1,
314 V2,
315}
316
317impl IpExt for Ipv6 {
318 type GmpProtoConfigMode = MldConfigMode;
319
320 fn should_perform_gmp(group_addr: MulticastAddr<Ipv6Addr>) -> bool {
321 group_addr != Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS
334 && ![Ipv6Scope::Reserved(Ipv6ReservedScope::Scope0), Ipv6Scope::InterfaceLocal]
335 .contains(&group_addr.scope())
336 }
337}
338
339#[derive(Debug, Eq, PartialEq, Copy, Clone)]
341pub struct MldMode(GmpMode);
342
343impl From<MldMode> for GmpMode {
344 fn from(MldMode(v): MldMode) -> Self {
345 v
346 }
347}
348
349impl Default for MldMode {
352 fn default() -> Self {
353 Self(GmpMode::V2)
354 }
355}
356
357impl InspectableValue for MldMode {
358 fn record<I: Inspector>(&self, name: &str, inspector: &mut I) {
359 let Self(gmp_mode) = self;
360 let v = match gmp_mode {
361 GmpMode::V1 { compat: false } => "MLDv1(compat)",
362 GmpMode::V1 { compat: true } => "MLDv1",
363 GmpMode::V2 => "MLDv2",
364 };
365 inspector.record_str(name, v);
366 }
367}
368
369pub enum MldTypeLayout {}
371
372impl<BT: MldBindingsTypes> GmpTypeLayout<Ipv6, BT> for MldTypeLayout {
373 type Config = MldConfig;
374 type ProtoMode = MldMode;
375}
376
377impl<BT: MldBindingsTypes, CC: MldStateContext<BT>> GmpStateContext<Ipv6, BT> for CC {
378 type TypeLayout = MldTypeLayout;
379
380 fn with_gmp_state<
381 O,
382 F: FnOnce(
383 &MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, BT>>,
384 &GmpState<Ipv6, MldTypeLayout, BT>,
385 ) -> O,
386 >(
387 &mut self,
388 device: &Self::DeviceId,
389 cb: F,
390 ) -> O {
391 self.with_mld_state(device, cb)
392 }
393}
394
395impl<BC: MldBindingsContext, CC: MldContext<BC>> GmpContext<Ipv6, BC> for CC {
396 type TypeLayout = MldTypeLayout;
397 type Inner<'a> = CC::SendContext<'a>;
398
399 fn with_gmp_state_mut_and_ctx<
400 O,
401 F: FnOnce(Self::Inner<'_>, GmpStateRef<'_, Ipv6, Self::TypeLayout, BC>) -> O,
402 >(
403 &mut self,
404 device: &Self::DeviceId,
405 cb: F,
406 ) -> O {
407 self.with_mld_state_mut(device, cb)
408 }
409}
410
411impl<BC: MldBindingsContext, CC: MldSendContext<BC>> GmpContextInner<Ipv6, BC> for CC {
412 type TypeLayout = MldTypeLayout;
413 fn send_message_v1(
414 &mut self,
415 bindings_ctx: &mut BC,
416 device: &Self::DeviceId,
417 _cur_mode: &MldMode,
418 group_addr: GmpEnabledGroup<Ipv6Addr>,
419 msg_type: gmp::v1::GmpMessageType,
420 ) {
421 let group_addr = group_addr.into_multicast_addr();
422 let result = match msg_type {
423 gmp::v1::GmpMessageType::Report => {
424 self.increment_both(device, |counters: &MldCounters| &counters.tx_mldv1_report);
425 send_mld_v1_packet::<_, _>(
426 self,
427 bindings_ctx,
428 device,
429 group_addr,
430 MldMessage::ListenerReport { group_addr },
431 )
432 }
433 gmp::v1::GmpMessageType::Leave => {
434 self.increment_both(device, |counters: &MldCounters| &counters.tx_leave_group);
435 send_mld_v1_packet::<_, _>(
436 self,
437 bindings_ctx,
438 device,
439 Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
440 MldMessage::ListenerDone { group_addr },
441 )
442 }
443 };
444
445 match result {
446 Ok(()) => {}
447 Err(err) => {
448 self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
449 debug!(
450 "error sending MLD message ({msg_type:?}) on device {device:?} for group \
451 {group_addr}: {err}",
452 )
453 }
454 }
455 }
456
457 fn send_report_v2(
458 &mut self,
459 bindings_ctx: &mut BC,
460 device: &Self::DeviceId,
461 groups: impl Iterator<Item: gmp::v2::VerifiedReportGroupRecord<Ipv6Addr> + Clone> + Clone,
462 ) {
463 let dst_ip = ALL_MLDV2_CAPABLE_ROUTERS;
464 let (ipv6, icmp) =
465 new_ip_and_icmp_builders(self, device, dst_ip, MulticastListenerReportV2);
466 let header = ipv6.constraints().header_len() + icmp.constraints().header_len();
467 let avail_len = usize::from(self.get_mtu(device)).saturating_sub(header);
468 let reports = match Mldv2ReportMessageBuilder::new(groups).with_len_limits(avail_len) {
469 Ok(msg) => msg,
470 Err(e) => {
471 self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
472 error!("MTU too small to send MLD reports: {e:?}");
479 return;
480 }
481 };
482 for report in reports {
483 self.increment_both(device, |counters: &MldCounters| &counters.tx_mldv2_report);
484 let mut report = report.into_serializer().wrap_in(icmp.clone());
485 let report = DynTransportSerializer::new(&mut report);
486 let destination = IpPacketDestination::Multicast(dst_ip);
487 let ip_frame = report.wrap_in(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 mut message =
743 Mldv1MessageBuilder::<$type>::new_with_max_resp_delay($group_addr, ())
744 .into_serializer()
745 .wrap_in(icmp);
746 let message = DynTransportSerializer::new(&mut message);
747 let ip_frame = message.wrap_in(ipv6);
748 let destination = IpPacketDestination::Multicast(dst_ip);
749 IpLayerHandler::send_ip_frame(core_ctx, bindings_ctx, &device, destination, ip_frame)
750 .map_err(|_| MldError::SendFailure { addr: $group_addr.into() })
751 }};
752 }
753
754 match msg {
755 MldMessage::ListenerReport { group_addr } => {
756 send!(MulticastListenerReport, MulticastListenerReport, group_addr)
757 }
758 MldMessage::ListenerDone { group_addr } => {
759 send!(MulticastListenerDone, MulticastListenerDone, group_addr)
760 }
761 }
762}
763
764#[derive(Default, Debug)]
768#[cfg_attr(
769 any(test, feature = "testutils"),
770 derive(PartialEq, netstack3_macros::CounterCollection)
771)]
772pub struct MldCounters<C = Counter> {
773 rx_mldv1_query: C,
775 rx_mldv2_query: C,
777 rx_mldv1_report: C,
779 rx_mldv2_report: C,
781 rx_leave_group: C,
783 rx_err_bad_src_addr: C,
785 rx_err_bad_hop_limit: C,
787 rx_err_missing_router_alert: C,
789 tx_mldv1_report: C,
791 tx_mldv2_report: C,
793 tx_leave_group: C,
795 tx_err: C,
797}
798
799impl Inspectable for MldCounters {
800 fn record<I: Inspector>(&self, inspector: &mut I) {
801 let Self {
802 rx_mldv1_query,
803 rx_mldv2_query,
804 rx_mldv1_report,
805 rx_mldv2_report,
806 rx_leave_group,
807 rx_err_bad_src_addr,
808 rx_err_bad_hop_limit,
809 rx_err_missing_router_alert,
810 tx_mldv1_report,
811 tx_mldv2_report,
812 tx_leave_group,
813 tx_err,
814 } = self;
815 inspector.record_child("Rx", |inspector| {
816 inspector.record_counter("MLDv1Query", rx_mldv1_query);
817 inspector.record_counter("MLDv2Query", rx_mldv2_query);
818 inspector.record_counter("MLDv1Report", rx_mldv1_report);
819 inspector.record_counter("MLDv2Report", rx_mldv2_report);
820 inspector.record_counter("LeaveGroup", rx_leave_group);
821 inspector.record_child("Errors", |inspector| {
822 inspector.record_counter("BadSourceAddress", rx_err_bad_src_addr);
823 inspector.record_counter("BadHopLimit", rx_err_bad_hop_limit);
824 inspector.record_counter("MissingRouterAlert", rx_err_missing_router_alert);
825 })
826 });
827 inspector.record_child("Tx", |inspector| {
828 inspector.record_counter("MLDv1Report", tx_mldv1_report);
829 inspector.record_counter("MLDv2Report", tx_mldv2_report);
830 inspector.record_counter("LeaveGroup", tx_leave_group);
831 inspector.record_child("Errors", |inspector| {
832 inspector.record_counter("SendFailed", tx_err);
833 });
834 });
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use alloc::rc::Rc;
841 use alloc::vec;
842 use alloc::vec::Vec;
843 use core::cell::RefCell;
844 use packet::Serializer;
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 NetworkSerializationContext, NetworkSerializer, 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: NetworkSerializer,
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(&mut NetworkSerializationContext::default())
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(&mut NetworkSerializationContext::default())
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(&mut NetworkSerializationContext::default())
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}