54 Commits

Author SHA1 Message Date
ca2da8ae30 Uprev for a new release 2020-01-08 10:37:12 +00:00
156ae55da0 Merge pull request #116 from achanda/cleanup
Cleanup comment on IPv6 implementation
2020-01-08 10:35:57 +00:00
4dfdf142d0 Cleanup comment on IPv6 implementation 2020-01-08 10:29:58 +00:00
6851a0b272 Merge pull request #115 from achanda/maintenance
Set a maintenance status
2020-01-06 20:29:55 +00:00
00256cf885 Set a maintenance status
Putting this as passively-maintained since this crate is mostly feature
complete.
2020-01-05 13:27:46 +00:00
80c6794829 Merge pull request #114 from achanda/ipv6-netmask
Check against specific errors in parsing test
2020-01-05 13:00:30 +00:00
18677e3b69 Run cargo fmt 2020-01-05 11:38:28 +00:00
986ee0ba1c Check against specific errors in parsing test 2020-01-05 11:36:49 +00:00
f3ac9ba3fe Merge pull request #113 from achanda/bench-netmask
Add a benchmark for parsing IPv4 netmask
2020-01-05 11:20:31 +00:00
bc1064e5f0 Add a benchmark for parsing IPv4 netmask 2020-01-05 11:14:06 +00:00
ddc9ef2f7b Merge pull request #112 from vorner/netmask-parse
Be able to parse the netmask format
2020-01-04 10:19:35 +00:00
87ea7b24fa Be able to parse the netmask format
Allow accepting the netmask format, eg 192.168.1.0/255.255.255.0, in
addition to the more modern /24 prefix version.

Adding support for IPv4 only, as IPv6 never actually used that format in
practice.

Closes #107.
2019-12-21 16:19:20 +01:00
08126f5103 Merge pull request #109 from mullvad/optional-serde
Make the serde dependency optional
2019-12-13 16:23:08 +00:00
376081d253 Merge pull request #108 from vorner/iterator
Iterator
2019-12-13 16:22:50 +00:00
fa85cdb500 Test without features on CI 2019-12-11 12:51:39 +01:00
fa5deed40b Make the serde dependency optional 2019-12-11 12:51:39 +01:00
5b09d82d43 Fix bug with overflow in iterator 2019-12-11 09:40:55 +01:00
dbf366cfdf Iterators: reproducer for overflow on high end of iteration 2019-12-11 09:40:55 +01:00
6cbb011597 Iterators for the IpNetwork
Version-agnostic iterator in addition to the version specific ones.
2019-12-11 09:40:55 +01:00
f7d9524f70 Make the iterators slightly better
* Implement some useful traits on them.
* Make reference an into-iterator.
2019-12-11 09:40:55 +01:00
e1f8566301 Merge pull request #110 from mullvad/metadata-cleanup
Metadata cleanup
2019-12-10 12:16:02 +00:00
a3197a8411 Remove the "heavily WIP" part of description 2019-12-10 09:40:08 +01:00
0479ca45a9 Add and remove appropriate keywords and categories 2019-12-10 09:40:08 +01:00
11025d3b1a Remove documentation URL that is already default 2019-12-10 09:28:26 +01:00
43a38d4117 Merge pull request #103 from lucab/ups/parse-net-pair
ipnetwork: add netmask constructors
2019-11-28 13:52:55 +00:00
152f1a8393 ipnetwork: add netmask constructors
This adds constructors to build all network types from a network
address and network mask pair.
2019-11-27 17:20:59 +00:00
31fc25f9ce cargo: rustfmt whole project 2019-11-27 15:50:47 +00:00
bdaa8a7843 Merge pull request #101 from achanda/release-v0.15.1
Uprev for a new release
2019-11-23 13:54:22 +00:00
d54f463f86 Uprev for a new release 2019-11-23 13:48:22 +00:00
fb782086ce Merge pull request #100 from LukasKalbertodt/master
Use `slice::iter` instead of `into_iter` to avoid future breakage
2019-10-30 22:17:22 +00:00
26706b3790 Use slice::iter instead of into_iter to avoid future breakage
`an_array.into_iter()` currently just works because of the autoref
feature, which then calls `<[T] as IntoIterator>::into_iter`. But
in the future, arrays will implement `IntoIterator`, too. In order
to avoid problems in the future, the call is replaced by `iter()`
which is shorter and more explicit.
2019-10-30 15:32:11 +01:00
2eed2de227 Merge pull request #99 from achanda/action-config
Add a github action config
2019-10-12 16:28:31 +01:00
7395af2658 Show current git branch 2019-10-12 16:17:29 +01:00
fefd674721 Add a checkout step 2019-10-12 16:11:31 +01:00
20ed033826 Add a github action config 2019-10-09 22:56:14 +01:00
7af6e16f98 Merge pull request #98 from achanda/release-0.15
Uprev for a new release
2019-09-12 20:34:06 +01:00
9e42aaeb3e Uprev for a new release 2019-09-12 20:25:48 +01:00
2b34ba991a Merge pull request #96 from achanda/dependabot/cargo/criterion-0.3.0
Update criterion requirement from 0.2.9 to 0.3.0
2019-08-26 08:25:21 +01:00
2dff6a0863 Update criterion requirement from 0.2.9 to 0.3.0
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version.
- [Release notes](https://github.com/bheisler/criterion.rs/releases)
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.2.9...0.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-26 05:35:47 +00:00
a45e76c92f Merge pull request #95 from faern/upgrade-to-rust2018
Upgrade to Rust 2018 edition
2019-03-25 16:57:22 +00:00
a6dfadc21d Manual import cleanup 2019-03-25 15:24:17 +01:00
61eeb5843f Run cargo fix --edition-idioms 2019-03-25 15:15:41 +01:00
d8f3cc1992 Upgrade to edition 2018 2019-03-25 15:15:21 +01:00
c2d4b9aeae Run cargo fix --edition 2019-03-25 15:13:50 +01:00
3479b6f272 Merge pull request #94 from mullvad/fix-deserialization
Deserialize from String rather &str for Ipv4Network and Ipv6Network
2019-02-20 16:49:28 +01:00
10d12cd945 Deserialize from String rather &str for ipnetwork:Ipv{4,6}Network 2019-02-20 11:07:29 +00:00
47a539f2f1 Merge pull request #93 from achanda/new-methods
Implement is_subnet_of, is_supernet_of and overlaps
2019-02-07 18:43:47 +01:00
e7c85b5b81 Implement overlaps for both types 2019-02-06 23:29:46 +00:00
439a0deeb7 Implement is_subnet_of and is_supernet_of 2019-02-06 23:14:07 +00:00
b2c458e6f1 Merge pull request #91 from achanda/contains-bench
Track benchmark for contains
2019-02-03 01:10:09 +01:00
1b06452e88 Optimise contains for IPv4 2019-02-02 23:57:25 +00:00
2b9936fa8e Track benchmark for contains 2019-02-02 23:36:08 +00:00
553fa0ba1c Merge pull request #90 from achanda/parse-bench
Add a benchmark for CIDR parsing
2019-02-03 00:22:15 +01:00
a19f24a9c6 Add a benchmark for CIDR parsing
Also optimise the cidr_parts implementation
2019-02-02 21:54:16 +00:00
9 changed files with 614 additions and 103 deletions

28
.github/workflows/rust.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Rust
on:
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable, beta, nightly]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
with:
ref: ${{ github.ref }}
- name: Show current git branch
run: git branch
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Build docs
run: cargo doc --verbose

View File

@ -9,6 +9,8 @@ env:
- FEATURES: default
script:
- cargo build --verbose --no-default-features
- cargo test --verbose --no-default-features
- cargo build --verbose
- cargo test --verbose
- cargo build --release --verbose

View File

@ -1,26 +1,32 @@
[package]
name = "ipnetwork"
version = "0.14.0" # When updating version, also modify html_root_url in the lib.rs
version = "0.16.0" # When updating version, also modify html_root_url in the lib.rs
authors = ["Abhishek Chanda <abhishek.becs@gmail.com>", "Linus Färnstrand <faern@faern.net>"]
description = "A library to work with IP CIDRs in Rust, heavily WIP"
description = "A library to work with IP CIDRs in Rust"
license = "Apache-2.0"
repository = "https://github.com/achanda/ipnetwork"
keywords = ["network", "ip", "address"]
keywords = ["network", "ip", "address", "cidr"]
readme = "README.md"
documentation = "https://docs.rs/ipnetwork/"
categories = ["network-programming", "os"]
categories = ["network-programming", "parser-implementations"]
edition = "2018"
[dependencies]
clippy = {version = "0.0.302", optional = true}
serde = ">=0.8.0, <2.0"
serde = { version = ">=0.8.0, <2.0", optional = true }
[dev-dependencies]
serde_json = "1.0"
serde_derive = ">=0.8.0, <2.0"
criterion = "0.3.0"
[badges]
travis-ci = { repository = "achanda/ipnetwork" }
maintenance = { status = "passively-maintained" }
[features]
default = []
default = ["serde"]
dev = ["clippy"]
[[bench]]
name = "parse_bench"
harness = false

49
benches/parse_bench.rs Normal file
View File

@ -0,0 +1,49 @@
use criterion::{criterion_group, criterion_main, Criterion};
use ipnetwork::{Ipv4Network, Ipv6Network};
use std::net::{Ipv4Addr, Ipv6Addr};
fn parse_ipv4_prefix_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4 prefix", |b| {
b.iter(|| "127.1.0.0/24".parse::<Ipv4Network>().unwrap())
});
}
fn parse_ipv6_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv6", |b| {
b.iter(|| "FF01:0:0:17:0:0:0:2/64".parse::<Ipv6Network>().unwrap())
});
}
fn parse_ipv4_netmask_benchmark(c: &mut Criterion) {
c.bench_function("parse ipv4 netmask", |b| {
b.iter(|| "127.1.0.0/255.255.255.0".parse::<Ipv4Network>().unwrap())
});
}
fn contains_ipv4_benchmark(c: &mut Criterion) {
c.bench_function("contains ipv4", |b| {
b.iter(|| {
let cidr = "74.125.227.0/25".parse::<Ipv4Network>().unwrap();
cidr.contains(Ipv4Addr::new(74, 125, 227, 4))
})
});
}
fn contains_ipv6_benchmark(c: &mut Criterion) {
c.bench_function("contains ipv6", |b| {
b.iter(|| {
let cidr = "FF01:0:0:17:0:0:0:2/65".parse::<Ipv6Network>().unwrap();
cidr.contains(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0x7fff, 0, 0, 0x2))
})
});
}
criterion_group!(
benches,
parse_ipv4_prefix_benchmark,
parse_ipv6_benchmark,
parse_ipv4_netmask_benchmark,
contains_ipv4_benchmark,
contains_ipv6_benchmark
);
criterion_main!(benches);

View File

@ -1,5 +1,4 @@
use std::error::Error;
use std::fmt;
use std::{error::Error, fmt};
/// Represents a bunch of errors that can occur while working with a `IpNetwork`
#[derive(Debug, Clone, PartialEq, Eq)]
@ -10,8 +9,8 @@ pub enum IpNetworkError {
}
impl fmt::Display for IpNetworkError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use IpNetworkError::*;
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use crate::IpNetworkError::*;
match *self {
InvalidAddr(ref s) => write!(f, "invalid address: {}", s),
InvalidPrefix => write!(f, "invalid prefix"),
@ -22,7 +21,7 @@ impl fmt::Display for IpNetworkError {
impl Error for IpNetworkError {
fn description(&self) -> &str {
use IpNetworkError::*;
use crate::IpNetworkError::*;
match *self {
InvalidAddr(_) => "address is invalid",
InvalidPrefix => "prefix is invalid",
@ -32,16 +31,22 @@ impl Error for IpNetworkError {
}
pub fn cidr_parts(cidr: &str) -> Result<(&str, Option<&str>), IpNetworkError> {
let parts = cidr.split('/').collect::<Vec<&str>>();
if parts.len() == 1 {
Ok((parts[0], None))
} else if parts.len() == 2 {
Ok((parts[0], Some(parts[1])))
// Try to find a single slash
if let Some(sep) = cidr.find('/') {
let (ip, prefix) = cidr.split_at(sep);
// Error if cidr has multiple slashes
if prefix[1..].find('/').is_some() {
return Err(IpNetworkError::InvalidCidrFormat(format!(
"CIDR must contain a single '/': {}",
cidr
)));
} else {
// Handle the case when cidr has exactly one slash
return Ok((ip, Some(&prefix[1..])));
}
} else {
Err(IpNetworkError::InvalidCidrFormat(format!(
"CIDR must contain a single '/': {}",
cidr
)))
// Handle the case when cidr does not have a slash
return Ok((cidr, None));
}
}

View File

@ -1,10 +1,5 @@
use std::fmt;
use std::net::Ipv4Addr;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use common::{cidr_parts, parse_prefix, IpNetworkError};
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{fmt, net::Ipv4Addr, str::FromStr};
const IPV4_BITS: u8 = 32;
@ -15,20 +10,22 @@ pub struct Ipv4Network {
prefix: u8,
}
impl<'de> Deserialize<'de> for Ipv4Network {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv4Network {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Ipv4Network::from_str(s).map_err(de::Error::custom)
let s = <String>::deserialize(deserializer)?;
Ipv4Network::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for Ipv4Network {
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv4Network {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -36,6 +33,7 @@ impl Serialize for Ipv4Network {
impl Ipv4Network {
/// Constructs a new `Ipv4Network` from any `Ipv4Addr` and a prefix denoting the network size.
///
/// If the prefix is larger than 32 this will return an `IpNetworkError::InvalidPrefix`.
pub fn new(addr: Ipv4Addr, prefix: u8) -> Result<Ipv4Network, IpNetworkError> {
if prefix > IPV4_BITS {
@ -45,13 +43,31 @@ impl Ipv4Network {
}
}
/// Constructs a new `Ipv4Network` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(
netaddr: Ipv4Addr,
netmask: Ipv4Addr,
) -> Result<Ipv4Network, IpNetworkError> {
let prefix = ipv4_mask_to_prefix(netmask)?;
let net = Self {
addr: netaddr,
prefix,
};
Ok(net)
}
/// Returns an iterator over `Ipv4Network`. Each call to `next` will return the next
/// `Ipv4Addr` in the given network. `None` will be returned when there are no more
/// addresses.
pub fn iter(&self) -> Ipv4NetworkIterator {
let start = u32::from(self.network());
let end = start + self.size();
Ipv4NetworkIterator { next: start, end }
let end = start + (self.size() - 1);
Ipv4NetworkIterator {
next: Some(start),
end,
}
}
pub fn ip(&self) -> Ipv4Addr {
@ -62,6 +78,23 @@ impl Ipv4Network {
self.prefix
}
/// Checks if the given `Ipv4Network` is a subnet of the other.
pub fn is_subnet_of(self, other: Ipv4Network) -> bool {
other.ip() <= self.ip() && other.broadcast() >= self.broadcast()
}
/// Checks if the given `Ipv4Network` is a supernet of the other.
pub fn is_supernet_of(self, other: Ipv4Network) -> bool {
other.is_subnet_of(self)
}
/// Checks if the given `Ipv4Network` is partly contained in other.
pub fn overlaps(self, other: Ipv4Network) -> bool {
other.contains(self.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
}
/// Returns the mask for this `Ipv4Network`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
@ -131,8 +164,8 @@ impl Ipv4Network {
/// assert!(!net.contains(Ipv4Addr::new(127, 0, 1, 70)));
/// ```
pub fn contains(&self, ip: Ipv4Addr) -> bool {
let net = u32::from(self.network());
let mask = u32::from(self.mask());
let mask = !(0xffff_ffff as u64 >> self.prefix) as u32;
let net = u32::from(self.addr) & mask;
(u32::from(ip) & mask) == net
}
@ -183,7 +216,7 @@ impl Ipv4Network {
}
impl fmt::Display for Ipv4Network {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}/{}", self.ip(), self.prefix())
}
}
@ -208,7 +241,13 @@ impl FromStr for Ipv4Network {
let addr = Ipv4Addr::from_str(addr_str)
.map_err(|_| IpNetworkError::InvalidAddr(addr_str.to_string()))?;
let prefix = match prefix_str {
Some(v) => parse_prefix(v, IPV4_BITS)?,
Some(v) => {
if let Ok(netmask) = Ipv4Addr::from_str(v) {
ipv4_mask_to_prefix(netmask)?
} else {
parse_prefix(v, IPV4_BITS)?
}
}
None => IPV4_BITS,
};
Ipv4Network::new(addr, prefix)
@ -224,8 +263,9 @@ impl From<Ipv4Addr> for Ipv4Network {
}
}
#[derive(Clone, Debug)]
pub struct Ipv4NetworkIterator {
next: u32,
next: Option<u32>,
end: u32,
}
@ -233,17 +273,26 @@ impl Iterator for Ipv4NetworkIterator {
type Item = Ipv4Addr;
fn next(&mut self) -> Option<Ipv4Addr> {
if self.next < self.end {
let next = Ipv4Addr::from(self.next as u32);
self.next += 1;
Some(next)
} else {
let next = self.next?;
self.next = if next == self.end {
None
}
} else {
Some(next + 1)
};
Some(next.into())
}
}
impl IntoIterator for &'_ Ipv4Network {
type IntoIter = Ipv4NetworkIterator;
type Item = Ipv4Addr;
fn into_iter(self) -> Ipv4NetworkIterator {
self.iter()
}
}
/// Converts a `Ipv4Addr` network mask into a prefix.
///
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv4_mask_to_prefix(mask: Ipv4Addr) -> Result<u8, IpNetworkError> {
let mask = u32::from(mask);
@ -429,6 +478,22 @@ mod test {
assert_eq!(prefix, 25);
}
/// Parse netmask as well as prefix
#[test]
fn parse_netmask() {
let from_netmask: Ipv4Network = "192.168.1.0/255.255.255.0".parse().unwrap();
let from_prefix: Ipv4Network = "192.168.1.0/24".parse().unwrap();
assert_eq!(from_netmask, from_prefix);
}
#[test]
fn parse_netmask_broken_v4() {
assert_eq!(
"192.168.1.0/255.0.255.0".parse::<Ipv4Network>(),
Err(IpNetworkError::InvalidPrefix)
);
}
#[test]
fn invalid_v4_mask_to_prefix() {
let mask = Ipv4Addr::new(255, 0, 255, 0);
@ -436,6 +501,24 @@ mod test {
assert!(prefix.is_err());
}
#[test]
fn ipv4network_with_netmask() {
{
// Positive test-case.
let addr = Ipv4Addr::new(127, 0, 0, 1);
let mask = Ipv4Addr::new(255, 0, 0, 0);
let net = Ipv4Network::with_netmask(addr, mask).unwrap();
let expected = Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 8).unwrap();
assert_eq!(net, expected);
}
{
// Negative test-case.
let addr = Ipv4Addr::new(127, 0, 0, 1);
let mask = Ipv4Addr::new(255, 0, 255, 0);
Ipv4Network::with_netmask(addr, mask).unwrap_err();
}
}
#[test]
fn ipv4network_from_ipv4addr() {
let net = Ipv4Network::from(Ipv4Addr::new(127, 0, 0, 1));
@ -454,4 +537,125 @@ mod test {
fn assert_sync<T: Sync>() {}
assert_sync::<Ipv4Network>();
}
// Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py
#[test]
fn test_is_subnet_of() {
let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new();
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.0.0/24".parse().unwrap(),
),
true,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.1.0/24".parse().unwrap(),
"10.0.0.0/30".parse().unwrap(),
),
false,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_is_supernet_of() {
let mut test_cases: HashMap<(Ipv4Network, Ipv4Network), bool> = HashMap::new();
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.0.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/30".parse().unwrap(),
"10.0.1.0/24".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"10.0.0.0/24".parse().unwrap(),
"10.0.0.0/30".parse().unwrap(),
),
true,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_overlaps() {
let other: Ipv4Network = "1.2.3.0/30".parse().unwrap();
let other2: Ipv4Network = "1.2.2.0/24".parse().unwrap();
let other3: Ipv4Network = "1.2.2.64/26".parse().unwrap();
let skynet: Ipv4Network = "1.2.3.0/24".parse().unwrap();
assert_eq!(skynet.overlaps(other), true);
assert_eq!(skynet.overlaps(other2), false);
assert_eq!(other2.overlaps(other3), true);
}
#[test]
fn edges() {
let low: Ipv4Network = "0.0.0.0/24".parse().unwrap();
let low_addrs: Vec<Ipv4Addr> = low.iter().collect();
assert_eq!(256, low_addrs.len());
assert_eq!("0.0.0.0".parse::<Ipv4Addr>().unwrap(), low_addrs[0]);
assert_eq!("0.0.0.255".parse::<Ipv4Addr>().unwrap(), low_addrs[255]);
let high: Ipv4Network = "255.255.255.0/24".parse().unwrap();
let high_addrs: Vec<Ipv4Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
assert_eq!("255.255.255.0".parse::<Ipv4Addr>().unwrap(), high_addrs[0]);
assert_eq!(
"255.255.255.255".parse::<Ipv4Addr>().unwrap(),
high_addrs[255]
);
}
}

View File

@ -1,11 +1,5 @@
use std::cmp;
use std::fmt;
use std::net::Ipv6Addr;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use common::{cidr_parts, parse_prefix, IpNetworkError};
use crate::common::{cidr_parts, parse_prefix, IpNetworkError};
use std::{cmp, fmt, net::Ipv6Addr, str::FromStr};
const IPV6_BITS: u8 = 128;
const IPV6_SEGMENT_BITS: u8 = 16;
@ -17,20 +11,22 @@ pub struct Ipv6Network {
prefix: u8,
}
impl<'de> Deserialize<'de> for Ipv6Network {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Ipv6Network {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Ipv6Network::from_str(s).map_err(de::Error::custom)
let s = <String>::deserialize(deserializer)?;
Ipv6Network::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for Ipv6Network {
#[cfg(feature = "serde")]
impl serde::Serialize for Ipv6Network {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -38,6 +34,7 @@ impl Serialize for Ipv6Network {
impl Ipv6Network {
/// Constructs a new `Ipv6Network` from any `Ipv6Addr` and a prefix denoting the network size.
///
/// If the prefix is larger than 128 this will return an `IpNetworkError::InvalidPrefix`.
pub fn new(addr: Ipv6Addr, prefix: u8) -> Result<Ipv6Network, IpNetworkError> {
if prefix > IPV6_BITS {
@ -47,6 +44,18 @@ impl Ipv6Network {
}
}
/// Constructs a new `Ipv6Network` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(netaddr: Ipv6Addr, netmask: Ipv6Addr) -> Result<Self, IpNetworkError> {
let prefix = ipv6_mask_to_prefix(netmask)?;
let net = Self {
addr: netaddr,
prefix,
};
Ok(net)
}
/// Returns an iterator over `Ipv6Network`. Each call to `next` will return the next
/// `Ipv6Addr` in the given network. `None` will be returned when there are no more
/// addresses.
@ -62,7 +71,7 @@ impl Ipv6Network {
let end: u128 = dec | mask;
Ipv6NetworkIterator {
next: start,
next: Some(start),
end: end,
}
}
@ -111,6 +120,23 @@ impl Ipv6Network {
self.prefix
}
/// Checks if the given `Ipv6Network` is a subnet of the other.
pub fn is_subnet_of(self, other: Ipv6Network) -> bool {
other.ip() <= self.ip() && other.broadcast() >= self.broadcast()
}
/// Checks if the given `Ipv6Network` is a supernet of the other.
pub fn is_supernet_of(self, other: Ipv6Network) -> bool {
other.is_subnet_of(self)
}
/// Checks if the given `Ipv6Network` is partly contained in other.
pub fn overlaps(self, other: Ipv6Network) -> bool {
other.contains(self.ip())
|| (other.contains(self.broadcast())
|| (self.contains(other.ip()) || (self.contains(other.broadcast()))))
}
/// Returns the mask for this `Ipv6Network`.
/// That means the `prefix` most significant bits will be 1 and the rest 0
///
@ -202,8 +228,9 @@ impl From<Ipv6Addr> for Ipv6Network {
}
}
#[derive(Clone, Debug)]
pub struct Ipv6NetworkIterator {
next: u128,
next: Option<u128>,
end: u128,
}
@ -211,18 +238,26 @@ impl Iterator for Ipv6NetworkIterator {
type Item = Ipv6Addr;
fn next(&mut self) -> Option<Ipv6Addr> {
if self.next <= self.end {
let next = Ipv6Addr::from(self.next);
self.next += 1;
Some(next)
} else {
let next = self.next?;
self.next = if next == self.end {
None
}
} else {
Some(next + 1)
};
Some(next.into())
}
}
impl IntoIterator for &'_ Ipv6Network {
type IntoIter = Ipv6NetworkIterator;
type Item = Ipv6Addr;
fn into_iter(self) -> Ipv6NetworkIterator {
self.iter()
}
}
impl fmt::Display for Ipv6Network {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}/{}", self.ip(), self.prefix())
}
}
@ -231,7 +266,7 @@ impl fmt::Display for Ipv6Network {
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
let mask = mask.segments();
let mut mask_iter = mask.into_iter();
let mut mask_iter = mask.iter();
// Count the number of set bits from the start of the address
let mut prefix = 0;
@ -265,6 +300,7 @@ pub fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8, IpNetworkError> {
#[cfg(test)]
mod test {
use super::*;
use std::collections::HashMap;
use std::net::Ipv6Addr;
#[test]
@ -273,6 +309,14 @@ mod test {
assert_eq!(cidr.prefix(), 24);
}
#[test]
fn parse_netmask_broken_v6() {
assert_eq!(
"FF01:0:0:17:0:0:0:2/255.255.255.0".parse::<Ipv6Network>(),
Err(IpNetworkError::InvalidPrefix)
);
}
#[test]
fn create_v6_invalid_prefix() {
let cidr = Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 129);
@ -353,6 +397,25 @@ mod test {
assert!(prefix.is_err());
}
#[test]
fn ipv6network_with_netmask() {
{
// Positive test-case.
let addr = Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2);
let mask = Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0, 0, 0, 0, 0);
let net = Ipv6Network::with_netmask(addr, mask).unwrap();
let expected =
Ipv6Network::new(Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2), 48).unwrap();
assert_eq!(net, expected);
}
{
// Negative test-case.
let addr = Ipv6Addr::new(0xff01, 0, 0, 0x17, 0, 0, 0, 0x2);
let mask = Ipv6Addr::new(0, 0, 0xffff, 0xffff, 0, 0, 0, 0);
Ipv6Network::with_netmask(addr, mask).unwrap_err();
}
}
#[test]
fn iterator_v6() {
let cidr: Ipv6Network = "2001:db8::/126".parse().unwrap();
@ -436,4 +499,116 @@ mod test {
fn assert_sync<T: Sync>() {}
assert_sync::<Ipv6Network>();
}
// Tests from cpython https://github.com/python/cpython/blob/e9bc4172d18db9c182d8e04dd7b033097a994c06/Lib/test/test_ipaddress.py
#[test]
fn test_is_subnet_of() {
let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new();
test_cases.insert(
(
"2000:999::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
true,
);
test_cases.insert(
(
"2000:bbb::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/48".parse().unwrap(),
"2000:aaa::/56".parse().unwrap(),
),
false,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_subnet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_is_supernet_of() {
let mut test_cases: HashMap<(Ipv6Network, Ipv6Network), bool> = HashMap::new();
test_cases.insert(
(
"2000:999::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:bbb::/56".parse().unwrap(),
"2000:aaa::/48".parse().unwrap(),
),
false,
);
test_cases.insert(
(
"2000:aaa::/48".parse().unwrap(),
"2000:aaa::/56".parse().unwrap(),
),
true,
);
for (key, val) in test_cases.iter() {
let (src, dest) = (key.0, key.1);
assert_eq!(
src.is_supernet_of(dest),
*val,
"testing with {} and {}",
src,
dest
);
}
}
#[test]
fn test_overlaps() {
let other: Ipv6Network = "2001:DB8:ACAD::1/64".parse().unwrap();
let other2: Ipv6Network = "2001:DB8:ACAD::20:2/64".parse().unwrap();
assert_eq!(other2.overlaps(other), true);
}
#[test]
fn edges() {
let low: Ipv6Network = "::0/120".parse().unwrap();
let low_addrs: Vec<Ipv6Addr> = low.iter().collect();
assert_eq!(256, low_addrs.len());
let high: Ipv6Network = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120"
.parse()
.unwrap();
let high_addrs: Vec<Ipv6Addr> = high.iter().collect();
assert_eq!(256, high_addrs.len());
}
}

View File

@ -1,27 +1,21 @@
//! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in
//! Rust. Implementation for IPv4 is more or less stable, IPv6 implementation
//! is still WIP.
//! Rust.
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]
#![crate_type = "lib"]
#![doc(html_root_url = "https://docs.rs/ipnetwork/0.14.0")]
#![doc(html_root_url = "https://docs.rs/ipnetwork/0.16.0")]
extern crate serde;
use std::fmt;
use std::net::IpAddr;
use std::{fmt, net::IpAddr, str::FromStr};
mod common;
mod ipv4;
mod ipv6;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
pub use common::IpNetworkError;
pub use ipv4::{ipv4_mask_to_prefix, Ipv4Network};
pub use ipv6::{ipv6_mask_to_prefix, Ipv6Network};
pub use crate::common::IpNetworkError;
use crate::ipv4::Ipv4NetworkIterator;
pub use crate::ipv4::{ipv4_mask_to_prefix, Ipv4Network};
use crate::ipv6::Ipv6NetworkIterator;
pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network};
/// Represents a generic network range. This type can have two variants:
/// the v4 and the v6 case.
@ -31,20 +25,22 @@ pub enum IpNetwork {
V6(Ipv6Network),
}
impl<'de> Deserialize<'de> for IpNetwork {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for IpNetwork {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
IpNetwork::from_str(&s).map_err(de::Error::custom)
IpNetwork::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for IpNetwork {
#[cfg(feature = "serde")]
impl serde::Serialize for IpNetwork {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -69,6 +65,14 @@ impl IpNetwork {
}
}
/// Constructs a new `IpNetwork` from a network address and a network mask.
///
/// If the netmask is not valid this will return an `IpNetworkError::InvalidPrefix`.
pub fn with_netmask(netaddr: IpAddr, netmask: IpAddr) -> Result<Self, IpNetworkError> {
let prefix = ip_mask_to_prefix(netmask)?;
Self::new(netaddr, prefix)
}
/// Returns the IP part of a given `IpNetwork`
pub fn ip(&self) -> IpAddr {
match *self {
@ -200,6 +204,10 @@ impl IpNetwork {
}
}
// TODO(abhishek) when TryFrom is stable, implement it for IpNetwork to
// variant conversions. Then use that to implement a generic is_subnet_of
// is_supernet_of, overlaps
/// Checks if a given `IpAddr` is in this `IpNetwork`
///
/// # Examples
@ -241,6 +249,17 @@ impl IpNetwork {
IpNetwork::V6(ref ip) => NetworkSize::V6(ip.size()),
}
}
/// Returns an iterator over the addresses contained in the network.
///
/// This lists all the addresses in the network range, in ascending order.
pub fn iter(&self) -> IpNetworkIterator {
let inner = match self {
IpNetwork::V4(ip) => IpNetworkIteratorInner::V4(ip.iter()),
IpNetwork::V6(ip) => IpNetworkIteratorInner::V6(ip.iter()),
};
IpNetworkIterator { inner }
}
}
/// Tries to parse the given string into a `IpNetwork`. Will first try to parse
@ -292,7 +311,7 @@ impl From<IpAddr> for IpNetwork {
}
impl fmt::Display for IpNetwork {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
IpNetwork::V4(net) => net.fmt(f),
IpNetwork::V6(net) => net.fmt(f),
@ -300,6 +319,34 @@ impl fmt::Display for IpNetwork {
}
}
#[derive(Clone, Debug)]
enum IpNetworkIteratorInner {
V4(Ipv4NetworkIterator),
V6(Ipv6NetworkIterator),
}
pub struct IpNetworkIterator {
inner: IpNetworkIteratorInner,
}
impl Iterator for IpNetworkIterator {
type Item = IpAddr;
fn next(&mut self) -> Option<IpAddr> {
match &mut self.inner {
IpNetworkIteratorInner::V4(iter) => iter.next().map(IpAddr::V4),
IpNetworkIteratorInner::V6(iter) => iter.next().map(IpAddr::V6),
}
}
}
impl IntoIterator for &'_ IpNetwork {
type IntoIter = IpNetworkIterator;
type Item = IpAddr;
fn into_iter(self) -> IpNetworkIterator {
self.iter()
}
}
/// Converts a `IpAddr` network mask into a prefix.
/// If the mask is invalid this will return an `IpNetworkError::InvalidPrefix`.
pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
@ -312,11 +359,13 @@ pub fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, IpNetworkError> {
#[cfg(test)]
mod test {
#[test]
#[cfg(feature = "serde")]
fn deserialize_from_serde_json_value() {
use super::*;
let network = IpNetwork::from_str("0.0.0.0/0").unwrap();
let val: serde_json::value::Value = serde_json::from_str(&serde_json::to_string(&network).unwrap()).unwrap();
let _deser: IpNetwork =
serde_json::from_value(val).expect("Fails to deserialize from json_value::value::Value");
let val: serde_json::value::Value =
serde_json::from_str(&serde_json::to_string(&network).unwrap()).unwrap();
let _deser: IpNetwork = serde_json::from_value(val)
.expect("Fails to deserialize from json_value::value::Value");
}
}

View File

@ -1,16 +1,9 @@
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate ipnetwork;
#![cfg(feature = "serde")]
#[cfg(test)]
mod tests {
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use serde_derive::{Deserialize, Serialize};
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]