use core::fmt; use byteorder::{ByteOrder, NetworkEndian}; use crate::{Error, Result}; use crate::wire::ip::checksum; use crate::time::Duration; use crate::wire::Ipv4Address; enum_with_unknown! { /// Internet Group Management Protocol v1/v2 message version/type. pub doc enum Message(u8) { /// Membership Query MembershipQuery = 0x11, /// Version 2 Membership Report MembershipReportV2 = 0x16, /// Leave Group LeaveGroup = 0x17, /// Version 1 Membership Report MembershipReportV1 = 0x12 } } /// A read/write wrapper around an Internet Group Management Protocol v1/v2 packet buffer. #[derive(Debug)] pub struct Packet> { buffer: T, } mod field { use crate::wire::field::*; pub const TYPE: usize = 0; pub const MAX_RESP_CODE: usize = 1; pub const CHECKSUM: Field = 2..4; pub const GROUP_ADDRESS: Field = 4..8; } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Message::MembershipQuery => write!(f, "membership query"), Message::MembershipReportV2 => write!(f, "version 2 membership report"), Message::LeaveGroup => write!(f, "leave group"), Message::MembershipReportV1 => write!(f, "version 1 membership report"), Message::Unknown(id) => write!(f, "{}", id), } } } /// Internet Group Management Protocol v1/v2 defined in [RFC 2236]. /// /// [RFC 2236]: https://tools.ietf.org/html/rfc2236 impl> Packet { /// Imbue a raw octet buffer with IGMPv2 packet structure. pub fn new_unchecked(buffer: T) -> Packet { Packet { buffer } } /// Shorthand for a combination of [new_unchecked] and [check_len]. /// /// [new_unchecked]: #method.new_unchecked /// [check_len]: #method.check_len pub fn new_checked(buffer: T) -> Result> { let packet = Self::new_unchecked(buffer); packet.check_len()?; Ok(packet) } /// Ensure that no accessor method will panic if called. /// Returns `Err(Error::Truncated)` if the buffer is too short. pub fn check_len(&self) -> Result<()> { let len = self.buffer.as_ref().len(); if len < field::GROUP_ADDRESS.end as usize { Err(Error::Truncated) } else { Ok(()) } } /// Consume the packet, returning the underlying buffer. pub fn into_inner(self) -> T { self.buffer } /// Return the message type field. #[inline] pub fn msg_type(&self) -> Message { let data = self.buffer.as_ref(); Message::from(data[field::TYPE]) } /// Return the maximum response time, using the encoding specified in /// [RFC 3376]: 4.1.1. Max Resp Code. /// /// [RFC 3376]: https://tools.ietf.org/html/rfc3376 #[inline] pub fn max_resp_code(&self) -> u8 { let data = self.buffer.as_ref(); data[field::MAX_RESP_CODE] } /// Return the checksum field. #[inline] pub fn checksum(&self) -> u16 { let data = self.buffer.as_ref(); NetworkEndian::read_u16(&data[field::CHECKSUM]) } /// Return the source address field. #[inline] pub fn group_addr(&self) -> Ipv4Address { let data = self.buffer.as_ref(); Ipv4Address::from_bytes(&data[field::GROUP_ADDRESS]) } /// Validate the header checksum. /// /// # Fuzzing /// This function always returns `true` when fuzzing. pub fn verify_checksum(&self) -> bool { if cfg!(fuzzing) { return true; } let data = self.buffer.as_ref(); checksum::data(data) == !0 } } impl + AsMut<[u8]>> Packet { /// Set the message type field. #[inline] pub fn set_msg_type(&mut self, value: Message) { let data = self.buffer.as_mut(); data[field::TYPE] = value.into() } /// Set the maximum response time, using the encoding specified in /// [RFC 3376]: 4.1.1. Max Resp Code. #[inline] pub fn set_max_resp_code(&mut self, value: u8) { let data = self.buffer.as_mut(); data[field::MAX_RESP_CODE] = value; } /// Set the checksum field. #[inline] pub fn set_checksum(&mut self, value: u16) { let data = self.buffer.as_mut(); NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) } /// Set the group address field #[inline] pub fn set_group_address(&mut self, addr: Ipv4Address) { let data = self.buffer.as_mut(); data[field::GROUP_ADDRESS].copy_from_slice(addr.as_bytes()); } /// Compute and fill in the header checksum. pub fn fill_checksum(&mut self) { self.set_checksum(0); let checksum = { let data = self.buffer.as_ref(); !checksum::data(data) }; self.set_checksum(checksum) } } /// A high-level representation of an Internet Group Management Protocol v1/v2 header. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Repr { MembershipQuery { max_resp_time: Duration, group_addr: Ipv4Address, version: IgmpVersion, }, MembershipReport { group_addr: Ipv4Address, version: IgmpVersion, }, LeaveGroup { group_addr: Ipv4Address, }, } /// Type of IGMP membership report version #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum IgmpVersion { /// IGMPv1 Version1, /// IGMPv2 Version2, } impl Repr { /// Parse an Internet Group Management Protocol v1/v2 packet and return /// a high-level representation. pub fn parse(packet: &Packet<&T>) -> Result where T: AsRef<[u8]> + ?Sized { // Check if the address is 0.0.0.0 or multicast let addr = packet.group_addr(); if !addr.is_unspecified() && !addr.is_multicast() { return Err(Error::Malformed); } // construct a packet based on the Type field match packet.msg_type() { Message::MembershipQuery => { let max_resp_time = max_resp_code_to_duration(packet.max_resp_code()); // See RFC 3376: 7.1. Query Version Distinctions let version = if packet.max_resp_code() == 0 { IgmpVersion::Version1 } else { IgmpVersion::Version2 }; Ok(Repr::MembershipQuery { max_resp_time, group_addr: addr, version, }) } Message::MembershipReportV2 => { Ok(Repr::MembershipReport { group_addr: packet.group_addr(), version: IgmpVersion::Version2, }) } Message::LeaveGroup => Ok(Repr::LeaveGroup { group_addr: packet.group_addr() }), Message::MembershipReportV1 => { // for backwards compatibility with IGMPv1 Ok(Repr::MembershipReport { group_addr: packet.group_addr(), version: IgmpVersion::Version1, }) } _ => Err(Error::Unrecognized), } } /// Return the length of a packet that will be emitted from this high-level representation. pub fn buffer_len(&self) -> usize { // always 8 bytes field::GROUP_ADDRESS.end } /// Emit a high-level representation into an Internet Group Management Protocol v2 packet. pub fn emit(&self, packet: &mut Packet<&mut T>) where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized { match *self { Repr::MembershipQuery { max_resp_time, group_addr, version } => { packet.set_msg_type(Message::MembershipQuery); match version { IgmpVersion::Version1 => packet.set_max_resp_code(0), IgmpVersion::Version2 => packet.set_max_resp_code(duration_to_max_resp_code(max_resp_time)), } packet.set_group_address(group_addr); } Repr::MembershipReport { group_addr, version, } => { match version { IgmpVersion::Version1 => packet.set_msg_type(Message::MembershipReportV1), IgmpVersion::Version2 => packet.set_msg_type(Message::MembershipReportV2), }; packet.set_max_resp_code(0); packet.set_group_address(group_addr); } Repr::LeaveGroup { group_addr } => { packet.set_msg_type(Message::LeaveGroup); packet.set_group_address(group_addr); } } packet.fill_checksum() } } fn max_resp_code_to_duration(value: u8) -> Duration { let value: u64 = value.into(); let decisecs = if value < 128 { value } else { let mant = value & 0xF; let exp = (value >> 4) & 0x7; (mant | 0x10) << (exp + 3) }; Duration::from_millis(decisecs * 100) } fn duration_to_max_resp_code(duration: Duration) -> u8 { let decisecs = duration.total_millis() / 100; if decisecs < 128 { decisecs as u8 } else if decisecs < 31744 { let mut mant = decisecs >> 3; let mut exp = 0u8; while mant > 0x1F && exp < 0x8 { mant >>= 1; exp += 1; } 0x80 | (exp << 4) | (mant as u8 & 0xF) } else { 0xFF } } impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match Repr::parse(self) { Ok(repr) => write!(f, "{}", repr), Err(err) => write!(f, "IGMP ({})", err), } } } impl<'a> fmt::Display for Repr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Repr::MembershipQuery { max_resp_time, group_addr, version, } => { write!(f, "IGMP membership query max_resp_time={} group_addr={} version={:?}", max_resp_time, group_addr, version) } Repr::MembershipReport { group_addr, version, } => { write!(f, "IGMP membership report group_addr={} version={:?}", group_addr, version) } Repr::LeaveGroup { group_addr } => { write!(f, "IGMP leave group group_addr={})", group_addr) } } } } use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; impl> PrettyPrint for Packet { fn pretty_print(buffer: &dyn AsRef<[u8]>, f: &mut fmt::Formatter, indent: &mut PrettyIndent) -> fmt::Result { match Packet::new_checked(buffer) { Err(err) => writeln!(f, "{}({})", indent, err), Ok(packet) => writeln!(f, "{}{}", indent, packet), } } } #[cfg(test)] mod test { use super::*; static LEAVE_PACKET_BYTES: [u8; 8] = [0x17, 0x00, 0x02, 0x69, 0xe0, 0x00, 0x06, 0x96]; static REPORT_PACKET_BYTES: [u8; 8] = [0x16, 0x00, 0x08, 0xda, 0xe1, 0x00, 0x00, 0x25]; #[test] fn test_leave_group_deconstruct() { let packet = Packet::new_unchecked(&LEAVE_PACKET_BYTES[..]); assert_eq!(packet.msg_type(), Message::LeaveGroup); assert_eq!(packet.max_resp_code(), 0); assert_eq!(packet.checksum(), 0x269); assert_eq!(packet.group_addr(), Ipv4Address::from_bytes(&[224, 0, 6, 150])); assert_eq!(packet.verify_checksum(), true); } #[test] fn test_report_deconstruct() { let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]); assert_eq!(packet.msg_type(), Message::MembershipReportV2); assert_eq!(packet.max_resp_code(), 0); assert_eq!(packet.checksum(), 0x08da); assert_eq!(packet.group_addr(), Ipv4Address::from_bytes(&[225, 0, 0, 37])); assert_eq!(packet.verify_checksum(), true); } #[test] fn test_leave_construct() { let mut bytes = vec![0xa5; 8]; let mut packet = Packet::new_unchecked(&mut bytes); packet.set_msg_type(Message::LeaveGroup); packet.set_max_resp_code(0); packet.set_group_address(Ipv4Address::from_bytes(&[224, 0, 6, 150])); packet.fill_checksum(); assert_eq!(&packet.into_inner()[..], &LEAVE_PACKET_BYTES[..]); } #[test] fn test_report_construct() { let mut bytes = vec![0xa5; 8]; let mut packet = Packet::new_unchecked(&mut bytes); packet.set_msg_type(Message::MembershipReportV2); packet.set_max_resp_code(0); packet.set_group_address(Ipv4Address::from_bytes(&[225, 0, 0, 37])); packet.fill_checksum(); assert_eq!(&packet.into_inner()[..], &REPORT_PACKET_BYTES[..]); } #[test] fn max_resp_time_to_duration_and_back() { for i in 0..256usize { let time1 = i as u8; let duration = max_resp_code_to_duration(time1); let time2 = duration_to_max_resp_code(duration); assert!(time1 == time2); } } #[test] fn duration_to_max_resp_time_max() { for duration in 31744..65536 { let time = duration_to_max_resp_code( Duration::from_millis(duration * 100) ); assert_eq!(time, 0xFF); } } }