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