Refactoring & compatibility with aggregate6 options

This commit is contained in:
2023-03-18 11:44:41 -07:00
parent f23e2fd83a
commit d8b48aba9a
4 changed files with 271 additions and 72 deletions

179
src/iputils.rs Normal file
View File

@ -0,0 +1,179 @@
use std::{
error::Error,
fmt::Display,
net::{IpAddr, Ipv4Addr},
str::FromStr,
};
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use iprange::{IpRange, IpRangeIter};
#[derive(Default)]
pub struct IpBothRange {
v4: IpRange<Ipv4Net>,
v6: IpRange<Ipv6Net>,
}
impl IpBothRange {
pub fn new() -> 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())),
},
}
}
pub fn simplify(&mut self) {
self.v4.simplify();
self.v6.simplify();
}
}
pub struct IpBothRangeIter<'a> {
v4_iter: IpRangeIter<'a, Ipv4Net>,
v6_iter: IpRangeIter<'a, Ipv6Net>,
_v4_done: bool,
}
impl<'a> Iterator for IpBothRangeIter<'a> {
type Item = IpNet;
fn next(&mut self) -> Option<Self::Item> {
if self._v4_done {
match self.v6_iter.next() {
Some(net) => return Some(net.into()),
None => return None,
}
}
match self.v4_iter.next() {
Some(net) => Some(net.into()),
None => {
self._v4_done = true;
match self.v6_iter.next() {
Some(net) => Some(net.into()),
None => None,
}
}
}
}
}
impl<'a> IntoIterator for &'a IpBothRange {
type Item = IpNet;
type IntoIter = IpBothRangeIter<'a>;
fn into_iter(self) -> Self::IntoIter {
IpBothRangeIter {
v4_iter: self.v4.iter(),
v6_iter: self.v6.iter(),
_v4_done: false,
}
}
}
pub enum IpOrNet {
IpNet(IpNet),
IpAddr(IpAddr),
}
#[derive(Debug, Clone)]
pub struct NetParseError {
#[allow(dead_code)]
msg: String,
}
impl Display for NetParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Unable to parse address")
}
}
impl Error for NetParseError {}
impl IpOrNet {
// Accepted formats:
// netmask - 1.1.1.0/255.255.255.0
// wildcard mask - 1.1.1.0/0.0.0.255
fn parse_mask(p: &str) -> Result<u8, Box<dyn Error>> {
let mask = p.parse::<Ipv4Addr>();
match mask {
Ok(mask) => {
let intrep: u32 = mask.into();
let lead_ones = intrep.leading_ones();
if lead_ones > 0 {
if lead_ones + intrep.trailing_zeros() == 32 {
Ok(lead_ones.try_into()?)
} else {
Err(Box::new(NetParseError {
msg: "Invalid subnet mask".to_owned(),
}))
}
} else {
let lead_zeros = intrep.leading_zeros();
if lead_zeros + intrep.trailing_ones() == 32 {
Ok(lead_zeros.try_into()?)
} else {
Err(Box::new(NetParseError {
msg: "Invalid wildcard mask".to_owned(),
}))
}
}
}
Err(e) => Err(Box::new(e)),
}
}
fn from_parts(ip: &str, pfxlen: &str) -> Result<Self, Box<dyn Error>> {
let ip = ip.parse::<IpAddr>()?;
let pfxlenp = pfxlen.parse::<u8>();
match pfxlenp {
Ok(pfxlen) => Ok(IpNet::new(ip, pfxlen)?.into()),
Err(_) => {
if ip.is_ipv4() {
Ok(IpNet::new(ip, IpOrNet::parse_mask(pfxlen)?)?.into())
} else {
Err(Box::new(NetParseError {
msg: "Mask form is not valid for IPv6 address".to_owned(),
}))
}
}
}
}
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,
},
}
}
}
impl FromStr for IpOrNet {
type Err = Box<dyn Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split_once('/');
match parts {
Some((ip, pfxlen)) => IpOrNet::from_parts(ip, pfxlen),
None => Ok(s.parse::<IpAddr>()?.into()),
}
}
}
impl From<IpNet> for IpOrNet {
fn from(net: IpNet) -> Self {
IpOrNet::IpNet(net)
}
}
impl From<IpAddr> for IpOrNet {
fn from(addr: IpAddr) -> Self {
IpOrNet::IpAddr(addr)
}
}

View File

@ -1,10 +1,11 @@
extern crate ipnet;
extern crate iprange;
mod iputils;
use iputils::{IpBothRange, IpOrNet};
use clio::*;
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use iprange::IpRange;
use std::{io::BufRead, net::IpAddr};
use std::io::BufRead;
use clap::Parser;
@ -13,80 +14,98 @@ use clap::Parser;
struct Args {
#[clap(value_parser, default_value = "-")]
input: Input,
#[arg(
short,
long,
default_value = "128",
help = "Sets the maximum prefix length for entries read. Longer prefixes will be discarded prior to processing."
)]
max_prefixlen: u8,
#[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"))]
only_v4: bool,
#[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,
truncate: false,
only_v4: false,
only_v6: false,
}
}
}
#[derive(Parser)]
struct IpParseError {
ip: String,
problem: String,
}
struct IpBothRange {
v4: IpRange<Ipv4Net>,
v6: IpRange<Ipv6Net>,
}
type Errors = Vec<IpParseError>;
fn simplify_input(mut input: Input) -> (IpBothRange, Errors) {
let mut res = IpBothRange {
v4: IpRange::new(),
v6: IpRange::new(),
};
let mut errors = Errors::new();
for line in input.lock().lines() {
for net in line.unwrap().split_whitespace().to_owned() {
match net.parse() {
Ok(ipnet) => match ipnet {
IpNet::V4(v4_net) => {
res.v4.add(v4_net.trunc());
()
#[derive(Default)]
struct App {
args: Args,
prefixes: IpBothRange,
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
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;
}
IpNet::V6(v6_net) => {
res.v6.add(v6_net.trunc());
()
}
},
Err(_) => {
// First try to add it as a bare IP
match net.parse() {
Ok(ip) => match ip {
IpAddr::V4(v4_ip) => {
res.v4.add(Ipv4Net::new(v4_ip, 32).unwrap());
()
}
IpAddr::V6(v6_ip) => {
res.v6.add(Ipv6Net::new(v6_ip, 128).unwrap());
()
}
},
Err(error) => {
eprintln!("ERROR: {} - {}, ignoring.", net, error.to_string());
errors.push(IpParseError {
ip: net.to_string(),
problem: error.to_string(),
});
}
}
IpOrNet::IpAddr(_) => (),
}
}
if pfx.prefix_len() <= self.args.max_prefixlen {
self.prefixes.add(pfx);
}
}
fn simplify_input(&mut self) {
for line in self.args.input.to_owned().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(),
});
eprintln!("ERROR: '{}' is not a valid IP network, ignoring.", net);
}
}
}
}
self.prefixes.simplify();
}
res.v4.simplify();
res.v6.simplify();
(res, errors)
fn main(&mut self) {
self.args = Args::parse();
self.simplify_input();
for net in &self.prefixes {
println!("{}", net);
}
}
}
fn main() {
let args = Args::parse();
let input = args.input;
let (res, _) = simplify_input(input);
for net in &res.v4 {
println!("{}", net);
}
for net in &res.v6 {
println!("{}", net);
}
let mut app = App::default();
app.main();
}