Implement wire::{IpCidr/Ipv4Cidr}.

This commit is contained in:
Egor Karavaev 2017-10-03 10:26:31 +03:00 committed by whitequark
parent 72481c2661
commit f57de10625
4 changed files with 237 additions and 2 deletions

View File

@ -1,6 +1,6 @@
use core::str::FromStr;
use core::result;
use wire::{EthernetAddress, IpAddress, Ipv4Address};
use wire::{EthernetAddress, IpAddress, Ipv4Address, Ipv4Cidr, IpCidr};
type Result<T> = result::Result<T, ()>;
@ -164,6 +164,29 @@ impl FromStr for IpAddress {
}
}
impl FromStr for Ipv4Cidr {
type Err = ();
/// Parse a string representation of an IPv4 CIDR.
fn from_str(s: &str) -> Result<Ipv4Cidr> {
Parser::new(s).until_eof(|p| {
let ip = p.accept_ipv4()?;
p.accept_char(b'/')?;
let prefix_len = p.accept_number(2, 33, false)? as u8;
Ok(Ipv4Cidr::new(ip, prefix_len))
})
}
}
impl FromStr for IpCidr {
type Err = ();
/// Parse a string representation of an IP CIDR.
fn from_str(s: &str) -> Result<IpCidr> {
Ipv4Cidr::from_str(s).map(IpCidr::Ipv4)
}
}
#[cfg(test)]
mod test {
use super::*;
@ -212,4 +235,36 @@ mod test {
Ok(IpAddress::Ipv4(Ipv4Address([1, 2, 3, 4]))));
assert_eq!(IpAddress::from_str("x"), Err(()));
}
#[test]
fn test_cidr() {
let tests = [
("127.0.0.1/8",
Ok(Ipv4Cidr::new(Ipv4Address([127, 0, 0, 1]), 8u8))),
("192.168.1.1/24",
Ok(Ipv4Cidr::new(Ipv4Address([192, 168, 1, 1]), 24u8))),
("8.8.8.8/32",
Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 32u8))),
("8.8.8.8/0",
Ok(Ipv4Cidr::new(Ipv4Address([8, 8, 8, 8]), 0u8))),
("", Err(())),
("1", Err(())),
("127.0.0.1", Err(())),
("127.0.0.1/", Err(())),
("127.0.0.1/33", Err(())),
("127.0.0.1/111", Err(())),
("/32", Err(())),
];
for &(s, cidr) in &tests {
assert_eq!(Ipv4Cidr::from_str(s), cidr);
assert_eq!(IpCidr::from_str(s), cidr.map(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)));
}
}
}
}

View File

@ -1,8 +1,9 @@
use core::fmt;
use core::convert::From;
use {Error, Result};
use phy::ChecksumCapabilities;
use super::{Ipv4Address, Ipv4Packet, Ipv4Repr};
use super::{Ipv4Address, Ipv4Packet, Ipv4Repr, Ipv4Cidr};
/// Internet protocol version.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
@ -133,6 +134,85 @@ impl fmt::Display for Address {
}
}
/// A specification of a CIDR block, containing an address and a variable-length
/// subnet masking prefix length.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Cidr {
Ipv4(Ipv4Cidr),
#[doc(hidden)]
__Nonexhaustive,
}
impl Cidr {
/// Create a CIDR block from the given address and prefix length.
///
/// # Panics
/// This function panics if the given address is unspecified, or
/// the given prefix length is invalid for the given address.
pub fn new(addr: Address, prefix_len: u8) -> Cidr {
match addr {
Address::Ipv4(addr) => Cidr::Ipv4(Ipv4Cidr::new(addr, prefix_len)),
Address::Unspecified =>
panic!("a CIDR block cannot be based on an unspecified address"),
Address::__Nonexhaustive =>
unreachable!()
}
}
/// Return the IP address of this CIDR block.
pub fn address(&self) -> Address {
match self {
&Cidr::Ipv4(cidr) => Address::Ipv4(cidr.address()),
&Cidr::__Nonexhaustive => unreachable!()
}
}
/// Return the prefix length of this CIDR block.
pub fn prefix_len(&self) -> u8 {
match self {
&Cidr::Ipv4(cidr) => cidr.prefix_len(),
&Cidr::__Nonexhaustive => unreachable!()
}
}
/// Query whether the subnetwork described by this CIDR block contains
/// the given address.
pub fn contains_addr(&self, addr: &Address) -> bool {
match (self, addr) {
(&Cidr::Ipv4(ref cidr), &Address::Ipv4(ref addr)) =>
cidr.contains_addr(addr),
(_, &Address::Unspecified) =>
// a fully unspecified address covers both IPv4 and IPv6,
// and no CIDR block can do that.
false,
(&Cidr::__Nonexhaustive, _) |
(_, &Address::__Nonexhaustive) =>
unreachable!()
}
}
/// Query whether the subnetwork described by this CIDR block contains
/// the subnetwork described by the given CIDR block.
pub fn contains_subnet(&self, subnet: &Cidr) -> bool {
match (self, subnet) {
(&Cidr::Ipv4(ref cidr), &Cidr::Ipv4(ref other)) =>
cidr.contains_subnet(other),
(&Cidr::__Nonexhaustive, _) |
(_, &Cidr::__Nonexhaustive) =>
unreachable!()
}
}
}
impl fmt::Display for Cidr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Cidr::Ipv4(cidr) => write!(f, "{}", cidr),
&Cidr::__Nonexhaustive => unreachable!()
}
}
}
/// An internet endpoint address.
///
/// An endpoint can be constructed from a port, in which case the address is unspecified.

View File

@ -78,6 +78,56 @@ impl fmt::Display for Address {
}
}
/// A specification of an IPv4 CIDR block, containing an address and a variable-length
/// subnet masking prefix length.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Cidr {
address: Address,
prefix_len: u8,
}
impl Cidr {
/// Create an IPv4 CIDR block from the given address and prefix length.
///
/// # Panics
/// This function panics if the prefix length is larger than 32.
pub fn new(address: Address, prefix_len: u8) -> Cidr {
assert!(prefix_len <= 32);
Cidr { address, prefix_len }
}
/// Return the address of this IPv4 CIDR block.
pub fn address(&self) -> Address {
self.address
}
/// Return the prefix length of this IPv4 CIDR block.
pub fn prefix_len(&self) -> u8 {
self.prefix_len
}
/// Query whether the subnetwork described by this IPv4 CIDR block contains
/// the given address.
pub fn contains_addr(&self, addr: &Address) -> bool {
let shift = 32 - self.prefix_len;
let self_prefix = NetworkEndian::read_u32(self.address.as_bytes()) >> shift;
let addr_prefix = NetworkEndian::read_u32(addr.as_bytes()) >> shift;
self_prefix == addr_prefix
}
/// Query whether the subnetwork described by this IPv4 CIDR block contains
/// the subnetwork described by the given IPv4 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)
}
}
/// A read/write wrapper around an Internet Protocol version 4 packet buffer.
#[derive(Debug)]
pub struct Packet<T: AsRef<[u8]>> {
@ -721,4 +771,52 @@ mod test {
assert!(!Address::BROADCAST.is_link_local());
assert!(!Address::BROADCAST.is_loopback());
}
#[test]
fn test_cidr() {
let cidr = Cidr::new(Address::new(192, 168, 1, 10), 24);
let inside_subnet = [
[192, 168, 1, 0], [192, 168, 1, 1],
[192, 168, 1, 2], [192, 168, 1, 10],
[192, 168, 1, 127], [192, 168, 1, 255],
];
let outside_subnet = [
[192, 168, 0, 0], [127, 0, 0, 1],
[192, 168, 2, 0], [192, 168, 0, 255],
[ 0, 0, 0, 0], [255, 255, 255, 255],
];
let subnets = [
([192, 168, 1, 0], 32),
([192, 168, 1, 255], 24),
([192, 168, 1, 10], 30),
];
let not_subnets = [
([192, 168, 1, 10], 23),
([127, 0, 0, 1], 8),
([192, 168, 1, 0], 0),
([192, 168, 0, 255], 32),
];
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::new(a[0], a[1], a[2], a[3]), p)) {
assert!(cidr.contains_subnet(&subnet));
}
for subnet in not_subnets.iter().map(
|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p)) {
assert!(!cidr.contains_subnet(&subnet));
}
}
}

View File

@ -100,10 +100,12 @@ pub use self::ip::Protocol as IpProtocol;
pub use self::ip::Address as IpAddress;
pub use self::ip::Endpoint as IpEndpoint;
pub use self::ip::IpRepr as IpRepr;
pub use self::ip::Cidr as IpCidr;
pub use self::ipv4::Address as Ipv4Address;
pub use self::ipv4::Packet as Ipv4Packet;
pub use self::ipv4::Repr as Ipv4Repr;
pub use self::ipv4::Cidr as Ipv4Cidr;
pub use self::icmpv4::Message as Icmpv4Message;
pub use self::icmpv4::DstUnreachable as Icmpv4DstUnreachable;