|
@@ -4,8 +4,6 @@ use super::*;
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
pub enum MulticastError {
|
|
|
- /// The hardware device transmit buffer is full. Try again later.
|
|
|
- Exhausted,
|
|
|
/// The table of joined multicast groups is already full.
|
|
|
GroupTableFull,
|
|
|
/// Cannot join/leave the given multicast group.
|
|
@@ -15,7 +13,6 @@ pub enum MulticastError {
|
|
|
impl core::fmt::Display for MulticastError {
|
|
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
|
match self {
|
|
|
- MulticastError::Exhausted => write!(f, "Exhausted"),
|
|
|
MulticastError::GroupTableFull => write!(f, "GroupTableFull"),
|
|
|
MulticastError::Unaddressable => write!(f, "Unaddressable"),
|
|
|
}
|
|
@@ -27,138 +24,52 @@ impl std::error::Error for MulticastError {}
|
|
|
|
|
|
impl Interface {
|
|
|
/// Add an address to a list of subscribed multicast IP addresses.
|
|
|
- ///
|
|
|
- /// Returns `Ok(announce_sent)` if the address was added successfully, where `announce_sent`
|
|
|
- /// indicates whether an initial immediate announcement has been sent.
|
|
|
- pub fn join_multicast_group<D, T: Into<IpAddress>>(
|
|
|
+ pub fn join_multicast_group<T: Into<IpAddress>>(
|
|
|
&mut self,
|
|
|
- device: &mut D,
|
|
|
addr: T,
|
|
|
- timestamp: Instant,
|
|
|
- ) -> Result<bool, MulticastError>
|
|
|
- where
|
|
|
- D: Device + ?Sized,
|
|
|
- {
|
|
|
+ ) -> Result<(), MulticastError> {
|
|
|
let addr = addr.into();
|
|
|
- self.inner.now = timestamp;
|
|
|
-
|
|
|
- let is_not_new = self
|
|
|
- .inner
|
|
|
- .multicast_groups
|
|
|
- .insert(addr, ())
|
|
|
- .map_err(|_| MulticastError::GroupTableFull)?
|
|
|
- .is_some();
|
|
|
- if is_not_new {
|
|
|
- return Ok(false);
|
|
|
+ if !addr.is_multicast() {
|
|
|
+ return Err(MulticastError::Unaddressable);
|
|
|
}
|
|
|
|
|
|
- match addr {
|
|
|
- IpAddress::Ipv4(addr) => {
|
|
|
- if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) {
|
|
|
- // Send initial membership report
|
|
|
- let tx_token = device
|
|
|
- .transmit(timestamp)
|
|
|
- .ok_or(MulticastError::Exhausted)?;
|
|
|
-
|
|
|
- // 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();
|
|
|
-
|
|
|
- Ok(true)
|
|
|
- } else {
|
|
|
- Ok(false)
|
|
|
- }
|
|
|
- }
|
|
|
- #[cfg(feature = "proto-ipv6")]
|
|
|
- IpAddress::Ipv6(addr) => {
|
|
|
- // Build report packet containing this new address
|
|
|
- if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new(
|
|
|
- MldRecordType::ChangeToInclude,
|
|
|
- addr,
|
|
|
- )]) {
|
|
|
- // Send initial membership report
|
|
|
- let tx_token = device
|
|
|
- .transmit(timestamp)
|
|
|
- .ok_or(MulticastError::Exhausted)?;
|
|
|
-
|
|
|
- // 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();
|
|
|
-
|
|
|
- Ok(true)
|
|
|
- } else {
|
|
|
- Ok(false)
|
|
|
- }
|
|
|
- }
|
|
|
- #[allow(unreachable_patterns)]
|
|
|
- _ => Err(MulticastError::Unaddressable),
|
|
|
+ if let Some(state) = self.inner.multicast_groups.get_mut(&addr) {
|
|
|
+ *state = match state {
|
|
|
+ MulticastGroupState::Joining => MulticastGroupState::Joining,
|
|
|
+ MulticastGroupState::Joined => MulticastGroupState::Joined,
|
|
|
+ MulticastGroupState::Leaving => MulticastGroupState::Joined,
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ self.inner
|
|
|
+ .multicast_groups
|
|
|
+ .insert(addr, MulticastGroupState::Joining)
|
|
|
+ .map_err(|_| MulticastError::GroupTableFull)?;
|
|
|
}
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
/// Remove an address from the subscribed multicast IP addresses.
|
|
|
- ///
|
|
|
- /// Returns `Ok(leave_sent)` if the address was removed successfully, where `leave_sent`
|
|
|
- /// indicates whether an immediate leave packet has been sent.
|
|
|
- pub fn leave_multicast_group<D, T: Into<IpAddress>>(
|
|
|
+ pub fn leave_multicast_group<T: Into<IpAddress>>(
|
|
|
&mut self,
|
|
|
- device: &mut D,
|
|
|
addr: T,
|
|
|
- timestamp: Instant,
|
|
|
- ) -> Result<bool, MulticastError>
|
|
|
- where
|
|
|
- D: Device + ?Sized,
|
|
|
- {
|
|
|
+ ) -> Result<(), MulticastError> {
|
|
|
let addr = addr.into();
|
|
|
- self.inner.now = timestamp;
|
|
|
- let was_not_present = self.inner.multicast_groups.remove(&addr).is_none();
|
|
|
- if was_not_present {
|
|
|
- return Ok(false);
|
|
|
+ if !addr.is_multicast() {
|
|
|
+ return Err(MulticastError::Unaddressable);
|
|
|
}
|
|
|
|
|
|
- match addr {
|
|
|
- IpAddress::Ipv4(addr) => {
|
|
|
- if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
|
|
|
- // Send group leave packet
|
|
|
- let tx_token = device
|
|
|
- .transmit(timestamp)
|
|
|
- .ok_or(MulticastError::Exhausted)?;
|
|
|
-
|
|
|
- // 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();
|
|
|
-
|
|
|
- Ok(true)
|
|
|
- } else {
|
|
|
- Ok(false)
|
|
|
- }
|
|
|
+ if let Some(state) = self.inner.multicast_groups.get_mut(&addr) {
|
|
|
+ let delete;
|
|
|
+ (*state, delete) = match state {
|
|
|
+ MulticastGroupState::Joining => (MulticastGroupState::Joined, true),
|
|
|
+ MulticastGroupState::Joined => (MulticastGroupState::Leaving, false),
|
|
|
+ MulticastGroupState::Leaving => (MulticastGroupState::Leaving, false),
|
|
|
+ };
|
|
|
+ if delete {
|
|
|
+ self.inner.multicast_groups.remove(&addr);
|
|
|
}
|
|
|
- #[cfg(feature = "proto-ipv6")]
|
|
|
- IpAddress::Ipv6(addr) => {
|
|
|
- if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new(
|
|
|
- MldRecordType::ChangeToExclude,
|
|
|
- addr,
|
|
|
- )]) {
|
|
|
- // Send group leave packet
|
|
|
- let tx_token = device
|
|
|
- .transmit(timestamp)
|
|
|
- .ok_or(MulticastError::Exhausted)?;
|
|
|
-
|
|
|
- // 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();
|
|
|
-
|
|
|
- Ok(true)
|
|
|
- } else {
|
|
|
- Ok(false)
|
|
|
- }
|
|
|
- }
|
|
|
- #[allow(unreachable_patterns)]
|
|
|
- _ => Err(MulticastError::Unaddressable),
|
|
|
}
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
/// Check whether the interface listens to given destination multicast IP address.
|
|
@@ -166,12 +77,101 @@ impl Interface {
|
|
|
self.inner.has_multicast_group(addr)
|
|
|
}
|
|
|
|
|
|
- /// Depending on `igmp_report_state` and the therein contained
|
|
|
- /// timeouts, send IGMP membership reports.
|
|
|
- pub(crate) fn igmp_egress<D>(&mut self, device: &mut D) -> bool
|
|
|
+ /// Do multicast egress.
|
|
|
+ ///
|
|
|
+ /// - Send join/leave packets according to the multicast group state.
|
|
|
+ /// - Depending on `igmp_report_state` and the therein contained
|
|
|
+ /// timeouts, send IGMP membership reports.
|
|
|
+ pub(crate) fn multicast_egress<D>(&mut self, device: &mut D) -> bool
|
|
|
where
|
|
|
D: Device + ?Sized,
|
|
|
{
|
|
|
+ // Process multicast joins.
|
|
|
+ while let Some((&addr, _)) = self
|
|
|
+ .inner
|
|
|
+ .multicast_groups
|
|
|
+ .iter()
|
|
|
+ .find(|(_, &state)| state == MulticastGroupState::Joining)
|
|
|
+ {
|
|
|
+ match addr {
|
|
|
+ IpAddress::Ipv4(addr) => {
|
|
|
+ if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) {
|
|
|
+ let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
|
+ break;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ IpAddress::Ipv6(addr) => {
|
|
|
+ if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new(
|
|
|
+ MldRecordType::ChangeToInclude,
|
|
|
+ addr,
|
|
|
+ )]) {
|
|
|
+ let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
|
+ break;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // NOTE(unwrap): this is always replacing an existing entry, so it can't fail due to the map being full.
|
|
|
+ self.inner
|
|
|
+ .multicast_groups
|
|
|
+ .insert(addr, MulticastGroupState::Joined)
|
|
|
+ .unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Process multicast leaves.
|
|
|
+ while let Some((&addr, _)) = self
|
|
|
+ .inner
|
|
|
+ .multicast_groups
|
|
|
+ .iter()
|
|
|
+ .find(|(_, &state)| state == MulticastGroupState::Leaving)
|
|
|
+ {
|
|
|
+ match addr {
|
|
|
+ IpAddress::Ipv4(addr) => {
|
|
|
+ if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
|
|
|
+ let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
|
+ break;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ IpAddress::Ipv6(addr) => {
|
|
|
+ if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new(
|
|
|
+ MldRecordType::ChangeToExclude,
|
|
|
+ addr,
|
|
|
+ )]) {
|
|
|
+ let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
|
+ break;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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_groups.remove(&addr);
|
|
|
+ }
|
|
|
+
|
|
|
match self.inner.igmp_report_state {
|
|
|
IgmpReportState::ToSpecificQuery {
|
|
|
version,
|