1 use super::{check, IgmpReportState, Interface, InterfaceInner, IpPacket}; 2 use crate::phy::Device; 3 use crate::time::{Duration, Instant}; 4 use crate::wire::*; 5 6 use core::result::Result; 7 8 /// Error type for `join_multicast_group`, `leave_multicast_group`. 9 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 #[cfg_attr(feature = "defmt", derive(defmt::Format))] 11 pub enum MulticastError { 12 /// The hardware device transmit buffer is full. Try again later. 13 Exhausted, 14 /// The table of joined multicast groups is already full. 15 GroupTableFull, 16 /// IPv6 multicast is not yet supported. 17 Ipv6NotSupported, 18 } 19 20 impl Interface { 21 /// Add an address to a list of subscribed multicast IP addresses. 22 /// 23 /// Returns `Ok(announce_sent)` if the address was added successfully, where `annouce_sent` 24 /// indicates whether an initial immediate announcement has been sent. join_multicast_group<D, T: Into<IpAddress>>( &mut self, device: &mut D, addr: T, timestamp: Instant, ) -> Result<bool, MulticastError> where D: Device + ?Sized,25 pub fn join_multicast_group<D, T: Into<IpAddress>>( 26 &mut self, 27 device: &mut D, 28 addr: T, 29 timestamp: Instant, 30 ) -> Result<bool, MulticastError> 31 where 32 D: Device + ?Sized, 33 { 34 self.inner.now = timestamp; 35 36 match addr.into() { 37 IpAddress::Ipv4(addr) => { 38 let is_not_new = self 39 .inner 40 .ipv4_multicast_groups 41 .insert(addr, ()) 42 .map_err(|_| MulticastError::GroupTableFull)? 43 .is_some(); 44 if is_not_new { 45 Ok(false) 46 } else if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) 47 { 48 // Send initial membership report 49 let tx_token = device 50 .transmit(timestamp) 51 .ok_or(MulticastError::Exhausted)?; 52 53 // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. 54 self.inner 55 .dispatch_ip(tx_token, pkt, &mut self.fragmenter) 56 .unwrap(); 57 58 Ok(true) 59 } else { 60 Ok(false) 61 } 62 } 63 // Multicast is not yet implemented for other address families 64 #[allow(unreachable_patterns)] 65 _ => Err(MulticastError::Ipv6NotSupported), 66 } 67 } 68 69 /// Remove an address from the subscribed multicast IP addresses. 70 /// 71 /// Returns `Ok(leave_sent)` if the address was removed successfully, where `leave_sent` 72 /// indicates whether an immediate leave packet has been sent. leave_multicast_group<D, T: Into<IpAddress>>( &mut self, device: &mut D, addr: T, timestamp: Instant, ) -> Result<bool, MulticastError> where D: Device + ?Sized,73 pub fn leave_multicast_group<D, T: Into<IpAddress>>( 74 &mut self, 75 device: &mut D, 76 addr: T, 77 timestamp: Instant, 78 ) -> Result<bool, MulticastError> 79 where 80 D: Device + ?Sized, 81 { 82 self.inner.now = timestamp; 83 84 match addr.into() { 85 IpAddress::Ipv4(addr) => { 86 let was_not_present = self.inner.ipv4_multicast_groups.remove(&addr).is_none(); 87 if was_not_present { 88 Ok(false) 89 } else if let Some(pkt) = self.inner.igmp_leave_packet(addr) { 90 // Send group leave packet 91 let tx_token = device 92 .transmit(timestamp) 93 .ok_or(MulticastError::Exhausted)?; 94 95 // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. 96 self.inner 97 .dispatch_ip(tx_token, pkt, &mut self.fragmenter) 98 .unwrap(); 99 100 Ok(true) 101 } else { 102 Ok(false) 103 } 104 } 105 // Multicast is not yet implemented for other address families 106 #[allow(unreachable_patterns)] 107 _ => Err(MulticastError::Ipv6NotSupported), 108 } 109 } 110 111 /// Check whether the interface listens to given destination multicast IP address. has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool112 pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool { 113 self.inner.has_multicast_group(addr) 114 } 115 116 /// Depending on `igmp_report_state` and the therein contained 117 /// timeouts, send IGMP membership reports. igmp_egress<D>(&mut self, device: &mut D) -> bool where D: Device + ?Sized,118 pub(crate) fn igmp_egress<D>(&mut self, device: &mut D) -> bool 119 where 120 D: Device + ?Sized, 121 { 122 match self.inner.igmp_report_state { 123 IgmpReportState::ToSpecificQuery { 124 version, 125 timeout, 126 group, 127 } if self.inner.now >= timeout => { 128 if let Some(pkt) = self.inner.igmp_report_packet(version, group) { 129 // Send initial membership report 130 if let Some(tx_token) = device.transmit(self.inner.now) { 131 // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. 132 self.inner 133 .dispatch_ip(tx_token, pkt, &mut self.fragmenter) 134 .unwrap(); 135 } else { 136 return false; 137 } 138 } 139 140 self.inner.igmp_report_state = IgmpReportState::Inactive; 141 true 142 } 143 IgmpReportState::ToGeneralQuery { 144 version, 145 timeout, 146 interval, 147 next_index, 148 } if self.inner.now >= timeout => { 149 let addr = self 150 .inner 151 .ipv4_multicast_groups 152 .iter() 153 .nth(next_index) 154 .map(|(addr, ())| *addr); 155 156 match addr { 157 Some(addr) => { 158 if let Some(pkt) = self.inner.igmp_report_packet(version, addr) { 159 // Send initial membership report 160 if let Some(tx_token) = device.transmit(self.inner.now) { 161 // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. 162 self.inner 163 .dispatch_ip(tx_token, pkt, &mut self.fragmenter) 164 .unwrap(); 165 } else { 166 return false; 167 } 168 } 169 170 let next_timeout = (timeout + interval).max(self.inner.now); 171 self.inner.igmp_report_state = IgmpReportState::ToGeneralQuery { 172 version, 173 timeout: next_timeout, 174 interval, 175 next_index: next_index + 1, 176 }; 177 true 178 } 179 180 None => { 181 self.inner.igmp_report_state = IgmpReportState::Inactive; 182 false 183 } 184 } 185 } 186 _ => false, 187 } 188 } 189 } 190 191 impl InterfaceInner { 192 /// Check whether the interface listens to given destination multicast IP address. 193 /// 194 /// If built without feature `proto-igmp` this function will 195 /// always return `false`. has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool196 pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool { 197 match addr.into() { 198 IpAddress::Ipv4(key) => { 199 key == Ipv4Address::MULTICAST_ALL_SYSTEMS 200 || self.ipv4_multicast_groups.get(&key).is_some() 201 } 202 #[allow(unreachable_patterns)] 203 _ => false, 204 } 205 } 206 207 /// Host duties of the **IGMPv2** protocol. 208 /// 209 /// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries. 210 /// Membership must not be reported immediately in order to avoid flooding the network 211 /// after a query is broadcasted by a router; this is not currently done. process_igmp<'frame>( &mut self, ipv4_repr: Ipv4Repr, ip_payload: &'frame [u8], ) -> Option<IpPacket<'frame>>212 pub(super) fn process_igmp<'frame>( 213 &mut self, 214 ipv4_repr: Ipv4Repr, 215 ip_payload: &'frame [u8], 216 ) -> Option<IpPacket<'frame>> { 217 let igmp_packet = check!(IgmpPacket::new_checked(ip_payload)); 218 let igmp_repr = check!(IgmpRepr::parse(&igmp_packet)); 219 220 // FIXME: report membership after a delay 221 match igmp_repr { 222 IgmpRepr::MembershipQuery { 223 group_addr, 224 version, 225 max_resp_time, 226 } => { 227 // General query 228 if group_addr.is_unspecified() 229 && ipv4_repr.dst_addr == Ipv4Address::MULTICAST_ALL_SYSTEMS 230 { 231 // Are we member in any groups? 232 if self.ipv4_multicast_groups.iter().next().is_some() { 233 let interval = match version { 234 IgmpVersion::Version1 => Duration::from_millis(100), 235 IgmpVersion::Version2 => { 236 // No dependence on a random generator 237 // (see [#24](https://github.com/m-labs/smoltcp/issues/24)) 238 // but at least spread reports evenly across max_resp_time. 239 let intervals = self.ipv4_multicast_groups.len() as u32 + 1; 240 max_resp_time / intervals 241 } 242 }; 243 self.igmp_report_state = IgmpReportState::ToGeneralQuery { 244 version, 245 timeout: self.now + interval, 246 interval, 247 next_index: 0, 248 }; 249 } 250 } else { 251 // Group-specific query 252 if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr { 253 // Don't respond immediately 254 let timeout = max_resp_time / 4; 255 self.igmp_report_state = IgmpReportState::ToSpecificQuery { 256 version, 257 timeout: self.now + timeout, 258 group: group_addr, 259 }; 260 } 261 } 262 } 263 // Ignore membership reports 264 IgmpRepr::MembershipReport { .. } => (), 265 // Ignore hosts leaving groups 266 IgmpRepr::LeaveGroup { .. } => (), 267 } 268 269 None 270 } 271 } 272