diff --git a/src/iface/ethernet.rs b/src/iface/ethernet.rs index 9e13fc6..fd17eb4 100644 --- a/src/iface/ethernet.rs +++ b/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!() diff --git a/src/wire/ethernet.rs b/src/wire/ethernet.rs index 0c00d01..50380df 100644 --- a/src/wire/ethernet.rs +++ b/src/wire/ethernet.rs @@ -225,6 +225,11 @@ impl> PrettyPrint for Frame { 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(()) } } diff --git a/src/wire/ip.rs b/src/wire/ip.rs index 915ced6..bf393c5 100644 --- a/src/wire/ip.rs +++ b/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 for Repr { } } +#[cfg(feature = "proto-ipv6")] +impl From 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 { + 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>(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::*; diff --git a/src/wire/ipv4.rs b/src/wire/ipv4.rs index 147bbb6..40ab681 100644 --- a/src/wire/ipv4.rs +++ b/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> PrettyPrint for Packet { 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> PrettyPrint for Packet { } }; - 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) } } diff --git a/src/wire/ipv6.rs b/src/wire/ipv6.rs index 7faeb11..c67ec7a 100644 --- a/src/wire/ipv6.rs +++ b/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> { + 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> Packet { + /// Create a raw octet buffer with an IPv6 packet structure. + #[inline] + pub fn new(buffer: T) -> Packet { + 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> { + 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 + AsMut<[u8]>> Packet { + /// 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 + ?Sized>(packet: &Packet<&T>) -> Result { + // 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 + AsMut<[u8]>>(&self, packet: &mut Packet) { + // 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> PrettyPrint for Packet { + 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::>::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"); + } } diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 32ccda7..bbb0973 100644 --- a/src/wire/mod.rs +++ b/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,