Browse Source

Add Ipv6Repr and Ipv6Packet to wire

 - Add the Ipv6Repr and Ipv6Packet structures to the wire module
 - Add basic tests for the new structures
 - Update IpRepr to include the Ipv6 variant
Dan Robertson 7 years ago
parent
commit
737b387f61
6 changed files with 673 additions and 71 deletions
  1. 2 0
      src/iface/ethernet.rs
  2. 5 0
      src/wire/ethernet.rs
  3. 152 24
      src/wire/ip.rs
  4. 2 44
      src/wire/ipv4.rs
  5. 510 3
      src/wire/ipv6.rs
  6. 2 0
      src/wire/mod.rs

+ 2 - 0
src/iface/ethernet.rs

@@ -671,6 +671,8 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
                 };
                 Ok(self.icmpv4_reply(ipv4_repr, icmpv4_reply_repr))
             },
+            #[cfg(feature = "proto-ipv6")]
+            IpRepr::Ipv6(_) => Err(Error::Unaddressable),
             IpRepr::Unspecified { .. } |
             IpRepr::__Nonexhaustive =>
                 unreachable!()

+ 5 - 0
src/wire/ethernet.rs

@@ -225,6 +225,11 @@ impl<T: AsRef<[u8]>> PrettyPrint for Frame<T> {
                 indent.increase(f)?;
                 super::Ipv4Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent)
             }
+            #[cfg(feature = "proto-ipv6")]
+            EtherType::Ipv6 => {
+                indent.increase(f)?;
+                super::Ipv6Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent)
+            }
             _ => Ok(())
         }
     }

+ 152 - 24
src/wire/ip.rs

@@ -5,7 +5,7 @@ use {Error, Result};
 use phy::ChecksumCapabilities;
 use super::{Ipv4Address, Ipv4Packet, Ipv4Repr, Ipv4Cidr};
 #[cfg(feature = "proto-ipv6")]
-use super::{Ipv6Address, Ipv6Cidr};
+use super::{Ipv6Address, Ipv6Cidr, Ipv6Packet, Ipv6Repr};
 
 /// Internet protocol version.
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
@@ -339,6 +339,8 @@ pub enum Repr {
         ttl:         u8
     },
     Ipv4(Ipv4Repr),
+    #[cfg(feature = "proto-ipv6")]
+    Ipv6(Ipv6Repr),
     #[doc(hidden)]
     __Nonexhaustive
 }
@@ -349,12 +351,21 @@ impl From<Ipv4Repr> for Repr {
     }
 }
 
+#[cfg(feature = "proto-ipv6")]
+impl From<Ipv6Repr> for Repr {
+    fn from(repr: Ipv6Repr) -> Repr {
+        Repr::Ipv6(repr)
+    }
+}
+
 impl Repr {
     /// Return the protocol version.
     pub fn version(&self) -> Version {
         match self {
             &Repr::Unspecified { .. } => Version::Unspecified,
             &Repr::Ipv4(_) => Version::Ipv4,
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(_) => Version::Ipv6,
             &Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -364,6 +375,8 @@ impl Repr {
         match self {
             &Repr::Unspecified { src_addr, .. } => src_addr,
             &Repr::Ipv4(repr) => Address::Ipv4(repr.src_addr),
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(repr) => Address::Ipv6(repr.src_addr),
             &Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -373,6 +386,8 @@ impl Repr {
         match self {
             &Repr::Unspecified { dst_addr, .. } => dst_addr,
             &Repr::Ipv4(repr) => Address::Ipv4(repr.dst_addr),
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(repr) => Address::Ipv6(repr.dst_addr),
             &Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -382,6 +397,8 @@ impl Repr {
         match self {
             &Repr::Unspecified { protocol, .. } => protocol,
             &Repr::Ipv4(repr) => repr.protocol,
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(repr) => repr.next_header,
             &Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -391,6 +408,8 @@ impl Repr {
         match self {
             &Repr::Unspecified { payload_len, .. } => payload_len,
             &Repr::Ipv4(repr) => repr.payload_len,
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(repr) => repr.payload_len,
             &Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -402,6 +421,9 @@ impl Repr {
                 *payload_len = length,
             &mut Repr::Ipv4(Ipv4Repr { ref mut payload_len, .. }) =>
                 *payload_len = length,
+            #[cfg(feature = "proto-ipv6")]
+            &mut Repr::Ipv6(Ipv6Repr { ref mut payload_len, .. }) =>
+                *payload_len = length,
             &mut Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -411,6 +433,8 @@ impl Repr {
         match self {
             &Repr::Unspecified { ttl, .. } => ttl,
             &Repr::Ipv4(Ipv4Repr { ttl, .. }) => ttl,
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(Ipv6Repr { hop_limit, ..}) => hop_limit,
             &Repr::__Nonexhaustive => unreachable!()
         }
     }
@@ -422,6 +446,25 @@ impl Repr {
     /// This function panics if source and destination addresses belong to different families,
     /// or the destination address is unspecified, since this indicates a logic error.
     pub fn lower(&self, fallback_src_addrs: &[Cidr]) -> Result<Repr> {
+        macro_rules! resolve_unspecified {
+            ($reprty:path, $ipty:path, $iprepr:expr, $fallbacks:expr) => {
+                if $iprepr.src_addr.is_unspecified() {
+                    for cidr in $fallbacks {
+                        match cidr.address() {
+                            $ipty(addr) => {
+                                $iprepr.src_addr = addr;
+                                return Ok($reprty($iprepr));
+                            },
+                            _ => ()
+                        }
+                    }
+                    Err(Error::Unaddressable)
+                } else {
+                    Ok($reprty($iprepr))
+                }
+            }
+        }
+
         match self {
             &Repr::Unspecified {
                 src_addr: Address::Ipv4(src_addr),
@@ -438,10 +481,18 @@ impl Repr {
 
             #[cfg(feature = "proto-ipv6")]
             &Repr::Unspecified {
-                src_addr: Address::Ipv6(_),
-                dst_addr: Address::Ipv6(_),
-                ..
-            } => Err(Error::Unaddressable),
+                src_addr: Address::Ipv6(src_addr),
+                dst_addr: Address::Ipv6(dst_addr),
+                protocol, payload_len, ttl
+            } => {
+                Ok(Repr::Ipv6(Ipv6Repr {
+                    src_addr:    src_addr,
+                    dst_addr:    dst_addr,
+                    next_header: protocol,
+                    payload_len: payload_len,
+                    hop_limit:   ttl
+                }))
+            }
 
             &Repr::Unspecified {
                 src_addr: Address::Unspecified,
@@ -467,26 +518,31 @@ impl Repr {
             #[cfg(feature = "proto-ipv6")]
             &Repr::Unspecified {
                 src_addr: Address::Unspecified,
-                dst_addr: Address::Ipv6(_),
-                ..
-            } => Err(Error::Unaddressable),
-
-            &Repr::Ipv4(mut repr) => {
-                if repr.src_addr.is_unspecified() {
-                    for cidr in fallback_src_addrs {
-                        match cidr.address() {
-                            Address::Ipv4(addr) => {
-                                repr.src_addr = addr;
-                                return Ok(Repr::Ipv4(repr));
-                            }
-                            _ => ()
-                        }
-                    }
-                    Err(Error::Unaddressable)
-                } else {
-                    Ok(Repr::Ipv4(repr))
+                dst_addr: Address::Ipv6(dst_addr),
+                protocol, payload_len, ttl
+            } => {
+                // Find a fallback address to use
+                match fallback_src_addrs.iter().filter_map(|cidr| match cidr.address() {
+                    Address::Ipv6(addr) => Some(addr),
+                    _ => None
+                }).next() {
+                    Some(addr) =>
+                        Ok(Repr::Ipv6(Ipv6Repr {
+                            src_addr:    addr,
+                            next_header: protocol,
+                            hop_limit:   ttl,
+                            dst_addr, payload_len
+                        })),
+                    None => Err(Error::Unaddressable)
                 }
-            },
+            }
+
+            &Repr::Ipv4(mut repr) =>
+                resolve_unspecified!(Repr::Ipv4, Address::Ipv4, repr, fallback_src_addrs),
+
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(mut repr) =>
+                resolve_unspecified!(Repr::Ipv6, Address::Ipv6, repr, fallback_src_addrs),
 
             &Repr::Unspecified { .. } =>
                 panic!("source and destination IP address families do not match"),
@@ -505,6 +561,9 @@ impl Repr {
                 panic!("unspecified IP representation"),
             &Repr::Ipv4(repr) =>
                 repr.buffer_len(),
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(repr) =>
+                repr.buffer_len(),
             &Repr::__Nonexhaustive =>
                 unreachable!()
         }
@@ -520,6 +579,9 @@ impl Repr {
                 panic!("unspecified IP representation"),
             &Repr::Ipv4(repr) =>
                 repr.emit(&mut Ipv4Packet::new(buffer), &checksum_caps),
+            #[cfg(feature = "proto-ipv6")]
+            &Repr::Ipv6(repr) =>
+                repr.emit(&mut Ipv6Packet::new(buffer)),
             &Repr::__Nonexhaustive =>
                 unreachable!()
         }
@@ -589,6 +651,18 @@ pub mod checksum {
                 ])
             },
 
+            #[cfg(feature = "proto-ipv6")]
+            (&Address::Ipv6(src_addr), &Address::Ipv6(dst_addr)) => {
+                let mut proto_len = [0u8; 8];
+                proto_len[7] = protocol.into();
+                NetworkEndian::write_u32(&mut proto_len[0..4], length);
+                combine(&[
+                    data(src_addr.as_bytes()),
+                    data(dst_addr.as_bytes()),
+                    data(&proto_len[..])
+                ])
+            }
+
             _ => panic!("Unexpected pseudo header addresses: {}, {}",
                         src_addr, dst_addr)
         }
@@ -604,6 +678,60 @@ pub mod checksum {
     }
 }
 
+use super::pretty_print::{PrettyPrint, PrettyIndent};
+
+pub fn pretty_print_ip_payload<T: Into<Repr>>(f: &mut fmt::Formatter, indent: &mut PrettyIndent,
+                                              ip_repr: T, payload: &[u8]) -> fmt::Result {
+    use wire::{Icmpv4Packet, TcpPacket, TcpRepr, UdpPacket, UdpRepr};
+    use wire::ip::checksum::format_checksum;
+
+    let checksum_caps = ChecksumCapabilities::ignored();
+    let repr = ip_repr.into();
+    match repr.protocol() {
+        Protocol::Icmp => {
+            indent.increase(f)?;
+            Icmpv4Packet::<&[u8]>::pretty_print(&payload.as_ref(), f, indent)
+        }
+        Protocol::Udp => {
+            indent.increase(f)?;
+            match UdpPacket::<&[u8]>::new_checked(payload.as_ref()) {
+                Err(err) => write!(f, "{}({})", indent, err),
+                Ok(udp_packet) => {
+                    match UdpRepr::parse(&udp_packet, &repr.src_addr(),
+                                         &repr.dst_addr(), &checksum_caps) {
+                        Err(err) => write!(f, "{}{} ({})", indent, udp_packet, err),
+                        Ok(udp_repr) => {
+                            write!(f, "{}{}", indent, udp_repr)?;
+                            let valid = udp_packet.verify_checksum(&repr.src_addr(),
+                                                                   &repr.dst_addr());
+                            format_checksum(f, valid)
+                        }
+                    }
+                }
+            }
+        }
+        Protocol::Tcp => {
+            indent.increase(f)?;
+            match TcpPacket::<&[u8]>::new_checked(payload.as_ref()) {
+                Err(err) => write!(f, "{}({})", indent, err),
+                Ok(tcp_packet) => {
+                    match TcpRepr::parse(&tcp_packet, &repr.src_addr(),
+                                         &repr.dst_addr(), &checksum_caps) {
+                        Err(err) => write!(f, "{}{} ({})", indent, tcp_packet, err),
+                        Ok(tcp_repr) => {
+                            write!(f, "{}{}", indent, tcp_repr)?;
+                            let valid = tcp_packet.verify_checksum(&repr.src_addr(),
+                                                                   &repr.dst_addr());
+                            format_checksum(f, valid)
+                        }
+                    }
+                }
+            }
+        }
+        _ => Ok(())
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;

+ 2 - 44
src/wire/ipv4.rs

@@ -3,7 +3,7 @@ use byteorder::{ByteOrder, NetworkEndian};
 
 use {Error, Result};
 use phy::ChecksumCapabilities;
-use super::ip::checksum;
+use super::ip::{checksum, pretty_print_ip_payload};
 
 pub use super::IpProtocol as Protocol;
 
@@ -568,7 +568,6 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
     fn pretty_print(buffer: &AsRef<[u8]>, f: &mut fmt::Formatter,
                     indent: &mut PrettyIndent) -> fmt::Result {
         use wire::ip::checksum::format_checksum;
-        use wire::{Icmpv4Packet, TcpPacket, TcpRepr, UdpPacket, UdpRepr};
 
         let checksum_caps = ChecksumCapabilities::ignored();
 
@@ -586,48 +585,7 @@ impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
             }
         };
 
-        let src_addr = ip_repr.src_addr.into();
-        let dst_addr = ip_repr.dst_addr.into();
-
-        match ip_repr.protocol {
-            Protocol::Icmp => {
-                indent.increase(f)?;
-                Icmpv4Packet::<&[u8]>::pretty_print(&payload.as_ref(), f, indent)
-            }
-            Protocol::Udp => {
-                indent.increase(f)?;
-                match UdpPacket::<&[u8]>::new_checked(payload.as_ref()) {
-                    Err(err) => write!(f, "{}({})", indent, err),
-                    Ok(udp_packet) => {
-                        match UdpRepr::parse(&udp_packet, &src_addr, &dst_addr, &checksum_caps) {
-                            Err(err) => write!(f, "{}{} ({})", indent, udp_packet, err),
-                            Ok(udp_repr) => {
-                                write!(f, "{}{}", indent, udp_repr)?;
-                                let valid = udp_packet.verify_checksum(&src_addr, &dst_addr);
-                                format_checksum(f, valid)
-                            }
-                        }
-                    }
-                }
-            }
-            Protocol::Tcp => {
-                indent.increase(f)?;
-                match TcpPacket::<&[u8]>::new_checked(payload.as_ref()) {
-                    Err(err) => write!(f, "{}({})", indent, err),
-                    Ok(tcp_packet) => {
-                        match TcpRepr::parse(&tcp_packet, &src_addr, &dst_addr, &checksum_caps) {
-                            Err(err) => write!(f, "{}{} ({})", indent, tcp_packet, err),
-                            Ok(tcp_repr) => {
-                                write!(f, "{}{}", indent, tcp_repr)?;
-                                let valid = tcp_packet.verify_checksum(&src_addr, &dst_addr);
-                                format_checksum(f, valid)
-                            }
-                        }
-                    }
-                }
-            }
-            _ => Ok(())
-        }
+        pretty_print_ip_payload(f, indent, ip_repr, payload)
     }
 }
 

+ 510 - 3
src/wire/ipv6.rs

@@ -1,8 +1,11 @@
-use core::fmt;
+#![deny(missing_docs)]
 
+use core::fmt;
 use byteorder::{ByteOrder, NetworkEndian};
 
+use {Error, Result};
 pub use super::IpProtocol as Protocol;
+use super::ip::pretty_print_ip_payload;
 
 /// A sixteen-octet IPv6 address.
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
@@ -248,9 +251,353 @@ impl fmt::Display for Cidr {
     }
 }
 
+/// A read/write wrapper around an Internet Protocol version 6 packet buffer.
+#[derive(Debug, PartialEq)]
+pub struct Packet<T: AsRef<[u8]>> {
+    buffer: T
+}
+
+// Ranges and constants describing the IPv6 header
+//
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |Version| Traffic Class |           Flow Label                  |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |         Payload Length        |  Next Header  |   Hop Limit   |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                                                               |
+// +                                                               +
+// |                                                               |
+// +                         Source Address                        +
+// |                                                               |
+// +                                                               +
+// |                                                               |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |                                                               |
+// +                                                               +
+// |                                                               |
+// +                      Destination Address                      +
+// |                                                               |
+// +                                                               +
+// |                                                               |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// See https://tools.ietf.org/html/rfc2460#section-3 for details.
+mod field {
+    use wire::field::*;
+    // 4-bit version number, 8-bit traffic class, and the
+    // 20-bit flow label.
+    pub const VER_TC_FLOW: Field = 0..4;
+    // 16-bit value representing the length of the payload.
+    // Note: Options are included in this length.
+    pub const LENGTH:      Field = 4..6;
+    // 8-bit value identifying the type of header following this
+    // one. Note: The same numbers are used in IPv4.
+    pub const NXT_HDR:     usize = 6;
+    // 8-bit value decremented by each node that forwards this
+    // packet. The packet is discarded when the value is 0.
+    pub const HOP_LIMIT:   usize = 7;
+    // IPv6 address of the source node.
+    pub const SRC_ADDR:    Field = 8..24;
+    // IPv6 address of the destination node.
+    pub const DST_ADDR:    Field = 24..40;
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Create a raw octet buffer with an IPv6 packet structure.
+    #[inline]
+    pub fn new(buffer: T) -> Packet<T> {
+        Packet { buffer }
+    }
+
+    /// Shorthand for a combination of [new] and [check_len].
+    ///
+    /// [new]: #method.new
+    /// [check_len]: #method.check_len
+    #[inline]
+    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+        let packet = Self::new(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.
+    ///
+    /// The result of this check is invalidated by calling [set_payload_len].
+    ///
+    /// [set_payload_len]: #method.set_payload_len
+    #[inline]
+    pub fn check_len(&self) -> Result<()> {
+        let len = self.buffer.as_ref().len();
+        if len < field::DST_ADDR.end || len < self.total_len() {
+            Err(Error::Truncated)
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Consume the packet, returning the underlying buffer.
+    #[inline]
+    pub fn into_inner(self) -> T {
+        self.buffer
+    }
+
+    /// Return the header length.
+    #[inline]
+    pub fn header_len(&self) -> usize {
+        // This is not a strictly necessary function, but it makes
+        // code more readable.
+        field::DST_ADDR.end
+    }
+
+    /// Return the version field.
+    #[inline]
+    pub fn version(&self) -> u8 {
+        let data = self.buffer.as_ref();
+        data[field::VER_TC_FLOW.start] >> 4
+    }
+
+    /// Return the traffic class.
+    #[inline]
+    pub fn traffic_class(&self) -> u8 {
+        let data = self.buffer.as_ref();
+        ((NetworkEndian::read_u16(&data[0..2]) & 0x0ff0) >> 4) as u8
+    }
+
+    /// Return the flow label field.
+    #[inline]
+    pub fn flow_label(&self) -> u32 {
+        let data = self.buffer.as_ref();
+        NetworkEndian::read_u24(&data[1..4]) & 0x000fffff
+    }
+
+    /// Return the payload length field.
+    #[inline]
+    pub fn payload_len(&self) -> u16 {
+        let data = self.buffer.as_ref();
+        NetworkEndian::read_u16(&data[field::LENGTH])
+    }
+
+    /// Return the payload length added to the known header length.
+    #[inline]
+    pub fn total_len(&self) -> usize {
+        self.header_len() + self.payload_len() as usize
+    }
+
+    /// Return the next header field.
+    #[inline]
+    pub fn next_header(&self) -> Protocol {
+        let data = self.buffer.as_ref();
+        Protocol::from(data[field::NXT_HDR])
+    }
+
+    /// Return the hop limit field.
+    #[inline]
+    pub fn hop_limit(&self) -> u8 {
+        let data = self.buffer.as_ref();
+        data[field::HOP_LIMIT]
+    }
+
+    /// Return the source address field.
+    #[inline]
+    pub fn src_addr(&self) -> Address {
+        let data = self.buffer.as_ref();
+        Address::from_bytes(&data[field::SRC_ADDR])
+    }
+
+    /// Return the destination address field.
+    #[inline]
+    pub fn dst_addr(&self) -> Address {
+        let data = self.buffer.as_ref();
+        Address::from_bytes(&data[field::DST_ADDR])
+    }
+}
+
+impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
+    /// Return a pointer to the payload.
+    #[inline]
+    pub fn payload(&self) -> &'a [u8] {
+        let data = self.buffer.as_ref();
+        let range = self.header_len()..self.total_len();
+        &data[range]
+    }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    /// Set the version field.
+    #[inline]
+    pub fn set_version(&mut self, value: u8) {
+        let data = self.buffer.as_mut();
+        // Make sure to retain the lower order bits which contain
+        // the higher order bits of the traffic class
+        data[0] = (data[0] & 0x0f) | ((value & 0x0f) << 4);
+    }
+
+    /// Set the traffic class field.
+    #[inline]
+    pub fn set_traffic_class(&mut self, value: u8) {
+        let data = self.buffer.as_mut();
+        // Put the higher order 4-bits of value in the lower order
+        // 4-bits of the first byte
+        data[0] = (data[0] & 0xf0) | ((value & 0xf0) >> 4);
+        // Put the lower order 4-bits of value in the higher order
+        // 4-bits of the second byte
+        data[1] = (data[1] & 0x0f) | ((value & 0x0f) << 4);
+    }
+
+    /// Set the flow label field.
+    #[inline]
+    pub fn set_flow_label(&mut self, value: u32) {
+        let data = self.buffer.as_mut();
+        // Retain the lower order 4-bits of the traffic class
+        let raw = (((data[1] & 0xf0) as u32) << 16) | (value & 0x0fffff);
+        NetworkEndian::write_u24(&mut data[1..4], raw);
+    }
+
+    /// Set the payload length field.
+    #[inline]
+    pub fn set_payload_len(&mut self, value: u16) {
+        let data = self.buffer.as_mut();
+        NetworkEndian::write_u16(&mut data[field::LENGTH], value);
+    }
+
+    /// Set the next header field.
+    #[inline]
+    pub fn set_next_header(&mut self, value: Protocol) {
+        let data = self.buffer.as_mut();
+        data[field::NXT_HDR] = value.into();
+    }
+
+    /// Set the hop limit field.
+    #[inline]
+    pub fn set_hop_limit(&mut self, value: u8) {
+        let data = self.buffer.as_mut();
+        data[field::HOP_LIMIT] = value;
+    }
+
+    /// Set the source address field.
+    #[inline]
+    pub fn set_src_addr(&mut self, value: Address) {
+        let data = self.buffer.as_mut();
+        data[field::SRC_ADDR].copy_from_slice(value.as_bytes());
+    }
+
+    /// Set the destination address field.
+    #[inline]
+    pub fn set_dst_addr(&mut self, value: Address) {
+        let data = self.buffer.as_mut();
+        data[field::DST_ADDR].copy_from_slice(value.as_bytes());
+    }
+}
+
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
+    /// Return a mutable pointer to the payload.
+    #[inline]
+    pub fn payload_mut(&mut self) -> &mut [u8] {
+        let range = self.header_len()..self.total_len();
+        let data = self.buffer.as_mut();
+        &mut data[range]
+    }
+}
+
+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, "IPv6 ({})", err)?;
+                Ok(())
+            }
+        }
+    }
+}
+
+/// A high-level representation of an Internet Protocol version 6 packet header.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct Repr {
+    /// IPv6 address of the source node.
+    pub src_addr:    Address,
+    /// IPv6 address of the destination node.
+    pub dst_addr:    Address,
+    /// Protocol contained in the next header.
+    pub next_header: Protocol,
+    /// Length of the payload including the extension headers.
+    pub payload_len: usize,
+    /// The 8-bit hop limit field.
+    pub hop_limit:   u8
+}
+
+impl Repr {
+    /// Parse an Internet Protocol version 6 packet and return a high-level representation.
+    pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&T>) -> Result<Repr> {
+        // Ensure basic accessors will work
+        packet.check_len()?;
+        if packet.version() != 6 { return Err(Error::Malformed); }
+        Ok(Repr {
+            src_addr:    packet.src_addr(),
+            dst_addr:    packet.dst_addr(),
+            next_header: packet.next_header(),
+            payload_len: packet.payload_len() as usize,
+            hop_limit:   packet.hop_limit()
+        })
+    }
+
+    /// Return the length of a header that will be emitted from this high-level representation.
+    pub fn buffer_len(&self) -> usize {
+        // This function is not strictly necessary, but it can make client code more readable.
+        field::DST_ADDR.end
+    }
+
+    /// Emit a high-level representation into an Internet Protocol version 6 packet.
+    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
+        // Make no assumptions about the original state of the packet buffer.
+        // Make sure to set every byte.
+        packet.set_version(6);
+        packet.set_traffic_class(0);
+        packet.set_flow_label(0);
+        packet.set_payload_len(self.payload_len as u16);
+        packet.set_hop_limit(self.hop_limit);
+        packet.set_next_header(self.next_header);
+        packet.set_src_addr(self.src_addr);
+        packet.set_dst_addr(self.dst_addr);
+    }
+}
+
+impl fmt::Display for Repr {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "IPv6 src={} dst={} nxt_hdr={} hop_limit={}",
+               self.src_addr, self.dst_addr, self.next_header, self.hop_limit)
+    }
+}
+
+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 {
+        let (ip_repr, payload) = match Packet::new_checked(buffer) {
+            Err(err) => return write!(f, "{}({})", indent, err),
+            Ok(ip_packet) => {
+                match Repr::parse(&ip_packet) {
+                    Err(_) => return Ok(()),
+                    Ok(ip_repr) => {
+                        write!(f, "{}{}", indent, ip_repr)?;
+                        (ip_repr, ip_packet.payload())
+                    }
+                }
+            }
+        };
+
+        pretty_print_ip_payload(f, indent, ip_repr, payload)
+    }
+}
+
 #[cfg(test)]
 mod test {
+    use Error;
     use super::{Address, Cidr};
+    use super::{Packet, Protocol, Repr};
+    use wire::pretty_print::{PrettyPrinter};
 
     static LINK_LOCAL_ADDR: Address = Address([0xfe, 0x80, 0x00, 0x00,
                                                0x00, 0x00, 0x00, 0x00,
@@ -426,13 +773,173 @@ mod test {
 
     #[test]
     #[should_panic(expected = "destination and source slices have different lengths")]
-    fn from_bytes_too_long() {
+    fn test_from_bytes_too_long() {
         let _ = Address::from_bytes(&[0u8; 15]);
     }
 
     #[test]
     #[should_panic(expected = "data.len() >= 8")]
-    fn from_parts_too_long() {
+    fn test_from_parts_too_long() {
         let _ = Address::from_parts(&[0u16; 7]);
     }
+
+    static REPR_PACKET_BYTES: [u8; 52] = [0x60, 0x00, 0x00, 0x00,
+                                          0x00, 0x0c, 0x11, 0x40,
+                                          0xfe, 0x80, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x01,
+                                          0xff, 0x02, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x01,
+                                          0x00, 0x01, 0x00, 0x02,
+                                          0x00, 0x0c, 0x02, 0x4e,
+                                          0xff, 0xff, 0xff, 0xff];
+    static REPR_PAYLOAD_BYTES: [u8; 12] = [0x00, 0x01, 0x00, 0x02,
+                                           0x00, 0x0c, 0x02, 0x4e,
+                                           0xff, 0xff, 0xff, 0xff];
+
+    fn packet_repr() -> Repr {
+        Repr {
+            src_addr:    Address([0xfe, 0x80, 0x00, 0x00,
+                                  0x00, 0x00, 0x00, 0x00,
+                                  0x00, 0x00, 0x00, 0x00,
+                                  0x00, 0x00, 0x00, 0x01]),
+            dst_addr:    Address::LINK_LOCAL_ALL_NODES,
+            next_header: Protocol::Udp,
+            payload_len: 12,
+            hop_limit:   64
+        }
+    }
+
+    #[test]
+    fn test_packet_deconstruction() {
+        let packet = Packet::new(&REPR_PACKET_BYTES[..]);
+        assert_eq!(packet.check_len(), Ok(()));
+        assert_eq!(packet.version(), 6);
+        assert_eq!(packet.traffic_class(), 0);
+        assert_eq!(packet.flow_label(), 0);
+        assert_eq!(packet.total_len(), 0x34);
+        assert_eq!(packet.payload_len() as usize, REPR_PAYLOAD_BYTES.len());
+        assert_eq!(packet.next_header(), Protocol::Udp);
+        assert_eq!(packet.hop_limit(), 0x40);
+        assert_eq!(packet.src_addr(), Address([0xfe, 0x80, 0x00, 0x00,
+                                               0x00, 0x00, 0x00, 0x00,
+                                               0x00, 0x00, 0x00, 0x00,
+                                               0x00, 0x00, 0x00, 0x01]));
+        assert_eq!(packet.dst_addr(), Address::LINK_LOCAL_ALL_NODES);
+        assert_eq!(packet.payload(), &REPR_PAYLOAD_BYTES[..]);
+    }
+
+    #[test]
+    fn test_packet_construction() {
+        let mut bytes = [0xff; 52];
+        let mut packet = Packet::new(&mut bytes[..]);
+        // Version, Traffic Class, and Flow Label are not
+        // byte aligned. make sure the setters and getters
+        // do not interfere with each other.
+        packet.set_version(6);
+        assert_eq!(packet.version(), 6);
+        packet.set_traffic_class(0x99);
+        assert_eq!(packet.version(), 6);
+        assert_eq!(packet.traffic_class(), 0x99);
+        packet.set_flow_label(0x54321);
+        assert_eq!(packet.traffic_class(), 0x99);
+        assert_eq!(packet.flow_label(), 0x54321);
+        packet.set_payload_len(0xc);
+        packet.set_next_header(Protocol::Udp);
+        packet.set_hop_limit(0xfe);
+        packet.set_src_addr(Address::LINK_LOCAL_ALL_ROUTERS);
+        packet.set_dst_addr(Address::LINK_LOCAL_ALL_NODES);
+        packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
+        let mut expected_bytes = [
+            0x69, 0x95, 0x43, 0x21, 0x00, 0x0c, 0x11, 0xfe,
+            0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+            0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00
+        ];
+        let start = expected_bytes.len() - REPR_PAYLOAD_BYTES.len();
+        expected_bytes[start..].copy_from_slice(&REPR_PAYLOAD_BYTES[..]);
+        assert_eq!(packet.check_len(), Ok(()));
+        assert_eq!(&packet.into_inner()[..], &expected_bytes[..]);
+    }
+
+    #[test]
+    fn test_overlong() {
+        let mut bytes = vec![];
+        bytes.extend(&REPR_PACKET_BYTES[..]);
+        bytes.push(0);
+
+        assert_eq!(Packet::new(&bytes).payload().len(),
+                   REPR_PAYLOAD_BYTES.len());
+        assert_eq!(Packet::new(&mut bytes).payload_mut().len(),
+                   REPR_PAYLOAD_BYTES.len());
+    }
+
+    #[test]
+    fn test_total_len_overflow() {
+        let mut bytes = vec![];
+        bytes.extend(&REPR_PACKET_BYTES[..]);
+        Packet::new(&mut bytes).set_payload_len(0x80);
+
+        assert_eq!(Packet::new_checked(&bytes).unwrap_err(),
+                   Error::Truncated);
+    }
+
+    #[test]
+    fn test_repr_parse_valid() {
+        let packet = Packet::new(&REPR_PACKET_BYTES[..]);
+        let repr = Repr::parse(&packet).unwrap();
+        assert_eq!(repr, packet_repr());
+    }
+
+    #[test]
+    fn test_repr_parse_bad_version() {
+        let mut bytes = vec![0; 40];
+        let mut packet = Packet::new(&mut bytes[..]);
+        packet.set_version(4);
+        packet.set_payload_len(0);
+        let packet = Packet::new(&*packet.into_inner());
+        assert_eq!(Repr::parse(&packet), Err(Error::Malformed));
+    }
+
+    #[test]
+    fn test_repr_parse_smaller_than_header() {
+        let mut bytes = vec![0; 40];
+        let mut packet = Packet::new(&mut bytes[..]);
+        packet.set_version(6);
+        packet.set_payload_len(39);
+        let packet = Packet::new(&*packet.into_inner());
+        assert_eq!(Repr::parse(&packet), Err(Error::Truncated));
+    }
+
+    #[test]
+    fn test_repr_parse_smaller_than_payload() {
+        let mut bytes = vec![0; 40];
+        let mut packet = Packet::new(&mut bytes[..]);
+        packet.set_version(6);
+        packet.set_payload_len(1);
+        let packet = Packet::new(&*packet.into_inner());
+        assert_eq!(Repr::parse(&packet), Err(Error::Truncated));
+    }
+
+    #[test]
+    fn test_basic_repr_emit() {
+        let repr = packet_repr();
+        let mut bytes = vec![0xff; repr.buffer_len() + REPR_PAYLOAD_BYTES.len()];
+        let mut packet = Packet::new(&mut bytes);
+        repr.emit(&mut packet);
+        packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES);
+        assert_eq!(&packet.into_inner()[..], &REPR_PACKET_BYTES[..]);
+    }
+
+    #[test]
+    fn test_pretty_print() {
+        assert_eq!(format!("{}", PrettyPrinter::<Packet<&'static [u8]>>::new("\n", &&REPR_PACKET_BYTES[..])),
+                   "\nIPv6 src=fe80::1 dst=ff02::1 nxt_hdr=UDP hop_limit=64\n \\ UDP src=1 dst=2 len=4");
+    }
 }

+ 2 - 0
src/wire/mod.rs

@@ -112,6 +112,8 @@ pub use self::ipv4::{Address as Ipv4Address,
 
 #[cfg(feature = "proto-ipv6")]
 pub use self::ipv6::{Address as Ipv6Address,
+                     Packet as Ipv6Packet,
+                     Repr as Ipv6Repr,
                      Cidr as Ipv6Cidr};
 
 pub use self::icmpv4::{Message as Icmpv4Message,