Browse Source

Add Packet support for NDISC packet types

Add packet layer support for NDISC packet types.

Closes: #180
Approved by: whitequark
Dan Robertson 7 years ago
parent
commit
4aeeac09d5
3 changed files with 312 additions and 31 deletions
  1. 86 31
      src/wire/icmpv6.rs
  2. 6 0
      src/wire/mod.rs
  3. 220 0
      src/wire/ndisc.rs

+ 86 - 31
src/wire/icmpv6.rs

@@ -10,17 +10,27 @@ enum_with_unknown! {
     /// Internet protocol control message type.
     pub doc enum Message(u8) {
         /// Destination Unreachable.
-        DstUnreachable = 0x01,
+        DstUnreachable  = 0x01,
         /// Packet Too Big.
-        PktTooBig      = 0x02,
+        PktTooBig       = 0x02,
         /// Time Exceeded.
-        TimeExceeded   = 0x03,
+        TimeExceeded    = 0x03,
         /// Parameter Problem.
-        ParamProblem   = 0x04,
+        ParamProblem    = 0x04,
         /// Echo Request
-        EchoRequest    = 0x80,
+        EchoRequest     = 0x80,
         /// Echo Reply
-        EchoReply      = 0x81
+        EchoReply       = 0x81,
+        /// Router Solicitation
+        RouterSolicit   = 0x85,
+        /// Router Advertisement
+        RouterAdvert    = 0x86,
+        /// Neighbor Solicitation
+        NeighborSolicit = 0x87,
+        /// Neighbor Advertisement
+        NeighborAdvert  = 0x88,
+        /// Redirect
+        Redirect        = 0x89
     }
 }
 
@@ -38,13 +48,18 @@ impl Message {
 impl fmt::Display for Message {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            &Message::DstUnreachable => write!(f, "destination unreachable"),
-            &Message::PktTooBig      => write!(f, "packet too big"),
-            &Message::TimeExceeded   => write!(f, "time exceeded"),
-            &Message::ParamProblem   => write!(f, "parameter problem"),
-            &Message::EchoReply      => write!(f, "echo reply"),
-            &Message::EchoRequest    => write!(f, "echo request"),
-            &Message::Unknown(id) => write!(f, "{}", id)
+            &Message::DstUnreachable  => write!(f, "destination unreachable"),
+            &Message::PktTooBig       => write!(f, "packet too big"),
+            &Message::TimeExceeded    => write!(f, "time exceeded"),
+            &Message::ParamProblem    => write!(f, "parameter problem"),
+            &Message::EchoReply       => write!(f, "echo reply"),
+            &Message::EchoRequest     => write!(f, "echo request"),
+            &Message::RouterSolicit   => write!(f, "router solicitation"),
+            &Message::RouterAdvert    => write!(f, "router advertisement"),
+            &Message::NeighborSolicit => write!(f, "neighbor solicitation"),
+            &Message::NeighborAdvert  => write!(f, "neighbor advert"),
+            &Message::Redirect        => write!(f, "redirect"),
+            &Message::Unknown(id)     => write!(f, "{}", id)
         }
     }
 }
@@ -145,25 +160,56 @@ impl fmt::Display for TimeExceeded {
 /// A read/write wrapper around an Internet Control Message Protocol version 6 packet buffer.
 #[derive(Debug, PartialEq, Clone)]
 pub struct Packet<T: AsRef<[u8]>> {
-    buffer: T
+    pub(super) buffer: T
 }
 
 // Ranges and constants describing key boundaries in the ICMPv6 header.
 // See https://tools.ietf.org/html/rfc4443 for details.
-mod field {
+pub(super) mod field {
     use wire::field::*;
 
-    pub const TYPE:       usize = 0;
-    pub const CODE:       usize = 1;
-    pub const CHECKSUM:   Field = 2..4;
+    pub const TYPE:          usize = 0;
+    pub const CODE:          usize = 1;
+    pub const CHECKSUM:      Field = 2..4;
 
-    pub const UNUSED:     Field = 4..8;
-    pub const MTU:        Field = 4..8;
-    pub const POINTER:    Field = 4..8;
-    pub const ECHO_IDENT: Field = 4..6;
-    pub const ECHO_SEQNO: Field = 6..8;
+    pub const UNUSED:        Field = 4..8;
+    pub const MTU:           Field = 4..8;
+    pub const POINTER:       Field = 4..8;
+    pub const ECHO_IDENT:    Field = 4..6;
+    pub const ECHO_SEQNO:    Field = 6..8;
 
-    pub const HEADER_END: usize = 8;
+    pub const HEADER_END:    usize = 8;
+
+    // Router Advertisement message offsets
+    pub const CUR_HOP_LIMIT: usize = 4;
+    pub const ROUTER_FLAGS:  usize = 5;
+    pub const ROUTER_LT:     Field = 6..8;
+    pub const REACHABLE_TM:  Field = 8..12;
+    pub const RETRANS_TM:    Field = 12..16;
+
+    // Neighbor Solicitation message offsets
+    pub const TARGET_ADDR:   Field = 8..24;
+
+    // Neighbor Advertisement message offsets
+    pub const NEIGH_FLAGS:   usize = 4;
+
+    // Redirected Header message offsets
+    pub const DEST_ADDR:     Field = 24..40;
+}
+
+bitflags! {
+    pub struct RouterFlags: u8 {
+        const MANAGED = 0b10000000;
+        const OTHER   = 0b01000000;
+    }
+}
+
+bitflags! {
+    pub struct NeighborFlags: u8 {
+        const ROUTER    = 0b10000000;
+        const SOLICITED = 0b01000000;
+        const OVERRIDE  = 0b00100000;
+    }
 }
 
 impl<T: AsRef<[u8]>> Packet<T> {
@@ -189,7 +235,11 @@ impl<T: AsRef<[u8]>> Packet<T> {
         if len < field::HEADER_END {
             Err(Error::Truncated)
         } else {
-            Ok(())
+            if len < self.header_len() {
+                Err(Error::Truncated)
+            } else {
+                Ok(())
+            }
         }
     }
 
@@ -251,12 +301,17 @@ impl<T: AsRef<[u8]>> Packet<T> {
     /// the message type field.
     pub fn header_len(&self) -> usize {
         match self.msg_type() {
-            Message::DstUnreachable => field::UNUSED.end,
-            Message::PktTooBig      => field::MTU.end,
-            Message::TimeExceeded   => field::UNUSED.end,
-            Message::ParamProblem   => field::POINTER.end,
-            Message::EchoRequest    => field::ECHO_SEQNO.end,
-            Message::EchoReply      => field::ECHO_SEQNO.end,
+            Message::DstUnreachable  => field::UNUSED.end,
+            Message::PktTooBig       => field::MTU.end,
+            Message::TimeExceeded    => field::UNUSED.end,
+            Message::ParamProblem    => field::POINTER.end,
+            Message::EchoRequest     => field::ECHO_SEQNO.end,
+            Message::EchoReply       => field::ECHO_SEQNO.end,
+            Message::RouterSolicit   => field::UNUSED.end,
+            Message::RouterAdvert    => field::RETRANS_TM.end,
+            Message::NeighborSolicit => field::TARGET_ADDR.end,
+            Message::NeighborAdvert  => field::TARGET_ADDR.end,
+            Message::Redirect        => field::DEST_ADDR.end,
             // For packets that are not included in RFC 4443, do not
             // include the last 32 bits of the ICMPv6 header in
             // `header_bytes`. This must be done so that these bytes

+ 6 - 0
src/wire/mod.rs

@@ -98,6 +98,8 @@ mod icmpv6;
 #[cfg(feature = "proto-ipv4")]
 mod igmp;
 #[cfg(feature = "proto-ipv6")]
+mod ndisc;
+#[cfg(feature = "proto-ipv6")]
 mod ndiscoption;
 mod udp;
 mod tcp;
@@ -173,6 +175,10 @@ pub use self::icmpv6::{Message as Icmpv6Message,
                        Packet as Icmpv6Packet,
                        Repr as Icmpv6Repr};
 
+#[cfg(feature = "proto-ipv6")]
+pub use self::icmpv6::{RouterFlags as NdiscRouterFlags,
+                       NeighborFlags as NdiscNeighborFlags};
+
 #[cfg(feature = "proto-ipv6")]
 pub use self::ndiscoption::{NdiscOption,
                             Repr as NdiscOptionRepr,

+ 220 - 0
src/wire/ndisc.rs

@@ -0,0 +1,220 @@
+use byteorder::{ByteOrder, NetworkEndian};
+
+use super::icmpv6::*;
+use time::Duration;
+use super::Ipv6Address;
+
+/// Getters for the Router Advertisement message header.
+/// See [RFC 4861 § 4.2].
+///
+/// [RFC 4861 § 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Return the current hop limit field.
+    #[inline]
+    pub fn current_hop_limit(&self) -> u8 {
+        let data = self.buffer.as_ref();
+        data[field::CUR_HOP_LIMIT]
+    }
+
+    /// Return the Router Advertisement flags.
+    #[inline]
+    pub fn router_flags(&self) -> RouterFlags {
+        let data = self.buffer.as_ref();
+        RouterFlags::from_bits_truncate(data[field::ROUTER_FLAGS])
+    }
+
+    /// Return the router lifetime field.
+    #[inline]
+    pub fn router_lifetime(&self) -> Duration {
+        let data = self.buffer.as_ref();
+        Duration::from_secs(NetworkEndian::read_u16(&data[field::ROUTER_LT]) as u64)
+    }
+
+    /// Return the reachable time field.
+    #[inline]
+    pub fn reachable_time(&self) -> Duration {
+        let data = self.buffer.as_ref();
+        Duration::from_millis(NetworkEndian::read_u32(&data[field::REACHABLE_TM]) as u64)
+    }
+
+    /// Return the retransmit time field.
+    #[inline]
+    pub fn retrans_time(&self) -> Duration {
+        let data = self.buffer.as_ref();
+        Duration::from_millis(NetworkEndian::read_u32(&data[field::RETRANS_TM]) as u64)
+    }
+}
+
+/// Getters for the [Neighbor Solicitation], [Neighbor Advertisement], and
+/// [Redirect] message types.
+///
+/// [Neighbor Solicitation]: https://tools.ietf.org/html/rfc4861#section-4.3
+/// [Neighbor Advertisement]: https://tools.ietf.org/html/rfc4861#section-4.4
+/// [Redirect]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Return the target address field.
+    #[inline]
+    pub fn target_addr(&self) -> Ipv6Address {
+        let data = self.buffer.as_ref();
+        Ipv6Address::from_bytes(&data[field::TARGET_ADDR])
+    }
+}
+
+
+/// Getters for the Neighbor Solicitation message header.
+/// See [RFC 4861 § 4.3].
+///
+/// [RFC 4861 § 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Return the Neighbor Solicitation flags.
+    #[inline]
+    pub fn neighbor_flags(&self) -> NeighborFlags {
+        let data = self.buffer.as_ref();
+        NeighborFlags::from_bits_truncate(data[field::NEIGH_FLAGS])
+    }
+}
+
+/// Getters for the Redirect message header.
+/// See [RFC 4861 § 4.5].
+///
+/// [RFC 4861 § 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Return the destination address field.
+    #[inline]
+    pub fn dest_addr(&self) -> Ipv6Address {
+        let data = self.buffer.as_ref();
+        Ipv6Address::from_bytes(&data[field::DEST_ADDR])
+    }
+}
+
+/// Setters for the Router Solicitation message header.
+/// See [RFC 4861 § 4.1].
+///
+/// [RFC 4861 § 4.1]: https://tools.ietf.org/html/rfc4861#section-4.1
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Clear the reserved field.
+    #[inline]
+    pub fn clear_reserved(&mut self) {
+        let data = self.buffer.as_mut();
+        NetworkEndian::write_u32(&mut data[field::UNUSED], 0);
+    }
+}
+
+/// Setters for the Router Advertisement message header.
+/// See [RFC 4861 § 4.2].
+///
+/// [RFC 4861 § 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Set the current hop limit field.
+    #[inline]
+    pub fn set_current_hop_limit(&mut self, value: u8) {
+        let data = self.buffer.as_mut();
+        data[field::CUR_HOP_LIMIT] = value;
+    }
+
+    /// Set the Router Advertisement flags.
+    #[inline]
+    pub fn set_router_flags(&mut self, flags: RouterFlags) {
+        self.buffer.as_mut()[field::ROUTER_FLAGS] = flags.bits();
+    }
+
+    /// Set the router lifetime field.
+    #[inline]
+    pub fn set_router_lifetime(&mut self, value: Duration) {
+        let data = self.buffer.as_mut();
+        NetworkEndian::write_u16(&mut data[field::ROUTER_LT], value.secs() as u16);
+    }
+
+    /// Set the reachable time field.
+    #[inline]
+    pub fn set_reachable_time(&mut self, value: Duration) {
+        let data = self.buffer.as_mut();
+        NetworkEndian::write_u32(&mut data[field::REACHABLE_TM], value.total_millis() as u32);
+    }
+
+    /// Set the retransmit time field.
+    #[inline]
+    pub fn set_retrans_time(&mut self, value: Duration) {
+        let data = self.buffer.as_mut();
+        NetworkEndian::write_u32(&mut data[field::RETRANS_TM], value.total_millis() as u32);
+    }
+}
+
+/// Setters for the [Neighbor Solicitation], [Neighbor Advertisement], and
+/// [Redirect] message types.
+///
+/// [Neighbor Solicitation]: https://tools.ietf.org/html/rfc4861#section-4.3
+/// [Neighbor Advertisement]: https://tools.ietf.org/html/rfc4861#section-4.4
+/// [Redirect]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Set the target address field.
+    #[inline]
+    pub fn set_target_addr(&mut self, value: Ipv6Address) {
+        let data = self.buffer.as_mut();
+        data[field::TARGET_ADDR].copy_from_slice(value.as_bytes());
+    }
+}
+
+/// Setters for the Neighbor Solicitation message header.
+/// See [RFC 4861 § 4.3].
+///
+/// [RFC 4861 § 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Set the Neighbor Solicitation flags.
+    #[inline]
+    pub fn set_neighbor_flags(&mut self, flags: NeighborFlags) {
+        self.buffer.as_mut()[field::NEIGH_FLAGS] = flags.bits();
+    }
+}
+
+/// Setters for the Redirect message header.
+/// See [RFC 4861 § 4.5].
+///
+/// [RFC 4861 § 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Set the destination address field.
+    #[inline]
+    pub fn set_dest_addr(&mut self, value: Ipv6Address) {
+        let data = self.buffer.as_mut();
+        data[field::DEST_ADDR].copy_from_slice(value.as_bytes());
+    }
+}
+
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    static ROUTER_ADVERT_BYTES: [u8; 16] =
+        [0x86, 0x00, 0x2e, 0xf3,
+         0x40, 0x80, 0x03, 0x84,
+         0x00, 0x00, 0x03, 0x84,
+         0x00, 0x00, 0x03, 0x84];
+
+    #[test]
+    fn test_router_advert_deconstruct() {
+        let packet = Packet::new(&ROUTER_ADVERT_BYTES[..]);
+        assert_eq!(packet.msg_type(), Message::RouterAdvert);
+        assert_eq!(packet.msg_code(), 0);
+        assert_eq!(packet.current_hop_limit(), 64);
+        assert_eq!(packet.router_flags(), RouterFlags::MANAGED);
+        assert_eq!(packet.router_lifetime(), Duration::from_secs(900));
+        assert_eq!(packet.reachable_time(), Duration::from_millis(900));
+        assert_eq!(packet.retrans_time(), Duration::from_millis(900));
+    }
+
+    #[test]
+    fn test_router_advert_construct() {
+        let mut bytes = vec![0x0; 16];
+        let mut packet = Packet::new(&mut bytes);
+        packet.set_msg_type(Message::RouterAdvert);
+        packet.set_msg_code(0);
+        packet.set_current_hop_limit(64);
+        packet.set_router_flags(RouterFlags::MANAGED);
+        packet.set_router_lifetime(Duration::from_secs(900));
+        packet.set_reachable_time(Duration::from_millis(900));
+        packet.set_retrans_time(Duration::from_millis(900));
+        packet.fill_checksum();
+        assert_eq!(&packet.into_inner()[..], &ROUTER_ADVERT_BYTES[..]);
+    }
+}