فهرست منبع

Implement IPv4 representation parsing and emission.

whitequark 8 سال پیش
والد
کامیت
a1587fef21
6فایلهای تغییر یافته به همراه194 افزوده شده و 37 حذف شده
  1. 11 7
      README.md
  2. 9 0
      src/lib.rs
  3. 2 2
      src/wire/arp.rs
  4. 2 0
      src/wire/ethernet.rs
  5. 169 28
      src/wire/ipv4.rs
  6. 1 0
      src/wire/mod.rs

+ 11 - 7
README.md

@@ -18,23 +18,27 @@ features are listed.
 The only supported medium is Ethernet.
 
   * Regular 802.3 frames are supported.
-  * 802.1Q is not supported.
-  * Jumbo frames are not supported.
-  * CRC calculation is not supported.
+  * 802.1Q is **not** supported.
+  * Jumbo frames are **not** supported.
+  * CRC calculation is **not** supported.
   * ARP packets are supported.
-  * ARP probes or announcements are not supported.
+  * ARP probes or announcements are **not** supported.
 
 ### IP layer
 
-IP is not supported yet.
+The only supported internetworking protocol is IPv4.
+
+  * IPv4 header checksum is supported.
+  * IPv4 fragmentation is **not** supported.
+  * IPv4 options are **not** supported.
 
 ### UDP layer
 
-UDP is not supported yet.
+UDP is **not** supported yet.
 
 ### TCP layer
 
-TCP is not supported yet.
+TCP is **not** supported yet.
 
 Installation
 ------------

+ 9 - 0
src/lib.rs

@@ -22,9 +22,16 @@ pub enum Error {
     /// for the underlying buffer.
     Truncated,
     /// An incoming packet could not be recognized and was dropped.
+    /// E.g. a packet with an unknown EtherType.
     Unrecognized,
+    /// An incoming packet was recognized but contained invalid data.
+    /// E.g. a packet with IPv4 EtherType but containing a value other than 4
+    /// in the version field.
+    Malformed,
     /// An incoming packet had an incorrect checksum and was dropped.
     Checksum,
+    /// An incoming packet has been fragmented and was dropped.
+    Fragmented,
 
     #[doc(hidden)]
     __Nonexhaustive
@@ -35,7 +42,9 @@ impl fmt::Display for Error {
         match self {
             &Error::Truncated    => write!(f, "truncated packet"),
             &Error::Unrecognized => write!(f, "unrecognized packet"),
+            &Error::Malformed    => write!(f, "malformed packet"),
             &Error::Checksum     => write!(f, "checksum error"),
+            &Error::Fragmented   => write!(f, "fragmented packet"),
             &Error::__Nonexhaustive => unreachable!()
         }
     }

+ 2 - 2
src/wire/arp.rs

@@ -241,7 +241,7 @@ pub enum Repr {
 }
 
 impl Repr {
-    /// Parse an Address Resolution Packet and return a high-level representation,
+    /// Parse an Address Resolution Protocol packet and return a high-level representation,
     /// or return `Err(())` if the packet is not recognized.
     pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr, Error> {
         match (packet.hardware_type(), packet.protocol_type(),
@@ -263,7 +263,7 @@ impl Repr {
         }
     }
 
-    /// Emit a high-level representation into an Address Resolution Packet.
+    /// Emit a high-level representation into an Address Resolution Protocol packet.
     pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
         match self {
             &Repr::EthernetIpv4 {

+ 2 - 0
src/wire/ethernet.rs

@@ -177,6 +177,8 @@ impl<T: AsRef<[u8]>> PrettyPrint for Frame<T> {
         match frame.ethertype() {
             EtherType::Arp =>
                 super::ArpPacket::<&[u8]>::pretty_print(&frame.payload(), f, indent),
+            EtherType::Ipv4 =>
+                super::Ipv4Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent),
             _ => Ok(())
         }
     }

+ 169 - 28
src/wire/ipv4.rs

@@ -81,8 +81,7 @@ mod field {
     pub const DSCP_ECN: usize = 1;
     pub const LENGTH:   Field = 2..4;
     pub const IDENT:    Field = 4..6;
-    pub const FLG_OFF1: usize = 6;
-    pub const FLG_OFF0: usize = 7;
+    pub const FLG_OFF:  Field = 6..8;
     pub const TTL:      usize = 8;
     pub const PROTOCOL: usize = 9;
     pub const CHECKSUM: Field = 10..12;
@@ -105,7 +104,7 @@ impl<T: AsRef<[u8]>> Packet<T> {
     /// is too small to contain one.
     pub fn new(buffer: T) -> Result<Packet<T>, Error> {
         let len = buffer.as_ref().len();
-        if len < field::VER_IHL {
+        if len < field::DST_ADDR.end {
             Err(Error::Truncated)
         } else {
             let packet = Packet { buffer: buffer };
@@ -126,26 +125,26 @@ impl<T: AsRef<[u8]>> Packet<T> {
     #[inline(always)]
     pub fn version(&self) -> u8 {
         let data = self.buffer.as_ref();
-        data[field::VER_IHL] & 0x04
+        data[field::VER_IHL] >> 4
     }
 
     /// Return the header length, in octets.
     #[inline(always)]
     pub fn header_len(&self) -> u8 {
         let data = self.buffer.as_ref();
-        (data[field::VER_IHL] >> 4) * 4
+        (data[field::VER_IHL] & 0x0f) * 4
     }
 
     /// Return the Differential Services Code Point field.
     pub fn dscp(&self) -> u8 {
         let data = self.buffer.as_ref();
-        data[field::DSCP_ECN] & 0x3f
+        data[field::DSCP_ECN] >> 2
     }
 
     /// Return the Explicit Congestion Notification field.
     pub fn ecn(&self) -> u8 {
         let data = self.buffer.as_ref();
-        data[field::DSCP_ECN] >> 6
+        data[field::DSCP_ECN] & 0x03
     }
 
     /// Return the total length field.
@@ -166,23 +165,21 @@ impl<T: AsRef<[u8]>> Packet<T> {
     #[inline(always)]
     pub fn dont_frag(&self) -> bool {
         let data = self.buffer.as_ref();
-        data[field::FLG_OFF1] & 0x02 != 0
+        NetworkEndian::read_u16(&data[field::FLG_OFF]) & 0x4000 != 0
     }
 
     /// Return the "more fragments" flag.
     #[inline(always)]
     pub fn more_frags(&self) -> bool {
         let data = self.buffer.as_ref();
-        data[field::FLG_OFF1] & 0x04 != 0
+        NetworkEndian::read_u16(&data[field::FLG_OFF]) & 0x2000 != 0
     }
 
     /// Return the fragment offset, in octets.
     #[inline(always)]
     pub fn frag_offset(&self) -> u16 {
         let data = self.buffer.as_ref();
-        let chunks = (((data[field::FLG_OFF1] >> 3) as u16) << 8) |
-                        data[field::FLG_OFF0] as u16;
-        chunks * 8
+        NetworkEndian::read_u16(&data[field::FLG_OFF]) << 3
     }
 
     /// Return the time to live field.
@@ -243,26 +240,26 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
     #[inline(always)]
     pub fn set_version(&mut self, value: u8) {
         let data = self.buffer.as_mut();
-        data[field::VER_IHL] = (data[field::VER_IHL] & !0x04) | (value & 0x04);
+        data[field::VER_IHL] = (data[field::VER_IHL] & !0xf0) | (value << 4);
     }
 
     /// Set the header length, in octets.
     #[inline(always)]
     pub fn set_header_len(&mut self, value: u8) {
         let data = self.buffer.as_mut();
-        data[field::VER_IHL] = (data[field::VER_IHL] & !0x40) | ((value / 4) << 4);
+        data[field::VER_IHL] = (data[field::VER_IHL] & !0x0f) | ((value / 4) & 0x0f);
     }
 
     /// Set the Differential Services Code Point field.
     pub fn set_dscp(&mut self, value: u8) {
         let data = self.buffer.as_mut();
-        data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0x3f) | (value & 0x3f)
+        data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0xfc) | (value << 2)
     }
 
     /// Set the Explicit Congestion Notification field.
     pub fn set_ecn(&mut self, value: u8) {
         let data = self.buffer.as_mut();
-        data[field::DSCP_ECN] = (data[field::DSCP_ECN] & 0x3f) | (value << 6)
+        data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0x03) | (value & 0x03)
     }
 
     /// Set the total length field.
@@ -283,26 +280,27 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
     #[inline(always)]
     pub fn set_dont_frag(&mut self, value: bool) {
         let data = self.buffer.as_mut();
-        let raw = data[field::FLG_OFF1];
-        data[field::FLG_OFF1] = if value { raw | 0x02 } else { raw & !0x02 };
+        let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+        let raw = if value { raw | 0x4000 } else { raw & !0x4000 };
+        NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
     }
 
     /// Set the "more fragments" flag.
     #[inline(always)]
     pub fn set_more_frags(&mut self, value: bool) {
         let data = self.buffer.as_mut();
-        let raw = data[field::FLG_OFF1];
-        data[field::FLG_OFF1] = if value { raw | 0x04 } else { raw & !0x04 };
+        let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+        let raw = if value { raw | 0x2000 } else { raw & !0x2000 };
+        NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
     }
 
     /// Set the fragment offset, in octets.
     #[inline(always)]
     pub fn set_frag_offset(&mut self, value: u16) {
         let data = self.buffer.as_mut();
-        let chunks = value / 8;
-        let raw = data[field::FLG_OFF1] & 0x7;
-        data[field::FLG_OFF1] = raw | (((chunks >> 8) << 3) as u8);
-        data[field::FLG_OFF0] = chunks as u8;
+        let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]);
+        let raw = (raw & 0xe000) | (value >> 3);
+        NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw);
     }
 
     /// Set the time to live field.
@@ -358,14 +356,126 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
     }
 }
 
+/// A high-level representation of an Internet Protocol version 4 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Repr {
+    pub src_addr: Address,
+    pub dst_addr: Address,
+    pub protocol: ProtocolType
+}
+
+impl Repr {
+    /// Parse an Internet Protocol version 4 packet and return a high-level representation,
+    /// or return `Err(())` if the packet is not recognized or is malformed.
+    pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr, Error> {
+        // Version 4 is expected.
+        if packet.version() != 4 { return Err(Error::Malformed) }
+        // Valid checksum is expected.
+        if !packet.verify_checksum() { return Err(Error::Checksum) }
+        // We do not support any IP options.
+        if packet.header_len() > 20 { return Err(Error::Unrecognized) }
+        // We do not support fragmentation.
+        if packet.more_frags() || packet.frag_offset() != 0 { return Err(Error::Fragmented) }
+        // Since the packet is not fragmented, it must include the entire payload.
+        let payload_len = packet.total_len() as usize - packet.header_len() as usize;
+        if packet.payload().len() < payload_len  { return Err(Error::Truncated) }
+
+        // All DSCP values are acceptable, since they are of no concern to receiving endpoint.
+        // All ECN values are acceptable, since ECN requires opt-in from both endpoints.
+        // All TTL values are acceptable, since we do not perform routing.
+        Ok(Repr {
+            src_addr: packet.src_addr(),
+            dst_addr: packet.dst_addr(),
+            protocol: packet.protocol()
+        })
+    }
+
+    /// Emit a high-level representation into an Internet Protocol version 4 packet.
+    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>,
+                                              payload_len: usize) {
+        packet.set_version(4);
+        packet.set_header_len(20);
+        packet.set_dscp(0);
+        packet.set_ecn(0);
+        let total_len = packet.header_len() as u16 + payload_len as u16;
+        packet.set_total_len(total_len);
+        packet.set_ident(0);
+        packet.set_more_frags(false);
+        packet.set_dont_frag(true);
+        packet.set_frag_offset(0);
+        packet.set_ttl(64);
+        packet.set_protocol(self.protocol);
+        packet.set_src_addr(self.src_addr);
+        packet.set_dst_addr(self.dst_addr);
+        packet.fill_checksum();
+    }
+}
+
+impl<T: AsRef<[u8]>> fmt::Display for Packet<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match Repr::parse(self) {
+            Ok(repr) => write!(f, "{}", repr),
+            _ => {
+                try!(write!(f, "IPv4 src={} dst={} proto={} ttl={}",
+                            self.src_addr(), self.dst_addr(), self.protocol(), self.ttl()));
+                if self.version() != 4 {
+                    try!(write!(f, " ver={}", self.version()))
+                }
+                if self.header_len() != 20 {
+                    try!(write!(f, " hlen={}", self.header_len()))
+                }
+                if self.dscp() != 0 {
+                    try!(write!(f, " dscp={}", self.dscp()))
+                }
+                if self.ecn() != 0 {
+                    try!(write!(f, " ecn={}", self.ecn()))
+                }
+                try!(write!(f, " tlen={}", self.total_len()));
+                if self.dont_frag() {
+                    try!(write!(f, " df"))
+                }
+                if self.more_frags() {
+                    try!(write!(f, " mf"))
+                }
+                if self.frag_offset() != 0 {
+                    try!(write!(f, " off={}", self.frag_offset()))
+                }
+                if self.more_frags() || self.frag_offset() != 0 {
+                    try!(write!(f, " id={}", self.ident()))
+                }
+                Ok(())
+            }
+        }
+    }
+}
+
+impl fmt::Display for Repr {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "IPv4 src={} dst={} proto={}",
+               self.src_addr, self.dst_addr, self.protocol)
+    }
+}
+
+use super::pretty_print::{PrettyPrint, PrettyIndent};
+
+impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
+    fn pretty_print(buffer: &AsRef<[u8]>, f: &mut fmt::Formatter,
+                    indent: &mut PrettyIndent) -> fmt::Result {
+        match Packet::new(buffer) {
+            Err(err)  => write!(f, "{}({})\n", indent, err),
+            Ok(frame) => write!(f, "{}{}\n", indent, frame)
+        }
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
 
     static PACKET_BYTES: [u8; 30] =
-        [0x54, 0x00, 0x00, 0x1e,
-         0x01, 0x02, 0x16, 0x03,
-         0x1a, 0x01, 0x12, 0x6f,
+        [0x45, 0x00, 0x00, 0x1e,
+         0x01, 0x02, 0x62, 0x03,
+         0x1a, 0x01, 0xd5, 0x6e,
          0x11, 0x12, 0x13, 0x14,
          0x21, 0x22, 0x23, 0x24,
          0xaa, 0x00, 0x00, 0x00,
@@ -391,7 +501,7 @@ mod test {
         assert_eq!(packet.frag_offset(), 0x203 * 8);
         assert_eq!(packet.ttl(), 0x1a);
         assert_eq!(packet.protocol(), ProtocolType::Icmp);
-        assert_eq!(packet.checksum(), 0x126f);
+        assert_eq!(packet.checksum(), 0xd56e);
         assert_eq!(packet.src_addr(), Address([0x11, 0x12, 0x13, 0x14]));
         assert_eq!(packet.dst_addr(), Address([0x21, 0x22, 0x23, 0x24]));
         assert_eq!(packet.verify_checksum(), true);
@@ -419,4 +529,35 @@ mod test {
         packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]);
         assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]);
     }
+
+    static REPR_PACKET_BYTES: [u8; 24] =
+        [0x45, 0x00, 0x00, 0x18,
+         0x00, 0x00, 0x40, 0x00,
+         0x40, 0x01, 0xd2, 0x79,
+         0x11, 0x12, 0x13, 0x14,
+         0x21, 0x22, 0x23, 0x24,
+         0x00, 0x00, 0x00, 0x00];
+
+    fn packet_repr() -> Repr {
+        Repr {
+            src_addr: Address([0x11, 0x12, 0x13, 0x14]),
+            dst_addr: Address([0x21, 0x22, 0x23, 0x24]),
+            protocol: ProtocolType::Icmp
+        }
+    }
+
+    #[test]
+    fn test_parse() {
+        let packet = Packet::new(&REPR_PACKET_BYTES[..]).unwrap();
+        let repr = Repr::parse(&packet).unwrap();
+        assert_eq!(repr, packet_repr());
+    }
+
+    #[test]
+    fn test_emit() {
+        let mut bytes = vec![0; 24];
+        let mut packet = Packet::new(&mut bytes).unwrap();
+        packet_repr().emit(&mut packet, 4);
+        assert_eq!(&packet.into_inner()[..], &REPR_PACKET_BYTES[..]);
+    }
 }

+ 1 - 0
src/wire/mod.rs

@@ -78,3 +78,4 @@ 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;