Skip to main content

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