commit 954e22b47c4fd799eb2371a0a8b74e58ab648284 Author: xenofem Date: Mon Jul 25 15:07:03 2022 -0400 subnet inversion calculator diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..82709ba --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "domnet" +version = "0.1.0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc2916cde080c1876ff40292a396541241fe0072ef928cd76582e9ea5d60d2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..848eb15 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "domnet" +version = "0.1.0" +edition = "2021" +authors = ["xenofem "] +license = "MIT" + +[dependencies] +thiserror = "1.0.31" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8db950d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,168 @@ +use std::fmt; +use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr}; +use std::num::ParseIntError; +use std::str::FromStr; + +use thiserror::Error; + +struct V4Subnet(Vec); +struct V6Subnet(Vec); + +#[derive(Error, Debug)] +enum SubnetParseError { + #[error("failed to parse CIDR block")] + Cidr, + #[error("failed to parse IP address")] + Addr(#[from] AddrParseError), + #[error("failed to parse prefix length")] + PrefixLength(#[from] ParseIntError), +} + +fn bitstring_to_int(b: &[bool], size: usize) -> u128 { + let mut result = 0; + for idx in 0..size { + result *= 2; + if let Some(true) = b.get(idx) { + result += 1; + } + } + result +} + +fn int_to_bitstring(val: u128, size: usize, prefix_len: usize) -> Vec { + let mut result = Vec::new(); + for idx in 0..prefix_len { + result.push((val >> (size - 1 - idx)) % 2 == 1); + } + result +} + +impl fmt::Display for V4Subnet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let addr = Ipv4Addr::from(bitstring_to_int(&self.0, 32) as u32); + write!(f, "{}/{}", addr, self.0.len()) + } +} + +impl FromStr for V4Subnet { + type Err = SubnetParseError; + + fn from_str(s: &str) -> Result { + let (addr, len) = s.split_once('/').ok_or(SubnetParseError::Cidr)?; + let val = u32::from(Ipv4Addr::from_str(addr)?); + let prefix_len = usize::from_str(len)?; + Ok(V4Subnet(int_to_bitstring(val as u128, 32, prefix_len))) + } +} + +impl fmt::Display for V6Subnet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let addr = Ipv6Addr::from(bitstring_to_int(&self.0, 128)); + write!(f, "{}/{}", addr, self.0.len()) + } +} + +impl FromStr for V6Subnet { + type Err = SubnetParseError; + + fn from_str(s: &str) -> Result { + let (addr, len) = s.split_once('/').ok_or(SubnetParseError::Cidr)?; + let val = u128::from(Ipv6Addr::from_str(addr)?); + let prefix_len = usize::from_str(len)?; + Ok(V6Subnet(int_to_bitstring(val, 128, prefix_len))) + } +} + +/// Finds the smallest set of subnets that complement the set of subnets provided +fn invert_subnets(subnets: &Vec>, size: usize) -> Vec> { + invert_subnets_helper(&mut Vec::new(), subnets, size) +} + +// Finds the smallest set of subnets that complement, within `root`, the set of subnets provided +fn invert_subnets_helper( + root: &mut Vec, + subnets: &Vec>, + size: usize, +) -> Vec> { + if subnets.iter().any(|subnet| root.starts_with(subnet)) { + // We're already within one of the provided subnets, bail out + return Vec::new(); + } + + if subnets.iter().any(|subnet| subnet.starts_with(root)) { + // We need to narrow down further + + if root.len() == size { + // There's nowhere further to go + return Vec::new(); + } + + // left branch + root.push(false); + let mut results = invert_subnets_helper(root, subnets, size); + root.pop(); + + // right branch + root.push(true); + results.append(&mut invert_subnets_helper(root, subnets, size)); + root.pop(); + + results + } else { + vec![root.to_owned()] + } +} + +fn usage(name: Option) { + eprintln!("usage: {} SUBNET ...", name.as_deref().unwrap_or("domnet")); + eprintln!("Calculate the inverse of a set of IP subnets."); + eprintln!("Arguments must be all IPv4 subnets or all IPv6 subnets, in CIDR block format."); + std::process::exit(1); +} + +fn main() { + let mut args = std::env::args(); + let name = args.next(); + let first = args.next(); + + let subnet = if let Some(s) = first { + s + } else { + return usage(name); + }; + if let Ok(v4subnet) = V4Subnet::from_str(&subnet) { + let rest = args + .map(|s| V4Subnet::from_str(&s)) + .collect::, SubnetParseError>>(); + let subnets = if let Ok(r) = rest { + std::iter::once(v4subnet) + .chain(r.into_iter()) + .map(|s| s.0) + .collect::>>() + } else { + return usage(name); + }; + let inverse = invert_subnets(&subnets, 32); + for subnet in inverse.into_iter() { + println!("{}", V4Subnet(subnet)); + } + } else if let Ok(v6subnet) = V6Subnet::from_str(&subnet) { + let rest = args + .map(|s| V6Subnet::from_str(&s)) + .collect::, SubnetParseError>>(); + let subnets = if let Ok(r) = rest { + std::iter::once(v6subnet) + .chain(r.into_iter()) + .map(|s| s.0) + .collect::>>() + } else { + return usage(name); + }; + let inverse = invert_subnets(&subnets, 128); + for subnet in inverse.into_iter() { + println!("{}", V6Subnet(subnet)); + } + } else { + usage(name); + } +}