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