Skip to content
[menu][close]

FOUNDRYNETNo. 001SEPTEMBER 2026ROUTING

Checking AI-Generated Subnet and ACL Math Before You Use It

Ask a chatbot to carve a /22 into VLAN subnets and the answer will look perfect. Often one boundary is off by a block, or a wildcard mask covers twice the range. The fix is not to stop asking, but to check the arithmetic with something that cannot hallucinate.

On this page

01 Why models slip on subnet arithmetic

Language models predict text, and subnet plans are text that looks the same whether the numbers are right or wrong. The common failures are consistent: a subnet that starts on a boundary its prefix length does not allow, two subnets that overlap by one block, a broadcast address given as usable, and wildcard masks that are the netmask rather than its inverse. Each is easy to miss in review because the output is well formatted. This procedure, in the routing section, checks the numbers with tools. It complements the broader method for verifying AI-written configurations.

02 The check, step by step

  1. List every prefix

    Extract each network and prefix length from the plan into one list, including the parent block it was carved from.

  2. Recompute with a library

    Parse each prefix with a CIDR library, such as the standard ipaddress module in Python, in strict mode. A strict parser rejects a network address that is not aligned, which catches the most common error immediately.

  3. Check containment and overlap

    Confirm each subnet is inside the parent block and that no two overlap. Sort by network address and compare each end with the next start.

  4. Recompute sizes and usable ranges

    For each subnet, compute the number of addresses, the first and last usable host and the broadcast address, and compare with the plan. Plans often claim one extra usable host.

  5. Convert wildcard masks back

    For each ACL entry, turn the address and wildcard back into a range and compare it with what the entry was meant to match. A netmask pasted where a wildcard belongs matches far more than intended.

  6. Test with addresses

    Feed the ACL or firewall rule the first and last address of each intended range and one address just outside, in a lab device or a policy analysis tool, and confirm each result.

Strict parsing catches a misaligned subnet, illustrative
$ python3 -c "import ipaddress; ipaddress.ip_network('10.20.7.0/23')"
ValueError: 10.20.7.0/23 has host bits set
$ python3 -c "import ipaddress; print(ipaddress.ip_network('10.20.7.0/23', strict=False))"
10.20.6.0/23

The first command fails because a /23 must start on an even third octet; the second shows the network the plan actually describes. In a VLAN plan, that single error puts two VLAN subnets and their gateways in the same address space.

03 Worked example: a generated /22 VLAN plan, recomputed

A campus team asked an assistant to carve 10.20.4.0/22 into five VLAN subnets: users, servers, printers, management and guest, with usable host counts. The answer arrived as a neat table with gateways and DHCP ranges. Recomputing it took under a minute and found two errors that would have reached production.

Two of five rows fail; the guest row would have put visitors in the server VLAN's address space.
VLANAs generatedRecomputedVerdict
10 Users10.20.4.0/23, 512 usable10.20.4.0 to 10.20.5.255, 510 usableRange right, count wrong
20 Servers10.20.6.0/24, 254 usable10.20.6.0 to 10.20.6.255, 254 usableCorrect
30 Printers10.20.7.0/25, 128 usable10.20.7.0 to 10.20.7.127, 126 usableCount wrong
40 Management10.20.7.128/26, 62 usable10.20.7.128 to 10.20.7.191, 62 usableCorrect
50 Guest10.20.7.0/23, 510 usableHost bits set; parent is 10.20.6.0/23Misaligned and overlaps 20 and 30
Containment and overlap check with a CIDR library, illustrative
$ cat plan.txt
10.20.4.0/23 10.20.6.0/24 10.20.7.0/25 10.20.7.128/26 10.20.7.0/23
$ python3 - <<'EOF'
import ipaddress as ip
parent = ip.ip_network('10.20.4.0/22')
nets = []
for s in open('plan.txt').read().split():
    try:
        n = ip.ip_network(s)
    except ValueError as e:
        print('FAIL', s, e); continue
    print('ok  ', s, 'in parent' if n.subnet_of(parent) else 'OUTSIDE', n.num_addresses - 2, 'usable')
    nets.append(n)
nets.sort(key=lambda n: n.network_address)
for a, b in zip(nets, nets[1:]):
    if a.overlaps(b): print('OVERLAP', a, b)
EOF
ok   10.20.4.0/23 in parent 510 usable
ok   10.20.6.0/24 in parent 254 usable
ok   10.20.7.0/25 in parent 126 usable
ok   10.20.7.128/26 in parent 62 usable
FAIL 10.20.7.0/23 10.20.7.0/23 has host bits set

The strict parser rejected the guest subnet before the overlap test ran, which is the right order: an unaligned prefix has no single meaning to test against. Had the plan been written as 10.20.6.0/23, the overlap loop would have flagged it against both the server and printer rows. The usable counts came out two lower than generated on every row because the model counted the network and broadcast addresses as hosts, a habit worth checking on every plan. The remaining space, 10.20.7.192/26, was unallocated and unmentioned; a good plan says so.

04 The errors worth looking for

All five appear regularly in generated plans.
ErrorExampleHow the check catches it
Misaligned network10.20.7.0/23Strict parse fails: host bits set
Overlap10.20.4.0/23 and 10.20.5.0/24Sorted comparison: second starts inside first
Wrong usable countA /27 listed with 32 hostsRecomputed: 30 usable plus network and broadcast
Netmask as wildcard0.0.0.255 intended, 255.255.255.0 writtenConverted range covers the wrong bits entirely
Reserved space usedDocumentation range 192.0.2.0/24 in productionCheck against the special-purpose registry

05 Wildcard mask arithmetic, checked by hand and by tool

A wildcard mask is the bitwise inverse of a netmask: a 0 bit means the address bit must match, a 1 bit means it is ignored. For a contiguous prefix the wildcard is 255.255.255.255 minus the netmask, so a /22 (netmask 255.255.252.0) has wildcard 0.0.3.255 and matches a block of 1,024 addresses. The two errors generated ACLs make most are pasting the netmask where the wildcard belongs, and pairing a correct wildcard with a base address that is not aligned to it.

Netmask plus wildcard always sums to 255 in every octet; a row that does not is wrong.
PrefixNetmaskWildcard maskBlock sizeMatches from 10.20.4.0
/22255.255.252.00.0.3.2551,02410.20.4.0 to 10.20.7.255
/23255.255.254.00.0.1.25551210.20.4.0 to 10.20.5.255
/24255.255.255.00.0.0.25525610.20.4.0 to 10.20.4.255
/25255.255.255.1280.0.0.12712810.20.4.0 to 10.20.4.127
/26255.255.255.1920.0.0.636410.20.4.0 to 10.20.4.63
/27255.255.255.2240.0.0.313210.20.4.0 to 10.20.4.31

The second error is subtler. An entry of permit ip 10.20.4.0 0.0.7.255 any looks like the /22 above with one digit off. The wildcard 0.0.7.255 ignores the low three bits of the third octet, and 4 is binary 00000100, so the entry matches 10.20.0.0 through 10.20.7.255: twice the intended range, including whatever lives in 10.20.0.0/22. Some platforms normalise the base address silently; others accept the entry as typed and match exactly this. Neither warns you.

Converting a wildcard entry back to a range, illustrative
$ python3 - <<'EOF'
import ipaddress as ip
def wild(base, wc):
    b, w = int(ip.ip_address(base)), int(ip.ip_address(wc))
    lo = b & ~w & 0xFFFFFFFF
    return ip.ip_address(lo), ip.ip_address(lo | w), bin(w).count('1')
print(wild('10.20.4.0', '0.0.3.255'))
print(wild('10.20.4.0', '0.0.7.255'))
print(wild('10.20.4.0', '255.255.252.0'))
EOF
(IPv4Address('10.20.4.0'), IPv4Address('10.20.7.255'), 10)
(IPv4Address('10.20.0.0'), IPv4Address('10.20.7.255'), 11)
(IPv4Address('0.0.0.0'), IPv4Address('255.255.252.0'), 22)

The first result confirms the intended /22. The second shows the one-digit slip doubling the range downwards to 10.20.0.0, with eleven ignored bits where ten were meant. The third, the netmask pasted as a wildcard, produces a range that means nothing, which is why the check must print the range and never just accept that the entry parsed. Wildcard masks need not be contiguous. 10.20.0.0 0.0.254.255 matches every even-numbered third octet under 10.20, which no prefix length can express; when a generated ACL uses one, check it by listing the ignored bit positions rather than by converting to a prefix.

06 Keeping the check in the change process

Make the recomputation part of review, not an optional extra: attach the tool output to the change ticket so the reviewer sees the numbers checked, not just the plan. The same principle runs through peer review for AI-assisted network changes: generated work is welcome, unverified generated work is not.

RULE OF THUMB

If a number decides where a packet goes, a tool must have computed it or checked it. A model may have suggested it.

07 Pitfalls in checking generated address plans

  • Point-to-point links. RFC 3021 allows a /31 with two usable addresses and no broadcast. A model that applies the usual minus-two rule reports zero hosts and may "fix" the link to a /30.
  • IPv6 alignment. Generated IPv6 plans put subnets on boundaries that are not nibble-aligned, or list a /64 with a host count. Parse them with the same strict library; the alignment rule is identical in 128 bits.
  • Gateway assumptions. Plans assign the first usable address as the gateway. Confirm it against the site convention before the DHCP scope excludes the wrong address.
  • Reserved space by accident. Models trained on documentation reproduce 192.0.2.0/24 and 203.0.113.0/24 as if they were free. The IANA special-purpose registry (RFC 6890) is the check.
  • Mixed notations. One ACL with prefix lengths on some lines and wildcards on others invites a mixed-up mask. Normalise to one form before review.
  • Rounding in summaries. A summary route generated to cover several subnets is often one bit too wide. Recompute the supernet from the members and compare.

08 Expert tips

  • Ask for the plan as one prefix per line, without prose; it pastes straight into the checker and gives the model less room to explain an error away.
  • Print every range as first address to last address in the ticket. A reviewer sees an overlap in two adjacent lines faster than in any table of prefixes.
  • Test ACLs with three addresses per entry, the first, the last and one just outside, in a lab device or a policy tool, and attach the results.
  • Keep the parent block in the check. A subnet that is aligned and non-overlapping but outside the parent is still wrong, and it is the error strict parsing cannot see.
  • Record the failures. Which errors a tool makes, and how often, decides how much of its output can be trusted next time; the addressing section of the dual-stack transition guide is where the IPv6 side of the same checks starts.

09 Questions

Can I trust AI chatbots with subnetting?

Use them for a first draft, never as the final answer. Recompute every prefix with a CIDR library or calculator and check alignment, containment and overlap.

How do I check a subnet is aligned?

Parse it strictly with a CIDR library. The network address must be a multiple of the block size; a strict parser raises an error when host bits are set.

What is a wildcard mask?

The bitwise inverse of a netmask, used in many ACL syntaxes. 0.0.0.255 matches a /24; writing the netmask 255.255.255.0 there matches something entirely different.

Which address ranges should never appear in production plans?

Documentation ranges such as 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 (RFC 5737), and other special-purpose blocks listed in the IANA registry described by RFC 6890.

How do I test an ACL before deployment?

Run the first and last address of each intended range and one just outside through a lab device or a configuration analysis tool, and compare the results with the intent.

Can a wildcard mask be non-contiguous?

Yes, in most ACL syntaxes. A wildcard such as 0.0.254.255 keeps one bit in the third octet and ignores the rest, so it matches alternating /24 blocks that no prefix length can express. Generated ACLs rarely intend this. Convert each entry to its matched range, or list the ignored bit positions, before approving it.