Bobobk

Practical Python, Linux and automation guides

Check CIDR Overlaps with Python Before Deployment

2026-10-30 Technology
Check CIDR Overlaps with Python Before Deployment
On this page

An inventory lists both 192.0.2.0/24 and 192.0.2.128/25. Each entry is valid, but their address ranges overlap. Discovering this after deploying containers, a VPN, or a test environment means revisiting the configuration under pressure.

This beginner-to-intermediate tutorial builds a read-only CIDR overlap checker with Python’s standard library. It reads a local inventory, reports the first conflicting pair with line numbers, and uses exit codes to stop later steps. It does not scan networks or change Linux routes, firewall rules, or service configuration.

1. Method: compare ranges without enumerating addresses

CIDR uses a network address followed by a prefix length: /24 means the first 24 bits of an IPv4 address identify the network. For the distinction between individual addresses and networks, start with IPv4/IPv6 address validation in Python. Here we check intersections between ranges, not the exercise-specific string rules.

The inventory follows these rules:

  1. One IPv4 or IPv6 network per line, with an explicit numeric prefix. Blank lines and comments after # are allowed.
  2. Network addresses must align with their prefixes. Reject host bits such as 192.0.2.9/24 instead of silently changing the range.
  3. Identical networks and containment count as overlaps. Adjacent ranges without an intersection pass. Compare IPv4 and IPv6 separately.
  4. Validate the entire file before finding the first conflicting pair in input order. An empty inventory is an input error, not a pass.

The official ip_network() documentation explains strict parsing; overlaps() checks network intersections. This example never expands a host list, even for large IPv6 ranges.

Overlap is not automatically a business error. Containment may be intentional in route summaries or allowlists. Use this tool for inventories whose networks must be disjoint within one planning scope, not as a general route-table validator.

2. Setup: create an isolated demo directory

Use Python 3.9 or newer, with no third-party dependencies. Commands were tested on Linux with Python 3.11.4 and work in bash or zsh.

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

Save the next section as check_cidrs.py in the current directory, or download the identical complete script. The later redirects create demo fixtures and overwrite matching names; use a fresh directory when repeating the experiment.

3. Implementation: parse strictly and compare matching versions

"""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())

The script only reads small regular files in a trusted local directory, with no active writer: at most 128 KiB and 1000 networks. It follows symlinks. It is not designed for hostile paths, concurrent file replacement, or network filesystems that may hang; a byte limit is not timeout protection.

Requiring /, numeric prefixes, and no % scope identifier are additional input rules defined by this tool. A format accepted by the library need not be appropriate for an inventory.

Python’s official introduction shows that strict=False clears host bits, while ip_interface() represents a host address together with its network. This tool keeps strict=True so the maintainer must confirm intent before correcting the inventory. Do not silently broaden a range to make an error disappear.

4. Usage: distinguish overlaps from failed checks

  1. View help and create an overlapping inventory:
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"

After the help text, the check prints:

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

Line numbers include comments and blank lines. The if branch preserves nonzero exit codes, so the result remains visible even when the shell has set -e enabled.

  1. If the intended plan is two disjoint /25 networks, create a separate corrected inventory:
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

This is a sample plan, not advice to change a live network. Confirm whether the /24 intentionally contains the later subnet before deciding what to edit.

Exit code Meaning Output destination
0 Entire inventory is valid; no overlaps found stdout, starting with OK:
1 First overlapping pair found stdout, starting with OVERLAP:
2 File, encoding, format, size, or argument error stderr; no success report

--help prints help and exits 0. Propagate failure explicitly in automation. Run the next snippet inside a standalone script: failure will terminate that script.

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

This only permits a later, separately reviewed step; it performs no deployment. A passing inventory does not establish connectivity. Continue with system DNS resolution troubleshooting for hostname failures and TCP port checks for service reachability.

5. Validation: boundaries, IPv6, and unchanged inputs

Run this self-test in the same directory. It creates only its own temporary directory, checks actual exit codes and output categories, and verifies that each input’s SHA-256 remains unchanged:

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

Expect seven PASS: lines. Adjacent networks and mixed versions pass; containment, duplicates, and IPv6 containment return 1; host bits and an empty inventory return 2. Any failed assertion stops the test instead of reporting success.

You can also reproduce a host-bit error. The diagnostic goes to stderr:

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. Troubleshooting: never mistake invalid input for a safe inventory

Symptom Direct action
has host bits set Confirm whether the entry describes a network or an interface. Supply an aligned network address; do not blindly switch to strict=False.
expected unscoped IP/prefix Include an explicit numeric prefix such as /24 or /64. Hostnames and %eth0 are not supported.
prefix must be decimal digits Use /24, not /255.255.255.0 or trailing prose; put comments after #.
Encoding error or BOM message Save a separate UTF-8 copy without a BOM, preserve the original, and check again.
Only one conflict appears This finds the first pair. Review and fix it, then rerun; it is not an all-conflicts report.
File or network-count limit exceeded Do not validate independent chunks and claim the full set is disjoint: overlaps can cross chunk boundaries. Use an algorithm that covers the entire inventory.

For 1000 networks, pairwise comparison requires at most 499500 checks, with O(n²) complexity. The limit suits small configuration reviews, not a large address-management platform. The tool does not determine public routability or inspect actual routes, firewall rules, or address usage.

7. Summary

Parse the inventory strictly, then compare ranges within the same IP version to catch containment and duplicate allocations before deployment. Keep line numbers and distinct exit codes, and test failure cases before connecting the checker to automation. A pass only means this inventory meets the disjoint-range rule; validate the actual network separately.

Tags

1024 2fa ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor argparse automation background backup bash batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn checkpoint cli cloudflare codex cofoldarena concurrency configparser configuration copy cprofile cpu monitoring 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 economics electron encoding esm esm2 esm3 esmc esmfold2 etag evaluation fable faceswap fasta fastmcp faulthandler ffmpeg file-descriptors file-io file-management filecmp filenames 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 multiprocessing 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 probability 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 seo sequence-identity sha256 shell signals sitemap sklearn socket solana somaticsignatures sorting spl sql sqlite ssh standardize static-site subprocess symlink sysadmin system-one tarfile tcp tempfile tensorflow tkinter tls toml totp tracemalloc trade tron tronpy troubleshooting turtle typesafe-ai ubuntu unicode upsert urllib usdt utf-8 uv venv vhhbert vite wallet web-tools webp wordcloud wordpress workflow xml yaml zipfile zoneinfo 迅雷解析

友情链接