Skip to content

Commit 6c9cbdb

Browse files
committed
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.
1 parent 3e61c90 commit 6c9cbdb

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

src/iface/interface/ipv6.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,16 @@ impl InterfaceInner {
422422
#[cfg(feature = "medium-ip")]
423423
Medium::Ip => None,
424424
},
425+
#[cfg(feature = "multicast")]
426+
Icmpv6Repr::Mld(repr) => match repr {
427+
// [RFC 3810 § 6.2], reception checks
428+
MldRepr::Query { .. }
429+
if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() =>
430+
{
431+
self.process_mldv2(ip_repr, repr)
432+
}
433+
_ => None,
434+
},
425435

426436
// Don't report an error if a packet with unknown type
427437
// has been handled by an ICMP socket

src/iface/interface/multicast.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ pub(crate) enum IgmpReportState {
3434
},
3535
}
3636

37+
#[cfg(feature = "proto-ipv6")]
38+
pub(crate) enum MldReportState {
39+
Inactive,
40+
ToGeneralQuery {
41+
timeout: crate::time::Instant,
42+
},
43+
ToSpecificQuery {
44+
group: Ipv6Address,
45+
timeout: crate::time::Instant,
46+
},
47+
}
48+
3749
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3850
enum GroupState {
3951
/// Joining group, we have to send the join packet.
@@ -49,6 +61,7 @@ pub(crate) struct State {
4961
/// When to report for (all or) the next multicast group membership via IGMP
5062
#[cfg(feature = "proto-ipv4")]
5163
igmp_report_state: IgmpReportState,
64+
mld_report_state: MldReportState,
5265
}
5366

5467
impl State {
@@ -57,6 +70,7 @@ impl State {
5770
groups: LinearMap::new(),
5871
#[cfg(feature = "proto-ipv4")]
5972
igmp_report_state: IgmpReportState::Inactive,
73+
mld_report_state: MldReportState::Inactive,
6074
}
6175
}
6276

@@ -306,6 +320,46 @@ impl Interface {
306320
}
307321
_ => {}
308322
}
323+
#[cfg(feature = "proto-ipv6")]
324+
match self.inner.multicast.mld_report_state {
325+
MldReportState::ToGeneralQuery { timeout } if self.inner.now >= timeout => {
326+
let records = self
327+
.inner
328+
.multicast
329+
.groups
330+
.iter()
331+
.filter_map(|(addr, _)| match addr {
332+
IpAddress::Ipv6(addr) => Some(MldAddressRecordRepr::new(
333+
MldRecordType::ModeIsExclude,
334+
*addr,
335+
)),
336+
#[allow(unreachable_patterns)]
337+
_ => None,
338+
})
339+
.collect::<Vec<_>>();
340+
if let Some(pkt) = self.inner.mldv2_report_packet(&records) {
341+
if let Some(tx_token) = device.transmit(self.inner.now) {
342+
self.inner
343+
.dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
344+
.unwrap();
345+
};
346+
};
347+
self.inner.multicast.mld_report_state = MldReportState::Inactive;
348+
}
349+
MldReportState::ToSpecificQuery { group, timeout } if self.inner.now >= timeout => {
350+
let record = MldAddressRecordRepr::new(MldRecordType::ModeIsExclude, group);
351+
if let Some(pkt) = self.inner.mldv2_report_packet(&[record]) {
352+
if let Some(tx_token) = device.transmit(self.inner.now) {
353+
// NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
354+
self.inner
355+
.dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
356+
.unwrap();
357+
}
358+
}
359+
self.inner.multicast.mld_report_state = MldReportState::Inactive;
360+
}
361+
_ => {}
362+
}
309363
}
310364
}
311365

@@ -425,4 +479,53 @@ impl InterfaceInner {
425479
)
426480
})
427481
}
482+
483+
/// Host duties of the **MLDv2** protocol.
484+
///
485+
/// Sets up `mld_report_state` for responding to MLD general/specific membership queries.
486+
/// Membership must not be reported immediately in order to avoid flooding the network
487+
/// after a query is broadcasted by a router; Currently the delay is fixed and not randomized.
488+
#[cfg(feature = "proto-ipv6")]
489+
pub(super) fn process_mldv2<'frame>(
490+
&mut self,
491+
ip_repr: Ipv6Repr,
492+
repr: MldRepr<'frame>,
493+
) -> Option<Packet<'frame>> {
494+
match repr {
495+
MldRepr::Query {
496+
mcast_addr,
497+
max_resp_code,
498+
..
499+
} => {
500+
// Do not respont immediately to the query
501+
let delay = crate::time::Duration::from_millis(max_resp_code.into()) / 3;
502+
// General query
503+
if mcast_addr.is_unspecified()
504+
&& (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES
505+
|| self.has_ip_addr(ip_repr.dst_addr))
506+
{
507+
let ipv6_multicast_group_count = self
508+
.multicast
509+
.groups
510+
.keys()
511+
.filter(|a| matches!(a, IpAddress::Ipv6(_)))
512+
.count();
513+
if ipv6_multicast_group_count != 0 {
514+
self.multicast.mld_report_state = MldReportState::ToGeneralQuery {
515+
timeout: self.now + delay,
516+
};
517+
}
518+
}
519+
if self.has_multicast_group(mcast_addr) && ip_repr.dst_addr == mcast_addr {
520+
self.multicast.mld_report_state = MldReportState::ToSpecificQuery {
521+
group: mcast_addr,
522+
timeout: self.now + delay,
523+
};
524+
}
525+
None
526+
}
527+
MldRepr::Report { .. } => None,
528+
MldRepr::ReportRecordReprs { .. } => None,
529+
}
530+
}
428531
}

0 commit comments

Comments
 (0)