Skip to content

Instantly share code, notes, and snippets.

@gzz2000
Created May 13, 2025 04:38
Show Gist options
  • Save gzz2000/0ede5a2aac343956bf4b9c28a468f566 to your computer and use it in GitHub Desktop.
Save gzz2000/0ede5a2aac343956bf4b9c28a468f566 to your computer and use it in GitHub Desktop.
IP CIDR Complement
#!/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