igmp.rs 10 KB

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