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;
26use packet::serialize::{PacketBuilder, Serializer};
27use packet_formats::icmp::mld::{
28    MldPacket, Mldv1Body, Mldv1MessageBuilder, Mldv1MessageType, Mldv2QueryBody,
29    Mldv2ReportMessageBuilder, MulticastListenerDone, MulticastListenerReport,
30    MulticastListenerReportV2,
31};
32use packet_formats::icmp::{IcmpMessage, IcmpPacketBuilder, IcmpSenderZeroCode};
33use packet_formats::ip::Ipv6Proto;
34use packet_formats::ipv6::ext_hdrs::{
35    ExtensionHeaderOptionAction, HopByHopOption, HopByHopOptionData,
36};
37use packet_formats::ipv6::{Ipv6PacketBuilder, Ipv6PacketBuilderWithHbhOptions};
38use packet_formats::utils::NonZeroDuration;
39use thiserror::Error;
40use zerocopy::SplitByteSlice;
41
42use crate::internal::base::{IpDeviceMtuContext, IpLayerHandler, IpPacketDestination};
43use crate::internal::gmp::{
44    self, GmpBindingsContext, GmpBindingsTypes, GmpContext, GmpContextInner, GmpEnabledGroup,
45    GmpGroupState, GmpMode, GmpState, GmpStateContext, GmpStateRef, GmpTimerId, GmpTypeLayout,
46    IpExt, MulticastGroupSet, NotAMemberErr,
47};
48use crate::internal::local_delivery::IpHeaderInfo;
49
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                    MldMessage::ListenerReport { group_addr },
432                )
433            }
434            gmp::v1::GmpMessageType::Leave => {
435                self.increment_both(device, |counters: &MldCounters| &counters.tx_leave_group);
436                send_mld_v1_packet::<_, _>(
437                    self,
438                    bindings_ctx,
439                    device,
440                    Ipv6::ALL_ROUTERS_LINK_LOCAL_MULTICAST_ADDRESS,
441                    MldMessage::ListenerDone { group_addr },
442                )
443            }
444        };
445
446        match result {
447            Ok(()) => {}
448            Err(err) => {
449                self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
450                debug!(
451                    "error sending MLD message ({msg_type:?}) on device {device:?} for group \
452                {group_addr}: {err}",
453                )
454            }
455        }
456    }
457
458    fn send_report_v2(
459        &mut self,
460        bindings_ctx: &mut BC,
461        device: &Self::DeviceId,
462        groups: impl Iterator<Item: gmp::v2::VerifiedReportGroupRecord<Ipv6Addr> + Clone> + Clone,
463    ) {
464        let dst_ip = ALL_MLDV2_CAPABLE_ROUTERS;
465        let (ipv6, icmp) =
466            new_ip_and_icmp_builders(self, device, dst_ip, MulticastListenerReportV2);
467        let header = ipv6.constraints().header_len() + icmp.constraints().header_len();
468        let avail_len = usize::from(self.get_mtu(device)).saturating_sub(header);
469        let reports = match Mldv2ReportMessageBuilder::new(groups).with_len_limits(avail_len) {
470            Ok(msg) => msg,
471            Err(e) => {
472                self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
473                // Warn here, we don't quite have a good global guarantee of
474                // minimal acceptable MTUs across both IPv4 and IPv6. This
475                // should effectively not happen though.
476                //
477                // TODO(https://fxbug.dev/383355972): Consider an assertion here
478                // instead.
479                error!("MTU too small to send MLD reports: {e:?}");
480                return;
481            }
482        };
483        for report in reports {
484            self.increment_both(device, |counters: &MldCounters| &counters.tx_mldv2_report);
485            let mut report = report.into_serializer().wrap_in(icmp.clone());
486            let report = DynTransportSerializer::new(&mut report);
487            let destination = IpPacketDestination::Multicast(dst_ip);
488            let ip_frame = report.wrap_in(ipv6.clone());
489            IpLayerHandler::send_ip_frame(self, bindings_ctx, device, destination, ip_frame)
490                .unwrap_or_else(|ErrorAndSerializer { error, .. }| {
491                    self.increment_both(device, |counters: &MldCounters| &counters.tx_err);
492                    debug!("failed to send MLDv2 report over {device:?}: {error:?}")
493                });
494        }
495    }
496
497    fn mode_update_from_v1_query<Q: gmp::v1::QueryMessage<Ipv6>>(
498        &mut self,
499        _bindings_ctx: &mut BC,
500        _query: &Q,
501        gmp_state: &GmpState<Ipv6, MldTypeLayout, BC>,
502        _config: &MldConfig,
503    ) -> MldMode {
504        let MldMode(gmp) = &gmp_state.mode;
505        MldMode(gmp.maybe_enter_v1_compat())
506    }
507
508    fn mode_to_config(MldMode(gmp_mode): &MldMode) -> MldConfigMode {
509        match gmp_mode {
510            GmpMode::V2 | GmpMode::V1 { compat: true } => MldConfigMode::V2,
511            GmpMode::V1 { compat: false } => MldConfigMode::V1,
512        }
513    }
514
515    fn config_to_mode(MldMode(cur_mode): &MldMode, config: MldConfigMode) -> MldMode {
516        MldMode(match config {
517            MldConfigMode::V1 => GmpMode::V1 { compat: false },
518            MldConfigMode::V2 => match cur_mode {
519                GmpMode::V1 { compat: true } => *cur_mode,
520                GmpMode::V1 { compat: false } | GmpMode::V2 => GmpMode::V2,
521            },
522        })
523    }
524
525    fn mode_on_disable(MldMode(cur_mode): &MldMode) -> MldMode {
526        MldMode(cur_mode.maybe_exit_v1_compat())
527    }
528
529    fn mode_on_exit_compat() -> MldMode {
530        MldMode(GmpMode::V2)
531    }
532}
533
534#[derive(Debug, Error, Eq, PartialEq)]
535pub(crate) enum MldError {
536    /// The host is trying to operate on an group address of which the host is
537    /// not a member.
538    #[error("the host has not already been a member of the address: {}", addr)]
539    NotAMember { addr: Ipv6Addr },
540    /// Failed to send an IGMP packet.
541    #[error("failed to send out an MLD packet to address: {}", addr)]
542    SendFailure { addr: Ipv6Addr },
543    /// Message ignored because of bad source address.
544    #[error("bad source address: {}", addr)]
545    BadSourceAddress { addr: Ipv6Addr },
546    /// Message ignored because of the router alter option was not present
547    #[error("router alert option not present")]
548    MissingRouterAlert,
549    /// Message ignored because of the hop limit was invalid.
550    #[error("message with incorrect hop limit: {hop_limit}")]
551    BadHopLimit { hop_limit: u8 },
552    /// MLD is disabled
553    #[error("MLD is disabled on interface")]
554    Disabled,
555}
556
557impl From<NotAMemberErr<Ipv6>> for MldError {
558    fn from(NotAMemberErr(addr): NotAMemberErr<Ipv6>) -> Self {
559        Self::NotAMember { addr }
560    }
561}
562
563impl From<gmp::v2::QueryError<Ipv6>> for MldError {
564    fn from(err: gmp::v2::QueryError<Ipv6>) -> Self {
565        match err {
566            gmp::v2::QueryError::NotAMember(addr) => Self::NotAMember { addr },
567            gmp::v2::QueryError::Disabled => Self::Disabled,
568        }
569    }
570}
571
572pub(crate) type MldResult<T> = Result<T, MldError>;
573
574#[derive(Debug)]
575pub struct MldConfig {
576    unsolicited_report_interval: Duration,
577    send_leave_anyway: bool,
578}
579
580/// The default value for `unsolicited_report_interval` [RFC 2710 Section 7.10]
581///
582/// [RFC 2710 Section 7.10]: https://tools.ietf.org/html/rfc2710#section-7.10
583pub const MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL: Duration = Duration::from_secs(10);
584
585impl Default for MldConfig {
586    fn default() -> Self {
587        MldConfig {
588            unsolicited_report_interval: MLD_DEFAULT_UNSOLICITED_REPORT_INTERVAL,
589            send_leave_anyway: false,
590        }
591    }
592}
593
594impl gmp::v1::ProtocolConfig for MldConfig {
595    fn unsolicited_report_interval(&self) -> Duration {
596        self.unsolicited_report_interval
597    }
598
599    fn send_leave_anyway(&self) -> bool {
600        self.send_leave_anyway
601    }
602
603    fn get_max_resp_time(&self, resp_time: Duration) -> Option<NonZeroDuration> {
604        NonZeroDuration::new(resp_time)
605    }
606}
607
608impl gmp::v2::ProtocolConfig for MldConfig {
609    fn query_response_interval(&self) -> NonZeroDuration {
610        gmp::v2::DEFAULT_QUERY_RESPONSE_INTERVAL
611    }
612
613    fn unsolicited_report_interval(&self) -> NonZeroDuration {
614        gmp::v2::DEFAULT_UNSOLICITED_REPORT_INTERVAL
615    }
616}
617
618/// An MLD timer to delay the sending of a report.
619#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
620pub struct MldTimerId<D: WeakDeviceIdentifier>(GmpTimerId<Ipv6, D>);
621
622impl<D: WeakDeviceIdentifier> MldTimerId<D> {
623    pub(crate) fn device_id(&self) -> &D {
624        let Self(this) = self;
625        this.device_id()
626    }
627
628    /// Creates a new [`MldTimerId`] for a GMP delayed report on `device`.
629    #[cfg(any(test, feature = "testutils"))]
630    pub fn new(device: D) -> Self {
631        Self(GmpTimerId { device, _marker: Default::default() })
632    }
633}
634
635impl<D: WeakDeviceIdentifier> From<GmpTimerId<Ipv6, D>> for MldTimerId<D> {
636    fn from(id: GmpTimerId<Ipv6, D>) -> MldTimerId<D> {
637        MldTimerId(id)
638    }
639}
640
641impl<BC: MldBindingsContext, CC: MldContext<BC>> HandleableTimer<CC, BC>
642    for MldTimerId<CC::WeakDeviceId>
643{
644    fn handle(self, core_ctx: &mut CC, bindings_ctx: &mut BC, _: BC::UniqueTimerId) {
645        let Self(id) = self;
646        gmp::handle_timer(core_ctx, bindings_ctx, id);
647    }
648}
649
650/// An iterator that generates the IP options for MLD packets.
651///
652/// This allows us to write `new_ip_and_icmp_builders` easily without a big mess
653/// of static lifetimes.
654///
655/// MLD messages require the Router Alert hop-by-hop extension. See [RFC 2710
656/// section 3] , [RFC 3810 section 5].
657///
658/// [RFC 2710 section 2]:
659///     https://datatracker.ietf.org/doc/html/rfc2710#section-3
660/// [RFC 3810 section 5]:
661///     https://datatracker.ietf.org/doc/html/rfc3810#section-5
662#[derive(Debug, Clone, Default)]
663struct MldIpOptions(bool);
664
665impl Iterator for MldIpOptions {
666    type Item = HopByHopOption<'static>;
667
668    fn next(&mut self) -> Option<Self::Item> {
669        let Self(yielded) = self;
670        if core::mem::replace(yielded, true) {
671            None
672        } else {
673            Some(HopByHopOption {
674                action: ExtensionHeaderOptionAction::SkipAndContinue,
675                mutable: false,
676                data: HopByHopOptionData::RouterAlert { data: 0 },
677            })
678        }
679    }
680}
681
682/// The required IP TTL for MLD messages.
683///
684/// See [RFC 2710 section 3] , [RFC 3810 section 5].
685///
686/// [RFC 2710 section 2]:
687///     https://datatracker.ietf.org/doc/html/rfc2710#section-3
688/// [RFC 3810 section 5]:
689///     https://datatracker.ietf.org/doc/html/rfc3810#section-5
690const MLD_IP_HOP_LIMIT: u8 = 1;
691
692fn new_ip_and_icmp_builders<
693    BC: MldBindingsContext,
694    CC: MldSendContext<BC>,
695    M: IcmpMessage<Ipv6, Code = IcmpSenderZeroCode> + filter::IcmpMessage<Ipv6>,
696>(
697    core_ctx: &mut CC,
698    device: &CC::DeviceId,
699    dst_ip: MulticastAddr<Ipv6Addr>,
700    msg: M,
701) -> (Ipv6PacketBuilderWithHbhOptions<'static, MldIpOptions>, IcmpPacketBuilder<Ipv6, M>) {
702    // According to https://tools.ietf.org/html/rfc3590#section-4, if a valid
703    // link-local address is not available for the device (e.g., one has not
704    // been configured), the message is sent with the unspecified address (::)
705    // as the IPv6 source address.
706    //
707    // TODO(https://fxbug.dev/42180878): Handle an IPv6 link-local address being
708    // assigned when reports were sent with the unspecified source address.
709    let src_ip =
710        core_ctx.get_ipv6_link_local_addr(device).map_or(Ipv6::UNSPECIFIED_ADDRESS, |x| x.get());
711
712    let ipv6 = Ipv6PacketBuilderWithHbhOptions::new(
713        Ipv6PacketBuilder::new(src_ip, dst_ip.get(), MLD_IP_HOP_LIMIT, Ipv6Proto::Icmpv6),
714        MldIpOptions::default(),
715    )
716    .unwrap();
717    let icmp = IcmpPacketBuilder::new(src_ip, dst_ip.get(), IcmpSenderZeroCode, msg);
718    (ipv6, icmp)
719}
720
721/// A type to allow implementing the required filtering traits on a concrete
722/// subset of message types.
723enum MldMessage {
724    ListenerReport { group_addr: <MulticastListenerReport as Mldv1MessageType>::GroupAddr },
725    ListenerDone { group_addr: <MulticastListenerDone as Mldv1MessageType>::GroupAddr },
726}
727
728/// Send an MLD packet.
729///
730/// The MLD packet being sent should have its `hop_limit` to be 1 and a
731/// `RouterAlert` option in its Hop-by-Hop Options extensions header.
732fn send_mld_v1_packet<BC: MldBindingsContext, CC: MldSendContext<BC>>(
733    core_ctx: &mut CC,
734    bindings_ctx: &mut BC,
735    device: &CC::DeviceId,
736    dst_ip: MulticastAddr<Ipv6Addr>,
737    msg: MldMessage,
738) -> MldResult<()> {
739    macro_rules! send {
740        ($type:ty, $struct:expr, $group_addr:expr) => {{
741            let (ipv6, icmp) = new_ip_and_icmp_builders(core_ctx, device, dst_ip, $struct);
742
743            let mut message =
744                Mldv1MessageBuilder::<$type>::new_with_max_resp_delay($group_addr, ())
745                    .into_serializer()
746                    .wrap_in(icmp);
747            let message = DynTransportSerializer::new(&mut message);
748            let ip_frame = message.wrap_in(ipv6);
749            let destination = IpPacketDestination::Multicast(dst_ip);
750            IpLayerHandler::send_ip_frame(core_ctx, bindings_ctx, &device, destination, ip_frame)
751                .map_err(|_| MldError::SendFailure { addr: $group_addr.into() })
752        }};
753    }
754
755    match msg {
756        MldMessage::ListenerReport { group_addr } => {
757            send!(MulticastListenerReport, MulticastListenerReport, group_addr)
758        }
759        MldMessage::ListenerDone { group_addr } => {
760            send!(MulticastListenerDone, MulticastListenerDone, group_addr)
761        }
762    }
763}
764
765/// Statistics about MLD.
766///
767/// The counter type `C` is generic to facilitate testing.
768#[derive(Default, Debug)]
769#[cfg_attr(
770    any(test, feature = "testutils"),
771    derive(PartialEq, netstack3_macros::CounterCollection)
772)]
773pub struct MldCounters<C = Counter> {
774    /// Count of MLDv1 queries received.
775    rx_mldv1_query: C,
776    /// Count of MLDv2 queries received.
777    rx_mldv2_query: C,
778    /// Count of MLDv1 reports received.
779    rx_mldv1_report: C,
780    /// Count of MLDv2 reports received.
781    rx_mldv2_report: C,
782    /// Count of Leave Group messages received.
783    rx_leave_group: C,
784    /// Count of MLD messages received with an invalid source address.
785    rx_err_bad_src_addr: C,
786    /// Count of MLD messages received with an invalid hop limit.
787    rx_err_bad_hop_limit: C,
788    /// Count of MLD messages received without the Router Alert option.
789    rx_err_missing_router_alert: C,
790    /// Count of MLDv1 reports sent.
791    tx_mldv1_report: C,
792    /// Count of MLDv2 reports sent.
793    tx_mldv2_report: C,
794    /// Count of Leave Group messages sent.
795    tx_leave_group: C,
796    /// Count of MLD messages that could not be sent.
797    tx_err: C,
798}
799
800impl Inspectable for MldCounters {
801    fn record<I: Inspector>(&self, inspector: &mut I) {
802        let Self {
803            rx_mldv1_query,
804            rx_mldv2_query,
805            rx_mldv1_report,
806            rx_mldv2_report,
807            rx_leave_group,
808            rx_err_bad_src_addr,
809            rx_err_bad_hop_limit,
810            rx_err_missing_router_alert,
811            tx_mldv1_report,
812            tx_mldv2_report,
813            tx_leave_group,
814            tx_err,
815        } = self;
816        inspector.record_child("Rx", |inspector| {
817            inspector.record_counter("MLDv1Query", rx_mldv1_query);
818            inspector.record_counter("MLDv2Query", rx_mldv2_query);
819            inspector.record_counter("MLDv1Report", rx_mldv1_report);
820            inspector.record_counter("MLDv2Report", rx_mldv2_report);
821            inspector.record_counter("LeaveGroup", rx_leave_group);
822            inspector.record_child("Errors", |inspector| {
823                inspector.record_counter("BadSourceAddress", rx_err_bad_src_addr);
824                inspector.record_counter("BadHopLimit", rx_err_bad_hop_limit);
825                inspector.record_counter("MissingRouterAlert", rx_err_missing_router_alert);
826            })
827        });
828        inspector.record_child("Tx", |inspector| {
829            inspector.record_counter("MLDv1Report", tx_mldv1_report);
830            inspector.record_counter("MLDv2Report", tx_mldv2_report);
831            inspector.record_counter("LeaveGroup", tx_leave_group);
832            inspector.record_child("Errors", |inspector| {
833                inspector.record_counter("SendFailed", tx_err);
834            });
835        });
836    }
837}
838
839#[cfg(test)]
840mod tests {
841    use alloc::rc::Rc;
842    use alloc::vec;
843    use alloc::vec::Vec;
844    use core::cell::RefCell;
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        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: Serializer,
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()
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()
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()
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}