1use alloc::vec;
5use alloc::vec::Vec;
6use core::net::Ipv4Addr;
7
8use crate::util::{internet_checksum, seal_internet_checksum, read_u16be, PacketBuilder};
9
10pub const IGMP_MIN_LEN: usize = 8;
11
12const TYPE_QUERY: u8 = 0x11;
14const TYPE_REPORT_V1: u8 = 0x12;
15const TYPE_REPORT_V2: u8 = 0x16;
16const TYPE_LEAVE_V2: u8 = 0x17;
17const TYPE_REPORT_V3: u8 = 0x22;
18
19#[derive(Debug, Clone)]
21pub enum IgmpMessage<'a> {
22 MembershipQueryV1 {
24 group_address: Ipv4Addr,
25 },
26 MembershipQueryV2 {
28 max_response: u8,
29 group_address: Ipv4Addr,
30 },
31 MembershipQueryV3 {
33 max_response: u8,
34 group_address: Ipv4Addr,
35 supress_router_side: bool,
37 robustness: u8,
39 query_interval: u8,
41 sources: &'a [u8],
43 },
44 MembershipReportV1 {
45 group_address: Ipv4Addr,
46 },
47 MembershipReportV2 {
48 max_response: u8,
49 group_address: Ipv4Addr,
50 },
51 LeaveGroup {
52 max_response: u8,
53 group_address: Ipv4Addr,
54 },
55 MembershipReportV3 {
56 group_records: &'a [u8],
57 },
58 Unknown {
59 type_: u8,
60 data: &'a [u8],
61 },
62}
63
64#[derive(Debug, Clone)]
66pub struct IgmpPacket<'a> {
67 buf: &'a [u8],
68}
69
70impl<'a> IgmpPacket<'a> {
71 crate::parser_new!(IGMP_MIN_LEN);
72
73 #[inline]
75 pub fn as_bytes(&self) -> &'a [u8] { self.buf }
76
77 #[inline] pub fn type_(&self) -> u8 { self.buf[0] }
78 #[inline] pub fn max_response(&self) -> u8 { self.buf[1] }
79 #[inline] pub fn checksum(&self) -> u16 { read_u16be(&self.buf[2..4]) }
80 #[inline] pub fn group_address(&self) -> Ipv4Addr {
81 let b = &self.buf[4..8];
82 Ipv4Addr::new(b[0], b[1], b[2], b[3])
83 }
84 #[inline] pub fn payload(&self) -> &'a [u8] { &self.buf[IGMP_MIN_LEN..] }
85
86 pub fn verify_checksum(&self) -> bool {
87 internet_checksum(self.buf) == 0
88 }
89
90 pub fn message(&self) -> IgmpMessage<'a> {
91 let payload = self.payload();
92 match self.type_() {
93 TYPE_QUERY => {
94 let grp = self.group_address();
95 let max_resp = self.max_response();
96 if payload.len() >= 4 && (payload.len() - 4).is_multiple_of(4) {
98 let sr_flags = payload[0];
99 IgmpMessage::MembershipQueryV3 {
100 max_response: max_resp,
101 group_address: grp,
102 supress_router_side: (sr_flags & 0x08) != 0,
103 robustness: sr_flags & 0x07,
104 query_interval: payload[1],
105 sources: &payload[4..],
106 }
107 } else if grp == Ipv4Addr::UNSPECIFIED && max_resp == 0 {
108 IgmpMessage::MembershipQueryV1 {
109 group_address: grp,
110 }
111 } else {
112 IgmpMessage::MembershipQueryV2 {
113 max_response: max_resp,
114 group_address: grp,
115 }
116 }
117 }
118 TYPE_REPORT_V1 => IgmpMessage::MembershipReportV1 {
119 group_address: self.group_address(),
120 },
121 TYPE_REPORT_V2 => IgmpMessage::MembershipReportV2 {
122 max_response: self.max_response(),
123 group_address: self.group_address(),
124 },
125 TYPE_LEAVE_V2 => IgmpMessage::LeaveGroup {
126 max_response: self.max_response(),
127 group_address: self.group_address(),
128 },
129 TYPE_REPORT_V3 => IgmpMessage::MembershipReportV3 {
130 group_records: payload,
131 },
132 t => IgmpMessage::Unknown {
133 type_: t,
134 data: payload,
135 },
136 }
137 }
138}
139
140pub struct IgmpPacketBuilder {
145 buf: Vec<u8>,
146 payload: Option<Vec<u8>>,
147}
148
149impl Default for IgmpPacketBuilder {
150 fn default() -> Self { Self::new(TYPE_QUERY, 0) }
151}
152
153impl IgmpPacketBuilder {
154 pub fn new(type_: u8, max_response: u8) -> Self {
155 let mut buf = vec![0u8; IGMP_MIN_LEN];
156 buf[0] = type_;
157 buf[1] = max_response;
158 Self { buf, payload: None }
159 }
160
161 pub fn query_v1() -> Self { Self::new(TYPE_QUERY, 0) }
162 pub fn query_v2(max_resp: u8) -> Self { Self::new(TYPE_QUERY, max_resp) }
163 pub fn report_v2(addr: Ipv4Addr) -> Self {
164 let mut s = Self::new(TYPE_REPORT_V2, 0);
165 s.buf[4..8].copy_from_slice(&addr.octets());
166 s
167 }
168 pub fn leave_v2(addr: Ipv4Addr) -> Self {
169 let mut s = Self::new(TYPE_LEAVE_V2, 0);
170 s.buf[4..8].copy_from_slice(&addr.octets());
171 s
172 }
173
174 pub fn group_address(mut self, addr: Ipv4Addr) -> Self {
175 self.buf[4..8].copy_from_slice(&addr.octets());
176 self
177 }
178
179 pub fn max_response(mut self, v: u8) -> Self {
180 self.buf[1] = v;
181 self
182 }
183
184 pub fn payload(mut self, data: &[u8]) -> Self {
185 self.payload = Some(data.to_vec());
186 self
187 }
188
189 pub fn build(self) -> Vec<u8> {
190 <Self as PacketBuilder>::build(self)
191 }
192}
193
194impl PacketBuilder for IgmpPacketBuilder {
195 type Output = Vec<u8>;
196
197 fn build(self) -> Vec<u8> {
198 let mut pkt = self.buf;
199 if let Some(p) = self.payload {
200 pkt.extend_from_slice(&p);
201 }
202 seal_internet_checksum(&mut pkt, 2);
203 pkt
204 }
205}
206
207#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn parse_v1_query() {
217 let data = &[0x11u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
218 let pkt = IgmpPacket::new(data).unwrap();
219 assert_eq!(pkt.type_(), TYPE_QUERY);
220 match pkt.message() {
221 IgmpMessage::MembershipQueryV1 { group_address } => {
222 assert_eq!(group_address, Ipv4Addr::UNSPECIFIED);
223 }
224 _ => panic!("expected MembershipQueryV1"),
225 }
226 }
227
228 #[test]
229 fn parse_v2_query() {
230 let mut data = vec![0x11u8, 100, 0x00, 0x00];
231 data.extend_from_slice(&[224u8, 0, 0, 1]); let pkt = IgmpPacket::new(&data).unwrap();
233 assert_eq!(pkt.max_response(), 100);
234 match pkt.message() {
235 IgmpMessage::MembershipQueryV2 { max_response, group_address } => {
236 assert_eq!(max_response, 100);
237 let _ = group_address;
238 }
239 _ => panic!("expected MembershipQueryV2"),
240 }
241 }
242
243 #[test]
244 fn parse_v2_report() {
245 let data = &[0x16u8, 0x00, 0x00, 0x00, 224, 0, 0, 1];
246 let pkt = IgmpPacket::new(data).unwrap();
247 match pkt.message() {
248 IgmpMessage::MembershipReportV2 { group_address, .. } => {
249 assert_eq!(group_address, Ipv4Addr::new(224, 0, 0, 1));
250 }
251 _ => panic!("expected MembershipReportV2"),
252 }
253 }
254
255 #[test]
256 fn parse_v2_leave() {
257 let data = &[0x17u8, 0x00, 0x00, 0x00, 224, 0, 0, 1];
258 let pkt = IgmpPacket::new(data).unwrap();
259 match pkt.message() {
260 IgmpMessage::LeaveGroup { group_address, .. } => {
261 assert_eq!(group_address, Ipv4Addr::new(224, 0, 0, 1));
262 }
263 _ => panic!("expected LeaveGroup"),
264 }
265 }
266
267 #[test]
268 fn parse_too_short() {
269 assert!(IgmpPacket::new(&[]).is_none());
270 assert!(IgmpPacket::new(&[0u8; 7]).is_none());
271 }
272
273 #[test]
274 fn verify_checksum() {
275 let pkt = IgmpPacketBuilder::query_v1().build();
276 let parsed = IgmpPacket::new(&pkt).unwrap();
277 assert!(parsed.verify_checksum());
278 }
279
280 #[test]
281 fn build_roundtrip() {
282 let addr = Ipv4Addr::new(224, 0, 0, 1);
283 let pkt = IgmpPacketBuilder::report_v2(addr)
284 .payload(&[0xAA, 0xBB])
285 .build();
286 let parsed = IgmpPacket::new(&pkt).unwrap();
287 assert_eq!(parsed.type_(), TYPE_REPORT_V2);
288 assert_eq!(parsed.group_address(), addr);
289 assert_eq!(parsed.payload(), &[0xAA, 0xBB]);
290 assert!(parsed.verify_checksum());
291 }
292}