wrote the tool

This commit is contained in:
pyro committed 2026-10-27 11:11:13 -05:00
1 parent 541584efa4
commit a3e1d5fdf0
3 files changed
+151 -1

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
[package]
name = "scope_filter"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.6.7", features = ["derive"] }
ipnet = "2.12.2"
+4 -1
View File
@@ -1,3 +1,6 @@
# scope_filter
Takes your scope and exclusion list, then removes the excluded IPs and outputs the remaining IPs.
Takes your scope and exclusion list, then removes the excluded IPs and outputs the remaining IPs.
# Usage:
scope_filter -s /path/to/scope/file (formated one ip/range/subnet per line) -e /path/to/exclusions/file (formated one ip/range/subnet per line) -o /path/to/output/file
+139
View File
@@ -0,0 +1,139 @@
use clap::Parser;
use ipnet::{Ipv4AddrRange, Ipv4Net};
use std::collections::HashSet;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Parser, Debug)]
#[command(
name = "Subnet Processor",
author,
version,
about = "Removes excluded IPs/subnets/ranges from input subnets, ranges, or IPs and outputs ranges or single IPs."
)]
struct Args {
#[arg(short, long, value_name = "SCOPE_FILE")]
scope_file: PathBuf,
#[arg(short, long, value_name = "EXCLUSION_FILE")]
exclusion_file: PathBuf,
#[arg(short, long, value_name = "OUTPUT_FILE")]
output_file: PathBuf,
}
fn main() -> io::Result<()> {
let args = Args::parse();
let subnet_lines = read_lines_to_vec(&args.scope_file)?;
let exclusion_lines = read_lines_to_vec(&args.exclusion_file)?;
let mut excluded_ips = HashSet::new();
for line in exclusion_lines {
match parse_ip_entry(&line) {
Ok(ips) => {
for ip in ips {
excluded_ips.insert(ip);
}
}
Err(e) => {
eprintln!("Invalid exclusion entry '{}': {}", line, e);
}
}
}
let mut output_file = File::create(&args.output_file)?;
for subnet_str in subnet_lines {
let subnet_ips = match parse_ip_entry(&subnet_str) {
Ok(ips) => ips,
Err(e) => {
eprintln!("Invalid input entry '{}': {}", subnet_str, e);
continue;
}
};
if subnet_ips.is_empty() {
continue;
}
let remaining_ips: Vec<Ipv4Addr> = subnet_ips
.into_iter()
.filter(|ip| !excluded_ips.contains(ip))
.collect();
if remaining_ips.is_empty() {
continue;
}
let mut ranges: Vec<(Ipv4Addr, Ipv4Addr)> = Vec::new();
let mut range_start = remaining_ips[0];
let mut range_end = remaining_ips[0];
for &ip in remaining_ips.iter().skip(1) {
if u32::from(ip) == u32::from(range_end) + 1 {
range_end = ip;
} else {
ranges.push((range_start, range_end));
range_start = ip;
range_end = ip;
}
}
ranges.push((range_start, range_end));
for (start, end) in ranges {
if start == end {
writeln!(output_file, "{}", start)?;
} else {
writeln!(output_file, "{}-{}", start, end)?;
}
}
}
println!(
"Processing complete! Output written to '{}'.",
args.output_file.display()
);
Ok(())
}
fn parse_ip_entry(s: &str) -> io::Result<Vec<Ipv4Addr>> {
let s = s.trim();
if s.is_empty() {
return Ok(Vec::new());
}
if let Ok(ip) = Ipv4Addr::from_str(s) {
return Ok(vec![ip]);
}
if let Ok(net) = Ipv4Net::from_str(s) {
let range = Ipv4AddrRange::new(net.network(), net.broadcast());
return Ok(range.collect());
}
if let Some((start_str, end_str)) = s.split_once('-') {
let start = Ipv4Addr::from_str(start_str.trim())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
let end = Ipv4Addr::from_str(end_str.trim())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
if u32::from(start) > u32::from(end) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid range {}: start IP is greater than end IP", s),
));
}
let range = Ipv4AddrRange::new(start, end);
return Ok(range.collect());
}
Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Unrecognized IP format: {}", s),
))
}
fn read_lines_to_vec<P>(filename: P) -> io::Result<Vec<String>>
where
P: AsRef<std::path::Path>,
{
let file = File::open(filename)?;
let reader = BufReader::new(file);
let mut lines = Vec::new();
for line in reader.lines() {
let line = line?;
let trimmed = line.trim();
if !trimmed.is_empty() {
lines.push(trimmed.to_string());
}
}
Ok(lines)
}