Преглед на файлове

Implement ICMPv4 echo request/reply packet support.

whitequark преди 8 години
родител
ревизия
e82e74458d
променени са 4 файла, в които са добавени 324 реда и са изтрити 16 реда
  1. 274 0
      src/wire/icmpv4.rs
  2. 11 0
      src/wire/ip.rs
  3. 4 12
      src/wire/ipv4.rs
  4. 35 4
      src/wire/mod.rs

+ 274 - 0
src/wire/icmpv4.rs

@@ -0,0 +1,274 @@
+use core::fmt;
+use byteorder::{ByteOrder, NetworkEndian};
+
+use Error;
+use super::ip::rfc1071_checksum;
+
+enum_with_unknown! {
+    /// Internet protocol control message type.
+    pub doc enum Type(u8) {
+        /// Echo reply
+        EchoReply      =  0,
+        /// Destination unreachable
+        DstUnreachable =  1,
+        /// Message redirect
+        Redirect       =  5,
+        /// Echo request
+        EchoRequest    =  8,
+        /// Router advertisement
+        RouterAdvert   =  9,
+        /// Router solicitation
+        RouterSolicit  = 10,
+        /// Time exceeded
+        TimeExceeded   = 11,
+        /// Parameter problem
+        ParamProblem   = 12,
+        /// Timestamp
+        Timestamp      = 13,
+        /// Timestamp reply
+        TimestampReply = 14
+    }
+}
+
+enum_with_unknown! {
+    /// Internet protocol control message subtype for type "Destination Unreachable".
+    pub doc enum DstUnreachable(u8) {
+        /// Destination network unreachable
+        NetUnreachable   =  0,
+        /// Destination host unreachable
+        HostUnreachable  =  1,
+        /// Destination protocol unreachable
+        ProtoUnreachable =  2,
+        /// Destination port unreachable
+        PortUnreachable  =  3,
+        /// Fragmentation required, and DF flag set
+        FragRequired     =  4,
+        /// Source route failed
+        SrcRouteFailed   =  5,
+        /// Destination network unknown
+        DstNetUnknown    =  6,
+        /// Destination host unknown
+        DstHostUnknown   =  7,
+        /// Source host isolated
+        SrcHostIsolated  =  8,
+        /// Network administratively prohibited
+        NetProhibited    =  9,
+        /// Host administratively prohibited
+        HostProhibited   = 10,
+        /// Network unreachable for ToS
+        NetUnreachToS    = 11,
+        /// Host unreachable for ToS
+        HostUnreachToS   = 12,
+        /// Communication administratively prohibited
+        CommProhibited   = 13,
+        /// Host precedence violation
+        HostPrecedViol   = 14,
+        /// Precedence cutoff in effect
+        PrecedCutoff     = 15
+    }
+}
+
+enum_with_unknown! {
+    /// Internet protocol control message subtype for type "Redirect Message".
+    pub doc enum Redirect(u8) {
+        /// Redirect Datagram for the Network
+        Net     = 0,
+        /// Redirect Datagram for the Host
+        Host    = 1,
+        /// Redirect Datagram for the ToS & network
+        NetToS  = 2,
+        /// Redirect Datagram for the ToS & host
+        HostToS = 3
+    }
+}
+
+enum_with_unknown! {
+    /// Internet protocol control message subtype for type "Time Exceeded".
+    pub doc enum TimeExceeded(u8) {
+        /// TTL expired in transit
+        TtlExpired  = 0,
+        /// Fragment reassembly time exceeded
+        FragExpired = 1
+    }
+}
+
+enum_with_unknown! {
+    /// Internet protocol control message subtype for type "Parameter Problem".
+    pub doc enum ParamProblem(u8) {
+        /// Pointer indicates the error
+        AtPointer     = 0,
+        /// Missing a required option
+        MissingOption = 1,
+        /// Bad length
+        BadLength     = 2
+    }
+}
+
+/// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer.
+#[derive(Debug)]
+pub struct Packet<T: AsRef<[u8]>> {
+    buffer: T
+}
+
+mod field {
+    #![allow(non_snake_case)]
+
+    use wire::field::*;
+
+    pub const TYPE:       usize = 0;
+    pub const CODE:       usize = 1;
+    pub const CHECKSUM:   Field = 2..4;
+
+    pub const ECHO_IDENT: Field = 4..6;
+    pub const ECHO_SEQNO: Field = 6..8;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Wrap a buffer with an ICMPv4 packet. Returns an error if the buffer
+    /// is too small to contain one.
+    pub fn new(buffer: T) -> Result<Packet<T>, Error> {
+        let len = buffer.as_ref().len();
+        if len < field::CHECKSUM.end {
+            Err(Error::Truncated)
+        } else {
+            let packet = Packet { buffer: buffer };
+            if len < packet.header_len() {
+                Err(Error::Truncated)
+            } else {
+                Ok(packet)
+            }
+        }
+    }
+
+    /// Consumes the packet, returning the underlying buffer.
+    pub fn into_inner(self) -> T {
+        self.buffer
+    }
+
+    /// Return the message type field.
+    #[inline(always)]
+    pub fn msg_type(&self) -> Type {
+        let data = self.buffer.as_ref();
+        Type::from(data[field::TYPE])
+    }
+
+    /// Return the message code field.
+    #[inline(always)]
+    pub fn msg_code(&self) -> u8 {
+        let data = self.buffer.as_ref();
+        data[field::CODE]
+    }
+
+    /// Return the checksum field.
+    #[inline(always)]
+    pub fn checksum(&self) -> u16 {
+        let data = self.buffer.as_ref();
+        NetworkEndian::read_u16(&data[field::CHECKSUM])
+    }
+
+    /// Return the identifier field (for echo request and reply packets).
+    ///
+    /// # Panics
+    /// This function may panic if this packet is not an echo request or reply packet.
+    #[inline(always)]
+    pub fn echo_ident(&self) -> u16 {
+        let data = self.buffer.as_ref();
+        NetworkEndian::read_u16(&data[field::ECHO_IDENT])
+    }
+
+    /// Return the sequence number field (for echo request and reply packets).
+    ///
+    /// # Panics
+    /// This function may panic if this packet is not an echo request or reply packet.
+    #[inline(always)]
+    pub fn echo_seq_no(&self) -> u16 {
+        let data = self.buffer.as_ref();
+        NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
+    }
+
+    /// Return the header length.
+    /// The result depends on the value of the message type field.
+    pub fn header_len(&self) -> usize {
+        match self.msg_type() {
+            Type::EchoRequest => field::ECHO_SEQNO.end,
+            Type::EchoReply   => field::ECHO_SEQNO.end,
+            _ => field::CHECKSUM.end // make a conservative assumption
+        }
+    }
+
+    /// Return a pointer to the type-specific data.
+    #[inline(always)]
+    pub fn data(&self) -> &[u8] {
+        let data = self.buffer.as_ref();
+        &data[self.header_len()..]
+    }
+
+    /// Validate the header checksum.
+    pub fn verify_checksum(&self) -> bool {
+        let checksum = {
+            let data = self.buffer.as_ref();
+            rfc1071_checksum(field::CHECKSUM.start, &data[..self.header_len()])
+        };
+        self.checksum() == checksum
+    }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Set the message type field.
+    #[inline(always)]
+    pub fn set_msg_type(&mut self, value: Type) {
+        let mut data = self.buffer.as_mut();
+        data[field::TYPE] = value.into()
+    }
+
+    /// Set the message code field.
+    #[inline(always)]
+    pub fn set_msg_code(&mut self, value: u8) {
+        let mut data = self.buffer.as_mut();
+        data[field::CODE] = value
+    }
+
+    /// Set the checksum field.
+    #[inline(always)]
+    pub fn set_checksum(&mut self, value: u16) {
+        let mut data = self.buffer.as_mut();
+        NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
+    }
+
+    /// Set the identifier field (for echo request and reply packets).
+    ///
+    /// # Panics
+    /// This function may panic if this packet is not an echo request or reply packet.
+    #[inline(always)]
+    pub fn set_echo_ident(&mut self, value: u16) {
+        let mut data = self.buffer.as_mut();
+        NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
+    }
+
+    /// Set the sequence number field (for echo request and reply packets).
+    ///
+    /// # Panics
+    /// This function may panic if this packet is not an echo request or reply packet.
+    #[inline(always)]
+    pub fn set_echo_seq_no(&mut self, value: u16) {
+        let mut data = self.buffer.as_mut();
+        NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
+    }
+
+    /// Return a mutable pointer to the type-specific data.
+    #[inline(always)]
+    pub fn data_mut(&mut self) -> &mut [u8] {
+        let range = self.header_len()..;
+        let mut data = self.buffer.as_mut();
+        &mut data[range]
+    }
+
+    /// Compute and fill in the header checksum.
+    pub fn fill_checksum(&mut self) {
+        let checksum = {
+            let data = self.buffer.as_ref();
+            rfc1071_checksum(field::CHECKSUM.start, &data[..self.header_len()])
+        };
+        self.set_checksum(checksum)
+    }
+}

+ 11 - 0
src/wire/ip.rs

@@ -1,4 +1,5 @@
 use core::fmt;
+use byteorder::{ByteOrder, NetworkEndian};
 
 enum_with_unknown! {
     /// Internet protocol type.
@@ -19,3 +20,13 @@ impl fmt::Display for ProtocolType {
         }
     }
 }
+
+pub fn rfc1071_checksum(checksum_at: usize, data: &[u8]) -> u16 {
+    let mut accum: u32 = 0;
+    for i in (0..data.len()).step_by(2) {
+        if i == checksum_at { continue }
+        let word = NetworkEndian::read_u16(&data[i..i + 2]) as u32;
+        accum += word;
+    }
+    !(((accum >> 16) as u16) + (accum as u16))
+}

+ 4 - 12
src/wire/ipv4.rs

@@ -1,6 +1,8 @@
 use core::fmt;
 use byteorder::{ByteOrder, NetworkEndian};
+
 use Error;
+use super::ip::rfc1071_checksum;
 
 pub use super::InternetProtocolType as ProtocolType;
 
@@ -89,16 +91,6 @@ mod field {
     pub const DST_ADDR: Field = 16..20;
 }
 
-fn checksum(data: &[u8]) -> u16 {
-    let mut accum: u32 = 0;
-    for i in (0..data.len()).step_by(2) {
-        if i == field::CHECKSUM.start { continue }
-        let word = NetworkEndian::read_u16(&data[i..i + 2]) as u32;
-        accum += word;
-    }
-    !(((accum >> 16) as u16) + (accum as u16))
-}
-
 impl<T: AsRef<[u8]>> Packet<T> {
     /// Wrap a buffer with an IPv4 packet. Returns an error if the buffer
     /// is too small to contain one.
@@ -229,7 +221,7 @@ impl<T: AsRef<[u8]>> Packet<T> {
     pub fn verify_checksum(&self) -> bool {
         let checksum = {
             let data = self.buffer.as_ref();
-            checksum(&data[..self.header_len() as usize])
+            rfc1071_checksum(field::CHECKSUM.start, &data[..self.header_len() as usize])
         };
         self.checksum() == checksum
     }
@@ -350,7 +342,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
     pub fn fill_checksum(&mut self) {
         let checksum = {
             let data = self.buffer.as_ref();
-            checksum(&data[..self.header_len() as usize])
+            rfc1071_checksum(field::CHECKSUM.start, &data[..self.header_len() as usize])
         };
         self.set_checksum(checksum)
     }

+ 35 - 4
src/wire/mod.rs

@@ -21,12 +21,35 @@
 //! as much as is necessary to build the representation.
 
 macro_rules! enum_with_unknown {
-    (#[$( $attr:meta ),*]
-     pub enum $name:ident($ty:ty) { $( $variant:ident = $value:expr ),+ }) => {
+    (
+        $( #[$enum_attr:meta] )*
+        pub enum $name:ident($ty:ty) {
+            $( $variant:ident = $value:expr ),+
+        }
+    ) => {
+        enum_with_unknown! {
+            $( #[$enum_attr] )*
+            pub doc enum $name($ty) {
+                $( #[doc(shown)] $variant = $value ),+
+            }
+        }
+    };
+    (
+        $( #[$enum_attr:meta] )*
+        pub doc enum $name:ident($ty:ty) {
+            $(
+              $( #[$variant_attr:meta] )+
+              $variant:ident = $value:expr
+            ),+
+        }
+    ) => {
         #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
-        #[$( $attr ),*]
+        $( #[$enum_attr] )*
         pub enum $name {
-            $( $variant ),*,
+            $(
+              $( #[$variant_attr] )*
+              $variant
+            ),*,
             Unknown($ty)
         }
 
@@ -61,6 +84,7 @@ mod ethernet;
 mod arp;
 mod ip;
 mod ipv4;
+mod icmpv4;
 
 pub use self::pretty_print::PrettyPrinter;
 
@@ -79,3 +103,10 @@ pub use self::ip::ProtocolType as InternetProtocolType;
 pub use self::ipv4::Address as Ipv4Address;
 pub use self::ipv4::Packet as Ipv4Packet;
 pub use self::ipv4::Repr as Ipv4Repr;
+
+pub use self::icmpv4::Type as Icmpv4Type;
+pub use self::icmpv4::DstUnreachable as Icmpv4DstUnreachable;
+pub use self::icmpv4::Redirect as Icmpv4Redirect;
+pub use self::icmpv4::TimeExceeded as Icmpv4TimeExceeded;
+pub use self::icmpv4::ParamProblem as Icmpv4ParamProblem;
+pub use self::icmpv4::Packet as Icmpv4Packet;