commit 1a2a19c7fa4228c76356520026a3a4d9f61e0f56 Author: Ben Charlton Date: Mon Jan 6 23:13:46 2025 +0000 initial release diff --git a/uniqcidr.py b/uniqcidr.py new file mode 100755 index 0000000..16ca43e --- /dev/null +++ b/uniqcidr.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import ipaddress +import sys +import argparse + +# Default to /32 and /128 for single IP addresses +start_v4 = 32 +start_v6 = 128 + +parser = argparse.ArgumentParser( + prog='uniqcidr', + description='Like uniq, but understands CIDR netmasks', + epilog='...but written by an idiot') + +parser.add_argument('-c', '--count', action='store_true', help="count number of IPs occurring in each subnet") +parser.add_argument('-n', '--network', action='store_true', help="Enable network mode, don't require contiguous IP addresses. Sets /48 and /24 thresholds") +parser.add_argument('-6', '--v6netmask', type=int, help=f'IPv6 netmask, default: {start_v6}') +parser.add_argument('-4', '--v4netmask', type=int, help=f'IPv4 netmask, default: {start_v4}') + +args = parser.parse_args() + +# Network mode counts every /24 or /48 +if args.network: + start_v4 = 24 + start_v6 = 48 + +# Custom subnet thresholds. -6 64 is probably the othe most useful here. +if args.v6netmask: + start_v6 = args.v6netmask +if args.v4netmask: + start_v4 = args.v4netmask + +def render_line(count, curr_network): + if args.count: + print(f"{count:>5} {curr_network}") + else: + print(curr_network) + +curr_network = None +count = 0 +for line in sys.stdin: + + line = line.rstrip() + try: + ifa = ipaddress.ip_interface(line) + except ValueError: + print(f"Not an ip address: {line}", file=sys.stderr) + continue + + prefix = start_v6 + if ifa.version == 4: + prefix=start_v4 + + network = ifa.network.supernet(new_prefix=prefix) + + if curr_network is None: + curr_network = network + continue + + count = count + 1 + + if network.version != curr_network.version: + render_line(count, curr_network) + curr_network = network + count = 0 + + if curr_network != network: + if network.subnet_of(curr_network): + pass + elif network.subnet_of(curr_network.supernet()): + curr_network = curr_network.supernet() + else: + render_line(count, curr_network) + curr_network = network + count = 0 + +count = count + 1 +render_line(count, curr_network)