|
@@ -1,4 +1,12 @@
|
|
-use super::*;
|
|
|
|
|
|
+use core::result::Result;
|
|
|
|
+use heapless::LinearMap;
|
|
|
|
+
|
|
|
|
+#[cfg(feature = "proto-ipv4")]
|
|
|
|
+use super::{check, IpPayload, Packet};
|
|
|
|
+use super::{Interface, InterfaceInner};
|
|
|
|
+use crate::config::IFACE_MAX_MULTICAST_GROUP_COUNT;
|
|
|
|
+use crate::phy::{Device, PacketMeta};
|
|
|
|
+use crate::wire::*;
|
|
|
|
|
|
/// Error type for `join_multicast_group`, `leave_multicast_group`.
|
|
/// Error type for `join_multicast_group`, `leave_multicast_group`.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
@@ -10,6 +18,60 @@ pub enum MulticastError {
|
|
Unaddressable,
|
|
Unaddressable,
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#[cfg(feature = "proto-ipv4")]
|
|
|
|
+pub(crate) enum IgmpReportState {
|
|
|
|
+ Inactive,
|
|
|
|
+ ToGeneralQuery {
|
|
|
|
+ version: IgmpVersion,
|
|
|
|
+ timeout: crate::time::Instant,
|
|
|
|
+ interval: crate::time::Duration,
|
|
|
|
+ next_index: usize,
|
|
|
|
+ },
|
|
|
|
+ ToSpecificQuery {
|
|
|
|
+ version: IgmpVersion,
|
|
|
|
+ timeout: crate::time::Instant,
|
|
|
|
+ group: Ipv4Address,
|
|
|
|
+ },
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
+enum GroupState {
|
|
|
|
+ /// Joining group, we have to send the join packet.
|
|
|
|
+ Joining,
|
|
|
|
+ /// We've already sent the join packet, we have nothing to do.
|
|
|
|
+ Joined,
|
|
|
|
+ /// We want to leave the group, we have to send a leave packet.
|
|
|
|
+ Leaving,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub(crate) struct State {
|
|
|
|
+ groups: LinearMap<IpAddress, GroupState, IFACE_MAX_MULTICAST_GROUP_COUNT>,
|
|
|
|
+ /// When to report for (all or) the next multicast group membership via IGMP
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
|
|
+ igmp_report_state: IgmpReportState,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl State {
|
|
|
|
+ pub(crate) fn new() -> Self {
|
|
|
|
+ Self {
|
|
|
|
+ groups: LinearMap::new(),
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
|
|
+ igmp_report_state: IgmpReportState::Inactive,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pub(crate) fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
|
|
|
|
+ // Return false if we don't have the multicast group,
|
|
|
|
+ // or we're leaving it.
|
|
|
|
+ match self.groups.get(&addr.into()) {
|
|
|
|
+ None => false,
|
|
|
|
+ Some(GroupState::Joining) => true,
|
|
|
|
+ Some(GroupState::Joined) => true,
|
|
|
|
+ Some(GroupState::Leaving) => false,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
impl core::fmt::Display for MulticastError {
|
|
impl core::fmt::Display for MulticastError {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
match self {
|
|
match self {
|
|
@@ -33,16 +95,17 @@ impl Interface {
|
|
return Err(MulticastError::Unaddressable);
|
|
return Err(MulticastError::Unaddressable);
|
|
}
|
|
}
|
|
|
|
|
|
- if let Some(state) = self.inner.multicast_groups.get_mut(&addr) {
|
|
|
|
|
|
+ if let Some(state) = self.inner.multicast.groups.get_mut(&addr) {
|
|
*state = match state {
|
|
*state = match state {
|
|
- MulticastGroupState::Joining => MulticastGroupState::Joining,
|
|
|
|
- MulticastGroupState::Joined => MulticastGroupState::Joined,
|
|
|
|
- MulticastGroupState::Leaving => MulticastGroupState::Joined,
|
|
|
|
|
|
+ GroupState::Joining => GroupState::Joining,
|
|
|
|
+ GroupState::Joined => GroupState::Joined,
|
|
|
|
+ GroupState::Leaving => GroupState::Joined,
|
|
};
|
|
};
|
|
} else {
|
|
} else {
|
|
self.inner
|
|
self.inner
|
|
- .multicast_groups
|
|
|
|
- .insert(addr, MulticastGroupState::Joining)
|
|
|
|
|
|
+ .multicast
|
|
|
|
+ .groups
|
|
|
|
+ .insert(addr, GroupState::Joining)
|
|
.map_err(|_| MulticastError::GroupTableFull)?;
|
|
.map_err(|_| MulticastError::GroupTableFull)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
Ok(())
|
|
@@ -58,15 +121,15 @@ impl Interface {
|
|
return Err(MulticastError::Unaddressable);
|
|
return Err(MulticastError::Unaddressable);
|
|
}
|
|
}
|
|
|
|
|
|
- if let Some(state) = self.inner.multicast_groups.get_mut(&addr) {
|
|
|
|
|
|
+ if let Some(state) = self.inner.multicast.groups.get_mut(&addr) {
|
|
let delete;
|
|
let delete;
|
|
(*state, delete) = match state {
|
|
(*state, delete) = match state {
|
|
- MulticastGroupState::Joining => (MulticastGroupState::Joined, true),
|
|
|
|
- MulticastGroupState::Joined => (MulticastGroupState::Leaving, false),
|
|
|
|
- MulticastGroupState::Leaving => (MulticastGroupState::Leaving, false),
|
|
|
|
|
|
+ GroupState::Joining => (GroupState::Joined, true),
|
|
|
|
+ GroupState::Joined => (GroupState::Leaving, false),
|
|
|
|
+ GroupState::Leaving => (GroupState::Leaving, false),
|
|
};
|
|
};
|
|
if delete {
|
|
if delete {
|
|
- self.inner.multicast_groups.remove(&addr);
|
|
|
|
|
|
+ self.inner.multicast.groups.remove(&addr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
Ok(())
|
|
@@ -82,18 +145,20 @@ impl Interface {
|
|
/// - Send join/leave packets according to the multicast group state.
|
|
/// - Send join/leave packets according to the multicast group state.
|
|
/// - Depending on `igmp_report_state` and the therein contained
|
|
/// - Depending on `igmp_report_state` and the therein contained
|
|
/// timeouts, send IGMP membership reports.
|
|
/// timeouts, send IGMP membership reports.
|
|
- pub(crate) fn multicast_egress<D>(&mut self, device: &mut D) -> bool
|
|
|
|
|
|
+ pub(crate) fn multicast_egress<D>(&mut self, device: &mut D)
|
|
where
|
|
where
|
|
D: Device + ?Sized,
|
|
D: Device + ?Sized,
|
|
{
|
|
{
|
|
// Process multicast joins.
|
|
// Process multicast joins.
|
|
while let Some((&addr, _)) = self
|
|
while let Some((&addr, _)) = self
|
|
.inner
|
|
.inner
|
|
- .multicast_groups
|
|
|
|
|
|
+ .multicast
|
|
|
|
+ .groups
|
|
.iter()
|
|
.iter()
|
|
- .find(|(_, &state)| state == MulticastGroupState::Joining)
|
|
|
|
|
|
+ .find(|(_, &state)| state == GroupState::Joining)
|
|
{
|
|
{
|
|
match addr {
|
|
match addr {
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
IpAddress::Ipv4(addr) => {
|
|
IpAddress::Ipv4(addr) => {
|
|
if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) {
|
|
if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) {
|
|
let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
@@ -126,19 +191,22 @@ impl Interface {
|
|
|
|
|
|
// NOTE(unwrap): this is always replacing an existing entry, so it can't fail due to the map being full.
|
|
// NOTE(unwrap): this is always replacing an existing entry, so it can't fail due to the map being full.
|
|
self.inner
|
|
self.inner
|
|
- .multicast_groups
|
|
|
|
- .insert(addr, MulticastGroupState::Joined)
|
|
|
|
|
|
+ .multicast
|
|
|
|
+ .groups
|
|
|
|
+ .insert(addr, GroupState::Joined)
|
|
.unwrap();
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
|
|
// Process multicast leaves.
|
|
// Process multicast leaves.
|
|
while let Some((&addr, _)) = self
|
|
while let Some((&addr, _)) = self
|
|
.inner
|
|
.inner
|
|
- .multicast_groups
|
|
|
|
|
|
+ .multicast
|
|
|
|
+ .groups
|
|
.iter()
|
|
.iter()
|
|
- .find(|(_, &state)| state == MulticastGroupState::Leaving)
|
|
|
|
|
|
+ .find(|(_, &state)| state == GroupState::Leaving)
|
|
{
|
|
{
|
|
match addr {
|
|
match addr {
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
IpAddress::Ipv4(addr) => {
|
|
IpAddress::Ipv4(addr) => {
|
|
if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
|
|
if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
|
|
let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
let Some(tx_token) = device.transmit(self.inner.now) else {
|
|
@@ -169,10 +237,11 @@ impl Interface {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- self.inner.multicast_groups.remove(&addr);
|
|
|
|
|
|
+ self.inner.multicast.groups.remove(&addr);
|
|
}
|
|
}
|
|
|
|
|
|
- match self.inner.igmp_report_state {
|
|
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
|
|
+ match self.inner.multicast.igmp_report_state {
|
|
IgmpReportState::ToSpecificQuery {
|
|
IgmpReportState::ToSpecificQuery {
|
|
version,
|
|
version,
|
|
timeout,
|
|
timeout,
|
|
@@ -185,13 +254,9 @@ impl Interface {
|
|
self.inner
|
|
self.inner
|
|
.dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
|
|
.dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
|
|
.unwrap();
|
|
.unwrap();
|
|
- } else {
|
|
|
|
- return false;
|
|
|
|
|
|
+ self.inner.multicast.igmp_report_state = IgmpReportState::Inactive;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- self.inner.igmp_report_state = IgmpReportState::Inactive;
|
|
|
|
- true
|
|
|
|
}
|
|
}
|
|
IgmpReportState::ToGeneralQuery {
|
|
IgmpReportState::ToGeneralQuery {
|
|
version,
|
|
version,
|
|
@@ -201,7 +266,8 @@ impl Interface {
|
|
} if self.inner.now >= timeout => {
|
|
} if self.inner.now >= timeout => {
|
|
let addr = self
|
|
let addr = self
|
|
.inner
|
|
.inner
|
|
- .multicast_groups
|
|
|
|
|
|
+ .multicast
|
|
|
|
+ .groups
|
|
.iter()
|
|
.iter()
|
|
.filter_map(|(addr, _)| match addr {
|
|
.filter_map(|(addr, _)| match addr {
|
|
IpAddress::Ipv4(addr) => Some(*addr),
|
|
IpAddress::Ipv4(addr) => Some(*addr),
|
|
@@ -224,28 +290,24 @@ impl Interface {
|
|
&mut self.fragmenter,
|
|
&mut self.fragmenter,
|
|
)
|
|
)
|
|
.unwrap();
|
|
.unwrap();
|
|
- } else {
|
|
|
|
- return false;
|
|
|
|
|
|
+
|
|
|
|
+ let next_timeout = (timeout + interval).max(self.inner.now);
|
|
|
|
+ self.inner.multicast.igmp_report_state =
|
|
|
|
+ IgmpReportState::ToGeneralQuery {
|
|
|
|
+ version,
|
|
|
|
+ timeout: next_timeout,
|
|
|
|
+ interval,
|
|
|
|
+ next_index: next_index + 1,
|
|
|
|
+ };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- let next_timeout = (timeout + interval).max(self.inner.now);
|
|
|
|
- self.inner.igmp_report_state = IgmpReportState::ToGeneralQuery {
|
|
|
|
- version,
|
|
|
|
- timeout: next_timeout,
|
|
|
|
- interval,
|
|
|
|
- next_index: next_index + 1,
|
|
|
|
- };
|
|
|
|
- true
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
None => {
|
|
None => {
|
|
- self.inner.igmp_report_state = IgmpReportState::Inactive;
|
|
|
|
- false
|
|
|
|
|
|
+ self.inner.multicast.igmp_report_state = IgmpReportState::Inactive;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- _ => false,
|
|
|
|
|
|
+ _ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -256,11 +318,14 @@ impl InterfaceInner {
|
|
/// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries.
|
|
/// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries.
|
|
/// Membership must not be reported immediately in order to avoid flooding the network
|
|
/// Membership must not be reported immediately in order to avoid flooding the network
|
|
/// after a query is broadcasted by a router; this is not currently done.
|
|
/// after a query is broadcasted by a router; this is not currently done.
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
pub(super) fn process_igmp<'frame>(
|
|
pub(super) fn process_igmp<'frame>(
|
|
&mut self,
|
|
&mut self,
|
|
ipv4_repr: Ipv4Repr,
|
|
ipv4_repr: Ipv4Repr,
|
|
ip_payload: &'frame [u8],
|
|
ip_payload: &'frame [u8],
|
|
) -> Option<Packet<'frame>> {
|
|
) -> Option<Packet<'frame>> {
|
|
|
|
+ use crate::time::Duration;
|
|
|
|
+
|
|
let igmp_packet = check!(IgmpPacket::new_checked(ip_payload));
|
|
let igmp_packet = check!(IgmpPacket::new_checked(ip_payload));
|
|
let igmp_repr = check!(IgmpRepr::parse(&igmp_packet));
|
|
let igmp_repr = check!(IgmpRepr::parse(&igmp_packet));
|
|
|
|
|
|
@@ -276,7 +341,8 @@ impl InterfaceInner {
|
|
&& ipv4_repr.dst_addr == Ipv4Address::MULTICAST_ALL_SYSTEMS
|
|
&& ipv4_repr.dst_addr == Ipv4Address::MULTICAST_ALL_SYSTEMS
|
|
{
|
|
{
|
|
let ipv4_multicast_group_count = self
|
|
let ipv4_multicast_group_count = self
|
|
- .multicast_groups
|
|
|
|
|
|
+ .multicast
|
|
|
|
+ .groups
|
|
.keys()
|
|
.keys()
|
|
.filter(|a| matches!(a, IpAddress::Ipv4(_)))
|
|
.filter(|a| matches!(a, IpAddress::Ipv4(_)))
|
|
.count();
|
|
.count();
|
|
@@ -293,7 +359,7 @@ impl InterfaceInner {
|
|
max_resp_time / intervals
|
|
max_resp_time / intervals
|
|
}
|
|
}
|
|
};
|
|
};
|
|
- self.igmp_report_state = IgmpReportState::ToGeneralQuery {
|
|
|
|
|
|
+ self.multicast.igmp_report_state = IgmpReportState::ToGeneralQuery {
|
|
version,
|
|
version,
|
|
timeout: self.now + interval,
|
|
timeout: self.now + interval,
|
|
interval,
|
|
interval,
|
|
@@ -305,7 +371,7 @@ impl InterfaceInner {
|
|
if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr {
|
|
if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr {
|
|
// Don't respond immediately
|
|
// Don't respond immediately
|
|
let timeout = max_resp_time / 4;
|
|
let timeout = max_resp_time / 4;
|
|
- self.igmp_report_state = IgmpReportState::ToSpecificQuery {
|
|
|
|
|
|
+ self.multicast.igmp_report_state = IgmpReportState::ToSpecificQuery {
|
|
version,
|
|
version,
|
|
timeout: self.now + timeout,
|
|
timeout: self.now + timeout,
|
|
group: group_addr,
|
|
group: group_addr,
|
|
@@ -321,4 +387,47 @@ impl InterfaceInner {
|
|
|
|
|
|
None
|
|
None
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
|
|
+ fn igmp_report_packet<'any>(
|
|
|
|
+ &self,
|
|
|
|
+ version: IgmpVersion,
|
|
|
|
+ group_addr: Ipv4Address,
|
|
|
|
+ ) -> Option<Packet<'any>> {
|
|
|
|
+ let iface_addr = self.ipv4_addr()?;
|
|
|
|
+ let igmp_repr = IgmpRepr::MembershipReport {
|
|
|
|
+ group_addr,
|
|
|
|
+ version,
|
|
|
|
+ };
|
|
|
|
+ let pkt = Packet::new_ipv4(
|
|
|
|
+ Ipv4Repr {
|
|
|
|
+ src_addr: iface_addr,
|
|
|
|
+ // Send to the group being reported
|
|
|
|
+ dst_addr: group_addr,
|
|
|
|
+ next_header: IpProtocol::Igmp,
|
|
|
|
+ payload_len: igmp_repr.buffer_len(),
|
|
|
|
+ hop_limit: 1,
|
|
|
|
+ // [#183](https://github.com/m-labs/smoltcp/issues/183).
|
|
|
|
+ },
|
|
|
|
+ IpPayload::Igmp(igmp_repr),
|
|
|
|
+ );
|
|
|
|
+ Some(pkt)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #[cfg(feature = "proto-ipv4")]
|
|
|
|
+ fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option<Packet<'any>> {
|
|
|
|
+ self.ipv4_addr().map(|iface_addr| {
|
|
|
|
+ let igmp_repr = IgmpRepr::LeaveGroup { group_addr };
|
|
|
|
+ Packet::new_ipv4(
|
|
|
|
+ Ipv4Repr {
|
|
|
|
+ src_addr: iface_addr,
|
|
|
|
+ dst_addr: Ipv4Address::MULTICAST_ALL_ROUTERS,
|
|
|
|
+ next_header: IpProtocol::Igmp,
|
|
|
|
+ payload_len: igmp_repr.buffer_len(),
|
|
|
|
+ hop_limit: 1,
|
|
|
|
+ },
|
|
|
|
+ IpPayload::Igmp(igmp_repr),
|
|
|
|
+ )
|
|
|
|
+ })
|
|
|
|
+ }
|
|
}
|
|
}
|