From b97c5926718fd32c299df2105409d36eaca08a99 Mon Sep 17 00:00:00 2001 From: Dan Robertson Date: Thu, 2 Nov 2017 03:55:10 +0000 Subject: [PATCH] Add IPv6 address and cidr to wire - Add the ipv6 feature - Ensure a travis build with the ipv6 feature enabled. - Add the necessary infrastructure to wire for ipv6 support. - Ipv6Address - Ipv6Cidr - Add Ipv6 Address and Cidr parsing to parsers - Add basic tests. --- .travis.yml | 2 + Cargo.toml | 1 + src/parsers.rs | 270 ++++++++++++++++++++++++++++-- src/socket/raw.rs | 2 + src/wire/ip.rs | 133 +++++++++++---- src/wire/ipv6.rs | 413 ++++++++++++++++++++++++++++++++++++++++++++++ src/wire/mod.rs | 6 + 7 files changed, 786 insertions(+), 41 deletions(-) create mode 100644 src/wire/ipv6.rs diff --git a/.travis.yml b/.travis.yml index 68d6111..fb3568b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ matrix: # actually test everything - rust: nightly env: FEATURES='default' MODE='test' + - rust: nightly + env: FEATURES='default proto-ipv6' MODE='test' - rust: nightly env: FEATURES='phy-raw_socket socket-udp' MODE='build' - rust: nightly diff --git a/Cargo.toml b/Cargo.toml index 90f2a92..53fce52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ alloc = ["managed/alloc"] verbose = [] "phy-raw_socket" = ["std", "libc"] "phy-tap_interface" = ["std", "libc"] +"proto-ipv6" = [] "socket-raw" = [] "socket-udp" = [] "socket-tcp" = [] diff --git a/src/parsers.rs b/src/parsers.rs index 13aebf3..db00dd8 100644 --- a/src/parsers.rs +++ b/src/parsers.rs @@ -1,6 +1,12 @@ +#![cfg_attr(not(feature = "proto-ipv6"), allow(dead_code))] + use core::str::FromStr; use core::result; -use wire::{EthernetAddress, IpAddress, Ipv4Address, Ipv4Cidr, IpCidr}; + +use wire::{EthernetAddress, IpAddress, IpCidr}; +use wire::{Ipv4Address, Ipv4Cidr}; +#[cfg(feature = "proto-ipv6")] +use wire::{Ipv6Address, Ipv6Cidr}; type Result = result::Result; @@ -17,6 +23,14 @@ impl<'a> Parser<'a> { } } + fn lookahead_char(&self, ch: u8) -> bool { + if self.pos < self.data.len() { + self.data[self.pos] == ch + } else { + false + } + } + fn advance(&mut self) -> Result { match self.data.get(self.pos) { Some(&chr) => { @@ -62,6 +76,13 @@ impl<'a> Parser<'a> { } } + fn accept_str(&mut self, string: &[u8]) -> Result<()> { + for byte in string.iter() { + self.accept_char(*byte)?; + } + Ok(()) + } + fn accept_digit(&mut self, hex: bool) -> Result { let digit = self.advance()?; if digit >= b'0' && digit <= b'9' { @@ -115,6 +136,103 @@ impl<'a> Parser<'a> { Err(()) } + #[cfg(feature = "proto-ipv6")] + fn accept_ipv6_part(&mut self, (head, tail): (&mut [u16; 8], &mut [u16; 6]), + (head_idx, tail_idx): (&mut usize, &mut usize), + mut use_tail: bool, is_cidr: bool) -> Result<()> { + let double_colon = match self.try(|p| p.accept_str(b"::")) { + Some(_) if !use_tail && *head_idx < 7 => { + // Found a double colon. Start filling out the + // tail and set the double colon flag in case + // this is the last character we can parse. + use_tail = true; + true + }, + Some(_) => { + // This is a bad address. Only one double colon is + // allowed and an address is only 128 bits. + return Err(()); + } + None => { + if *head_idx != 0 || use_tail && *tail_idx != 0 { + // If this is not the first number or the position following + // a double colon, we expect there to be a single colon. + self.accept_char(b':')?; + } + false + } + }; + + match self.try(|p| p.accept_number(4, 0x10000, true)) { + Some(part) if !use_tail && *head_idx < 8 => { + // Valid u16 to be added to the address + head[*head_idx] = part as u16; + *head_idx += 1; + Ok(()) + }, + Some(part) if *tail_idx < 6 => { + // Valid u16 to be added to the address + tail[*tail_idx] = part as u16; + *tail_idx += 1; + Ok(()) + }, + Some(_) => { + // Tail or head section is too long + Err(()) + } + None if double_colon && (is_cidr || self.pos == self.data.len()) => { + // The address ends with "::". E.g. 1234:: or :: + Ok(()) + } + None => { + // Invalid address + Err(()) + } + }?; + + if *head_idx + *tail_idx > 8 { + // The head and tail indexes add up to a bad address length. + Err(()) + } else if !self.lookahead_char(b':') { + if *head_idx < 8 && !use_tail { + // There was no double colon found, and the head is too short + return Err(()); + } + Ok(()) + } else { + // Continue recursing + self.accept_ipv6_part((head, tail), (head_idx, tail_idx), use_tail, is_cidr) + } + } + + #[cfg(feature = "proto-ipv6")] + fn accept_ipv6(&mut self, is_cidr: bool) -> Result { + // IPv6 addresses may contain a "::" to indicate a series of + // 16 bit sections that evaluate to 0. E.g. + // + // fe80:0000:0000:0000:0000:0000:0000:0001 + // + // May be written as + // + // fe80::1 + // + // As a result, we need to find the first section of colon + // delimited u16's before a possible "::", then the + // possible second section after the "::", and finally + // combine the second optional section to the end of the + // final address. + let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]); + let (mut head_idx, mut tail_idx) = (0, 0); + + self.accept_ipv6_part((&mut addr, &mut tail), (&mut head_idx, &mut tail_idx), false, is_cidr)?; + + // We need to copy the tail portion (the portion following the "::") to the + // end of the address. + addr[8 - tail_idx..].copy_from_slice(&tail[..tail_idx]); + + Ok(Ipv6Address::from_parts(&addr)) + } + fn accept_ipv4(&mut self) -> Result { let mut octets = [0u8; 4]; for n in 0..4 { @@ -130,6 +248,13 @@ impl<'a> Parser<'a> { if let Some(ipv4) = self.try(|p| p.accept_ipv4()) { return Ok(IpAddress::Ipv4(ipv4)) } + + #[cfg(feature = "proto-ipv6")] + match self.try(|p| p.accept_ipv6(false)) { + Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)), + None => () + } + Err(()) } } @@ -152,6 +277,16 @@ impl FromStr for Ipv4Address { } } +#[cfg(feature = "proto-ipv6")] +impl FromStr for Ipv6Address { + type Err = (); + + /// Parse a string representation of an IPv6 address. + fn from_str(s: &str) -> Result { + Parser::new(s).until_eof(|p| p.accept_ipv6(false)) + } +} + impl FromStr for IpAddress { type Err = (); @@ -175,12 +310,37 @@ impl FromStr for Ipv4Cidr { } } +#[cfg(feature = "proto-ipv6")] +impl FromStr for Ipv6Cidr { + type Err = (); + + /// Parse a string representation of an IPv6 CIDR. + fn from_str(s: &str) -> Result { + Parser::new(s).until_eof(|p| { + let ip = p.accept_ipv6(true)?; + p.accept_char(b'/')?; + let prefix_len = p.accept_number(3, 129, false)? as u8; + Ok(Ipv6Cidr::new(ip, prefix_len)) + }) + } +} + impl FromStr for IpCidr { type Err = (); /// Parse a string representation of an IP CIDR. fn from_str(s: &str) -> Result { - Ipv4Cidr::from_str(s).map(IpCidr::Ipv4) + if let Ok(ipv4) = Ipv4Cidr::from_str(s) { + return Ok(IpCidr::Ipv4(ipv4)) + } + + #[cfg(feature = "proto-ipv6")] + match Ipv6Cidr::from_str(s) { + Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)), + Err(_) => () + } + + Err(()) } } @@ -188,6 +348,21 @@ impl FromStr for IpCidr { mod test { use super::*; + macro_rules! check_cidr_test_array { + ($tests:expr, $from_str:path, $variant:path) => { + for &(s, cidr) in &$tests { + assert_eq!($from_str(s), cidr); + assert_eq!(IpCidr::from_str(s), cidr.map($variant)); + + if let Ok(cidr) = cidr { + assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr)); + assert_eq!(IpCidr::from_str(&format!("{}", cidr)), + Ok($variant(cidr))); + } + } + } + } + #[test] fn test_mac() { assert_eq!(EthernetAddress::from_str(""), Err(())); @@ -225,7 +400,52 @@ mod test { } #[test] - fn test_ip() { + #[cfg(feature = "proto-ipv6")] + fn test_ipv6() { + // Obviously not valid + assert_eq!(Ipv6Address::from_str(""), Err(())); + assert_eq!(Ipv6Address::from_str("fe80:0:0:0:0:0:0:1"), + Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))); + assert_eq!(Ipv6Address::from_str("::1"), + Ok(Ipv6Address::LOOPBACK)); + assert_eq!(Ipv6Address::from_str("::"), + Ok(Ipv6Address::UNSPECIFIED)); + assert_eq!(Ipv6Address::from_str("fe80::1"), + Ok(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1))); + assert_eq!(Ipv6Address::from_str("1234:5678::"), + Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0, 0))); + assert_eq!(Ipv6Address::from_str("1234:5678::8765:4321"), + Ok(Ipv6Address::new(0x1234, 0x5678, 0, 0, 0, 0, 0x8765, 0x4321))); + // Two double colons in address + assert_eq!(Ipv6Address::from_str("1234:5678::1::1"), + Err(())); + assert_eq!(Ipv6Address::from_str("4444:333:22:1::4"), + Ok(Ipv6Address::new(0x4444, 0x0333, 0x0022, 0x0001, 0, 0, 0, 4))); + assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1::"), + Ok(Ipv6Address::new(1, 1, 1, 1, 1, 1, 0, 0))); + assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1"), + Ok(Ipv6Address::new(0, 0, 1, 1, 1, 1, 1, 1))); + assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), + Err(())); + // Double colon appears too late indicating an address that is too long + assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1::"), + Err(())); + // Section after double colon is too long for a valid address + assert_eq!(Ipv6Address::from_str("::1:1:1:1:1:1:1"), + Err(())); + // Obviously too long + assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1:1:1"), + Err(())); + // Address is too short + assert_eq!(Ipv6Address::from_str("1:1:1:1:1:1:1"), + Err(())); + // Long number + assert_eq!(Ipv6Address::from_str("::000001"), + Err(())); + } + + #[test] + fn test_ip_ipv4() { assert_eq!(IpAddress::from_str(""), Err(())); assert_eq!(IpAddress::from_str("1.2.3.4"), Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4])))); @@ -233,7 +453,16 @@ mod test { } #[test] - fn test_cidr() { + #[cfg(feature = "proto-ipv6")] + fn test_ip_ipv6() { + assert_eq!(IpAddress::from_str(""), Err(())); + assert_eq!(IpAddress::from_str("fe80::1"), + Ok(IpAddress::Ipv6(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)))); + assert_eq!(IpAddress::from_str("x"), Err(())); + } + + #[test] + fn test_cidr_ipv4() { let tests = [ ("127.0.0.1/8", Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8))), @@ -252,15 +481,30 @@ mod test { ("/32", Err(())), ]; - for &(s, cidr) in &tests { - assert_eq!(Ipv4Cidr::from_str(s), cidr); - assert_eq!(IpCidr::from_str(s), cidr.map(IpCidr::Ipv4)); + check_cidr_test_array!(tests, Ipv4Cidr::from_str, IpCidr::Ipv4); + } - if let Ok(cidr) = cidr { - assert_eq!(Ipv4Cidr::from_str(&format!("{}", cidr)), Ok(cidr)); - assert_eq!(IpCidr::from_str(&format!("{}", cidr)), - Ok(IpCidr::Ipv4(cidr))); - } - } + #[test] + #[cfg(feature = "proto-ipv6")] + fn test_cidr_ipv6() { + let tests = [ + ("fe80::1/64", + Ok(Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64u8))), + ("fe80::/64", + Ok(Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 64u8))), + ("::1/128", + Ok(Ipv6Cidr::new(Ipv6Address::LOOPBACK, 128u8))), + ("::/128", + Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))), + ("fe80:0:0:0:0:0:0:1/64", + Ok(Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64u8))), + ("fe80:0:0:0:0:0:0:1|64", + Err(())), + ("fe80::|64", + Err(())), + ("fe80::1::/64", + Err(())) + ]; + check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6); } } diff --git a/src/socket/raw.rs b/src/socket/raw.rs index 882e755..f0544f7 100644 --- a/src/socket/raw.rs +++ b/src/socket/raw.rs @@ -201,6 +201,8 @@ impl<'a, 'b> RawSocket<'a, 'b> { let ipv4_repr = Ipv4Repr::parse(&packet, checksum_caps)?; Ok((IpRepr::Ipv4(ipv4_repr), packet.payload())) } + #[cfg(feature = "proto-ipv6")] + IpVersion::Ipv6 => Err(Error::Unrecognized), IpVersion::Unspecified => unreachable!(), IpVersion::__Nonexhaustive => unreachable!() } diff --git a/src/wire/ip.rs b/src/wire/ip.rs index 0a986a4..844882c 100644 --- a/src/wire/ip.rs +++ b/src/wire/ip.rs @@ -4,12 +4,16 @@ use core::convert::From; use {Error, Result}; use phy::ChecksumCapabilities; use super::{Ipv4Address, Ipv4Packet, Ipv4Repr, Ipv4Cidr}; +#[cfg(feature = "proto-ipv6")] +use super::{Ipv6Address, Ipv6Cidr}; /// Internet protocol version. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub enum Version { Unspecified, Ipv4, + #[cfg(feature = "proto-ipv6")] + Ipv6, #[doc(hidden)] __Nonexhaustive, } @@ -22,6 +26,8 @@ impl Version { pub fn of_packet(data: &[u8]) -> Result { match data[0] >> 4 { 4 => Ok(Version::Ipv4), + #[cfg(feature = "proto-ipv6")] + 6 => Ok(Version::Ipv6), _ => Err(Error::Unrecognized) } } @@ -32,6 +38,8 @@ impl fmt::Display for Version { match self { &Version::Unspecified => write!(f, "IPv?"), &Version::Ipv4 => write!(f, "IPv4"), + #[cfg(feature = "proto-ipv6")] + &Version::Ipv6 => write!(f, "IPv6"), &Version::__Nonexhaustive => unreachable!() } } @@ -40,18 +48,30 @@ impl fmt::Display for Version { enum_with_unknown! { /// IP datagram encapsulated protocol. pub enum Protocol(u8) { - Icmp = 0x01, - Tcp = 0x06, - Udp = 0x11 + HopByHop = 0x00, + Icmp = 0x01, + Tcp = 0x06, + Udp = 0x11, + Ipv6Route = 0x2b, + Ipv6Frag = 0x2c, + Icmpv6 = 0x3a, + Ipv6NoNxt = 0x3b, + Ipv6Opts = 0x3c } } impl fmt::Display for Protocol { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Protocol::Icmp => write!(f, "ICMP"), - &Protocol::Tcp => write!(f, "TCP"), - &Protocol::Udp => write!(f, "UDP"), + &Protocol::HopByHop => write!(f, "Hop-by-Hop"), + &Protocol::Icmp => write!(f, "ICMP"), + &Protocol::Tcp => write!(f, "TCP"), + &Protocol::Udp => write!(f, "UDP"), + &Protocol::Ipv6Route => write!(f, "IPv6-Route"), + &Protocol::Ipv6Frag => write!(f, "IPv6-Frag"), + &Protocol::Icmpv6 => write!(f, "ICMPv6"), + &Protocol::Ipv6NoNxt => write!(f, "IPv6-NoNxt"), + &Protocol::Ipv6Opts => write!(f, "IPv6-Opts"), &Protocol::Unknown(id) => write!(f, "0x{:02x}", id) } } @@ -65,6 +85,9 @@ pub enum Address { Unspecified, /// An IPv4 address. Ipv4(Ipv4Address), + /// An IPv6 address. + #[cfg(feature = "proto-ipv6")] + Ipv6(Ipv6Address), #[doc(hidden)] __Nonexhaustive } @@ -75,11 +98,20 @@ impl Address { Address::Ipv4(Ipv4Address::new(a0, a1, a2, a3)) } + /// Create an address wrapping an IPv6 address with the given octets. + #[cfg(feature = "proto-ipv6")] + pub fn v6(a0: u16, a1: u16, a2: u16, a3: u16, + a4: u16, a5: u16, a6: u16, a7: u16) -> Address { + Address::Ipv6(Ipv6Address::new(a0, a1, a2, a3, a4, a5, a6, a7)) + } + /// Query whether the address is a valid unicast address. pub fn is_unicast(&self) -> bool { match self { - &Address::Unspecified => false, - &Address::Ipv4(addr) => addr.is_unicast(), + &Address::Unspecified => false, + &Address::Ipv4(addr) => addr.is_unicast(), + #[cfg(feature = "proto-ipv6")] + &Address::Ipv6(addr) => addr.is_unicast(), &Address::__Nonexhaustive => unreachable!() } } @@ -87,8 +119,10 @@ impl Address { /// Query whether the address is the broadcast address. pub fn is_broadcast(&self) -> bool { match self { - &Address::Unspecified => false, - &Address::Ipv4(addr) => addr.is_broadcast(), + &Address::Unspecified => false, + &Address::Ipv4(addr) => addr.is_broadcast(), + #[cfg(feature = "proto-ipv6")] + &Address::Ipv6(_) => false, &Address::__Nonexhaustive => unreachable!() } } @@ -96,8 +130,10 @@ impl Address { /// Query whether the address falls into the "unspecified" range. pub fn is_unspecified(&self) -> bool { match self { - &Address::Unspecified => true, - &Address::Ipv4(addr) => addr.is_unspecified(), + &Address::Unspecified => true, + &Address::Ipv4(addr) => addr.is_unspecified(), + #[cfg(feature = "proto-ipv6")] + &Address::Ipv6(addr) => addr.is_unspecified(), &Address::__Nonexhaustive => unreachable!() } } @@ -105,8 +141,10 @@ impl Address { /// Return an unspecified address that has the same IP version as `self`. pub fn to_unspecified(&self) -> Address { match self { - &Address::Unspecified => Address::Unspecified, - &Address::Ipv4(_) => Address::Ipv4(Ipv4Address::UNSPECIFIED), + &Address::Unspecified => Address::Unspecified, + &Address::Ipv4(_) => Address::Ipv4(Ipv4Address::UNSPECIFIED), + #[cfg(feature = "proto-ipv6")] + &Address::Ipv6(_) => Address::Ipv6(Ipv6Address::UNSPECIFIED), &Address::__Nonexhaustive => unreachable!() } } @@ -124,11 +162,20 @@ impl From for Address { } } +#[cfg(feature = "proto-ipv6")] +impl From for Address { + fn from(addr: Ipv6Address) -> Self { + Address::Ipv6(addr) + } +} + impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Address::Unspecified => write!(f, "*"), - &Address::Ipv4(addr) => write!(f, "{}", addr), + &Address::Unspecified => write!(f, "*"), + &Address::Ipv4(addr) => write!(f, "{}", addr), + #[cfg(feature = "proto-ipv6")] + &Address::Ipv6(addr) => write!(f, "{}", addr), &Address::__Nonexhaustive => unreachable!() } } @@ -139,6 +186,8 @@ impl fmt::Display for Address { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Cidr { Ipv4(Ipv4Cidr), + #[cfg(feature = "proto-ipv6")] + Ipv6(Ipv6Cidr), #[doc(hidden)] __Nonexhaustive, } @@ -152,6 +201,8 @@ impl Cidr { pub fn new(addr: Address, prefix_len: u8) -> Cidr { match addr { Address::Ipv4(addr) => Cidr::Ipv4(Ipv4Cidr::new(addr, prefix_len)), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => Cidr::Ipv6(Ipv6Cidr::new(addr, prefix_len)), Address::Unspecified => panic!("a CIDR block cannot be based on an unspecified address"), Address::__Nonexhaustive => @@ -162,7 +213,9 @@ impl Cidr { /// Return the IP address of this CIDR block. pub fn address(&self) -> Address { match self { - &Cidr::Ipv4(cidr) => Address::Ipv4(cidr.address()), + &Cidr::Ipv4(cidr) => Address::Ipv4(cidr.address()), + #[cfg(feature = "proto-ipv6")] + &Cidr::Ipv6(cidr) => Address::Ipv6(cidr.address()), &Cidr::__Nonexhaustive => unreachable!() } } @@ -170,7 +223,9 @@ impl Cidr { /// Return the prefix length of this CIDR block. pub fn prefix_len(&self) -> u8 { match self { - &Cidr::Ipv4(cidr) => cidr.prefix_len(), + &Cidr::Ipv4(cidr) => cidr.prefix_len(), + #[cfg(feature = "proto-ipv6")] + &Cidr::Ipv6(cidr) => cidr.prefix_len(), &Cidr::__Nonexhaustive => unreachable!() } } @@ -181,6 +236,12 @@ impl Cidr { match (self, addr) { (&Cidr::Ipv4(ref cidr), &Address::Ipv4(ref addr)) => cidr.contains_addr(addr), + #[cfg(feature = "proto-ipv6")] + (&Cidr::Ipv6(ref cidr), &Address::Ipv6(ref addr)) => + cidr.contains_addr(addr), + #[cfg(feature = "proto-ipv6")] + (&Cidr::Ipv4(_), &Address::Ipv6(_)) | (&Cidr::Ipv6(_), &Address::Ipv4(_)) => + false, (_, &Address::Unspecified) => // a fully unspecified address covers both IPv4 and IPv6, // and no CIDR block can do that. @@ -197,6 +258,12 @@ impl Cidr { match (self, subnet) { (&Cidr::Ipv4(ref cidr), &Cidr::Ipv4(ref other)) => cidr.contains_subnet(other), + #[cfg(feature = "proto-ipv6")] + (&Cidr::Ipv6(ref cidr), &Cidr::Ipv6(ref other)) => + cidr.contains_subnet(other), + #[cfg(feature = "proto-ipv6")] + (&Cidr::Ipv4(_), &Cidr::Ipv6(_)) | (&Cidr::Ipv6(_), &Cidr::Ipv4(_)) => + false, (&Cidr::__Nonexhaustive, _) | (_, &Cidr::__Nonexhaustive) => unreachable!() @@ -207,7 +274,9 @@ impl Cidr { impl fmt::Display for Cidr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Cidr::Ipv4(cidr) => write!(f, "{}", cidr), + &Cidr::Ipv4(cidr) => write!(f, "{}", cidr), + #[cfg(feature = "proto-ipv6")] + &Cidr::Ipv6(cidr) => write!(f, "{}", cidr), &Cidr::__Nonexhaustive => unreachable!() } } @@ -367,6 +436,13 @@ impl Repr { })) } + #[cfg(feature = "proto-ipv6")] + &Repr::Unspecified { + src_addr: Address::Ipv6(_), + dst_addr: Address::Ipv6(_), + .. + } => Err(Error::Unaddressable), + &Repr::Unspecified { src_addr: Address::Unspecified, dst_addr: Address::Ipv4(dst_addr), @@ -388,11 +464,12 @@ impl Repr { })) } - &Repr::Unspecified { dst_addr: Address::Unspecified, .. } => - panic!("unspecified destination IP address"), - - // &Repr::Unspecified { .. } => - // panic!("source and destination IP address families do not match"), + #[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() { @@ -411,10 +488,10 @@ impl Repr { } }, - &Repr::__Nonexhaustive | - &Repr::Unspecified { src_addr: Address::__Nonexhaustive, .. } | - &Repr::Unspecified { dst_addr: Address::__Nonexhaustive, .. } => - unreachable!() + &Repr::Unspecified { .. } => + panic!("source and destination IP address families do not match"), + + &Repr::__Nonexhaustive => unreachable!() } } diff --git a/src/wire/ipv6.rs b/src/wire/ipv6.rs new file mode 100644 index 0000000..a9c9a17 --- /dev/null +++ b/src/wire/ipv6.rs @@ -0,0 +1,413 @@ +use core::fmt; + +use byteorder::{ByteOrder, NetworkEndian}; + +pub use super::IpProtocol as Protocol; + +/// A sixteen-octet IPv6 address. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct Address(pub [u8; 16]); + +impl Address { + /// An unspecified address. + pub const UNSPECIFIED: Address = Address([0x00; 16]); + + /// Link local all routers multicast address. + pub const LINK_LOCAL_ALL_NODES: Address = + Address([0xff, 0x02, 0x00, 0x0, 0x00, 0x00, 0x00, 0x0, + 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 0x1]); + + /// Link local all nodes multicast address. + pub const LINK_LOCAL_ALL_ROUTERS: Address = + Address([0xff, 0x02, 0x00, 0x0, 0x00, 0x00, 0x00, 0x0, + 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 0x2]); + + /// Loopback address. + pub const LOOPBACK: Address = + Address([0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 0x0, + 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 0x1]); + + /// Construct an IPv6 address from parts. + pub fn new(a0: u16, a1: u16, a2: u16, a3: u16, + a4: u16, a5: u16, a6: u16, a7: u16) -> Address { + let mut addr = [0u8; 16]; + NetworkEndian::write_u16(&mut addr[0..2], a0); + NetworkEndian::write_u16(&mut addr[2..4], a1); + NetworkEndian::write_u16(&mut addr[4..6], a2); + NetworkEndian::write_u16(&mut addr[6..8], a3); + NetworkEndian::write_u16(&mut addr[8..10], a4); + NetworkEndian::write_u16(&mut addr[10..12], a5); + NetworkEndian::write_u16(&mut addr[12..14], a6); + NetworkEndian::write_u16(&mut addr[14..16], a7); + Address(addr) + } + + /// Construct an IPv6 address from a sequence of octets, in big-endian. + /// + /// # Panics + /// The function panics if `data` is not sixteen octets long. + pub fn from_bytes(data: &[u8]) -> Address { + let mut bytes = [0; 16]; + bytes.copy_from_slice(data); + Address(bytes) + } + + /// Construct an IPv6 address from a sequence of words, in big-endian. + /// + /// # Panics + /// The function panics if `data` is not 8 words long. + pub fn from_parts(data: &[u16]) -> Address { + assert!(data.len() >= 8); + let mut bytes = [0; 16]; + for word_idx in 0..8 { + let byte_idx = word_idx * 2; + NetworkEndian::write_u16(&mut bytes[byte_idx..(byte_idx + 2)], data[word_idx]); + } + Address(bytes) + } + + /// Write a IPv6 address to the given slice. + /// + /// # Panics + /// The function panics if `data` is not 8 words long. + pub fn write_parts(&self, data: &mut [u16]) { + assert!(data.len() >= 8); + for i in 0..8 { + let byte_idx = i * 2; + data[i] = NetworkEndian::read_u16(&self.0[byte_idx..(byte_idx + 2)]); + } + } + + /// Return an IPv6 address as a sequence of octets, in big-endian. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Query whether the IPv6 address is an unicast address. + pub fn is_unicast(&self) -> bool { + !(self.is_multicast() || self.is_unspecified()) + } + + /// Query whether the IPv6 address is a multicast address. + pub fn is_multicast(&self) -> bool { + self.0[0] == 0xff + } + + /// Query whether the IPv6 address is the "unspecified" address. + pub fn is_unspecified(&self) -> bool { + self.0 == [0x00; 16] + } + + /// Query whether the IPv6 address is in the "link-local" range. + pub fn is_link_local(&self) -> bool { + self.0[0..8] == [0xfe, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00] + } + + /// Query whether the IPv6 address is the "loopback" address. + pub fn is_loopback(&self) -> bool { + *self == Self::LOOPBACK + } + + /// Helper function used to mask an addres given a prefix. + /// + /// # Panics + /// This function panics if `mask` is greater than 128. + pub(super) fn mask(&self, mask: u8) -> [u8; 16] { + assert!(mask <= 128); + let mut bytes = [0u8; 16]; + let idx = (mask as usize) / 8; + let modulus = (mask as usize) % 8; + let (first, second) = self.0.split_at(idx); + bytes[0..idx].copy_from_slice(&first); + if idx < 16 { + let part = second[0]; + bytes[idx] = part & (!(0xff >> modulus) as u8); + } + bytes + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + enum State { + Head, + HeadBody, + Tail, + TailBody + } + let mut words = [0u16; 8]; + self.write_parts(&mut words); + let mut state = State::Head; + for word in words.iter() { + state = match (*word, &state) { + // Once a u16 equal to zero write a double colon and + // skip to the next non-zero u16. + (0, &State::Head) | (0, &State::HeadBody) => { + write!(f, "::")?; + State::Tail + }, + // Continue iterating without writing any characters until + // we hit anothing non-zero value. + (0, &State::Tail) => State::Tail, + // When the state is Head or Tail write a u16 in hexadecimal + // without the leading colon if the value is not 0. + (_, &State::Head) => { + write!(f, "{:x}", word)?; + State::HeadBody + }, + (_, &State::Tail) => { + write!(f, "{:x}", word)?; + State::TailBody + }, + // Write the u16 with a leading colon when parsing a value + // that isn't the first in a section + (_, &State::HeadBody) | (_, &State::TailBody) => { + write!(f, ":{:x}", word)?; + state + } + } + } + Ok(()) + } +} + +/// A specification of an IPv6 CIDR block, containing an address and a variable-length +/// subnet masking prefix length. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Cidr { + address: Address, + prefix_len: u8, +} + +impl Cidr { + /// Create an IPv6 CIDR block from the given address and prefix length. + /// + /// # Panics + /// This function panics if the prefix length is larger than 128. + pub fn new(address: Address, prefix_len: u8) -> Cidr { + assert!(prefix_len <= 128); + Cidr { address, prefix_len } + } + + /// Return the address of this IPv6 CIDR block. + pub fn address(&self) -> Address { + self.address + } + + /// Return the prefix length of this IPv6 CIDR block. + pub fn prefix_len(&self) -> u8 { + self.prefix_len + } + + /// Query whether the subnetwork described by this IPv6 CIDR block contains + /// the given address. + pub fn contains_addr(&self, addr: &Address) -> bool { + // right shift by 128 is not legal + if self.prefix_len == 0 { return true } + + let shift = 128 - self.prefix_len; + self.address.mask(shift) == addr.mask(shift) + } + + /// Query whether the subnetwork described by this IPV6 CIDR block contains + /// the subnetwork described by the given IPv6 CIDR block. + pub fn contains_subnet(&self, subnet: &Cidr) -> bool { + self.prefix_len <= subnet.prefix_len && self.contains_addr(&subnet.address) + } +} + +impl fmt::Display for Cidr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.address, self.prefix_len) + } +} + +#[cfg(test)] +mod test { + use super::{Address, Cidr}; + + static LINK_LOCAL_ADDR: Address = Address([0xfe, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01]); + #[test] + fn test_basic_multicast() { + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unspecified()); + assert!(Address::LINK_LOCAL_ALL_ROUTERS.is_multicast()); + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_link_local()); + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_loopback()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_unspecified()); + assert!(Address::LINK_LOCAL_ALL_NODES.is_multicast()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_link_local()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_loopback()); + } + + #[test] + fn test_basic_link_local() { + assert!(!LINK_LOCAL_ADDR.is_unspecified()); + assert!(!LINK_LOCAL_ADDR.is_multicast()); + assert!(LINK_LOCAL_ADDR.is_link_local()); + assert!(!LINK_LOCAL_ADDR.is_loopback()); + } + + #[test] + fn test_basic_loopback() { + assert!(!Address::LOOPBACK.is_unspecified()); + assert!(!Address::LOOPBACK.is_multicast()); + assert!(!Address::LOOPBACK.is_link_local()); + assert!(Address::LOOPBACK.is_loopback()); + } + + #[test] + fn test_address_format() { + assert_eq!("ff02::1", + format!("{}", Address::LINK_LOCAL_ALL_NODES)); + assert_eq!("fe80::1", + format!("{}", LINK_LOCAL_ADDR)); + assert_eq!("fe80::7f00:0:1", + format!("{}", Address::new(0xfe80, 0, 0, 0, 0, 0x7f00, 0x0000, 0x0001))); + } + + #[test] + fn test_new() { + assert_eq!(Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1), + Address::LINK_LOCAL_ALL_NODES); + assert_eq!(Address::new(0xff02, 0, 0, 0, 0, 0, 0, 2), + Address::LINK_LOCAL_ALL_ROUTERS); + assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 1), + Address::LOOPBACK); + assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 0), + Address::UNSPECIFIED); + assert_eq!(Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + LINK_LOCAL_ADDR); + } + + #[test] + fn test_from_parts() { + assert_eq!(Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 1]), + Address::LINK_LOCAL_ALL_NODES); + assert_eq!(Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 2]), + Address::LINK_LOCAL_ALL_ROUTERS); + assert_eq!(Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 1]), + Address::LOOPBACK); + assert_eq!(Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 0]), + Address::UNSPECIFIED); + assert_eq!(Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 1]), + LINK_LOCAL_ADDR); + } + + #[test] + fn test_write_parts() { + let mut bytes = [0u16; 8]; + { + Address::LOOPBACK.write_parts(&mut bytes); + assert_eq!(Address::LOOPBACK, Address::from_parts(&bytes)); + } + { + Address::LINK_LOCAL_ALL_ROUTERS.write_parts(&mut bytes); + assert_eq!(Address::LINK_LOCAL_ALL_ROUTERS, Address::from_parts(&bytes)); + } + { + LINK_LOCAL_ADDR.write_parts(&mut bytes); + assert_eq!(LINK_LOCAL_ADDR, Address::from_parts(&bytes)); + } + } + + #[test] + fn test_mask() { + let addr = Address::new(0x0123, 0x4567, 0x89ab, 0, 0, 0, 0, 1); + assert_eq!(addr.mask(11), [0x01, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(addr.mask(15), [0x01, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(addr.mask(26), [0x01, 0x23, 0x45, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(addr.mask(128), [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + assert_eq!(addr.mask(127), [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn test_cidr() { + let cidr = Cidr::new(LINK_LOCAL_ADDR, 64); + + let inside_subnet = [ + [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02], + [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff] + ]; + + let outside_subnet = [ + [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + [0x00, 0x00, 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], + [0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02] + ]; + + let subnets = [ + ([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + 65), + ([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + 128), + ([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78], + 96) + ]; + + let not_subnets = [ + ([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + 63), + ([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + 64), + ([0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + 65), + ([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + 128) + ]; + + for addr in inside_subnet.iter().map(|a| Address::from_bytes(a)) { + assert!(cidr.contains_addr(&addr)); + } + + for addr in outside_subnet.iter().map(|a| Address::from_bytes(a)) { + assert!(!cidr.contains_addr(&addr)); + } + + for subnet in subnets.iter().map( + |&(a, p)| Cidr::new(Address(a), p)) { + assert!(cidr.contains_subnet(&subnet)); + } + + for subnet in not_subnets.iter().map( + |&(a, p)| Cidr::new(Address(a), p)) { + assert!(!cidr.contains_subnet(&subnet)); + } + + let cidr_without_prefix = Cidr::new(LINK_LOCAL_ADDR, 0); + assert!(cidr_without_prefix.contains_addr(&Address::LOOPBACK)); + } + + #[test] + #[should_panic(expected = "destination and source slices have different lengths")] + fn from_bytes_too_long() { + let _ = Address::from_bytes(&[0u8; 15]); + } + + #[test] + #[should_panic(expected = "data.len() >= 8")] + fn from_parts_too_long() { + let _ = Address::from_parts(&[0u16; 7]); + } +} diff --git a/src/wire/mod.rs b/src/wire/mod.rs index 1c0e65f..4d0b0be 100644 --- a/src/wire/mod.rs +++ b/src/wire/mod.rs @@ -81,6 +81,8 @@ mod ethernet; mod arp; mod ip; mod ipv4; +#[cfg(feature = "proto-ipv6")] +mod ipv6; mod icmpv4; mod udp; mod tcp; @@ -108,6 +110,10 @@ pub use self::ipv4::Packet as Ipv4Packet; pub use self::ipv4::Repr as Ipv4Repr; pub use self::ipv4::Cidr as Ipv4Cidr; +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6::{Address as Ipv6Address, + Cidr as Ipv6Cidr}; + pub use self::icmpv4::Message as Icmpv4Message; pub use self::icmpv4::DstUnreachable as Icmpv4DstUnreachable; pub use self::icmpv4::Redirect as Icmpv4Redirect;