Created
May 13, 2025 04:38
-
-
Save gzz2000/0ede5a2aac343956bf4b9c28a468f566 to your computer and use it in GitHub Desktop.
IP CIDR Complement
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
ip_complement.py – 计算一组 IPv4 / IPv6 CIDR 的互补网段 | |
用法示例: | |
# 直接在命令行给出网段 | |
python ip_complement.py 10.0.0.0/8 192.168.0.0/16 2001:db8::/32 | |
# 从文件或管道读取,再输出 JSON | |
cat cidr.txt | python ip_complement.py -j | |
""" | |
import argparse | |
import ipaddress | |
import json | |
import sys | |
from typing import Iterable, List | |
# ---------- 核心算法 ---------- # | |
def _complement_one_family(nets: List[ipaddress._BaseNetwork]) -> List[ipaddress._BaseNetwork]: | |
"""求同一地址族(net.version相同)的互补集。""" | |
if not nets: | |
return [] | |
version = nets[0].version | |
if any(net.version != version for net in nets): | |
raise ValueError("所有网段必须属于同一地址族(IPv4 或 IPv6)") | |
# 对全集的定义 | |
full = ipaddress.IPv4Network("0.0.0.0/0") if version == 4 else ipaddress.IPv6Network("::/0") | |
# 先合并、排序,确保无重叠且有序 | |
merged = sorted(ipaddress.collapse_addresses(nets), key=lambda n: int(n.network_address)) | |
complement: List[ipaddress._BaseNetwork] = [] | |
cursor = full.network_address | |
for net in merged: | |
if cursor < net.network_address: | |
gap_end = net.network_address - 1 | |
complement.extend(ipaddress.summarize_address_range(cursor, gap_end)) | |
next_int = int(net.broadcast_address) + 1 | |
if next_int > int(full.broadcast_address): | |
cursor = None # 已经到头 | |
break | |
cursor = ipaddress.ip_address(next_int) | |
if cursor is not None and int(cursor) <= int(full.broadcast_address): | |
complement.extend( | |
ipaddress.summarize_address_range(cursor, full.broadcast_address) | |
) | |
return complement | |
def complement_cidrs(cidrs: Iterable[str]) -> List[str]: | |
""" | |
传入一串 CIDR 字符串,返回 *字符串* 形式的补集列表。 | |
为方便起见,这里不区分输入次序,可同时混合 IPv4 / IPv6。 | |
""" | |
ipv4_nets, ipv6_nets = [], [] | |
for s in cidrs: | |
net = ipaddress.ip_network(s, strict=False) | |
(ipv4_nets if net.version == 4 else ipv6_nets).append(net) | |
result = ( | |
_complement_one_family(ipv4_nets) | |
+ _complement_one_family(ipv6_nets) | |
) | |
# 再按地址排序输出(IPv4 在前,随后 IPv6) | |
result.sort(key=lambda n: (n.version, int(n.network_address))) | |
return [str(n) for n in result] | |
# ---------- CLI 包装 ---------- # | |
def main() -> None: | |
p = argparse.ArgumentParser(description="IPv4 / IPv6 互补网段计算器") | |
p.add_argument("cidrs", nargs="*", help="要排除的 CIDR(留空或指定 - 以从 stdin 读取)") | |
p.add_argument( | |
"-j", "--json", action="store_true", help="以 JSON 数组形式输出(默认按行输出)" | |
) | |
args = p.parse_args() | |
cidr_inputs: List[str] | |
if args.cidrs and args.cidrs != ["-"]: | |
cidr_inputs = args.cidrs | |
else: | |
cidr_inputs = [line.strip() for line in sys.stdin if line.strip()] | |
if not cidr_inputs: | |
p.error("至少提供一个 CIDR") | |
complements = complement_cidrs(cidr_inputs) | |
if args.json: | |
print(json.dumps(complements, indent=2, ensure_ascii=False)) | |
else: | |
for n in complements: | |
print(n) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment