DEV Community

yuanke215
yuanke215

Posted on Originally published at ipcalcplus.com AI-assisted

The Subnet Overlap Nobody Saw Coming (and the 30-Second Check That Catches It)

Two offices merge. Both teams have been happily using 10.34.x.x for years. The network team stitches the sites together with a new VPN tunnel, updates the firewall, and goes home.

Three days later the help desk floods with tickets: the build server answers pings but not SSH, a printer works on Tuesdays, and one department swears the CRM "loads eventually, if you refresh five times."

Nothing is on fire. Everything is intermittent. Welcome to the worst failure mode in subnetting: overlapping subnets.

The case that started it

Site A was assigned 10.34.12.0/23. That block covers:

10.34.12.0  -  10.34.13.255   (512 addresses)
Enter fullscreen mode Exit fullscreen mode

Site B, allocated by a different admin in a different year, got 10.34.13.0/24:

10.34.13.0  -  10.34.13.255   (256 addresses)
Enter fullscreen mode Exit fullscreen mode

On paper both allocations look fine, and neither admin ever saw the other's spreadsheet. But B's entire /24 is inside A's /23. Every one of B's 256 addresses is claimed twice.

Once the tunnel comes up, packets for 10.34.13.x have two valid destinations. The routing table picks whichever route was installed last, ARP caches fight over the same targets, and traffic lands on whichever site the router currently prefers. Intermittent, direction-dependent, and maddening to troubleshoot.

The math is simpler than the debugging

Two subnets overlap exactly when their address intervals share at least one address. Forget prefix lengths — expand both blocks into a start and end address and compare intervals:

overlap(A, B)  =  start(A) <= end(B)  AND  start(B) <= end(A)
Enter fullscreen mode Exit fullscreen mode

That's it. It works for equal prefixes, different prefixes, a /23 versus a /24, or a single host versus a whole /8.

In Python, the whole check fits on a napkin:

def ip_to_int(ip):
    a, b, c, d = (int(x) for x in ip.split("."))
    return (a << 24) | (b << 16) | (c << 8) | d

def mask_of(prefix):
    return (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF

def block_span(ip, prefix):
    base = ip_to_int(ip) & mask_of(prefix)
    return base, base + (1 << (32 - prefix)) - 1

def overlaps(a_ip, a_pfx, b_ip, b_pfx):
    a_start, a_end = block_span(a_ip, a_pfx)
    b_start, b_end = block_span(b_ip, b_pfx)
    return a_start <= b_end and b_start <= a_end

print(overlaps("10.34.12.0", 23, "10.34.28.0", 23))  # False - 3,584 apart, fine
print(overlaps("10.34.12.0", 23, "10.34.13.0", 24))  # True  - the incident above
Enter fullscreen mode Exit fullscreen mode

When the blocks do not overlap, the same numbers tell you how much room is left between them — useful when you're about to hand out the next slice.

Three mistakes I keep seeing

1. Comparing prefixes instead of intervals. "It's a /23 and a /24, so they're different" is not a safety check. Nested prefixes overlap by definition. So do 10.34.13.0/24 and 10.34.13.128/25.

2. Trusting the network address someone typed. 10.34.12.7/23 and 10.34.12.0/23 are the same block — the network address is where the mask says it is, not where the host octets happen to sit. Always normalize before comparing.

3. Assuming private space is infinite and therefore safe. RFC 1918 gives you a lot of room, but mergers are exactly when two orgs discover they both carved up 10.x.x.x differently:

Block Range Addresses Typical owner
10.0.0.0/8 10.0.0.0 – 10.255.255.255 16,777,216 Large enterprises
172.16.0.0/12 172.16.0.0 – 172.31.255.255 1,048,576 Mid-size networks, Docker defaults
192.168.0.0/16 192.168.0.0 – 192.168.255.255 65,536 Home and small office

One more trap: 100.64.0.0/10 is not private. RFC 6598 reserved it for carrier-grade NAT, and mobile carriers hand addresses from it to millions of devices. It looks internal (it isn't globally routed), but it isn't yours to subnet either. If you inherit an address plan using that block, treat it as someone else's shared space, not a free pool.

The 30-second pre-flight check

Before any allocation goes into the IPAM tool, run the interval check against every existing block in the same region:

  1. Normalize the new block to its true start and end address.
  2. Compare intervals against all neighbors — different prefix lengths included.
  3. If anything overlaps, stop and re-plan. If nothing does, note the gap so the next allocation starts cleanly.

You can do this in a REPL, in a spreadsheet, or in a tool built for exactly this job — I keep a private IP range calculator bookmarked for the check, because it also tells me which RFC 1918 block (or special range like CGNAT, link-local, or loopback) an address falls into, which catches the third mistake in the same pass.

Why overlap is worse than a full outage

A hard outage gets paged immediately and fixed loudly. An overlap fails quietly: it usually doesn't break the path the monitoring probes use, so dashboards stay green while a subset of hosts flips between destinations. The failure surface is routing order, ARP timing, and DHCP lease renewals — all nondeterministic from the help desk's point of view.

That's why prevention is the whole game. The check costs 30 seconds before deployment; the incident costs a week of packet captures after.

Top comments (0)