igmp.rs 13 KB

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