From a3e1d5fdf05eb74810f29f29d57bc8e3d741797f Mon Sep 17 00:00:00 2001 From: pyro Date: Tue, 27 Oct 2026 11:11:13 -0500 Subject: [PATCH] wrote the tool --- Cargo.toml | 8 +++ README.md | 5 +- src/main.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..92a4f0f --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 00d3051..175df75 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # scope_filter -Takes your scope and exclusion list, then removes the excluded IPs and outputs the remaining IPs. \ No newline at end of file +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 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5dc3632 --- /dev/null +++ b/src/main.rs @@ -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 = 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> { + 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

(filename: P) -> io::Result> +where + P: AsRef, +{ + 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) +}