1use alloc::vec;
5use alloc::vec::Vec;
6use core::net::Ipv4Addr;
7
8use crate::util::{internet_checksum, read_u16be, write_u16be};
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, Copy, PartialEq, Eq)]
21pub enum IgmpVersion { V1, V2, V3 }
22
23#[derive(Debug, Clone)]
25pub enum IgmpMessage<'a> {
26 MembershipQuery {
27 version: IgmpVersion,
28 max_response: u8,
29 group_address: Ipv4Addr,
30 supress_router_side: bool,
32 robustness: u8,
34 query_interval: u8,
36 sources: &'a [u8],
38 },
39 MembershipReportV1 {
40 group_address: Ipv4Addr,
41 },
42 MembershipReportV2 {
43 max_response: u8,
44 group_address: Ipv4Addr,
45 },
46 LeaveGroup {
47 max_response: u8,
48 group_address: Ipv4Addr,
49 },
50 MembershipReportV3 {
51 group_records: &'a [u8],
52 },
53 Unknown {
54 type_: u8,
55 data: &'a [u8],
56 },
57}
58
59#[derive(Debug, Clone)]
61pub struct IgmpPacket<'a> {
62 buf: &'a [u8],
63}
64
65impl<'a> IgmpPacket<'a> {
66 pub fn new(buf: &'a [u8]) -> Option<Self> {
67 if buf.len() < IGMP_MIN_LEN { return None; }
68 Some(Self { buf })
69 }
70
71 #[inline] pub fn type_(&self) -> u8 { self.buf[0] }
72 #[inline] pub fn max_response(&self) -> u8 { self.buf[1] }
73 #[inline] pub fn checksum(&self) -> u16 { read_u16be(&self.buf[2..4]) }
74 #[inline] pub fn group_address(&self) -> Ipv4Addr {
75 let b = &self.buf[4..8];
76 Ipv4Addr::new(b[0], b[1], b[2], b[3])
77 }
78 #[inline] pub fn payload(&self) -> &'a [u8] { &self.buf[IGMP_MIN_LEN..] }
79
80 pub fn verify_checksum(&self) -> bool {
81 internet_checksum(self.buf) == 0
82 }
83
84 pub fn message(&self) -> IgmpMessage<'a> {
85 let payload = self.payload();
86 match self.type_() {
87 TYPE_QUERY => {
88 let grp = self.group_address();
89 if payload.len() >= 4 {
90 let sr_flags = payload[0];
92 IgmpMessage::MembershipQuery {
93 version: IgmpVersion::V3,
94 max_response: self.max_response(),
95 group_address: grp,
96 supress_router_side: (sr_flags & 0x08) != 0,
97 robustness: sr_flags & 0x07,
98 query_interval: payload[1],
99 sources: &payload[4..],
100 }
101 } else if grp == Ipv4Addr::UNSPECIFIED {
102 IgmpMessage::MembershipQuery {
103 version: IgmpVersion::V1,
104 max_response: 0,
105 group_address: grp,
106 supress_router_side: false,
107 robustness: 0,
108 query_interval: 0,
109 sources: &[],
110 }
111 } else {
112 IgmpMessage::MembershipQuery {
113 version: IgmpVersion::V2,
114 max_response: self.max_response(),
115 group_address: grp,
116 supress_router_side: false,
117 robustness: 0,
118 query_interval: 0,
119 sources: &[],
120 }
121 }
122 }
123 TYPE_REPORT_V1 => IgmpMessage::MembershipReportV1 {
124 group_address: self.group_address(),
125 },
126 TYPE_REPORT_V2 => IgmpMessage::MembershipReportV2 {
127 max_response: self.max_response(),
128 group_address: self.group_address(),
129 },
130 TYPE_LEAVE_V2 => IgmpMessage::LeaveGroup {
131 max_response: self.max_response(),
132 group_address: self.group_address(),
133 },
134 TYPE_REPORT_V3 => IgmpMessage::MembershipReportV3 {
135 group_records: payload,
136 },
137 t => IgmpMessage::Unknown {
138 type_: t,
139 data: payload,
140 },
141 }
142 }
143}
144
145pub struct IgmpPacketBuilder {
150 buf: Vec<u8>,
151 payload: Option<Vec<u8>>,
152}
153
154impl Default for IgmpPacketBuilder {
155 fn default() -> Self { Self::new(TYPE_QUERY, 0) }
156}
157
158impl IgmpPacketBuilder {
159 pub fn new(type_: u8, max_response: u8) -> Self {
160 let mut buf = vec![0u8; IGMP_MIN_LEN];
161 buf[0] = type_;
162 buf[1] = max_response;
163 Self { buf, payload: None }
164 }
165
166 pub fn query_v1() -> Self { Self::new(TYPE_QUERY, 0) }
167 pub fn query_v2(max_resp: u8) -> Self { Self::new(TYPE_QUERY, max_resp) }
168 pub fn report_v2(addr: Ipv4Addr) -> Self {
169 let mut s = Self::new(TYPE_REPORT_V2, 0);
170 s.buf[4..8].copy_from_slice(&addr.octets());
171 s
172 }
173 pub fn leave_v2(addr: Ipv4Addr) -> Self {
174 let mut s = Self::new(TYPE_LEAVE_V2, 0);
175 s.buf[4..8].copy_from_slice(&addr.octets());
176 s
177 }
178
179 pub fn group_address(mut self, addr: Ipv4Addr) -> Self {
180 self.buf[4..8].copy_from_slice(&addr.octets());
181 self
182 }
183
184 pub fn max_response(mut self, v: u8) -> Self {
185 self.buf[1] = v;
186 self
187 }
188
189 pub fn payload(mut self, data: &[u8]) -> Self {
190 self.payload = Some(data.to_vec());
191 self
192 }
193
194 pub fn build(mut self) -> Vec<u8> {
195 self.buf[2] = 0; self.buf[3] = 0;
196 let mut pkt = self.buf;
197 if let Some(p) = self.payload {
198 pkt.extend_from_slice(&p);
199 }
200 let csum = internet_checksum(&pkt);
201 write_u16be(&mut pkt[2..4], csum);
202 pkt
203 }
204}
205
206#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn parse_v1_query() {
216 let data = &[0x11u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
217 let pkt = IgmpPacket::new(data).unwrap();
218 assert_eq!(pkt.type_(), TYPE_QUERY);
219 match pkt.message() {
220 IgmpMessage::MembershipQuery { version, group_address, .. } => {
221 assert_eq!(version, IgmpVersion::V1);
222 assert_eq!(group_address, Ipv4Addr::UNSPECIFIED);
223 }
224 _ => panic!("expected MembershipQuery"),
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::MembershipQuery { version, max_response, .. } => {
236 assert_eq!(version, IgmpVersion::V2);
237 assert_eq!(max_response, 100);
238 }
239 _ => panic!("expected MembershipQuery v2"),
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}