Handle parsing and display of IPv4 mapped IPv6 addresses.

* The IPv6 address parser now handles IPv4 mapped IPv6 addresses which
  take on the form ::ffff:x.x.x.x.
* Implement Display for IPv4 mapped IPv6 addresses
* Implement From<IPv4Address> for IPv6Address

Fixes #86
This commit is contained in:
Herman J. Radtke III 2018-01-13 00:49:24 -08:00 committed by whitequark
parent 8d908127d3
commit 07b78a8d50
2 changed files with 122 additions and 2 deletions

View File

@ -137,6 +137,18 @@ impl<'a> Parser<'a> {
Err(())
}
#[cfg(feature = "proto-ipv6")]
fn accept_ipv4_mapped_ipv6_part(&mut self, parts: &mut [u16], idx: &mut usize) -> Result<()> {
let octets = self.accept_ipv4_octets()?;
parts[*idx] = ((octets[0] as u16) << 8) | (octets[1] as u16);
*idx += 1;
parts[*idx] = ((octets[2] as u16) << 8) | (octets[3] as u16);
*idx += 1;
Ok(())
}
#[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),
@ -169,12 +181,27 @@ impl<'a> Parser<'a> {
// Valid u16 to be added to the address
head[*head_idx] = part as u16;
*head_idx += 1;
if *head_idx == 6 && head[0..*head_idx] == [0, 0, 0, 0, 0, 0xffff] {
self.try(|p| {
p.accept_char(b':')?;
p.accept_ipv4_mapped_ipv6_part(head, head_idx)
});
}
Ok(())
},
Some(part) if *tail_idx < 6 => {
// Valid u16 to be added to the address
tail[*tail_idx] = part as u16;
*tail_idx += 1;
if *tail_idx == 1 && tail[0] == 0xffff
&& head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] {
self.try(|p| {
p.accept_char(b':')?;
p.accept_ipv4_mapped_ipv6_part(tail, tail_idx)
});
}
Ok(())
},
Some(_) => {
@ -237,8 +264,7 @@ impl<'a> Parser<'a> {
Ok(Ipv6Address::from_parts(&addr))
}
#[cfg(feature = "proto-ipv4")]
fn accept_ipv4(&mut self) -> Result<Ipv4Address> {
fn accept_ipv4_octets(&mut self) -> Result<[u8; 4]> {
let mut octets = [0u8; 4];
for n in 0..4 {
octets[n] = self.accept_number(3, 0x100, false)? as u8;
@ -246,6 +272,12 @@ impl<'a> Parser<'a> {
self.accept_char(b'.')?;
}
}
Ok(octets)
}
#[cfg(feature = "proto-ipv4")]
fn accept_ipv4(&mut self) -> Result<Ipv4Address> {
let octets = self.accept_ipv4_octets()?;
Ok(Ipv4Address(octets))
}
@ -510,6 +542,27 @@ mod test {
// Long number
assert_eq!(Ipv6Address::from_str("::000001"),
Err(()));
// IPv4-Mapped address
assert_eq!(Ipv6Address::from_str("::ffff:192.168.1.1"),
Ok(Ipv6Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1])));
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:192.168.1.1"),
Ok(Ipv6Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1])));
assert_eq!(Ipv6Address::from_str("0::ffff:192.168.1.1"),
Ok(Ipv6Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1])));
// Only ffff is allowed in position 6 when IPv4 mapped
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:eeee:192.168.1.1"),
Err(()));
// Positions 1-5 must be 0 when IPv4 mapped
assert_eq!(Ipv6Address::from_str("0:0:0:0:1:ffff:192.168.1.1"),
Err(()));
assert_eq!(Ipv6Address::from_str("1::ffff:192.168.1.1"),
Err(()));
// Out of range ipv4 octet
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:256.168.1.1"),
Err(()));
// Invalid hex in ipv4 octet
assert_eq!(Ipv6Address::from_str("0:0:0:0:0:ffff:c0.168.1.1"),
Err(()));
}
#[test]

View File

@ -135,6 +135,23 @@ impl Address {
*self == Self::LOOPBACK
}
/// Query whether the IPv6 address is an [IPv4 mapped IPv6 address].
///
/// [IPv4 mapped IPv6 address]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2
pub fn is_ipv4_mapped(&self) -> bool {
self.0[0..12] == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff]
}
#[cfg(feature = "proto-ipv4")]
/// Convert an IPv4 mapped IPv6 address to an IPv4 address.
pub fn as_ipv4(&self) -> Option<::wire::ipv4::Address> {
if self.is_ipv4_mapped() {
Some(::wire::ipv4::Address::new(self.0[12], self.0[13], self.0[14], self.0[15]))
} else {
None
}
}
/// Helper function used to mask an addres given a prefix.
///
/// # Panics
@ -156,6 +173,10 @@ impl Address {
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_ipv4_mapped() {
return write!(f, "::ffff:{}.{}.{}.{}", self.0[12], self.0[13], self.0[14], self.0[15])
}
// The string representation of an IPv6 address should
// collapse a series of 16 bit sections that evaluate
// to 0 to "::"
@ -204,6 +225,16 @@ impl fmt::Display for Address {
}
}
#[cfg(feature = "proto-ipv4")]
/// Convert the given IPv4 address into a IPv4-mapped IPv6 address
impl From<::wire::ipv4::Address> for Address {
fn from(address: ::wire::ipv4::Address) -> Self {
let octets = address.0;
Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff,
octets[0], octets[1], octets[2], octets[3]])
}
}
/// 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)]
@ -606,6 +637,9 @@ mod test {
use super::{Packet, Protocol, Repr};
use wire::pretty_print::{PrettyPrinter};
#[cfg(feature = "proto-ipv4")]
use wire::ipv4::Address as Ipv4Address;
static LINK_LOCAL_ADDR: Address = Address([0xfe, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
@ -646,6 +680,14 @@ mod test {
format!("{}", LINK_LOCAL_ADDR));
assert_eq!("fe80::7f00:0:1",
format!("{}", Address::new(0xfe80, 0, 0, 0, 0, 0x7f00, 0x0000, 0x0001)));
assert_eq!("::",
format!("{}", Address::UNSPECIFIED));
assert_eq!("::1",
format!("{}", Address::LOOPBACK));
#[cfg(feature = "proto-ipv4")]
assert_eq!("::ffff:192.168.1.1",
format!("{}", Address::from(Ipv4Address::new(192, 168, 1, 1))));
}
#[test]
@ -703,6 +745,31 @@ mod test {
assert_eq!(addr.mask(127), [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
#[cfg(feature = "proto-ipv4")]
#[test]
fn test_is_ipv4_mapped() {
assert_eq!(false, Address::UNSPECIFIED.is_ipv4_mapped());
assert_eq!(true, Address::from(Ipv4Address::new(192, 168, 1, 1)).is_ipv4_mapped());
}
#[cfg(feature = "proto-ipv4")]
#[test]
fn test_as_ipv4() {
assert_eq!(None, Address::UNSPECIFIED.as_ipv4());
let ipv4 = Ipv4Address::new(192, 168, 1, 1);
assert_eq!(Some(ipv4), Address::from(ipv4).as_ipv4());
}
#[cfg(feature = "proto-ipv4")]
#[test]
fn test_from_ipv4_address() {
assert_eq!(Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1]),
Address::from(Ipv4Address::new(192, 168, 1, 1)));
assert_eq!(Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 222, 1, 41, 90]),
Address::from(Ipv4Address::new(222, 1, 41, 90)));
}
#[test]
fn test_cidr() {
let cidr = Cidr::new(LINK_LOCAL_ADDR, 64);