From 612118cedc4ad07eab5c6be46d9241520a970358 Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Fri, 8 Nov 2024 15:59:23 +0100 Subject: [PATCH] feat: Add MLDv2 Listener Query response support This add support for responding to MLDv2 Listener Queries. Both general queries and multicast specific queries are supported. --- src/iface/interface/ipv6.rs | 10 +++ src/iface/interface/multicast.rs | 105 +++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/src/iface/interface/ipv6.rs b/src/iface/interface/ipv6.rs index 96e999f7e..ca8c2ef35 100644 --- a/src/iface/interface/ipv6.rs +++ b/src/iface/interface/ipv6.rs @@ -422,6 +422,16 @@ impl InterfaceInner { #[cfg(feature = "medium-ip")] Medium::Ip => None, }, + #[cfg(feature = "multicast")] + Icmpv6Repr::Mld(repr) => match repr { + // [RFC 3810 ยง 6.2], reception checks + MldRepr::Query { .. } + if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() => + { + self.process_mldv2(ip_repr, repr) + } + _ => None, + }, // Don't report an error if a packet with unknown type // has been handled by an ICMP socket diff --git a/src/iface/interface/multicast.rs b/src/iface/interface/multicast.rs index 68b1c7750..9f92e8901 100644 --- a/src/iface/interface/multicast.rs +++ b/src/iface/interface/multicast.rs @@ -34,6 +34,18 @@ pub(crate) enum IgmpReportState { }, } +#[cfg(feature = "proto-ipv6")] +pub(crate) enum MldReportState { + Inactive, + ToGeneralQuery { + timeout: crate::time::Instant, + }, + ToSpecificQuery { + group: Ipv6Address, + timeout: crate::time::Instant, + }, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum GroupState { /// Joining group, we have to send the join packet. @@ -49,6 +61,8 @@ pub(crate) struct State { /// When to report for (all or) the next multicast group membership via IGMP #[cfg(feature = "proto-ipv4")] igmp_report_state: IgmpReportState, + #[cfg(feature = "proto-ipv6")] + mld_report_state: MldReportState, } impl State { @@ -57,6 +71,8 @@ impl State { groups: LinearMap::new(), #[cfg(feature = "proto-ipv4")] igmp_report_state: IgmpReportState::Inactive, + #[cfg(feature = "proto-ipv6")] + mld_report_state: MldReportState::Inactive, } } @@ -306,6 +322,46 @@ impl Interface { } _ => {} } + #[cfg(feature = "proto-ipv6")] + match self.inner.multicast.mld_report_state { + MldReportState::ToGeneralQuery { timeout } if self.inner.now >= timeout => { + let records = self + .inner + .multicast + .groups + .iter() + .filter_map(|(addr, _)| match addr { + IpAddress::Ipv6(addr) => Some(MldAddressRecordRepr::new( + MldRecordType::ModeIsExclude, + *addr, + )), + #[allow(unreachable_patterns)] + _ => None, + }) + .collect::>(); + if let Some(pkt) = self.inner.mldv2_report_packet(&records) { + if let Some(tx_token) = device.transmit(self.inner.now) { + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + }; + }; + self.inner.multicast.mld_report_state = MldReportState::Inactive; + } + MldReportState::ToSpecificQuery { group, timeout } if self.inner.now >= timeout => { + let record = MldAddressRecordRepr::new(MldRecordType::ModeIsExclude, group); + if let Some(pkt) = self.inner.mldv2_report_packet(&[record]) { + if let Some(tx_token) = device.transmit(self.inner.now) { + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + } + } + self.inner.multicast.mld_report_state = MldReportState::Inactive; + } + _ => {} + } } } @@ -425,4 +481,53 @@ impl InterfaceInner { ) }) } + + /// Host duties of the **MLDv2** protocol. + /// + /// Sets up `mld_report_state` for responding to MLD general/specific membership queries. + /// Membership must not be reported immediately in order to avoid flooding the network + /// after a query is broadcasted by a router; Currently the delay is fixed and not randomized. + #[cfg(feature = "proto-ipv6")] + pub(super) fn process_mldv2<'frame>( + &mut self, + ip_repr: Ipv6Repr, + repr: MldRepr<'frame>, + ) -> Option> { + match repr { + MldRepr::Query { + mcast_addr, + max_resp_code, + .. + } => { + // Do not respont immediately to the query + let delay = crate::time::Duration::from_millis(max_resp_code.into()) / 3; + // General query + if mcast_addr.is_unspecified() + && (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES + || self.has_ip_addr(ip_repr.dst_addr)) + { + let ipv6_multicast_group_count = self + .multicast + .groups + .keys() + .filter(|a| matches!(a, IpAddress::Ipv6(_))) + .count(); + if ipv6_multicast_group_count != 0 { + self.multicast.mld_report_state = MldReportState::ToGeneralQuery { + timeout: self.now + delay, + }; + } + } + if self.has_multicast_group(mcast_addr) && ip_repr.dst_addr == mcast_addr { + self.multicast.mld_report_state = MldReportState::ToSpecificQuery { + group: mcast_addr, + timeout: self.now + delay, + }; + } + None + } + MldRepr::Report { .. } => None, + MldRepr::ReportRecordReprs { .. } => None, + } + } }