multicast.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. use core::result::Result;
  2. use heapless::{LinearMap, Vec};
  3. #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
  4. use super::{check, IpPayload, Packet};
  5. use super::{Interface, InterfaceInner};
  6. use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT};
  7. use crate::phy::{Device, PacketMeta};
  8. use crate::wire::*;
  9. /// Error type for `join_multicast_group`, `leave_multicast_group`.
  10. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  11. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  12. pub enum MulticastError {
  13. /// The table of joined multicast groups is already full.
  14. GroupTableFull,
  15. /// Cannot join/leave the given multicast group.
  16. Unaddressable,
  17. }
  18. #[cfg(feature = "proto-ipv4")]
  19. pub(crate) enum IgmpReportState {
  20. Inactive,
  21. ToGeneralQuery {
  22. version: IgmpVersion,
  23. timeout: crate::time::Instant,
  24. interval: crate::time::Duration,
  25. next_index: usize,
  26. },
  27. ToSpecificQuery {
  28. version: IgmpVersion,
  29. timeout: crate::time::Instant,
  30. group: Ipv4Address,
  31. },
  32. }
  33. #[cfg(feature = "proto-ipv6")]
  34. pub(crate) enum MldReportState {
  35. Inactive,
  36. ToGeneralQuery {
  37. timeout: crate::time::Instant,
  38. },
  39. ToSpecificQuery {
  40. group: Ipv6Address,
  41. timeout: crate::time::Instant,
  42. },
  43. }
  44. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  45. enum GroupState {
  46. /// Joining group, we have to send the join packet.
  47. Joining,
  48. /// We've already sent the join packet, we have nothing to do.
  49. Joined,
  50. /// We want to leave the group, we have to send a leave packet.
  51. Leaving,
  52. }
  53. pub(crate) struct State {
  54. groups: LinearMap<IpAddress, GroupState, IFACE_MAX_MULTICAST_GROUP_COUNT>,
  55. /// When to report for (all or) the next multicast group membership via IGMP
  56. #[cfg(feature = "proto-ipv4")]
  57. igmp_report_state: IgmpReportState,
  58. #[cfg(feature = "proto-ipv6")]
  59. mld_report_state: MldReportState,
  60. }
  61. impl State {
  62. pub(crate) fn new() -> Self {
  63. Self {
  64. groups: LinearMap::new(),
  65. #[cfg(feature = "proto-ipv4")]
  66. igmp_report_state: IgmpReportState::Inactive,
  67. #[cfg(feature = "proto-ipv6")]
  68. mld_report_state: MldReportState::Inactive,
  69. }
  70. }
  71. pub(crate) fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
  72. // Return false if we don't have the multicast group,
  73. // or we're leaving it.
  74. match self.groups.get(&addr.into()) {
  75. None => false,
  76. Some(GroupState::Joining) => true,
  77. Some(GroupState::Joined) => true,
  78. Some(GroupState::Leaving) => false,
  79. }
  80. }
  81. }
  82. impl core::fmt::Display for MulticastError {
  83. fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
  84. match self {
  85. MulticastError::GroupTableFull => write!(f, "GroupTableFull"),
  86. MulticastError::Unaddressable => write!(f, "Unaddressable"),
  87. }
  88. }
  89. }
  90. #[cfg(feature = "std")]
  91. impl std::error::Error for MulticastError {}
  92. impl Interface {
  93. /// Add an address to a list of subscribed multicast IP addresses.
  94. pub fn join_multicast_group<T: Into<IpAddress>>(
  95. &mut self,
  96. addr: T,
  97. ) -> Result<(), MulticastError> {
  98. let addr = addr.into();
  99. if !addr.is_multicast() {
  100. return Err(MulticastError::Unaddressable);
  101. }
  102. if let Some(state) = self.inner.multicast.groups.get_mut(&addr) {
  103. *state = match state {
  104. GroupState::Joining => GroupState::Joining,
  105. GroupState::Joined => GroupState::Joined,
  106. GroupState::Leaving => GroupState::Joined,
  107. };
  108. } else {
  109. self.inner
  110. .multicast
  111. .groups
  112. .insert(addr, GroupState::Joining)
  113. .map_err(|_| MulticastError::GroupTableFull)?;
  114. }
  115. Ok(())
  116. }
  117. /// Remove an address from the subscribed multicast IP addresses.
  118. pub fn leave_multicast_group<T: Into<IpAddress>>(
  119. &mut self,
  120. addr: T,
  121. ) -> Result<(), MulticastError> {
  122. let addr = addr.into();
  123. if !addr.is_multicast() {
  124. return Err(MulticastError::Unaddressable);
  125. }
  126. if let Some(state) = self.inner.multicast.groups.get_mut(&addr) {
  127. let delete;
  128. (*state, delete) = match state {
  129. GroupState::Joining => (GroupState::Joined, true),
  130. GroupState::Joined => (GroupState::Leaving, false),
  131. GroupState::Leaving => (GroupState::Leaving, false),
  132. };
  133. if delete {
  134. self.inner.multicast.groups.remove(&addr);
  135. }
  136. }
  137. Ok(())
  138. }
  139. /// Check whether the interface listens to given destination multicast IP address.
  140. pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
  141. self.inner.has_multicast_group(addr)
  142. }
  143. #[cfg(feature = "proto-ipv6")]
  144. pub(super) fn update_solicited_node_groups(&mut self) {
  145. // Remove old solicited-node multicast addresses
  146. let removals: Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT> = self
  147. .inner
  148. .multicast
  149. .groups
  150. .keys()
  151. .filter_map(|group_addr| match group_addr {
  152. IpAddress::Ipv6(address)
  153. if address.is_solicited_node_multicast()
  154. && self.inner.has_solicited_node(*address) =>
  155. {
  156. Some(*group_addr)
  157. }
  158. _ => None,
  159. })
  160. .collect();
  161. for removal in removals {
  162. let _ = self.leave_multicast_group(removal);
  163. }
  164. let cidrs: Vec<IpCidr, IFACE_MAX_ADDR_COUNT> = Vec::from_slice(self.ip_addrs()).unwrap();
  165. for cidr in cidrs {
  166. if let IpCidr::Ipv6(cidr) = cidr {
  167. let _ = self.join_multicast_group(cidr.address().solicited_node());
  168. }
  169. }
  170. }
  171. /// Do multicast egress.
  172. ///
  173. /// - Send join/leave packets according to the multicast group state.
  174. /// - Depending on `igmp_report_state` and the therein contained
  175. /// timeouts, send IGMP membership reports.
  176. pub(crate) fn multicast_egress(&mut self, device: &mut (impl Device + ?Sized)) {
  177. // Process multicast joins.
  178. while let Some((&addr, _)) = self
  179. .inner
  180. .multicast
  181. .groups
  182. .iter()
  183. .find(|(_, &state)| state == GroupState::Joining)
  184. {
  185. match addr {
  186. #[cfg(feature = "proto-ipv4")]
  187. IpAddress::Ipv4(addr) => {
  188. if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) {
  189. let Some(tx_token) = device.transmit(self.inner.now) else {
  190. break;
  191. };
  192. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  193. self.inner
  194. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  195. .unwrap();
  196. }
  197. }
  198. #[cfg(feature = "proto-ipv6")]
  199. IpAddress::Ipv6(addr) => {
  200. if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new(
  201. MldRecordType::ChangeToInclude,
  202. addr,
  203. )]) {
  204. let Some(tx_token) = device.transmit(self.inner.now) else {
  205. break;
  206. };
  207. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  208. self.inner
  209. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  210. .unwrap();
  211. }
  212. }
  213. }
  214. // NOTE(unwrap): this is always replacing an existing entry, so it can't fail due to the map being full.
  215. self.inner
  216. .multicast
  217. .groups
  218. .insert(addr, GroupState::Joined)
  219. .unwrap();
  220. }
  221. // Process multicast leaves.
  222. while let Some((&addr, _)) = self
  223. .inner
  224. .multicast
  225. .groups
  226. .iter()
  227. .find(|(_, &state)| state == GroupState::Leaving)
  228. {
  229. match addr {
  230. #[cfg(feature = "proto-ipv4")]
  231. IpAddress::Ipv4(addr) => {
  232. if let Some(pkt) = self.inner.igmp_leave_packet(addr) {
  233. let Some(tx_token) = device.transmit(self.inner.now) else {
  234. break;
  235. };
  236. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  237. self.inner
  238. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  239. .unwrap();
  240. }
  241. }
  242. #[cfg(feature = "proto-ipv6")]
  243. IpAddress::Ipv6(addr) => {
  244. if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new(
  245. MldRecordType::ChangeToExclude,
  246. addr,
  247. )]) {
  248. let Some(tx_token) = device.transmit(self.inner.now) else {
  249. break;
  250. };
  251. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  252. self.inner
  253. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  254. .unwrap();
  255. }
  256. }
  257. }
  258. self.inner.multicast.groups.remove(&addr);
  259. }
  260. #[cfg(feature = "proto-ipv4")]
  261. match self.inner.multicast.igmp_report_state {
  262. IgmpReportState::ToSpecificQuery {
  263. version,
  264. timeout,
  265. group,
  266. } if self.inner.now >= timeout => {
  267. if let Some(pkt) = self.inner.igmp_report_packet(version, group) {
  268. // Send initial membership report
  269. if let Some(tx_token) = device.transmit(self.inner.now) {
  270. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  271. self.inner
  272. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  273. .unwrap();
  274. self.inner.multicast.igmp_report_state = IgmpReportState::Inactive;
  275. }
  276. }
  277. }
  278. IgmpReportState::ToGeneralQuery {
  279. version,
  280. timeout,
  281. interval,
  282. next_index,
  283. } if self.inner.now >= timeout => {
  284. let addr = self
  285. .inner
  286. .multicast
  287. .groups
  288. .iter()
  289. .filter_map(|(addr, _)| match addr {
  290. IpAddress::Ipv4(addr) => Some(*addr),
  291. #[allow(unreachable_patterns)]
  292. _ => None,
  293. })
  294. .nth(next_index);
  295. match addr {
  296. Some(addr) => {
  297. if let Some(pkt) = self.inner.igmp_report_packet(version, addr) {
  298. // Send initial membership report
  299. if let Some(tx_token) = device.transmit(self.inner.now) {
  300. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  301. self.inner
  302. .dispatch_ip(
  303. tx_token,
  304. PacketMeta::default(),
  305. pkt,
  306. &mut self.fragmenter,
  307. )
  308. .unwrap();
  309. let next_timeout = (timeout + interval).max(self.inner.now);
  310. self.inner.multicast.igmp_report_state =
  311. IgmpReportState::ToGeneralQuery {
  312. version,
  313. timeout: next_timeout,
  314. interval,
  315. next_index: next_index + 1,
  316. };
  317. }
  318. }
  319. }
  320. None => {
  321. self.inner.multicast.igmp_report_state = IgmpReportState::Inactive;
  322. }
  323. }
  324. }
  325. _ => {}
  326. }
  327. #[cfg(feature = "proto-ipv6")]
  328. match self.inner.multicast.mld_report_state {
  329. MldReportState::ToGeneralQuery { timeout } if self.inner.now >= timeout => {
  330. let records = self
  331. .inner
  332. .multicast
  333. .groups
  334. .iter()
  335. .filter_map(|(addr, _)| match addr {
  336. IpAddress::Ipv6(addr) => Some(MldAddressRecordRepr::new(
  337. MldRecordType::ModeIsExclude,
  338. *addr,
  339. )),
  340. #[allow(unreachable_patterns)]
  341. _ => None,
  342. })
  343. .collect::<heapless::Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT>>();
  344. if let Some(pkt) = self.inner.mldv2_report_packet(&records) {
  345. if let Some(tx_token) = device.transmit(self.inner.now) {
  346. self.inner
  347. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  348. .unwrap();
  349. };
  350. };
  351. self.inner.multicast.mld_report_state = MldReportState::Inactive;
  352. }
  353. MldReportState::ToSpecificQuery { group, timeout } if self.inner.now >= timeout => {
  354. let record = MldAddressRecordRepr::new(MldRecordType::ModeIsExclude, group);
  355. if let Some(pkt) = self.inner.mldv2_report_packet(&[record]) {
  356. if let Some(tx_token) = device.transmit(self.inner.now) {
  357. // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
  358. self.inner
  359. .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
  360. .unwrap();
  361. }
  362. }
  363. self.inner.multicast.mld_report_state = MldReportState::Inactive;
  364. }
  365. _ => {}
  366. }
  367. }
  368. }
  369. impl InterfaceInner {
  370. /// Host duties of the **IGMPv2** protocol.
  371. ///
  372. /// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries.
  373. /// Membership must not be reported immediately in order to avoid flooding the network
  374. /// after a query is broadcasted by a router; this is not currently done.
  375. #[cfg(feature = "proto-ipv4")]
  376. pub(super) fn process_igmp<'frame>(
  377. &mut self,
  378. ipv4_repr: Ipv4Repr,
  379. ip_payload: &'frame [u8],
  380. ) -> Option<Packet<'frame>> {
  381. use crate::time::Duration;
  382. let igmp_packet = check!(IgmpPacket::new_checked(ip_payload));
  383. let igmp_repr = check!(IgmpRepr::parse(&igmp_packet));
  384. // FIXME: report membership after a delay
  385. match igmp_repr {
  386. IgmpRepr::MembershipQuery {
  387. group_addr,
  388. version,
  389. max_resp_time,
  390. } => {
  391. // General query
  392. if group_addr.is_unspecified() && ipv4_repr.dst_addr == IPV4_MULTICAST_ALL_SYSTEMS {
  393. let ipv4_multicast_group_count = self
  394. .multicast
  395. .groups
  396. .keys()
  397. .filter(|a| matches!(a, IpAddress::Ipv4(_)))
  398. .count();
  399. // Are we member in any groups?
  400. if ipv4_multicast_group_count != 0 {
  401. let interval = match version {
  402. IgmpVersion::Version1 => Duration::from_millis(100),
  403. IgmpVersion::Version2 => {
  404. // No dependence on a random generator
  405. // (see [#24](https://github.com/m-labs/smoltcp/issues/24))
  406. // but at least spread reports evenly across max_resp_time.
  407. let intervals = ipv4_multicast_group_count as u32 + 1;
  408. max_resp_time / intervals
  409. }
  410. };
  411. self.multicast.igmp_report_state = IgmpReportState::ToGeneralQuery {
  412. version,
  413. timeout: self.now + interval,
  414. interval,
  415. next_index: 0,
  416. };
  417. }
  418. } else {
  419. // Group-specific query
  420. if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr {
  421. // Don't respond immediately
  422. let timeout = max_resp_time / 4;
  423. self.multicast.igmp_report_state = IgmpReportState::ToSpecificQuery {
  424. version,
  425. timeout: self.now + timeout,
  426. group: group_addr,
  427. };
  428. }
  429. }
  430. }
  431. // Ignore membership reports
  432. IgmpRepr::MembershipReport { .. } => (),
  433. // Ignore hosts leaving groups
  434. IgmpRepr::LeaveGroup { .. } => (),
  435. }
  436. None
  437. }
  438. #[cfg(feature = "proto-ipv4")]
  439. fn igmp_report_packet<'any>(
  440. &self,
  441. version: IgmpVersion,
  442. group_addr: Ipv4Address,
  443. ) -> Option<Packet<'any>> {
  444. let iface_addr = self.ipv4_addr()?;
  445. let igmp_repr = IgmpRepr::MembershipReport {
  446. group_addr,
  447. version,
  448. };
  449. let pkt = Packet::new_ipv4(
  450. Ipv4Repr {
  451. src_addr: iface_addr,
  452. // Send to the group being reported
  453. dst_addr: group_addr,
  454. next_header: IpProtocol::Igmp,
  455. payload_len: igmp_repr.buffer_len(),
  456. hop_limit: 1,
  457. // [#183](https://github.com/m-labs/smoltcp/issues/183).
  458. },
  459. IpPayload::Igmp(igmp_repr),
  460. );
  461. Some(pkt)
  462. }
  463. #[cfg(feature = "proto-ipv4")]
  464. fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option<Packet<'any>> {
  465. self.ipv4_addr().map(|iface_addr| {
  466. let igmp_repr = IgmpRepr::LeaveGroup { group_addr };
  467. Packet::new_ipv4(
  468. Ipv4Repr {
  469. src_addr: iface_addr,
  470. dst_addr: IPV4_MULTICAST_ALL_ROUTERS,
  471. next_header: IpProtocol::Igmp,
  472. payload_len: igmp_repr.buffer_len(),
  473. hop_limit: 1,
  474. },
  475. IpPayload::Igmp(igmp_repr),
  476. )
  477. })
  478. }
  479. /// Host duties of the **MLDv2** protocol.
  480. ///
  481. /// Sets up `mld_report_state` for responding to MLD general/specific membership queries.
  482. /// Membership must not be reported immediately in order to avoid flooding the network
  483. /// after a query is broadcasted by a router; Currently the delay is fixed and not randomized.
  484. #[cfg(feature = "proto-ipv6")]
  485. pub(super) fn process_mldv2<'frame>(
  486. &mut self,
  487. ip_repr: Ipv6Repr,
  488. repr: MldRepr<'frame>,
  489. ) -> Option<Packet<'frame>> {
  490. match repr {
  491. MldRepr::Query {
  492. mcast_addr,
  493. max_resp_code,
  494. ..
  495. } => {
  496. // Do not respont immediately to the query, but wait a random time
  497. let delay = crate::time::Duration::from_millis(
  498. (self.rand.rand_u16() % max_resp_code).into(),
  499. );
  500. // General query
  501. if mcast_addr.is_unspecified()
  502. && (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES
  503. || self.has_ip_addr(ip_repr.dst_addr))
  504. {
  505. let ipv6_multicast_group_count = self
  506. .multicast
  507. .groups
  508. .keys()
  509. .filter(|a| matches!(a, IpAddress::Ipv6(_)))
  510. .count();
  511. if ipv6_multicast_group_count != 0 {
  512. self.multicast.mld_report_state = MldReportState::ToGeneralQuery {
  513. timeout: self.now + delay,
  514. };
  515. }
  516. }
  517. if self.has_multicast_group(mcast_addr) && ip_repr.dst_addr == mcast_addr {
  518. self.multicast.mld_report_state = MldReportState::ToSpecificQuery {
  519. group: mcast_addr,
  520. timeout: self.now + delay,
  521. };
  522. }
  523. None
  524. }
  525. MldRepr::Report { .. } => None,
  526. MldRepr::ReportRecordReprs { .. } => None,
  527. }
  528. }
  529. }