igmp.rs 10 KB

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