More refactoring, add tests, aggregate6 compatibility

This commit is contained in:
2023-03-18 21:37:13 -07:00
parent d8b48aba9a
commit 8034822ec8
19 changed files with 1770617 additions and 63 deletions
Generated
+513 -7
View File
@@ -2,12 +2,81 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "anstyle"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
[[package]]
name = "assert_cmd"
version = "2.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0b2340f55d9661d76793b2bfc2eb0e62689bd79d067a95707ea762afd5e9dd"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "assert_fs"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9d5bf7e5441c6393b5a9670a5036abe6b4847612f594b870f7332dbf10cf6fa"
dependencies = [
"anstyle",
"doc-comment",
"globwalk",
"predicates",
"predicates-core",
"predicates-tree",
"tempfile",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5dd14596c0e5b954530d0e6f1fd99b89c03e313aa2086e8da4303701a09e1cf"
[[package]]
name = "bstr"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
dependencies = [
"memchr",
"once_cell",
"regex-automata",
"serde",
]
[[package]]
name = "cc"
version = "1.0.79"
@@ -15,12 +84,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "clap"
version = "4.1.10"
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce38afc168d8665cfc75c7b1dd9672e50716a137f433f070991619744a67342a"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098"
dependencies = [
"bitflags",
"bitflags 2.0.1",
"clap_derive",
"clap_lex",
"is-terminal",
@@ -62,6 +137,24 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.2.8"
@@ -83,6 +176,155 @@ dependencies = [
"libc",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
dependencies = [
"num-traits",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "futures"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
[[package]]
name = "futures-executor"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91"
[[package]]
name = "futures-macro"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
[[package]]
name = "futures-task"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags 1.3.2",
"ignore",
"walkdir",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -95,6 +337,32 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "ignore"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
dependencies = [
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.8"
@@ -133,6 +401,21 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.140"
@@ -145,6 +428,36 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@@ -157,6 +470,49 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "predicates"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba7d6ead3e3966038f68caa9fc1f860185d95a793180bbcfe0d0da47b3961ed"
dependencies = [
"anstyle",
"difflib",
"float-cmp",
"itertools",
"normalize-line-endings",
"predicates-core",
"regex",
]
[[package]]
name = "predicates-core"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
[[package]]
name = "predicates-tree"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -199,23 +555,95 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rs-aggregate"
version = "0.2.0"
dependencies = [
"assert_cmd",
"assert_fs",
"clap",
"clio",
"glob",
"ipnet",
"iprange",
"predicates",
"rstest",
]
[[package]]
name = "rstest"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
dependencies = [
"futures",
"futures-timer",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"rustc_version",
"syn",
"unicode-ident",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.36.9"
version = "0.36.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@@ -223,6 +651,36 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.157"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca"
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "strsim"
version = "0.10.0"
@@ -240,6 +698,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.42.0",
]
[[package]]
name = "termcolor"
version = "1.2.0"
@@ -249,6 +720,22 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "termtree"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
@@ -261,6 +748,25 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "winapi"
version = "0.3.9"
+14 -3
View File
@@ -1,9 +1,13 @@
[package]
name = "rs-aggregate"
version = "0.2.0"
authors = ["Keenan Tims <ktims@gotroot.ca>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
description = "Aggregate a list of IP prefixes into their minimum equivalent representation"
readme = "README.md"
repository = "https://github.com/ktims/rs-aggregate"
license = "MIT"
categories = ["network-programming"]
[dependencies]
clap = { version = "4.1.8", features = ["derive"] }
@@ -11,5 +15,12 @@ clio = { version = "0.2.7", features = ["clap-parse"] }
ipnet = "2.7.1"
iprange = "0.6.7"
[dev-dependencies]
assert_cmd = "2.0.10"
assert_fs = "1.0.12"
predicates = "3.0.1"
rstest = "0.16.0"
glob = "0.3.1"
[[bin]]
name = "rs-aggregate"
name = "rs-aggregate"
+20 -2
View File
@@ -1,6 +1,24 @@
# rs-aggregate
rs-aggregate will aggregate an unsorted list of IP prefixes
Intended to be [aggregate6](https://github.com/job/aggregate6) with better performance.
Intended to be a drop-in replacement for [aggregate6](https://github.com/job/aggregate6) with better performance.
Takes a list of whitespace-separated IPs or IP networks and aggregates them to their minimal representation.
Takes a list of whitespace-separated IPs or IP networks and aggregates them to their minimal representation.
## Known discrepancies with `aggregate6`
* `rs-aggregate` accepts subnet and wilcard mask formats in addition to CIDR, ie all these are valid and equivalent:
* `1.1.1.0/255.255.255.0`
* `1.1.1.0/0.0.0.255`
* `1.1.1.0/24`
* `-m/--max-prefixlen` supports different maximums for each address family as ipv4,ipv6 format
## Performance
Performance comparison of `rs-aggregate` vs `aggregate6`. A speedup of >100x is achieved on DFZ data.
Full DFZ (1154968 total, 202729 aggregates):
![dfz perf comparison](perfdata/perfcomp_all.png)
IPv4 DFZ (968520 total, 154061 aggregates):
![ipv4 dfz perf comparison](perfdata/perfcomp_v4.png)
+297 -22
View File
@@ -1,7 +1,7 @@
use std::{
error::Error,
fmt::Display,
net::{IpAddr, Ipv4Addr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
@@ -19,21 +19,23 @@ impl IpBothRange {
IpBothRange::default()
}
pub fn add(&mut self, net: IpOrNet) {
match net {
IpOrNet::IpNet(net) => match net {
IpNet::V4(v4_net) => drop(self.v4.add(v4_net)),
IpNet::V6(v6_net) => drop(self.v6.add(v6_net)),
},
IpOrNet::IpAddr(addr) => match addr {
IpAddr::V4(v4_addr) => drop(self.v4.add(v4_addr.into())),
IpAddr::V6(v6_addr) => drop(self.v6.add(v6_addr.into())),
},
match net.net {
IpNet::V4(v4_net) => drop(self.v4.add(v4_net)),
IpNet::V6(v6_net) => drop(self.v6.add(v6_net)),
}
}
pub fn simplify(&mut self) {
self.v4.simplify();
self.v6.simplify();
}
pub fn v4_iter(&self) -> IpRangeIter<Ipv4Net> {
self.v4.iter()
}
pub fn v6_iter(&self) -> IpRangeIter<Ipv6Net> {
self.v6.iter()
}
}
pub struct IpBothRangeIter<'a> {
@@ -76,9 +78,9 @@ impl<'a> IntoIterator for &'a IpBothRange {
}
}
pub enum IpOrNet {
IpNet(IpNet),
IpAddr(IpAddr),
#[derive(Debug, PartialEq)]
pub struct IpOrNet {
pub net: IpNet,
}
#[derive(Debug, Clone)]
@@ -145,13 +147,13 @@ impl IpOrNet {
}
}
pub fn prefix_len(&self) -> u8 {
match self {
Self::IpNet(net) => net.prefix_len(),
Self::IpAddr(addr) => match addr {
IpAddr::V4(_) => 32,
IpAddr::V6(_) => 128,
},
}
self.net.prefix_len()
}
pub fn is_ipv4(&self) -> bool {
self.net.network().is_ipv4()
}
pub fn is_ipv6(&self) -> bool {
self.net.network().is_ipv6()
}
}
@@ -166,14 +168,287 @@ impl FromStr for IpOrNet {
}
}
impl Display for IpOrNet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.net.fmt(f)
}
}
impl From<IpNet> for IpOrNet {
fn from(net: IpNet) -> Self {
IpOrNet::IpNet(net)
IpOrNet { net }
}
}
impl From<IpAddr> for IpOrNet {
fn from(addr: IpAddr) -> Self {
IpOrNet::IpAddr(addr)
IpOrNet { net: addr.into() }
}
}
impl From<Ipv4Net> for IpOrNet {
fn from(net: Ipv4Net) -> Self {
IpOrNet { net: net.into() }
}
}
impl From<Ipv6Net> for IpOrNet {
fn from(net: Ipv6Net) -> Self {
IpOrNet { net: net.into() }
}
}
impl From<Ipv4Addr> for IpOrNet {
fn from(addr: Ipv4Addr) -> Self {
IpOrNet {
net: IpAddr::from(addr).into(),
}
}
}
impl From<Ipv6Addr> for IpOrNet {
fn from(addr: Ipv6Addr) -> Self {
IpOrNet {
net: IpAddr::from(addr).into(),
}
}
}
#[derive(Clone, Debug)]
pub struct PrefixlenPair {
pub v4: u8,
pub v6: u8,
}
impl Default for PrefixlenPair {
fn default() -> Self {
PrefixlenPair { v4: 32, v6: 128 }
}
}
impl Display for PrefixlenPair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(format!("{},{}", self.v4, self.v6).as_str())
}
}
impl PartialEq<IpOrNet> for PrefixlenPair {
fn eq(&self, other: &IpOrNet) -> bool {
match other.net {
IpNet::V4(net) => self.v4 == net.prefix_len(),
IpNet::V6(net) => self.v6 == net.prefix_len(),
}
}
}
impl PartialEq<PrefixlenPair> for PrefixlenPair {
fn eq(&self, other: &PrefixlenPair) -> bool {
self.v4 == other.v4 && self.v6 == other.v6
}
}
impl PartialOrd<IpOrNet> for PrefixlenPair {
fn ge(&self, other: &IpOrNet) -> bool {
match other.net {
IpNet::V4(net) => self.v4 >= net.prefix_len(),
IpNet::V6(net) => self.v6 >= net.prefix_len(),
}
}
fn gt(&self, other: &IpOrNet) -> bool {
match other.net {
IpNet::V4(net) => self.v4 > net.prefix_len(),
IpNet::V6(net) => self.v6 > net.prefix_len(),
}
}
fn le(&self, other: &IpOrNet) -> bool {
match other.net {
IpNet::V4(net) => self.v4 <= net.prefix_len(),
IpNet::V6(net) => self.v6 <= net.prefix_len(),
}
}
fn lt(&self, other: &IpOrNet) -> bool {
match other.net {
IpNet::V4(net) => self.v4 < net.prefix_len(),
IpNet::V6(net) => self.v6 < net.prefix_len(),
}
}
fn partial_cmp(&self, other: &IpOrNet) -> Option<std::cmp::Ordering> {
match other.net {
IpNet::V4(net) => self.v4.partial_cmp(&net.prefix_len()),
IpNet::V6(net) => self.v6.partial_cmp(&net.prefix_len()),
}
}
}
#[derive(Debug)]
pub struct ParsePrefixlenError {
msg: String,
}
impl Display for ParsePrefixlenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.msg.as_str())
}
}
impl std::error::Error for ParsePrefixlenError {}
impl FromStr for PrefixlenPair {
type Err = ParsePrefixlenError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.split_once(',') {
Some(pair) => {
let v4 = u8::from_str(pair.0).or(Err(ParsePrefixlenError {
msg: "Unable to parse integer".to_owned(),
}))?;
let v6 = u8::from_str(pair.1).or(Err(ParsePrefixlenError {
msg: "Unable to parse integer".to_owned(),
}))?;
if v4 > 32 || v6 > 128 {
return Err(ParsePrefixlenError {
msg: "Invalid prefix length".to_owned(),
});
}
Ok(PrefixlenPair { v4, v6 })
}
None => {
let len = u8::from_str(s).or(Err(ParsePrefixlenError {
msg: "Unable to parse integer".to_owned(),
}))?;
if len > 128 {
return Err(ParsePrefixlenError {
msg: "Invalid prefix length".to_owned(),
});
}
Ok(PrefixlenPair { v4: len, v6: len })
}
}
}
}
#[cfg(test)]
mod tests {
use core::panic;
use std::net::Ipv6Addr;
const TEST_V4_ADDR: Ipv4Addr = Ipv4Addr::new(198, 51, 100, 123);
const TEST_V6_ADDR: Ipv6Addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0x23ab, 0xf007);
const TEST_V4_NET: Ipv4Net = match Ipv4Net::new(Ipv4Addr::new(192, 0, 2, 0), 24) {
Ok(net) => net,
Err(_) => panic!("Couldn't unwrap test vector"),
};
const TEST_V6_NET: Ipv6Net =
match Ipv6Net::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0x23ab, 0), 64) {
Ok(net) => net,
Err(_) => panic!("Couldn't unwrap test vector"),
};
const TEST_V4_ALLNET: Ipv4Net = match Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0) {
Ok(net) => net,
Err(_) => panic!("Couldn't unwrap test vector"),
};
const TEST_V6_ALLNET: Ipv6Net = match Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0) {
Ok(net) => net,
Err(_) => panic!("Couldn't unwrap test vector"),
};
use super::*;
#[test]
fn parse_bare_v4() {
let ip: IpOrNet = "198.51.100.123".parse().unwrap();
assert_eq!(ip, TEST_V4_ADDR.into());
}
#[test]
fn parse_bare_v6() {
let ip: IpOrNet = "2001:db8::23ab:f007".parse().unwrap();
assert_eq!(ip, TEST_V6_ADDR.into());
}
#[test]
fn parse_cidr_v4() {
let net: IpOrNet = "192.0.2.0/24".parse().unwrap();
assert_eq!(net, TEST_V4_NET.into());
}
#[test]
fn parse_cidr_v4_min() {
let net: IpOrNet = "0.0.0.0/0".parse().unwrap();
assert_eq!(net, TEST_V4_ALLNET.into());
}
#[test]
fn parse_cidr_v4_max() {
let net: IpOrNet = "198.51.100.123/32".parse().unwrap();
assert_eq!(net, TEST_V4_ADDR.into());
}
#[test]
fn parse_cidr_v6() {
let net: IpOrNet = "2001:db8::23ab:0/64".parse().unwrap();
assert_eq!(net, TEST_V6_NET.into());
}
#[test]
fn parse_cidr_v6_min() {
let net: IpOrNet = "::/0".parse().unwrap();
assert_eq!(net, TEST_V6_ALLNET.into());
}
#[test]
fn parse_netmask_v4() {
let net: IpOrNet = "192.0.2.0/255.255.255.0".parse().unwrap();
assert_eq!(net, TEST_V4_NET.into());
}
#[test]
fn parse_wildmask_v4() {
let net: IpOrNet = "192.0.2.0/0.0.0.255".parse().unwrap();
assert_eq!(net, TEST_V4_NET.into());
}
#[test]
#[should_panic]
fn reject_v4_mask_v6() {
let _net: IpOrNet = "2001:db8::23ab:0/255.255.255.0".parse().unwrap();
}
#[test]
#[should_panic]
fn reject_v6_mask_v6() {
let _net: IpOrNet = "2001:db8::23ab:0/ffff:ffff:ffff:ffff:ffff:ffff:ffff:0"
.parse()
.unwrap();
}
#[test]
#[should_panic]
fn reject_v4_invalid_pfxlen() {
let _net: IpOrNet = "192.0.2.0/33".parse().unwrap();
}
#[test]
#[should_panic]
fn reject_v6_invalid_pfxlen() {
let _net: IpOrNet = "2001:db8::32ab:0/129".parse().unwrap();
}
#[test]
fn parse_single_prefixlen() {
let pfxlen: PrefixlenPair = "20".parse().unwrap();
assert_eq!(pfxlen, PrefixlenPair { v4: 20, v6: 20 });
}
#[test]
fn parse_pair_prefixlen() {
let pfxlen: PrefixlenPair = "20,32".parse().unwrap();
assert_eq!(pfxlen, PrefixlenPair { v4: 20, v6: 32 });
}
#[test]
#[should_panic]
fn reject_single_prefixlen_invalid() {
let _pfxlen: PrefixlenPair = "129".parse().unwrap();
}
#[test]
#[should_panic]
fn reject_pair_prefixlen_invalid_v4() {
let _pfxlen: PrefixlenPair = "33,32".parse().unwrap();
}
#[test]
#[should_panic]
fn reject_pair_prefixlen_invalid_v6() {
let _pfxlen: PrefixlenPair = "32,129".parse().unwrap();
}
#[test]
#[should_panic]
fn reject_single_prefixlen_negative() {
let _pfxlen: PrefixlenPair = "-32".parse().unwrap();
}
}
+47 -29
View File
@@ -2,7 +2,7 @@ extern crate ipnet;
extern crate iprange;
mod iputils;
use iputils::{IpBothRange, IpOrNet};
use iputils::{IpBothRange, IpOrNet, PrefixlenPair};
use clio::*;
use std::io::BufRead;
@@ -13,27 +13,37 @@ use clap::Parser;
#[command(author, version, about, long_about=None)]
struct Args {
#[clap(value_parser, default_value = "-")]
input: Input,
#[arg(
input: Vec<Input>,
#[structopt(
short,
long,
default_value = "128",
help = "Sets the maximum prefix length for entries read. Longer prefixes will be discarded prior to processing."
default_value = "32,128",
help = "Maximum prefix length for prefixes read. Single value applies to IPv4 and IPv6, comma-separated [IPv4],[IPv6]."
)]
max_prefixlen: u8,
max_prefixlen: PrefixlenPair,
#[arg(short, long, help = "truncate IP/mask to network/mask (else ignore)")]
truncate: bool,
#[arg(id="4", short, help = "Only output IPv4 prefixes", conflicts_with("6"))]
#[arg(
id = "4",
short,
help = "Only output IPv4 prefixes",
conflicts_with("6")
)]
only_v4: bool,
#[arg(id="6", short, help = "Only output IPv6 prefixes", conflicts_with("4"))]
#[arg(
id = "6",
short,
help = "Only output IPv6 prefixes",
conflicts_with("4")
)]
only_v6: bool,
}
impl Default for Args {
fn default() -> Self {
Args {
input: clio::Input::default(),
max_prefixlen: 128,
input: Vec::from([clio::Input::default()]),
max_prefixlen: PrefixlenPair::default(),
truncate: false,
only_v4: false,
only_v6: false,
@@ -48,56 +58,64 @@ struct IpParseError {
problem: String,
}
type Errors = Vec<IpParseError>;
// type Errors = Vec<IpParseError>;
#[derive(Default)]
struct App {
args: Args,
prefixes: IpBothRange,
errors: Errors,
// errors: Errors,
}
impl App {
fn add_prefix(&mut self, pfx: IpOrNet) {
// Parser accepts host bits set, so detect that case and error if not truncate mode
// Note: aggregate6 errors in this case regardless of -4, -6 so do the same
if !self.args.truncate {
match pfx {
IpOrNet::IpNet(net) => {
if net.addr() != net.network() {
eprintln!("ERROR: '{}' is not a valid IP network, ignoring.", net);
return;
}
}
IpOrNet::IpAddr(_) => (),
if pfx.net.addr() != pfx.net.network() {
eprintln!("ERROR: '{}' is not a valid IP network, ignoring.", pfx);
return;
}
}
if pfx.prefix_len() <= self.args.max_prefixlen {
// Don't bother saving if we won't display.
if self.args.only_v4 && pfx.is_ipv6() {
return;
} else if self.args.only_v6 && pfx.is_ipv4() {
return;
}
if self.args.max_prefixlen >= pfx {
self.prefixes.add(pfx);
}
}
fn simplify_input(&mut self) {
for line in self.args.input.to_owned().lock().lines() {
fn consume_input(&mut self, input: &mut Input) {
for line in input.lock().lines() {
for net in line.unwrap().split_whitespace().to_owned() {
let pnet = net.parse::<IpOrNet>();
match pnet {
Ok(pnet) => self.add_prefix(pnet),
Err(e) => {
self.errors.push(IpParseError {
ip: net.to_string(),
problem: e.to_string(),
});
Err(_e) => {
// self.errors.push(IpParseError {
// ip: net.to_string(),
// problem: e.to_string(),
// });
eprintln!("ERROR: '{}' is not a valid IP network, ignoring.", net);
}
}
}
}
}
fn simplify_inputs(&mut self) {
let inputs = self.args.input.to_owned();
for mut input in inputs {
self.consume_input(&mut input);
}
self.prefixes.simplify();
}
fn main(&mut self) {
self.args = Args::parse();
self.simplify_input();
self.simplify_inputs();
for net in &self.prefixes {
println!("{}", net);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
../dfz_combined/input
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
../dfz_combined/input
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
../dfz_combined/input
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
../dfz_combined/input
+86
View File
@@ -0,0 +1,86 @@
use assert_cmd::Command;
use glob::glob;
use predicates::prelude::*; // Used for writing assertions
use rstest::*;
use std::{error::Error, fs::File, io::Read, path::Path};
// Really should normalize the data (lex sort) before comparison
#[rstest]
#[case("test-data/dfz_combined", "")] // Basic aggregation test
#[case("test-data/max_pfxlen", "-m 20")] // Filter on prefix length
#[case("test-data/max_pfxlen_split", "-m 20,32")] // Filter on prefix length (split v4/v6)
#[case("test-data/v4_only", "-4")] // Filter v4 only
#[case("test-data/v6_only", "-6")] // Filter v4 only
fn dfz_test(#[case] path: &str, #[case] args: &str) -> Result<(), Box<dyn Error>> {
let mut cmd = Command::cargo_bin("rs-aggregate")?;
let in_path = Path::new(path).join("input");
let expect_path = Path::new(path).join("expected");
let mut expect_file = File::open(expect_path)?;
let mut expect_data: Vec<u8> =
Vec::with_capacity(expect_file.metadata()?.len().try_into().unwrap());
expect_file.read_to_end(&mut expect_data)?;
let assert = cmd
.arg(in_path)
.args(args.split_whitespace())
.timeout(std::time::Duration::from_secs(30))
.assert();
assert
.success()
.stdout(predicate::eq(expect_data))
.stderr(predicate::str::is_empty());
Ok(())
}
#[rstest]
#[case("2001:db8::23ab:f007/64", "2001:db8::/64")]
#[case("198.51.100.123/24", "198.51.100.0/24")]
fn truncate_test(#[case] input: &str, #[case] expect: &str) -> Result<(), Box<dyn Error>> {
let mut cmd = Command::cargo_bin("rs-aggregate")?;
let assert = cmd.write_stdin(input).assert();
assert
.success()
.stdout(predicate::str::is_empty())
.stderr(predicate::eq(format!(
"ERROR: '{}' is not a valid IP network, ignoring.\n",
input
)));
let assert = cmd.arg("-t").write_stdin(input).assert();
assert
.success()
.stdout(predicate::eq(format!("{}\n", expect)))
.stderr(predicate::str::is_empty());
Ok(())
}
#[rstest]
#[case("test-data/multi_input", "")]
fn multi_input_test(#[case] path: &str, #[case] args: &str) -> Result<(), Box<dyn Error>> {
let mut cmd = Command::cargo_bin("rs-aggregate")?;
let inputs = glob((path.to_owned() + "/input*").as_str())?;
let expect_path = Path::new(path).join("expected");
let mut expect_file = File::open(expect_path)?;
let mut expect_data: Vec<u8> =
Vec::with_capacity(expect_file.metadata()?.len().try_into().unwrap());
expect_file.read_to_end(&mut expect_data)?;
let assert = cmd
.args(args.split_whitespace())
.args(inputs.map(|x| x.unwrap()))
.timeout(std::time::Duration::from_secs(30))
.assert();
assert
.success()
.stdout(predicate::eq(expect_data))
.stderr(predicate::str::is_empty());
Ok(())
}