Skip to main content

netstack3_ip/gmp/
mld.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Multicast Listener Discovery (MLD).
6//!
7//! MLD is derived from version 2 of IPv4's Internet Group Management Protocol,
8//! IGMPv2. One important difference to note is that MLD uses ICMPv6 (IP
9//! Protocol 58) message types, rather than IGMP (IP Protocol 2) message types.
10
11use 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
49/// The destination address for all MLDv2 reports.
50///
51/// Defined in [RFC 3376 section 5.2.14].
52///
53/// [RFC 3376 section 5.2.14]:
54///     https://datatracker.ietf.org/doc/html/rfc3810#section-5.2.14
55const ALL_MLDV2_CAPABLE_ROUTERS: MulticastAddr<Ipv6Addr> =
56    unsafe { MulticastAddr::new_unchecked(net_ip_v6!("FF02::16")) };
57
58/// The bindings types for MLD.
59pub trait MldBindingsTypes: GmpBindingsTypes {}
60impl<BT> MldBindingsTypes for BT where BT: GmpBindingsTypes {}
61
62/// The bindings execution context for MLD.
63pub(crate) trait MldBindingsContext: GmpBindingsContext {}
64impl<BC> MldBindingsContext for BC where BC: GmpBindingsContext {}
65
66/// Provides immutable access to MLD state.
67pub trait MldStateContext<BT: MldBindingsTypes>:
68    DeviceIdContext<AnyDevice> + MldContextMarker
69{
70    /// Calls the function with an immutable reference to the device's MLD
71    /// state.
72    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
85/// The execution context capable of sending frames for MLD.
86pub trait MldSendContext<BT: MldBindingsTypes>:
87    DeviceIdContext<AnyDevice>
88    + IpLayerHandler<Ipv6, BT>
89    + IpDeviceMtuContext<Ipv6>
90    + MldContextMarker
91    + ResourceCounterContext<Self::DeviceId, MldCounters>
92{
93    /// Gets the IPv6 link local address on `device`.
94    fn get_ipv6_link_local_addr(
95        &mut self,
96        device: &Self::DeviceId,
97    ) -> Option<LinkLocalUnicastAddr<Ipv6Addr>>;
98}
99
100/// A marker context for MLD traits to allow for GMP test fakes.
101pub trait MldContextMarker {}
102
103/// The execution context for the Multicast Listener Discovery (MLD) protocol.
104pub trait MldContext<BT: MldBindingsTypes>:
105    DeviceIdContext<AnyDevice> + MldContextMarker + ResourceCounterContext<Self::DeviceId, MldCounters>
106{
107    /// The inner context given to `with_mld_state_mut`.
108    type SendContext<'a>: MldSendContext<BT, DeviceId = Self::DeviceId> + 'a;
109
110    /// Calls the function with a mutable reference to the device's MLD state
111    /// and whether or not MLD is enabled for the `device`.
112    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
122/// A handler for incoming MLD packets.
123///
124/// A blanket implementation is provided for all `C: MldContext`.
125pub trait MldPacketHandler<BC, DeviceId> {
126    /// Receive an MLD packet.
127    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    // MLDv2 Specifies that all received queries & reports with an invalid hop
152    // limit (not equal to 1) should be dropped (See RFC 3810 Section 6.2 and
153    // Section 7.4).
154    //
155    // MLDv1 does not specify the expected behavior when receiving a message
156    // with an invalid hop limit, however it does specify that all senders of
157    // MLDv1 messages must set the hop limit to 1 (See RFC 2710 Section 3). Our
158    // interpretation of this is to drop MLDv1 messages without a hop limit of
159    // 1, as any sender that generates them is violating the RFC.
160    //
161    // This could be considered a violation of the Robustness Principle, but a
162    // it is our belief that a packet with a different hop limit is more likely
163    // to be malicious than a poor implementation. Note that the same rationale
164    // is applied to IGMP.
165    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            // From RFC 2710 section 5:
174            //
175            //  - To be valid, the Query message MUST come from a link-
176            //  local IPv6 Source Address [...]
177            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            // From RFC 3810 section 5.1.14:
188            //
189            // If a node (router or host) receives a Query message with
190            // the IPv6 Source Address set to the unspecified address (::), or any
191            // other address that is not a valid IPv6 link-local address, it MUST
192            // silently discard the message.
193            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            // From RFC 3810 section 6.2:
200            //
201            // Upon reception of an MLD message that contains a Query, the node
202            // checks [...] and if the Router Alert option is present in the
203            // Hop-By-Hop Options header of the IPv6 packet. If any of these
204            // checks fails, the packet is dropped.
205            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            // From RFC 2710 section 5:
218            //
219            //  - To be valid, the Report message MUST come from a link-
220            //   local IPv6 Source Address [...]
221            //
222            // However, RFC 3810 allows MLD reports to be sent from
223            // unspecified addresses and we in fact send those, so we relax
224            // to allow accepting from unspecified addresses as well.
225            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/// The MLD mode controllable by the user.
310#[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        // Per [RFC 3810 Section 6]:
322        //
323        // > No MLD messages are ever sent regarding neither the link-scope
324        // > all-nodes multicast address, nor any multicast address of scope 0
325        // > (reserved) or 1 (node-local).
326        //
327        // We abide by this requirement by not executing [`Actions`] on these
328        // addresses. Executing [`Actions`] only produces externally-visible side
329        // effects, and is not required to maintain the correctness of the MLD state
330        // machines.
331        //
332        // [RFC 3810 Section 6]: https://tools.ietf.org/html/rfc3810#section-6
333        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/// Newtype over [`GmpMode`] to tailor it to GMP.
340#[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
349// NB: This could be derived, but it feels better to have it called out
350// explicitly in MLD.
351impl 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
369/// Uninstantiable type marking a [`GmpState`] as having MLD types.
370pub 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                // Warn here, we don't quite have a good global guarantee of
473                // minimal acceptable MTUs across both IPv4 and IPv6. This
474                // should effectively not happen though.
475                //
476                // TODO(https://fxbug.dev/383355972): Consider an assertion here
477                // instead.
478                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    /// The host is trying to operate on an group address of which the host is
536    /// not a member.
537    #[error("the host has not already been a member of the address: {}", addr)]
538    NotAMember { addr: Ipv6Addr },
539    /// Failed to send an IGMP packet.
540    #[error("failed to send out an MLD packet to address: {}", addr)]
541    SendFailure { addr: Ipv6Addr },
542    /// Message ignored because of bad source address.
543    #[error("bad source address: {}", addr)]
544    BadSourceAddress { addr: Ipv6Addr },
545    /// Message ignored because of the router alter option was not present
546    #[error("router alert option not present")]
547    MissingRouterAlert,
548    /// Message ignored because of the hop limit was invalid.
549    #[error("message with incorrect hop limit: {hop_limit}")]
550    BadHopLimit { hop_limit: u8 },
551    /// MLD is disabled
552    #[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
579/// The default value for `unsolicited_report_interval` [RFC 2710 Section 7.10]
580///
581/// [RFC 2710 Section 7.10]: https://tools.ietf.org/html/rfc2710#section-7.10
582pub 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/// An MLD timer to delay the sending of a report.
618#[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    /// Creates a new [`MldTimerId`] for a GMP delayed report on `device`.
628    #[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/// An iterator that generates the IP options for MLD packets.
650///
651/// This allows us to write `new_ip_and_icmp_builders` easily without a big mess
652/// of static lifetimes.
653///
654/// MLD messages require the Router Alert hop-by-hop extension. See [RFC 2710
655/// section 3] , [RFC 3810 section 5].
656///
657/// [RFC 2710 section 2]:
658///     https://datatracker.ietf.org/doc/html/rfc2710#section-3
659/// [RFC 3810 section 5]:
660///     https://datatracker.ietf.org/doc/html/rfc3810#section-5
661#[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
681/// The required IP TTL for MLD messages.
682///
683/// See [RFC 2710 section 3] , [RFC 3810 section 5].
684///
685/// [RFC 2710 section 2]:
686///     https://datatracker.ietf.org/doc/html/rfc2710#section-3
687/// [RFC 3810 section 5]:
688///     https://datatracker.ietf.org/doc/html/rfc3810#section-5
689const 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    // According to https://tools.ietf.org/html/rfc3590#section-4, if a valid
702    // link-local address is not available for the device (e.g., one has not
703    // been configured), the message is sent with the unspecified address (::)
704    // as the IPv6 source address.
705    //
706    // TODO(https://fxbug.dev/42180878): Handle an IPv6 link-local address being
707    // assigned when reports were sent with the unspecified source address.
708    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
720/// A type to allow implementing the required filtering traits on a concrete
721/// subset of message types.
722enum MldMessage {
723    ListenerReport { group_addr: <MulticastListenerReport as Mldv1MessageType>::GroupAddr },
724    ListenerDone { group_addr: <MulticastListenerDone as Mldv1MessageType>::GroupAddr },
725}
726
727/// Send an MLD packet.
728///
729/// The MLD packet being sent should have its `hop_limit` to be 1 and a
730/// `RouterAlert` option in its Hop-by-Hop Options extensions header.
731fn 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/// Statistics about MLD.
765///
766/// The counter type `C` is generic to facilitate testing.
767#[derive(Default, Debug)]
768#[cfg_attr(
769    any(test, feature = "testutils"),
770    derive(PartialEq, netstack3_macros::CounterCollection)
771)]
772pub struct MldCounters<C = Counter> {
773    /// Count of MLDv1 queries received.
774    rx_mldv1_query: C,
775    /// Count of MLDv2 queries received.
776    rx_mldv2_query: C,
777    /// Count of MLDv1 reports received.
778    rx_mldv1_report: C,
779    /// Count of MLDv2 reports received.
780    rx_mldv2_report: C,
781    /// Count of Leave Group messages received.
782    rx_leave_group: C,
783    /// Count of MLD messages received with an invalid source address.
784    rx_err_bad_src_addr: C,
785    /// Count of MLD messages received with an invalid hop limit.
786    rx_err_bad_hop_limit: C,
787    /// Count of MLD messages received without the Router Alert option.
788    rx_err_missing_router_alert: C,
789    /// Count of MLDv1 reports sent.
790    tx_mldv1_report: C,
791    /// Count of MLDv2 reports sent.
792    tx_mldv2_report: C,
793    /// Count of Leave Group messages sent.
794    tx_leave_group: C,
795    /// Count of MLD messages that could not be sent.
796    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    /// Metadata for sending an MLD packet in an IP packet.
875    #[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    /// A fake [`MldContext`] that stores the [`MldInterface`] and an optional
888    /// IPv6 link-local address that may be returned in calls to
889    /// [`MldContext::get_ipv6_link_local_addr`].
890    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    /// The parts of `FakeMldCtx` that are behind a RefCell, mocking a lock.
923    struct Shared {
924        groups: MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>>,
925        gmp_state: GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
926        config: MldConfig,
927    }
928
929    /// Creates a new test context in MLDv1.
930    ///
931    /// A historical note: a number of tests were originally written when only
932    /// MLDv1 was supported.
933    fn new_mldv1_context() -> FakeCtxImpl {
934        FakeCtxImpl::with_default_bindings_ctx(|bindings_ctx| {
935            // We start with enabled true to make tests easier to write.
936            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            // Most of the test surface is covered by the GMP implementation,
1099            // MLD specific part is mostly passthrough. This test case is here
1100            // because MLD allows a router to ask for report immediately, by
1101            // specifying the `MaxRespDelay` to be 0. If this is the case, the
1102            // host should send the report immediately instead of setting a
1103            // timer.
1104            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    // Ensure the ttl is 1.
1260    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    // Ensure the destination address field in the ICMPv6 packet is correct.
1271    fn ensure_dst_addr(frame: &[u8], ip: Ipv6Addr) {
1272        ensure_slice_addr(frame, 24, 40, ip);
1273    }
1274
1275    // Ensure the multicast address field in the MLD packet is correct.
1276    fn ensure_multicast_addr(frame: &[u8], ip: Ipv6Addr) {
1277        ensure_slice_addr(frame, 56, 72, ip);
1278    }
1279
1280    // Ensure a sent frame meets the requirement.
1281    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        // Ensure the length our payload is 32 = 8 (hbh_ext_hdr) + 24 (mld)
1290        assert_eq!(frame[5], 32);
1291        // Ensure the next header is our HopByHop Extension Header.
1292        assert_eq!(frame[6], 0);
1293        // Ensure there is a RouterAlert HopByHopOption in our sent frame
1294        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            // We should get two MLD reports - one for the unsolicited one for
1325            // the host to turn into Delay Member state and the other one for
1326            // the timer being fired.
1327            assert_eq!(core_ctx.frames().len(), 2);
1328            // The frames are all reports.
1329            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            // The query says that it wants to hear from us immediately.
1358            assert_eq!(core_ctx.frames().len(), 2);
1359            // There should be no timers set.
1360            assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1361            // The frames are all reports.
1362            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            // We have received a query, hence we are falling back to Delay
1400            // Member state.
1401            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            // The frames are all reports.
1415            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            // Since it is an immediate query, we will send a report immediately
1453            // and turn into Idle state again.
1454            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            // No timers!
1461            assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1462            assert_eq!(core_ctx.frames().len(), 3);
1463            // The frames are all reports.
1464            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        // This seed was carefully chosen to produce a substantial duration
1478        // value below.
1479        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        // This new timer should be sooner.
1503        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        // The frames are all reports.
1508        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            // The initial unsolicited report.
1534            assert_eq!(core_ctx.frames().len(), 1);
1535            assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1536            // The report after the delay.
1537            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            // Our leave message.
1543            assert_eq!(core_ctx.frames().len(), 3);
1544            // The first two messages should be reports.
1545            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            // The last one should be the done message whose destination is all
1550            // routers.
1551            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            // The report should be discarded because we have received from someone
1583            // else.
1584            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            // A leave message is not sent.
1590            assert_eq!(core_ctx.frames().len(), 1);
1591            // The frames are all reports.
1592            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            // Test that we do not perform MLD for addresses that we're supposed
1630            // to skip or when MLD is disabled.
1631            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                // Assert that no observable effects have taken place.
1635                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                // We should join the group but left in the GMP's non-member
1646                // state.
1647                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                // We should have done no state transitions/work.
1652                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                // We should have done no state transitions/work.
1662                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                // We should have left the group but not executed any `Actions`.
1670                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 that we skip executing `Actions` for addresses we're
1684            // supposed to skip.
1685            test(new_ctx(), Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS);
1686            let mut bytes = Ipv6::MULTICAST_SUBNET.network().ipv6_bytes();
1687            // Manually set the "scope" field to 0.
1688            bytes[1] = bytes[1] & 0xF0;
1689            let reserved0 = MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap();
1690            // Manually set the "scope" field to 1 (interface-local).
1691            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            // Test that we skip executing `Actions` when MLD is disabled on the
1697            // device.
1698            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            // Simple MLD integration test to check that when we call top-level
1709            // multicast join and leave functions, MLD is performed.
1710            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            // Should not perform MLD for the all-nodes address.
1776            //
1777            // As per RFC 3810 Section 6,
1778            //
1779            //   No MLD messages are ever sent regarding neither the link-scope,
1780            //   all-nodes multicast address, nor any multicast address of scope
1781            //   0 (reserved) or 1 (node-local).
1782            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            // Should do nothing.
1811            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            // Should send done message.
1817            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            // Should do nothing.
1836            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            // Should send report message.
1842            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 the basics of MLDv2 report sending.
1864    #[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        // Unspecified is okay however.
2018        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        // Ignore first reports.
2117        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        // No side-effects.
2125        assert_eq!(core_ctx.take_frames(), Vec::new());
2126
2127        // If we receive a v1 query, we'll go into compat mode but still report
2128        // v2 to the user.
2129        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        // Acknowledge query response.
2132        assert_eq!(core_ctx.take_frames().len(), 1);
2133        assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V2);
2134
2135        // Even if user attempts to set V2 again we'll keep it in compat.
2136        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        // Forcing V1 mode, however, exits compat.
2144        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}