#!/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)