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