packet_formats/igmp/
mod.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Parsing and serialization of Internet Group Management Protocol (IGMP)
6//! packets.
7//!
8//! This module supports both IGMPv2 and IGMPv3.
9//!
10//! The IGMPv2 packet format is defined in [RFC 2236 Section 2], and the IGMPv3
11//! packet format is defined in [RFC 3376 Section 4].
12//!
13//! [RFC 2236 Section 2]: https://datatracker.ietf.org/doc/html/rfc2236#section-2
14//! [RFC 3376 Section 4]: https://datatracker.ietf.org/doc/html/rfc3376#section-4
15
16pub mod messages;
17mod types;
18
19#[cfg(test)]
20mod testdata;
21
22pub use self::types::*;
23
24use core::fmt::Debug;
25use core::marker::PhantomData;
26use core::mem;
27
28use internet_checksum::Checksum;
29use net_types::ip::Ipv4Addr;
30use packet::{
31    AsFragmentedByteSlice, BufferView, FragmentedByteSlice, FragmentedBytesMut, InnerPacketBuilder,
32    PacketBuilder, PacketConstraints, ParsablePacket, ParseMetadata, SerializeTarget,
33};
34use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
35
36use self::messages::IgmpMessageType;
37use crate::error::ParseError;
38
39/// Trait specifying serialization behavior for IGMP messages.
40///
41/// IGMP messages are broken into 3 parts:
42///
43/// - `HeaderPrefix`: common to all IGMP messages;
44/// - `FixedHeader`: A fixed part of the message following the HeaderPrefix;
45/// - `VariableBody`: A variable-length body;
46///
47/// `MessageType` specifies the types used for `FixedHeader` and `VariableBody`,
48/// `HeaderPrefix` is shared among all message types.
49pub trait MessageType<B> {
50    /// The fixed header type used for the message type.
51    ///
52    /// These are the bytes immediately following the checksum bytes in an IGMP
53    /// message. Most IGMP messages' `FixedHeader` is an IPv4 address.
54    type FixedHeader: Sized
55        + Copy
56        + Clone
57        + FromBytes
58        + IntoBytes
59        + KnownLayout
60        + Immutable
61        + Unaligned
62        + Debug;
63
64    /// The variable-length body for the message type.
65    type VariableBody: Sized;
66
67    /// The type corresponding to this message type.
68    ///
69    /// The value of the "type" field in the IGMP header corresponding to
70    /// messages of this type.
71    const TYPE: IgmpMessageType;
72
73    /// A type specializing how to parse the `max_resp_code` field in the
74    /// `HeaderPrefix`.
75    ///
76    /// Specifies how to transform *Max Resp Code* (transmitted value)
77    /// into *Max Resp Time* (semantic meaning). Provides a single interface to
78    /// deal with differences between IGMPv2 ([RFC 2236]) and
79    /// IGMPv3 ([RFC 3376]).
80    ///
81    /// The *Max Resp Code* field in `HeaderPrefix` only has meaning for IGMP
82    /// *Query* messages, and should be set to zero for all other outgoing
83    /// and ignored for all other incoming messages. For that case,
84    /// an implementation of `MaxRespTime` for `()` is provided.
85    ///
86    /// [RFC 2236]: https://tools.ietf.org/html/rfc2236
87    /// [RFC 3376]: https://tools.ietf.org/html/rfc3376
88    type MaxRespTime: Sized + IgmpMaxRespCode + Debug;
89
90    /// Parses the variable body part of the IGMP message.
91    fn parse_body<BV: BufferView<B>>(
92        header: &Self::FixedHeader,
93        bytes: BV,
94    ) -> Result<Self::VariableBody, ParseError>
95    where
96        B: SplitByteSlice;
97
98    /// Retrieves the underlying bytes of `VariableBody`.
99    // Note: this is delegating the responsibility of getting
100    // `VariableBody` as `[u8]` to `MessageType` as opposed to just enforcing
101    // a trait in `VariableBody` to do so. The decision to go this way is to
102    // be able to relax the `ByteSlice` requirement on `B` elsewhere and it
103    // also plays better with existing traits on `Ref` and
104    // `Records`.
105    fn body_bytes(body: &Self::VariableBody) -> &[u8]
106    where
107        B: SplitByteSlice;
108}
109
110/// Trait for treating the max_resp_code field of `HeaderPrefix`.
111///
112/// There are parsing differences between IGMP v2 and v3  for the maximum
113/// response code (in IGMP v2 is in fact called maximum response time). That's
114/// the reasoning behind making this a trait, so it can be specialized
115/// differently with thin wrappers by the messages implementation.
116pub trait IgmpMaxRespCode {
117    /// The serialized value of the response code
118    fn as_code(&self) -> u8;
119    /// Parses from a code value.
120    fn from_code(code: u8) -> Self;
121}
122
123/// Max Resp Code should be ignored on all non-query incoming messages and
124/// set to zero on all non-query outgoing messages, the implementation for `()`
125/// provides that.
126impl IgmpMaxRespCode for () {
127    fn as_code(&self) -> u8 {
128        0
129    }
130
131    fn from_code(_code: u8) {}
132}
133
134/// A marker trait for implementers of [`MessageType`]. Only [`MessageType`]s
135/// whose `VariableBody` implements `IgmpNonEmptyBody` (or is `()` for empty
136/// bodies) can be built using [`IgmpPacketBuilder`].
137pub trait IgmpNonEmptyBody {}
138
139/// A builder for IGMP packets.
140#[derive(Debug)]
141pub struct IgmpPacketBuilder<B, M: MessageType<B>> {
142    max_resp_time: M::MaxRespTime,
143    message_header: M::FixedHeader,
144    _marker: PhantomData<B>,
145}
146
147impl<B, M: MessageType<B, MaxRespTime = ()>> IgmpPacketBuilder<B, M> {
148    /// Construct a new `IgmpPacketBuilder`.
149    pub fn new(msg_header: M::FixedHeader) -> IgmpPacketBuilder<B, M> {
150        IgmpPacketBuilder { max_resp_time: (), message_header: msg_header, _marker: PhantomData }
151    }
152}
153
154impl<B, M: MessageType<B>> IgmpPacketBuilder<B, M> {
155    /// Construct a new `IgmpPacketBuilder` with provided `max_resp_time`.
156    pub fn new_with_resp_time(
157        msg_header: M::FixedHeader,
158        max_resp_time: M::MaxRespTime,
159    ) -> IgmpPacketBuilder<B, M> {
160        IgmpPacketBuilder { max_resp_time, message_header: msg_header, _marker: PhantomData }
161    }
162}
163
164impl<B, M: MessageType<B>> IgmpPacketBuilder<B, M> {
165    fn serialize_headers<BB: packet::Fragment>(
166        &self,
167        mut headers_buff: &mut [u8],
168        body: FragmentedByteSlice<'_, BB>,
169    ) {
170        use packet::BufferViewMut;
171        let mut bytes = &mut headers_buff;
172        // SECURITY: Use _zero constructors to ensure we zero memory to prevent
173        // leaking information from packets previously stored in this buffer.
174        let mut header_prefix =
175            bytes.take_obj_front_zero::<HeaderPrefix>().expect("too few bytes for IGMP message");
176        header_prefix.set_msg_type(M::TYPE);
177        header_prefix.max_resp_code = self.max_resp_time.as_code();
178
179        let mut header =
180            bytes.take_obj_front_zero::<M::FixedHeader>().expect("too few bytes for IGMP message");
181        *header = self.message_header;
182
183        let checksum = compute_checksum_fragmented(&header_prefix, &Ref::bytes(&header), &body);
184        header_prefix.checksum = checksum;
185    }
186}
187
188const fn total_header_size<F>() -> usize {
189    mem::size_of::<HeaderPrefix>() + mem::size_of::<F>()
190}
191
192// All messages that do not have a VariableBody,
193// can have an InnerPacketBuilder impl.
194impl<B, M: MessageType<B, VariableBody = ()>> InnerPacketBuilder for IgmpPacketBuilder<B, M> {
195    fn bytes_len(&self) -> usize {
196        total_header_size::<M::FixedHeader>()
197    }
198
199    fn serialize(&self, buffer: &mut [u8]) {
200        let empty = FragmentedByteSlice::<&'static [u8]>::new_empty();
201        self.serialize_headers(buffer, empty);
202    }
203}
204
205impl<B, M: MessageType<B>> PacketBuilder for IgmpPacketBuilder<B, M>
206where
207    M::VariableBody: IgmpNonEmptyBody,
208{
209    fn constraints(&self) -> PacketConstraints {
210        PacketConstraints::new(total_header_size::<M::FixedHeader>(), 0, 0, core::usize::MAX)
211    }
212
213    fn serialize(
214        &self,
215        target: &mut SerializeTarget<'_>,
216        message_body: FragmentedBytesMut<'_, '_>,
217    ) {
218        self.serialize_headers(target.header, message_body);
219    }
220}
221
222/// `HeaderPrefix` represents the first 4 octets of every IGMP message.
223///
224/// The `HeaderPrefix` carries the message type information, which is used to
225/// parse which IGMP message follows.
226///
227/// Note that even though `max_rsp_time` is part of `HeaderPrefix`, it is not
228/// meaningful or used in every message.
229#[derive(Default, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
230#[repr(C)]
231pub struct HeaderPrefix {
232    msg_type: u8,
233    /// The Max Response Time field is meaningful only in Membership Query
234    /// messages, and specifies the maximum allowed time before sending a
235    /// responding report. In all other messages, it is set to zero by the
236    /// sender and ignored by receivers. The parsing of `max_resp_code` into
237    /// a value *Max Response Time* is performed by the `MaxRespType` type in
238    /// the `MessageType` trait.
239    max_resp_code: u8,
240    checksum: [u8; 2],
241}
242
243impl HeaderPrefix {
244    fn set_msg_type<T: Into<u8>>(&mut self, msg_type: T) {
245        self.msg_type = msg_type.into();
246    }
247}
248
249/// An IGMP message.
250///
251/// An `IgmpMessage` is a struct representing an IGMP message in memory;
252/// it holds the 3 IGMP message parts and is characterized by the
253/// `MessageType` trait.
254#[derive(Debug)]
255pub struct IgmpMessage<B: SplitByteSlice, M: MessageType<B>> {
256    prefix: Ref<B, HeaderPrefix>,
257    header: Ref<B, M::FixedHeader>,
258    body: M::VariableBody,
259}
260
261impl<B: SplitByteSlice, M: MessageType<B>> IgmpMessage<B, M> {
262    /// Construct a builder with the same contents as this packet.
263    pub fn builder(&self) -> IgmpPacketBuilder<B, M> {
264        IgmpPacketBuilder::new_with_resp_time(*self.header, self.max_response_time())
265    }
266
267    /// Gets the interpreted *Max Response Time* for the message
268    pub fn max_response_time(&self) -> M::MaxRespTime {
269        M::MaxRespTime::from_code(self.prefix.max_resp_code)
270    }
271
272    /// Returns the header.
273    pub fn header(&self) -> &M::FixedHeader {
274        &self.header
275    }
276
277    /// Returns the body.
278    pub fn body(&self) -> &M::VariableBody {
279        &self.body
280    }
281}
282
283fn compute_checksum_fragmented<BB: packet::Fragment>(
284    header_prefix: &HeaderPrefix,
285    header: &[u8],
286    body: &FragmentedByteSlice<'_, BB>,
287) -> [u8; 2] {
288    let mut c = Checksum::new();
289    c.add_bytes(&[header_prefix.msg_type, header_prefix.max_resp_code]);
290    c.add_bytes(&header_prefix.checksum);
291    c.add_bytes(header);
292    for p in body.iter_fragments() {
293        c.add_bytes(p);
294    }
295    c.checksum()
296}
297
298impl<B: SplitByteSlice, M: MessageType<B>> IgmpMessage<B, M> {
299    fn compute_checksum(header_prefix: &HeaderPrefix, header: &[u8], body: &[u8]) -> [u8; 2] {
300        let mut body = [body];
301        compute_checksum_fragmented(header_prefix, header, &body.as_fragmented_byte_slice())
302    }
303}
304
305impl<B: SplitByteSlice, M: MessageType<B, FixedHeader = Ipv4Addr>> IgmpMessage<B, M> {
306    /// Returns the group address.
307    pub fn group_addr(&self) -> Ipv4Addr {
308        *self.header
309    }
310}
311
312impl<B: SplitByteSlice, M: MessageType<B>> ParsablePacket<B, ()> for IgmpMessage<B, M> {
313    type Error = ParseError;
314
315    fn parse_metadata(&self) -> ParseMetadata {
316        let header_len = Ref::bytes(&self.prefix).len() + Ref::bytes(&self.header).len();
317        ParseMetadata::from_packet(header_len, M::body_bytes(&self.body).len(), 0)
318    }
319
320    fn parse<BV: BufferView<B>>(mut buffer: BV, _args: ()) -> Result<Self, ParseError> {
321        let prefix = buffer
322            .take_obj_front::<HeaderPrefix>()
323            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header prefix"))?;
324
325        let header = buffer
326            .take_obj_front::<M::FixedHeader>()
327            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?;
328
329        let checksum = Self::compute_checksum(&prefix, &Ref::bytes(&header), buffer.as_ref());
330        if checksum != [0, 0] {
331            return debug_err!(
332                Err(ParseError::Checksum),
333                "invalid checksum, got 0x{:x?}",
334                prefix.checksum
335            );
336        }
337
338        if prefix.msg_type != M::TYPE.into() {
339            return debug_err!(Err(ParseError::NotExpected), "unexpected message type");
340        }
341
342        let body = M::parse_body(&header, buffer)?;
343
344        Ok(IgmpMessage { prefix, header, body })
345    }
346}
347
348/// Peek at an IGMP header to see what message type is present.
349///
350/// Since `IgmpPacket` is statically typed with the message type expected, this
351/// type must be known ahead of time before calling `parse`. If multiple
352/// different types are valid in a given parsing context, and so the caller
353/// cannot know ahead of time which type to use, `peek_message_type` can be used
354/// to peek at the header first to figure out which static type should be used
355/// in a subsequent call to `parse`.
356///
357/// Because IGMP reuses the message type field for different semantic
358/// meanings between IGMP v2 and v3, `peek_message_type` also returns a boolean
359/// indicating if it's a "long message", which should direct parsers into parsing
360/// an IGMP v3 message.
361///
362/// Note that `peek_message_type` only inspects certain fields in the header,
363/// and so `peek_message_type` succeeding does not guarantee that a subsequent
364/// call to `parse` will also succeed.
365pub fn peek_message_type<MessageType: TryFrom<u8>>(
366    bytes: &[u8],
367) -> Result<(MessageType, bool), ParseError> {
368    // a long message is any message for which the size exceeds the common HeaderPrefix +
369    // a single Ipv4Address
370    let long_message =
371        bytes.len() > (core::mem::size_of::<HeaderPrefix>() + core::mem::size_of::<Ipv4Addr>());
372    let (header, _) = Ref::<_, HeaderPrefix>::from_prefix(bytes).map_err(Into::into).map_err(
373        |_: zerocopy::SizeError<_, _>| debug_err!(ParseError::Format, "too few bytes for header"),
374    )?;
375    let msg_type = MessageType::try_from(header.msg_type).map_err(|_| {
376        debug_err!(ParseError::NotSupported, "unrecognized message type: {:x}", header.msg_type,)
377    })?;
378    Ok((msg_type, long_message))
379}
380
381#[cfg(test)]
382mod tests {
383
384    use packet::{ParseBuffer, Serializer};
385
386    use super::*;
387    use crate::igmp::messages::*;
388    use crate::ip::Ipv4Proto;
389    use crate::ipv4::options::Ipv4Option;
390    use crate::ipv4::{Ipv4Header, Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
391
392    fn serialize_to_bytes<
393        B: SplitByteSlice + Debug,
394        M: MessageType<B, VariableBody = ()> + Debug,
395    >(
396        igmp: &IgmpMessage<B, M>,
397        src_ip: Ipv4Addr,
398        dst_ip: Ipv4Addr,
399    ) -> Vec<u8> {
400        let ipv4 = Ipv4PacketBuilder::new(src_ip, dst_ip, 1, Ipv4Proto::Igmp);
401        let with_options =
402            Ipv4PacketBuilderWithOptions::new(ipv4, &[Ipv4Option::RouterAlert { data: 0 }])
403                .unwrap();
404
405        igmp.builder()
406            .into_serializer()
407            .encapsulate(with_options)
408            .serialize_vec_outer()
409            .unwrap()
410            .as_ref()
411            .to_vec()
412    }
413
414    fn test_parse_and_serialize<
415        M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
416        F: for<'a> FnOnce(&Ipv4Packet<&'a [u8]>),
417        G: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
418    >(
419        mut pkt: &[u8],
420        check_ip: F,
421        check_igmp: G,
422    ) {
423        let orig_req = pkt;
424
425        let ip = pkt.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
426        let src_ip = ip.src_ip();
427        let dst_ip = ip.dst_ip();
428        check_ip(&ip);
429        let mut req: &[u8] = pkt;
430        let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
431        check_igmp(&igmp);
432
433        let data = serialize_to_bytes(&igmp, src_ip, dst_ip);
434        assert_eq!(&data[..], orig_req);
435    }
436
437    // The following tests are basically still testing serialization and
438    // parsing of IGMP messages. Besides IGMP messages themselves, the
439    // following tests also test whether the RTRALRT option in the enclosing
440    // IPv4 packet can be parsed/serialized correctly.
441
442    #[test]
443    fn test_parse_and_serialize_igmpv2_report_with_options() {
444        use crate::testdata::igmpv2_membership::report::*;
445        test_parse_and_serialize::<IgmpMembershipReportV2, _, _>(
446            IP_PACKET_BYTES,
447            |ip| {
448                assert_eq!(ip.ttl(), 1);
449                assert_eq!(ip.iter_options().count(), 1);
450                let option = ip.iter_options().next().unwrap();
451                assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
452                assert_eq!(ip.header_len(), 24);
453                assert_eq!(ip.src_ip(), SOURCE);
454                assert_eq!(ip.dst_ip(), MULTICAST);
455            },
456            |igmp| {
457                assert_eq!(*igmp.header, MULTICAST);
458            },
459        )
460    }
461
462    #[test]
463    fn test_parse_and_serialize_igmpv2_query_with_options() {
464        use crate::testdata::igmpv2_membership::query::*;
465        test_parse_and_serialize::<IgmpMembershipQueryV2, _, _>(
466            IP_PACKET_BYTES,
467            |ip| {
468                assert_eq!(ip.ttl(), 1);
469                assert_eq!(ip.iter_options().count(), 1);
470                let option = ip.iter_options().next().unwrap();
471                assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
472                assert_eq!(ip.header_len(), 24);
473                assert_eq!(ip.src_ip(), SOURCE);
474                assert_eq!(ip.dst_ip(), MULTICAST);
475            },
476            |igmp| {
477                assert_eq!(*igmp.header, MULTICAST);
478            },
479        )
480    }
481
482    #[test]
483    fn test_parse_and_serialize_igmpv2_leave_with_options() {
484        use crate::testdata::igmpv2_membership::leave::*;
485        test_parse_and_serialize::<IgmpLeaveGroup, _, _>(
486            IP_PACKET_BYTES,
487            |ip| {
488                assert_eq!(ip.ttl(), 1);
489                assert_eq!(ip.iter_options().count(), 1);
490                let option = ip.iter_options().next().unwrap();
491                assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
492                assert_eq!(ip.header_len(), 24);
493                assert_eq!(ip.src_ip(), SOURCE);
494                assert_eq!(ip.dst_ip(), DESTINATION);
495            },
496            |igmp| {
497                assert_eq!(*igmp.header, MULTICAST);
498            },
499        )
500    }
501}