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 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
50/// The destination address for all MLDv2 reports.
51///
52/// Defined in [RFC 3376 section 5.2.14].
53///
54/// [RFC 3376 section 5.2.14]:
55///     https://datatracker.ietf.org/doc/html/rfc3810#section-5.2.14
56const ALL_MLDV2_CAPABLE_ROUTERS: MulticastAddr<Ipv6Addr> =
57    unsafe { MulticastAddr::new_unchecked(net_ip_v6!("FF02::16")) };
58
59/// The bindings types for MLD.
60pub trait MldBindingsTypes: GmpBindingsTypes {}
61impl<BT> MldBindingsTypes for BT where BT: GmpBindingsTypes {}
62
63/// The bindings execution context for MLD.
64pub(crate) trait MldBindingsContext: GmpBindingsContext {}
65impl<BC> MldBindingsContext for BC where BC: GmpBindingsContext {}
66
67/// Provides immutable access to MLD state.
68pub trait MldStateContext<BT: MldBindingsTypes>:
69    DeviceIdContext<AnyDevice> + MldContextMarker
70{
71    /// Calls the function with an immutable reference to the device's MLD
72    /// state.
73    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
86/// The execution context capable of sending frames for MLD.
87pub trait MldSendContext<BT: MldBindingsTypes>:
88    DeviceIdContext<AnyDevice>
89    + IpLayerHandler<Ipv6, BT>
90    + IpDeviceMtuContext<Ipv6>
91    + MldContextMarker
92    + ResourceCounterContext<Self::DeviceId, MldCounters>
93{
94    /// Gets the IPv6 link local address on `device`.
95    fn get_ipv6_link_local_addr(
96        &mut self,
97        device: &Self::DeviceId,
98    ) -> Option<LinkLocalUnicastAddr<Ipv6Addr>>;
99}
100
101/// A marker context for MLD traits to allow for GMP test fakes.
102pub trait MldContextMarker {}
103
104/// The execution context for the Multicast Listener Discovery (MLD) protocol.
105pub trait MldContext<BT: MldBindingsTypes>:
106    DeviceIdContext<AnyDevice> + MldContextMarker + ResourceCounterContext<Self::DeviceId, MldCounters>
107{
108    /// The inner context given to `with_mld_state_mut`.
109    type SendContext<'a>: MldSendContext<BT, DeviceId = Self::DeviceId> + 'a;
110
111    /// Calls the function with a mutable reference to the device's MLD state
112    /// and whether or not MLD is enabled for the `device`.
113    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
123/// A handler for incoming MLD packets.
124///
125/// A blanket implementation is provided for all `C: MldContext`.
126pub trait MldPacketHandler<BC, DeviceId> {
127    /// Receive an MLD packet.
128    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    // MLDv2 Specifies that all received queries & reports with an invalid hop
153    // limit (not equal to 1) should be dropped (See RFC 3810 Section 6.2 and
154    // Section 7.4).
155    //
156    // MLDv1 does not specify the expected behavior when receiving a message
157    // with an invalid hop limit, however it does specify that all senders of
158    // MLDv1 messages must set the hop limit to 1 (See RFC 2710 Section 3). Our
159    // interpretation of this is to drop MLDv1 messages without a hop limit of
160    // 1, as any sender that generates them is violating the RFC.
161    //
162    // This could be considered a violation of the Robustness Principle, but a
163    // it is our belief that a packet with a different hop limit is more likely
164    // to be malicious than a poor implementation. Note that the same rationale
165    // is applied to IGMP.
166    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            // From RFC 2710 section 5:
175            //
176            //  - To be valid, the Query message MUST come from a link-
177            //  local IPv6 Source Address [...]
178            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            // From RFC 3810 section 5.1.14:
189            //
190            // If a node (router or host) receives a Query message with
191            // the IPv6 Source Address set to the unspecified address (::), or any
192            // other address that is not a valid IPv6 link-local address, it MUST
193            // silently discard the message.
194            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            // From RFC 3810 section 6.2:
201            //
202            // Upon reception of an MLD message that contains a Query, the node
203            // checks [...] and if the Router Alert option is present in the
204            // Hop-By-Hop Options header of the IPv6 packet. If any of these
205            // checks fails, the packet is dropped.
206            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            // From RFC 2710 section 5:
219            //
220            //  - To be valid, the Report message MUST come from a link-
221            //   local IPv6 Source Address [...]
222            //
223            // However, RFC 3810 allows MLD reports to be sent from
224            // unspecified addresses and we in fact send those, so we relax
225            // to allow accepting from unspecified addresses as well.
226            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/// The MLD mode controllable by the user.
311#[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        // Per [RFC 3810 Section 6]:
323        //
324        // > No MLD messages are ever sent regarding neither the link-scope
325        // > all-nodes multicast address, nor any multicast address of scope 0
326        // > (reserved) or 1 (node-local).
327        //
328        // We abide by this requirement by not executing [`Actions`] on these
329        // addresses. Executing [`Actions`] only produces externally-visible side
330        // effects, and is not required to maintain the correctness of the MLD state
331        // machines.
332        //
333        // [RFC 3810 Section 6]: https://tools.ietf.org/html/rfc3810#section-6
334        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/// Newtype over [`GmpMode`] to tailor it to GMP.
341#[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
350// NB: This could be derived, but it feels better to have it called out
351// explicitly in MLD.
352impl 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
370/// Uninstantiable type marking a [`GmpState`] as having MLD types.
371pub 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                // Warn here, we don't quite have a good global guarantee of
478                // minimal acceptable MTUs across both IPv4 and IPv6. This
479                // should effectively not happen though.
480                //
481                // TODO(https://fxbug.dev/383355972): Consider an assertion here
482                // instead.
483                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    /// The host is trying to operate on an group address of which the host is
540    /// not a member.
541    #[error("the host has not already been a member of the address: {}", addr)]
542    NotAMember { addr: Ipv6Addr },
543    /// Failed to send an IGMP packet.
544    #[error("failed to send out an MLD packet to address: {}", addr)]
545    SendFailure { addr: Ipv6Addr },
546    /// Message ignored because of bad source address.
547    #[error("bad source address: {}", addr)]
548    BadSourceAddress { addr: Ipv6Addr },
549    /// Message ignored because of the router alter option was not present
550    #[error("router alert option not present")]
551    MissingRouterAlert,
552    /// Message ignored because of the hop limit was invalid.
553    #[error("message with incorrect hop limit: {hop_limit}")]
554    BadHopLimit { hop_limit: u8 },
555    /// MLD is disabled
556    #[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
583/// The default value for `unsolicited_report_interval` [RFC 2710 Section 7.10]
584///
585/// [RFC 2710 Section 7.10]: https://tools.ietf.org/html/rfc2710#section-7.10
586pub 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/// An MLD timer to delay the sending of a report.
622#[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    /// Creates a new [`MldTimerId`] for a GMP delayed report on `device`.
632    #[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/// An iterator that generates the IP options for MLD packets.
654///
655/// This allows us to write `new_ip_and_icmp_builders` easily without a big mess
656/// of static lifetimes.
657///
658/// MLD messages require the Router Alert hop-by-hop extension. See [RFC 2710
659/// section 3] , [RFC 3810 section 5].
660///
661/// [RFC 2710 section 2]:
662///     https://datatracker.ietf.org/doc/html/rfc2710#section-3
663/// [RFC 3810 section 5]:
664///     https://datatracker.ietf.org/doc/html/rfc3810#section-5
665#[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
685/// The required IP TTL for MLD messages.
686///
687/// See [RFC 2710 section 3] , [RFC 3810 section 5].
688///
689/// [RFC 2710 section 2]:
690///     https://datatracker.ietf.org/doc/html/rfc2710#section-3
691/// [RFC 3810 section 5]:
692///     https://datatracker.ietf.org/doc/html/rfc3810#section-5
693const 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    // According to https://tools.ietf.org/html/rfc3590#section-4, if a valid
706    // link-local address is not available for the device (e.g., one has not
707    // been configured), the message is sent with the unspecified address (::)
708    // as the IPv6 source address.
709    //
710    // TODO(https://fxbug.dev/42180878): Handle an IPv6 link-local address being
711    // assigned when reports were sent with the unspecified source address.
712    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
724/// Send an MLD packet.
725///
726/// The MLD packet being sent should have its `hop_limit` to be 1 and a
727/// `RouterAlert` option in its Hop-by-Hop Options extensions header.
728fn 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/// Statistics about MLD.
753///
754/// The counter type `C` is generic to facilitate testing.
755#[derive(Debug, Default)]
756#[cfg_attr(test, derive(PartialEq))]
757pub struct MldCounters<C = Counter> {
758    /// Count of MLDv1 queries received.
759    rx_mldv1_query: C,
760    /// Count of MLDv2 queries received.
761    rx_mldv2_query: C,
762    /// Count of MLDv1 reports received.
763    rx_mldv1_report: C,
764    /// Count of MLDv2 reports received.
765    rx_mldv2_report: C,
766    /// Count of Leave Group messages received.
767    rx_leave_group: C,
768    /// Count of MLD messages received with an invalid source address.
769    rx_err_bad_src_addr: C,
770    /// Count of MLD messages received with an invalid hop limit.
771    rx_err_bad_hop_limit: C,
772    /// Count of MLD messages received without the Router Alert option.
773    rx_err_missing_router_alert: C,
774    /// Count of MLDv1 reports sent.
775    tx_mldv1_report: C,
776    /// Count of MLDv2 reports sent.
777    tx_mldv2_report: C,
778    /// Count of Leave Group messages sent.
779    tx_leave_group: C,
780    /// Count of MLD messages that could not be sent.
781    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    /// Metadata for sending an MLD packet in an IP packet.
858    #[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    /// A fake [`MldContext`] that stores the [`MldInterface`] and an optional
871    /// IPv6 link-local address that may be returned in calls to
872    /// [`MldContext::get_ipv6_link_local_addr`].
873    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    /// The parts of `FakeMldCtx` that are behind a RefCell, mocking a lock.
906    struct Shared {
907        groups: MulticastGroupSet<Ipv6Addr, GmpGroupState<Ipv6, FakeBindingsCtxImpl>>,
908        gmp_state: GmpState<Ipv6, MldTypeLayout, FakeBindingsCtxImpl>,
909        config: MldConfig,
910    }
911
912    /// Creates a new test context in MLDv1.
913    ///
914    /// A historical note: a number of tests were originally written when only
915    /// MLDv1 was supported.
916    fn new_mldv1_context() -> FakeCtxImpl {
917        FakeCtxImpl::with_default_bindings_ctx(|bindings_ctx| {
918            // We start with enabled true to make tests easier to write.
919            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            // Most of the test surface is covered by the GMP implementation,
1119            // MLD specific part is mostly passthrough. This test case is here
1120            // because MLD allows a router to ask for report immediately, by
1121            // specifying the `MaxRespDelay` to be 0. If this is the case, the
1122            // host should send the report immediately instead of setting a
1123            // timer.
1124            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    // Ensure the ttl is 1.
1272    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    // Ensure the destination address field in the ICMPv6 packet is correct.
1283    fn ensure_dst_addr(frame: &[u8], ip: Ipv6Addr) {
1284        ensure_slice_addr(frame, 24, 40, ip);
1285    }
1286
1287    // Ensure the multicast address field in the MLD packet is correct.
1288    fn ensure_multicast_addr(frame: &[u8], ip: Ipv6Addr) {
1289        ensure_slice_addr(frame, 56, 72, ip);
1290    }
1291
1292    // Ensure a sent frame meets the requirement.
1293    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        // Ensure the length our payload is 32 = 8 (hbh_ext_hdr) + 24 (mld)
1302        assert_eq!(frame[5], 32);
1303        // Ensure the next header is our HopByHop Extension Header.
1304        assert_eq!(frame[6], 0);
1305        // Ensure there is a RouterAlert HopByHopOption in our sent frame
1306        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            // We should get two MLD reports - one for the unsolicited one for
1337            // the host to turn into Delay Member state and the other one for
1338            // the timer being fired.
1339            assert_eq!(core_ctx.frames().len(), 2);
1340            // The frames are all reports.
1341            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            // The query says that it wants to hear from us immediately.
1370            assert_eq!(core_ctx.frames().len(), 2);
1371            // There should be no timers set.
1372            assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1373            // The frames are all reports.
1374            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            // We have received a query, hence we are falling back to Delay
1412            // Member state.
1413            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            // The frames are all reports.
1427            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            // Since it is an immediate query, we will send a report immediately
1465            // and turn into Idle state again.
1466            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            // No timers!
1473            assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), None);
1474            assert_eq!(core_ctx.frames().len(), 3);
1475            // The frames are all reports.
1476            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        // This seed was carefully chosen to produce a substantial duration
1490        // value below.
1491        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        // This new timer should be sooner.
1515        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        // The frames are all reports.
1520        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            // The initial unsolicited report.
1546            assert_eq!(core_ctx.frames().len(), 1);
1547            assert_eq!(bindings_ctx.trigger_next_timer(&mut core_ctx), Some(TIMER_ID));
1548            // The report after the delay.
1549            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            // Our leave message.
1555            assert_eq!(core_ctx.frames().len(), 3);
1556            // The first two messages should be reports.
1557            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            // The last one should be the done message whose destination is all
1562            // routers.
1563            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            // The report should be discarded because we have received from someone
1595            // else.
1596            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            // A leave message is not sent.
1602            assert_eq!(core_ctx.frames().len(), 1);
1603            // The frames are all reports.
1604            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            // Test that we do not perform MLD for addresses that we're supposed
1642            // to skip or when MLD is disabled.
1643            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                // Assert that no observable effects have taken place.
1647                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                // We should join the group but left in the GMP's non-member
1658                // state.
1659                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                // We should have done no state transitions/work.
1664                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                // We should have done no state transitions/work.
1674                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                // We should have left the group but not executed any `Actions`.
1682                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 that we skip executing `Actions` for addresses we're
1696            // supposed to skip.
1697            test(new_ctx(), Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS);
1698            let mut bytes = Ipv6::MULTICAST_SUBNET.network().ipv6_bytes();
1699            // Manually set the "scope" field to 0.
1700            bytes[1] = bytes[1] & 0xF0;
1701            let reserved0 = MulticastAddr::new(Ipv6Addr::from_bytes(bytes)).unwrap();
1702            // Manually set the "scope" field to 1 (interface-local).
1703            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            // Test that we skip executing `Actions` when MLD is disabled on the
1709            // device.
1710            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            // Simple MLD integration test to check that when we call top-level
1721            // multicast join and leave functions, MLD is performed.
1722            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            // Should not perform MLD for the all-nodes address.
1788            //
1789            // As per RFC 3810 Section 6,
1790            //
1791            //   No MLD messages are ever sent regarding neither the link-scope,
1792            //   all-nodes multicast address, nor any multicast address of scope
1793            //   0 (reserved) or 1 (node-local).
1794            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            // Should do nothing.
1823            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            // Should send done message.
1829            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            // Should do nothing.
1848            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            // Should send report message.
1854            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 the basics of MLDv2 report sending.
1876    #[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        // Unspecified is okay however.
2030        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        // Ignore first reports.
2129        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        // No side-effects.
2137        assert_eq!(core_ctx.take_frames(), Vec::new());
2138
2139        // If we receive a v1 query, we'll go into compat mode but still report
2140        // v2 to the user.
2141        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        // Acknowledge query response.
2144        assert_eq!(core_ctx.take_frames().len(), 1);
2145        assert_eq!(core_ctx.gmp_get_mode(&FakeDeviceId), MldConfigMode::V2);
2146
2147        // Even if user attempts to set V2 again we'll keep it in compat.
2148        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        // Forcing V1 mode, however, exits compat.
2156        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}