春江暮客

春江暮客的个人学习分享网站

用 Python 检查 CIDR 网段重叠:部署前发现重复与包含

2026-10-30 技术
用 Python 检查 CIDR 网段重叠:部署前发现重复与包含

网段清单里同时写了 192.0.2.0/24 和 192.0.2.128/25,两行语法都合法,却分配了重复的地址范围。等容器、VPN 或测试环境部署以后再排查,往往要把配置重新翻一遍。

本文面向初学者到中级读者,用 Python 标准库做一个只读的 CIDR 重叠检查器:检查本地清单,报告第一对冲突及行号,用退出码阻止后续操作。不扫描网络,不修改 Linux 路由、防火墙或服务配置。

1. 方法:检查范围,不逐个枚举地址

CIDR 写成“网络地址/前缀长度”,例如 /24 表示 IPv4 地址的前 24 位属于网络部分。如果还分不清单个地址与网络,可先看Python 验证 IPv4/IPv6 地址;这里检查的是整段地址的交集,不是刷题中的字符串规则。

清单采用以下规则:

  1. 每行一个 IPv4 或 IPv6 网段,必须显式写数字前缀;允许空行和 # 后的注释。
  2. 网络地址必须对齐前缀,拒绝 192.0.2.9/24 这类主机位不为零的输入,不自动改成另一个范围。
  3. 相同网段、包含关系都视为重叠;相邻但没有交集的两个网段允许通过。IPv4、IPv6 分开比较。
  4. 先校验完整份文件,再按输入顺序查第一对冲突。空清单不是“通过”,而是输入错误。

ip_network() 官方文档说明了严格解析;overlaps()用于判断网络交集。本例不展开主机列表,即使包含 IPv6 大网段也不会枚举每个地址。

重叠不一定是业务错误。 路由汇总、允许列表中的包含关系可能是有意的。本工具适合“同一规划范围内,网段必须互不相交”的清单,不应直接拿来判定任意路由表是否正确。

2. 准备:新建独立演示目录

需要 Python 3.9 或更新版本,无第三方依赖。本文命令在 Linux、Python 3.11.4 下测试,适用于 bash、zsh。

python3 --version
mkdir cidr-check-demo
cd cidr-check-demo

把下一节保存为当前目录的 check_cidrs.py,也可下载同一份完整脚本。后面的重定向只用于演示文件,会覆盖同名文件;重复实验请新建目录。

3. 实现:严格解析、按版本比较

"""Find the first overlap in a small local CIDR inventory. Python 3.9+."""
import argparse
import ipaddress
from pathlib import Path
import sys

MAX_BYTES = 128 * 1024
MAX_NETWORKS = 1000


def read_networks(path):
    if not path.is_file():
        raise ValueError("input must be a regular file")
    with path.open("rb") as stream:
        raw = stream.read(MAX_BYTES + 1)
    if len(raw) > MAX_BYTES:
        raise ValueError(f"input exceeds {MAX_BYTES} bytes")
    text = raw.decode("utf-8")
    if text.startswith("\ufeff"):
        raise ValueError("remove the UTF-8 BOM first")
    rows = []
    for line_number, line in enumerate(text.splitlines(), 1):
        token = line.split("#", 1)[0].strip()
        if not token:
            continue
        if len(rows) >= MAX_NETWORKS:
            raise ValueError(f"more than {MAX_NETWORKS} networks")
        if token.count("/") != 1 or "%" in token:
            raise ValueError(f"line {line_number}: expected unscoped IP/prefix")
        prefix = token.split("/", 1)[1]
        if not prefix.isascii() or not prefix.isdecimal():
            raise ValueError(f"line {line_number}: prefix must be decimal digits")
        try:
            network = ipaddress.ip_network(token, strict=True)
        except ValueError as exc:
            raise ValueError(f"line {line_number}: {exc}") from exc
        rows.append((line_number, network))
    if not rows:
        raise ValueError("no networks found")
    return rows


def first_overlap(rows):
    for index, (left_line, left) in enumerate(rows):
        for right_line, right in rows[index + 1:]:
            if left.version == right.version and left.overlaps(right):
                return left_line, left, right_line, right
    return None


def main():
    parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
    parser.add_argument("file", type=Path, help="UTF-8 file: one CIDR per line")
    args = parser.parse_args()
    try:
        rows = read_networks(args.file)
        conflict = first_overlap(rows)
    except (OSError, ValueError) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 2
    if conflict:
        left_line, left, right_line, right = conflict
        print(f"OVERLAP: line {left_line} {left} <-> line {right_line} {right}")
        return 1
    print(f"OK: {len(rows)} networks; no overlaps within each IP version")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

脚本只读取可信本地目录中、已经停止写入的小型普通文件:最多 128 KiB、1000 个网段,符号链接会跟随目标。不用于恶意路径、并发替换的文件或可能挂起的网络文件系统;读取上限不等于超时保护。

数字前缀、必须有 /、拒绝 % 作用域标识,都是本工具额外定义的输入约束。不要因为库接受某种写法,就默认库存清单也应该接受它。

Python 官方入门说明展示了 strict=False 会清掉主机位,ip_interface() 则适合表示“某个主机地址及其网络”。本工具保留 strict=True,要求维护者先确认意图再修正清单;不能为了消除错误就静默扩大范围。

4. 运行:区分冲突与检查失败

  1. 查看帮助,再创建含有重叠的测试清单:
python3 check_cidrs.py --help
cat > networks.txt <<'CIDRS'
# planned subnets
192.0.2.0/24
192.0.2.128/25
2001:db8:1::/64
CIDRS
if python3 check_cidrs.py networks.txt; then
    rc=0
else
    rc=$?
fi
printf 'exit=%s\n' "$rc"

帮助信息之后,检查命令输出:

OVERLAP: line 2 192.0.2.0/24 <-> line 3 192.0.2.128/25
exit=1

行号包含注释和空行。if 分支保留非零退出码,因此即使 shell 启用了 set -e,也能看到冲突结果。

  1. 如果真实意图是两个互不重叠的 /25,创建修正后的另一份清单:
cat > networks-fixed.txt <<'CIDRS'
192.0.2.0/25
192.0.2.128/25
2001:db8:1::/64
CIDRS
python3 check_cidrs.py networks-fixed.txt
OK: 3 networks; no overlaps within each IP version

这是示例规划,不是对真实网络的改动建议。先确认 /24 是否本就要包含后面的子网,再决定改哪一行。

退出码 含义 输出位置
0 完整清单合法,未发现重叠 标准输出 OK:
1 找到第一对重叠网段 标准输出 OVERLAP:
2 文件、编码、格式、规模或参数错误 标准错误;没有成功报告

--help 输出帮助并退出 0。自动化中应显式传递失败状态;把下一段放在独立脚本里运行,失败时会结束那个脚本:

if python3 check_cidrs.py networks-fixed.txt; then
    printf 'NEXT: inventory passed; review the deployment separately\n'
else
    rc=$?
    printf 'STOP: CIDR preflight exit=%s\n' "$rc" >&2
    exit "$rc"
fi

这里只允许进入人工审阅后的下一步,并不真正部署。网段检查通过也不代表连通性正常:域名问题继续看系统 DNS 解析排查,服务可达性则用TCP 端口检查单独验证。

5. 验证:边界、IPv6 与输入不变

在同一目录执行自检。测试只创建自己的临时目录,检查实际退出码和输出类别,并确认输入 SHA-256 没有变化:

python3 - <<'PY_CHECK'
import hashlib
from pathlib import Path
import subprocess
import sys
import tempfile

cases = [
    ("adjacent", "192.0.2.0/25\n192.0.2.128/25\n", 0),
    ("nested", "192.0.2.0/24\n192.0.2.128/25\n", 1),
    ("duplicate", "192.0.2.0/24\n192.0.2.0/24\n", 1),
    ("ipv6", "2001:db8::/32\n2001:db8:1::/48\n", 1),
    ("mixed", "192.0.2.0/24\n2001:db8::/32\n", 0),
    ("host-bits", "192.0.2.9/24\n", 2),
    ("empty", "# nothing planned yet\n", 2),
]
with tempfile.TemporaryDirectory(prefix="cidr-tests-", dir=".") as folder:
    path = Path(folder) / "input.txt"
    for label, text, expected in cases:
        path.write_text(text, encoding="utf-8")
        before = hashlib.sha256(path.read_bytes()).digest()
        result = subprocess.run(
            [sys.executable, "check_cidrs.py", str(path)],
            capture_output=True, text=True, timeout=10,
        )
        assert result.returncode == expected, (label, result)
        if expected == 2:
            assert not result.stdout and result.stderr.startswith("ERROR:")
        else:
            assert not result.stderr
            assert result.stdout.startswith("OVERLAP:" if expected else "OK:")
        assert hashlib.sha256(path.read_bytes()).digest() == before
        print(f"PASS: {label}; exit={result.returncode}; input unchanged")
PY_CHECK

预期打印 7 行 PASS::相邻网段和混合版本通过;包含、重复、IPv6 包含返回 1;主机位错误和空清单返回 2。任一断言失败都会停止,而不是继续报告通过。

还可以复现主机位错误,注意诊断写到标准错误:

printf '192.0.2.9/24\n' > host-bits.txt
if python3 check_cidrs.py host-bits.txt; then
    rc=0
else
    rc=$?
fi
printf 'exit=%s\n' "$rc"
ERROR: line 1: 192.0.2.9/24 has host bits set
exit=2

6. 排错:不要把错误清单当成安全结果

现象 直接处理
has host bits set 确认是网络还是接口地址;网络清单应填写对齐后的网络地址,不要盲目切换 strict=False。
expected unscoped IP/prefix 显式填写 /24、/64 等数字前缀;该清单不接受主机名或 %eth0。
prefix must be decimal digits 使用 /24,不要使用 /255.255.255.0 或行末额外文字;注释放在 # 后。
编码错误或 BOM 提示 将原文件另存为无 BOM 的 UTF-8,保留原始副本后重新检查。
只显示一个冲突 这是“第一对”检查器;人工修正该冲突后重跑,不是全部冲突报告。
文件过大或网段太多 不要按块独立检查并宣称整体无冲突;跨块仍可能重叠。应改用能覆盖全量输入的算法。

最多 1000 个网段时,成对比较最多 499500 次,复杂度为 O(n²)。这个限制适合小型配置审阅,不是大型地址管理平台。工具不判断地址是否能在公网路由,也不替你检查实际路由、防火墙或地址占用。

7. 总结

先把网段清单严格解析,再检查同一 IP 版本内的交集,可以在部署前发现包含和重复分配。保留行号、区分退出码,并用故障样本验证检查器,才能把它接到自动化入口。检查通过只代表这份清单满足互不重叠的规则,实际网络仍需单独验证。

标签

1024 12306 2fa ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor argparse automation backup bash batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn chatgpt checkpoint cli cloudflare codex cofoldarena concurrency configuration copy cprofile cpu监控 cron csv cuda curl data-leakage data-processing data-quality data-validation datascience datavisualization datetime debugging decimal deployment desktop-app devtools difflib disk-space disown dns docker dovecot download electron encoding esm esm2 esm3 esmc esmfold2 etag evaluation faceswap fasta fastmcp faulthandler ffmpeg file-descriptors file-io file-management files flashppi flask folium frontend game generator git github-actions google google-research gpt-6.1-sol grep gzip harness heapq hls html http hugo imports indexnow ipaddress jailbreak javascript jev json jsonl just k-means kaggle langfuse leecode linux list litellm llm llms.txt logging logs lollipop lsof m3u8 machine-learning macos manacher matplotlib mcp memory mirror mmcif model-evaluation monitoring mp3 mp4 mpnn multiomics mutation mysql nanobert nanobodies networking networkx nginx normalize numpy ollama omegatherm pandas password pathlib pdb pep-723 performance permissions phaser pillow pip postfix preprocessing print prompt-engineering protein-alignment protein-design protein-embeddings protein-interactions protein-language-models protein-stability protein-structure proxy pydantic pyecharts pyqt python python3 r raincloud reproducibility requests reservoir-sampling rfoptimization rg ripgrep rosettafold3 roundcube rrsi rsi rsync s-tui sampling scale scikit-learn scrapy screen seaborn security selenium seo sequence-identity sha256 shell signals sitemap sklearn socket solana somaticsignatures sorting spl sqlite ssh standardize static-site subprocess symlink sysadmin system-one tarfile tcp tempfile tensorflow tkinter tls toml totp tracemalloc tron tronpy troubleshooting turtle typesafe-ai ubuntu unicode urllib usdt utf-8 uv vhhbert vite web-tools webp wordcloud wordpress workflow xml yaml zipfile zoneinfo 后台 寓言 概率 经济 贸易 迅雷解析 钱包

友情链接

其它