Skip to content

Commit

Permalink
feat: Add MLDv2 Listener Query response support
Browse files Browse the repository at this point in the history
This add support for responding to MLDv2 Listener Queries. Both general
queries and multicast specific queries are supported.
  • Loading branch information
bergzand committed Nov 8, 2024
1 parent 3e61c90 commit 612118c
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/iface/interface/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions src/iface/interface/multicast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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::<heapless::Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT>>();
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;
}
_ => {}
}
}
}

Expand Down Expand Up @@ -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<Packet<'frame>> {
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,
}
}
}

0 comments on commit 612118c

Please sign in to comment.