From 07f09911bcf36057c5232405cf2687799ed3ec5a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 12 Sep 2024 16:16:08 +0200 Subject: [PATCH] multicast: use single Cargo feature for both ipv4 and ipv6. The multicast feature was somewhat broken before, there was no way to do IPv6-only multicast since a lot of multicast code was gated behind `proto-igmp` which force-enables ipv4. Solution: - Added feature `multicast` which enables multicast for ipv4 and/or ipv6, whatever the user has enabled. - Removed feature `proto-igmp`. IGMP support in `wire` is always built if ipv4 is enabled. This causes no bloat if unused, and the increase in compile time is negligible. `iface` only uses it if the `multicast` feature is enabled. --- Cargo.toml | 9 +- ci.sh | 12 +- src/iface/interface/ipv4.rs | 45 +--- src/iface/interface/mod.rs | 76 ++----- src/iface/interface/{igmp.rs => multicast.rs} | 199 ++++++++++++++---- src/iface/interface/tests/ipv4.rs | 4 +- src/iface/interface/tests/mod.rs | 4 +- src/iface/mod.rs | 4 +- src/iface/packet.rs | 22 +- src/wire/mod.rs | 4 +- 10 files changed, 197 insertions(+), 182 deletions(-) rename src/iface/interface/{igmp.rs => multicast.rs} (66%) diff --git a/Cargo.toml b/Cargo.toml index 2f3e266bd..9d42b06a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,6 @@ defmt = ["dep:defmt", "heapless/defmt-03"] "proto-ipv4" = [] "proto-ipv4-fragmentation" = ["proto-ipv4", "_proto-fragmentation"] -"proto-igmp" = ["proto-ipv4"] "proto-dhcpv4" = ["proto-ipv4"] "proto-ipv6" = [] "proto-ipv6-hbh" = ["proto-ipv6"] @@ -64,6 +63,8 @@ defmt = ["dep:defmt", "heapless/defmt-03"] "proto-ipsec-ah" = [] "proto-ipsec-esp" = [] +"multicast" = [] + "socket" = [] "socket-raw" = ["socket"] "socket-udp" = ["socket"] @@ -96,10 +97,10 @@ default = [ "std", "log", # needed for `cargo test --no-default-features --features default` :/ "medium-ethernet", "medium-ip", "medium-ieee802154", "phy-raw_socket", "phy-tuntap_interface", - "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-dns", + "proto-ipv4", "proto-dhcpv4", "proto-ipv6", "proto-dns", "proto-ipv4-fragmentation", "proto-sixlowpan-fragmentation", "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "socket-dns", "socket-mdns", - "packetmeta-id", "async" + "packetmeta-id", "async", "multicast" ] # Private features @@ -301,7 +302,7 @@ required-features = ["std", "log", "medium-ethernet", "proto-ipv4", "socket-tcp" [[example]] name = "multicast" -required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-igmp", "socket-udp"] +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "multicast", "socket-udp"] [[example]] name = "multicast6" diff --git a/ci.sh b/ci.sh index 9a9cc1f65..d8a6cf680 100755 --- a/ci.sh +++ b/ci.sh @@ -18,10 +18,10 @@ FEATURES_TEST=( "std,medium-ethernet,phy-raw_socket,proto-ipv6,socket-udp,socket-dns" "std,medium-ethernet,phy-tuntap_interface,proto-ipv6,socket-udp" "std,medium-ethernet,proto-ipv4,proto-ipv4-fragmentation,socket-raw,socket-dns" - "std,medium-ethernet,proto-ipv4,proto-igmp,socket-raw,socket-dns" + "std,medium-ethernet,proto-ipv4,multicast,socket-raw,socket-dns" "std,medium-ethernet,proto-ipv4,socket-udp,socket-tcp,socket-dns" "std,medium-ethernet,proto-ipv4,proto-dhcpv4,socket-udp" - "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv6,proto-igmp,proto-rpl,socket-udp,socket-dns" + "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv6,multicast,proto-rpl,socket-udp,socket-dns" "std,medium-ethernet,proto-ipv6,socket-tcp" "std,medium-ethernet,medium-ip,proto-ipv4,socket-icmp,socket-tcp" "std,medium-ip,proto-ipv6,socket-icmp,socket-tcp" @@ -29,7 +29,7 @@ FEATURES_TEST=( "std,medium-ieee802154,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp" "std,medium-ieee802154,proto-rpl,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp" "std,medium-ip,proto-ipv4,proto-ipv6,socket-tcp,socket-udp" - "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv4,proto-ipv6,proto-igmp,proto-rpl,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv4,proto-ipv6,multicast,proto-rpl,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" "std,medium-ieee802154,medium-ip,proto-ipv4,socket-raw" "std,medium-ethernet,proto-ipv4,proto-ipsec,socket-raw" ) @@ -39,9 +39,9 @@ FEATURES_TEST_NIGHTLY=( ) FEATURES_CHECK=( - "medium-ip,medium-ethernet,medium-ieee802154,proto-ipv6,proto-ipv6,proto-igmp,proto-dhcpv4,proto-ipsec,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" - "defmt,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,proto-igmp,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" - "defmt,alloc,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,proto-igmp,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "medium-ip,medium-ethernet,medium-ieee802154,proto-ipv6,proto-ipv6,multicast,proto-dhcpv4,proto-ipsec,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "defmt,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,multicast,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "defmt,alloc,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6,multicast,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" ) test() { diff --git a/src/iface/interface/ipv4.rs b/src/iface/interface/ipv4.rs index e75159ea2..ef5766585 100644 --- a/src/iface/interface/ipv4.rs +++ b/src/iface/interface/ipv4.rs @@ -209,7 +209,7 @@ impl InterfaceInner { match ipv4_repr.next_header { IpProtocol::Icmp => self.process_icmpv4(sockets, ipv4_repr, ip_payload), - #[cfg(feature = "proto-igmp")] + #[cfg(feature = "multicast")] IpProtocol::Igmp => self.process_igmp(ipv4_repr, ip_payload), #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] @@ -464,47 +464,4 @@ impl InterfaceInner { frag.ipv4.frag_offset += payload_len as u16; }) } - - #[cfg(feature = "proto-igmp")] - pub(super) fn igmp_report_packet<'any>( - &self, - version: IgmpVersion, - group_addr: Ipv4Address, - ) -> Option> { - 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-igmp")] - pub(super) fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option> { - 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), - ) - }) - } } diff --git a/src/iface/interface/mod.rs b/src/iface/interface/mod.rs index b2cbdc399..c850d3c8f 100644 --- a/src/iface/interface/mod.rs +++ b/src/iface/interface/mod.rs @@ -17,20 +17,17 @@ mod ipv6; #[cfg(feature = "proto-sixlowpan")] mod sixlowpan; -#[cfg(feature = "proto-igmp")] -mod igmp; +#[cfg(feature = "multicast")] +pub(crate) mod multicast; #[cfg(feature = "socket-tcp")] mod tcp; #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] mod udp; -#[cfg(feature = "proto-igmp")] -pub use igmp::MulticastError; - use super::packet::*; use core::result::Result; -use heapless::{LinearMap, Vec}; +use heapless::Vec; #[cfg(feature = "_proto-fragmentation")] use super::fragmentation::FragKey; @@ -41,10 +38,7 @@ use super::fragmentation::{Fragmenter, FragmentsBuffer}; #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] use super::neighbor::{Answer as NeighborAnswer, Cache as NeighborCache}; use super::socket_set::SocketSet; -use crate::config::{ - IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT, - IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT, -}; +use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT}; use crate::iface::Routes; use crate::phy::PacketMeta; use crate::phy::{ChecksumCapabilities, Device, DeviceCapabilities, Medium, RxToken, TxToken}; @@ -82,16 +76,6 @@ pub struct Interface { fragmenter: Fragmenter, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum MulticastGroupState { - /// 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, -} - /// The device independent part of an Ethernet network interface. /// /// Separating the device from the data required for processing and dispatching makes @@ -121,11 +105,8 @@ pub struct InterfaceInner { ip_addrs: Vec, any_ip: bool, routes: Routes, - #[cfg(any(feature = "proto-igmp", feature = "proto-ipv6"))] - multicast_groups: LinearMap, - /// When to report for (all or) the next multicast group membership via IGMP - #[cfg(feature = "proto-igmp")] - igmp_report_state: IgmpReportState, + #[cfg(feature = "multicast")] + multicast: multicast::State, } /// Configuration structure used for creating a network interface. @@ -234,10 +215,8 @@ impl Interface { routes: Routes::new(), #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] neighbor_cache: NeighborCache::new(), - #[cfg(any(feature = "proto-igmp", feature = "proto-ipv6"))] - multicast_groups: LinearMap::new(), - #[cfg(feature = "proto-igmp")] - igmp_report_state: IgmpReportState::Inactive, + #[cfg(feature = "multicast")] + multicast: multicast::State::new(), #[cfg(feature = "medium-ieee802154")] sequence_no, #[cfg(feature = "medium-ieee802154")] @@ -445,10 +424,8 @@ impl Interface { let mut readiness_may_have_changed = self.socket_ingress(device, sockets); readiness_may_have_changed |= self.socket_egress(device, sockets); - #[cfg(feature = "proto-igmp")] - { - readiness_may_have_changed |= self.multicast_egress(device); - } + #[cfg(feature = "multicast")] + self.multicast_egress(device); readiness_may_have_changed } @@ -755,36 +732,23 @@ impl InterfaceInner { } /// Check whether the interface listens to given destination multicast IP address. - /// - /// If built without feature `proto-igmp` this function will - /// always return `false` when using IPv4. fn has_multicast_group>(&self, addr: T) -> bool { - /// Return false if we don't have the multicast group, - /// or we're leaving it. - fn wanted_state(x: Option<&MulticastGroupState>) -> bool { - match x { - None => false, - Some(MulticastGroupState::Joining) => true, - Some(MulticastGroupState::Joined) => true, - Some(MulticastGroupState::Leaving) => false, - } + let addr = addr.into(); + + #[cfg(feature = "multicast")] + if self.multicast.has_multicast_group(addr) { + return true; } - let addr = addr.into(); match addr { - #[cfg(feature = "proto-igmp")] - IpAddress::Ipv4(key) => { - key == Ipv4Address::MULTICAST_ALL_SYSTEMS - || wanted_state(self.multicast_groups.get(&addr)) - } + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(key) => key == Ipv4Address::MULTICAST_ALL_SYSTEMS, + #[cfg(feature = "proto-rpl")] + IpAddress::Ipv6(Ipv6Address::LINK_LOCAL_ALL_RPL_NODES) => true, #[cfg(feature = "proto-ipv6")] IpAddress::Ipv6(key) => { - key == Ipv6Address::LINK_LOCAL_ALL_NODES - || self.has_solicited_node(key) - || wanted_state(self.multicast_groups.get(&addr)) + key == Ipv6Address::LINK_LOCAL_ALL_NODES || self.has_solicited_node(key) } - #[cfg(feature = "proto-rpl")] - IpAddress::Ipv6(Ipv6Address::LINK_LOCAL_ALL_RPL_NODES) => true, #[allow(unreachable_patterns)] _ => false, } diff --git a/src/iface/interface/igmp.rs b/src/iface/interface/multicast.rs similarity index 66% rename from src/iface/interface/igmp.rs rename to src/iface/interface/multicast.rs index e5506a98e..d66ba4d17 100644 --- a/src/iface/interface/igmp.rs +++ b/src/iface/interface/multicast.rs @@ -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`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -10,6 +18,60 @@ pub enum MulticastError { 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, + /// 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>(&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 { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match self { @@ -33,16 +95,17 @@ impl Interface { 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 { - 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 { self.inner - .multicast_groups - .insert(addr, MulticastGroupState::Joining) + .multicast + .groups + .insert(addr, GroupState::Joining) .map_err(|_| MulticastError::GroupTableFull)?; } Ok(()) @@ -58,15 +121,15 @@ impl Interface { 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; (*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 { - self.inner.multicast_groups.remove(&addr); + self.inner.multicast.groups.remove(&addr); } } Ok(()) @@ -82,18 +145,20 @@ impl Interface { /// - 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(&mut self, device: &mut D) -> bool + pub(crate) fn multicast_egress(&mut self, device: &mut D) where D: Device + ?Sized, { // Process multicast joins. while let Some((&addr, _)) = self .inner - .multicast_groups + .multicast + .groups .iter() - .find(|(_, &state)| state == MulticastGroupState::Joining) + .find(|(_, &state)| state == GroupState::Joining) { match addr { + #[cfg(feature = "proto-ipv4")] 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 { @@ -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. self.inner - .multicast_groups - .insert(addr, MulticastGroupState::Joined) + .multicast + .groups + .insert(addr, GroupState::Joined) .unwrap(); } // Process multicast leaves. while let Some((&addr, _)) = self .inner - .multicast_groups + .multicast + .groups .iter() - .find(|(_, &state)| state == MulticastGroupState::Leaving) + .find(|(_, &state)| state == GroupState::Leaving) { match addr { + #[cfg(feature = "proto-ipv4")] IpAddress::Ipv4(addr) => { if let Some(pkt) = self.inner.igmp_leave_packet(addr) { 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 { version, timeout, @@ -185,13 +254,9 @@ impl Interface { self.inner .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) .unwrap(); - } else { - return false; + self.inner.multicast.igmp_report_state = IgmpReportState::Inactive; } } - - self.inner.igmp_report_state = IgmpReportState::Inactive; - true } IgmpReportState::ToGeneralQuery { version, @@ -201,7 +266,8 @@ impl Interface { } if self.inner.now >= timeout => { let addr = self .inner - .multicast_groups + .multicast + .groups .iter() .filter_map(|(addr, _)| match addr { IpAddress::Ipv4(addr) => Some(*addr), @@ -224,28 +290,24 @@ impl Interface { &mut self.fragmenter, ) .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 => { - 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. /// 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. + #[cfg(feature = "proto-ipv4")] pub(super) fn process_igmp<'frame>( &mut self, ipv4_repr: Ipv4Repr, ip_payload: &'frame [u8], ) -> Option> { + use crate::time::Duration; + let igmp_packet = check!(IgmpPacket::new_checked(ip_payload)); let igmp_repr = check!(IgmpRepr::parse(&igmp_packet)); @@ -276,7 +341,8 @@ impl InterfaceInner { && ipv4_repr.dst_addr == Ipv4Address::MULTICAST_ALL_SYSTEMS { let ipv4_multicast_group_count = self - .multicast_groups + .multicast + .groups .keys() .filter(|a| matches!(a, IpAddress::Ipv4(_))) .count(); @@ -293,7 +359,7 @@ impl InterfaceInner { max_resp_time / intervals } }; - self.igmp_report_state = IgmpReportState::ToGeneralQuery { + self.multicast.igmp_report_state = IgmpReportState::ToGeneralQuery { version, timeout: self.now + interval, interval, @@ -305,7 +371,7 @@ impl InterfaceInner { if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr { // Don't respond immediately let timeout = max_resp_time / 4; - self.igmp_report_state = IgmpReportState::ToSpecificQuery { + self.multicast.igmp_report_state = IgmpReportState::ToSpecificQuery { version, timeout: self.now + timeout, group: group_addr, @@ -321,4 +387,47 @@ impl InterfaceInner { None } + + #[cfg(feature = "proto-ipv4")] + fn igmp_report_packet<'any>( + &self, + version: IgmpVersion, + group_addr: Ipv4Address, + ) -> Option> { + 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> { + 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), + ) + }) + } } diff --git a/src/iface/interface/tests/ipv4.rs b/src/iface/interface/tests/ipv4.rs index c9e100fc8..85fb82c5e 100644 --- a/src/iface/interface/tests/ipv4.rs +++ b/src/iface/interface/tests/ipv4.rs @@ -659,9 +659,9 @@ fn test_icmpv4_socket(#[case] medium: Medium) { #[rstest] #[case(Medium::Ip)] -#[cfg(all(feature = "proto-igmp", feature = "medium-ip"))] +#[cfg(all(feature = "multicast", feature = "medium-ip"))] #[case(Medium::Ethernet)] -#[cfg(all(feature = "proto-igmp", feature = "medium-ethernet"))] +#[cfg(all(feature = "multicast", feature = "medium-ethernet"))] fn test_handle_igmp(#[case] medium: Medium) { fn recv_igmp( device: &mut crate::tests::TestingDevice, diff --git a/src/iface/interface/tests/mod.rs b/src/iface/interface/tests/mod.rs index 3cb5e103c..db8cc89dd 100644 --- a/src/iface/interface/tests/mod.rs +++ b/src/iface/interface/tests/mod.rs @@ -5,7 +5,7 @@ mod ipv6; #[cfg(feature = "proto-sixlowpan")] mod sixlowpan; -#[cfg(feature = "proto-igmp")] +#[allow(unused)] use std::vec::Vec; use crate::tests::setup; @@ -27,7 +27,7 @@ fn fill_slice(s: &mut [u8], val: u8) { } } -#[cfg(feature = "proto-igmp")] +#[allow(unused)] fn recv_all(device: &mut crate::tests::TestingDevice, timestamp: Instant) -> Vec> { let mut pkts = Vec::new(); while let Some((rx, _tx)) = device.receive(timestamp) { diff --git a/src/iface/mod.rs b/src/iface/mod.rs index 3076088a0..8abcbd6e6 100644 --- a/src/iface/mod.rs +++ b/src/iface/mod.rs @@ -16,8 +16,8 @@ mod socket_set; mod packet; -#[cfg(feature = "proto-igmp")] -pub use self::interface::MulticastError; +#[cfg(feature = "multicast")] +pub use self::interface::multicast::MulticastError; pub use self::interface::{Config, Interface, InterfaceInner as Context}; pub use self::route::{Route, RouteTableFull, Routes}; diff --git a/src/iface/packet.rs b/src/iface/packet.rs index 13f24256e..1ccc6766b 100644 --- a/src/iface/packet.rs +++ b/src/iface/packet.rs @@ -81,7 +81,7 @@ impl<'p> Packet<'p> { IpPayload::Icmpv4(icmpv4_repr) => { icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &caps.checksum) } - #[cfg(feature = "proto-igmp")] + #[cfg(all(feature = "proto-ipv4", feature = "multicast"))] IpPayload::Igmp(igmp_repr) => igmp_repr.emit(&mut IgmpPacket::new_unchecked(payload)), #[cfg(feature = "proto-ipv6")] IpPayload::Icmpv6(icmpv6_repr) => { @@ -207,7 +207,7 @@ pub(crate) struct PacketV6<'p> { pub(crate) enum IpPayload<'p> { #[cfg(feature = "proto-ipv4")] Icmpv4(Icmpv4Repr<'p>), - #[cfg(feature = "proto-igmp")] + #[cfg(all(feature = "proto-ipv4", feature = "multicast"))] Igmp(IgmpRepr), #[cfg(feature = "proto-ipv6")] Icmpv6(Icmpv6Repr<'p>), @@ -235,7 +235,7 @@ impl<'p> IpPayload<'p> { Self::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6), #[cfg(feature = "proto-ipv6")] Self::HopByHopIcmpv6(_, _) => unreachable!(), - #[cfg(feature = "proto-igmp")] + #[cfg(all(feature = "proto-ipv4", feature = "multicast"))] Self::Igmp(_) => unreachable!(), #[cfg(feature = "socket-tcp")] Self::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp), @@ -259,19 +259,3 @@ pub(crate) fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) // - IP Header Size * 2 - ICMPv4 DstUnreachable hdr size len.min(mtu - header_len * 2 - 8) } - -#[cfg(feature = "proto-igmp")] -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, - }, -} diff --git a/src/wire/mod.rs b/src/wire/mod.rs index c197afed9..64ac65323 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -93,7 +93,7 @@ mod icmpv4; mod icmpv6; #[cfg(feature = "medium-ieee802154")] pub mod ieee802154; -#[cfg(feature = "proto-igmp")] +#[cfg(feature = "proto-ipv4")] mod igmp; pub(crate) mod ip; #[cfg(feature = "proto-ipv4")] @@ -225,7 +225,7 @@ pub use self::icmpv4::{ TimeExceeded as Icmpv4TimeExceeded, }; -#[cfg(feature = "proto-igmp")] +#[cfg(feature = "proto-ipv4")] pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr}; #[cfg(feature = "proto-ipv6")]