Plate 35
ipaddress vs String Prefix Allowlist: Localhost Lab
Hands-on ipaddress CIDR containment vs naive string prefix allowlists: real checks/s, measured on Linux localhost today in this hands-on lab for SREs.
Aditya Challa3 min read
Intro — what this post promises
Check whether client IPs fall in allowlist CIDRs via ipaddress vs naive str.startswith vs a precomputed int bitmask. This lab reports checks/s on Linux localhost.
Related links:
chainmap vs dict merge localhost lab
tomllib vs json localhost lab
path glob vs fnmatch localhost lab
dataclass replace vs manual localhost lab
zoneinfo vs utc offset localhost lab
heapq merge vs sorted localhost lab
islice vs list slice localhost lab
stat vs path stat localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. 20000 IPv4 strings; CIDRs 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 203.0.113.0/24.
Verdict up front: int bitmask ~6853718 checks/s; string startswith ~6223453; ipaddress (pre-parsed) ~2589147; parse-each-str ~508942. Prefer ipaddress for correctness; bitmask if you own the hot path and pre-parse.
Arms
ArmNotesip in IPv4Networkpre-parsed IPv4Addressip_address(s) each timeparse tax(ip_int & mask) == networkbitmasks.startswith(prefix)fragile footgun
Lab topology
n=20000 · 4 CIDRs · 7 rounds · p50
metric: checks/s = n / p50_s
Script: lab-evidence/119-ipaddress-vs-string-prefix/results/run_lab.py.
Lead table (p50 checks/s)
Armchecks/sint bitmask6853718string startswith6223453ipaddress pre-parsed2589147ipaddress parse each508942
Bitmask matched ipaddress hits (15000). String matched this fixture’s count by luck but is unsafe for 172.16.0.0/12 — 172.32.1.1 is outside yet can match a sloppy "172.3" prefix.
Footgun detail
172.16.0.0/12 covers 172.16.0.0–172.31.255.255. A prefix like "172.3" also matches 172.32.0.0/16. Equal hit counts on a lucky synthetic mix are not a correctness proof — the counterexample disagrees.
Hot-path recipe
- Parse CIDRs once to
ip_network(or bitmask tuples). - Parse request IP once to
ip_address/ int. - Loop networks with
ip in netor bitmask. - Skip string ops entirely in the deny/allow decision.
Reading it
- Allowlists / deny lists →
ipaddress(or bitmask built from it). - Never ship prefix strings for non-/8-aligned CIDRs.
- Parse once at the edge (
IPv4Address), check many networks. - IPv6 needs the same module — string prefixes get worse.
IPv6 and dual-stack
String prefixes collapse completely for IPv6 compressed forms (2001:db8::1). ipaddress normalizes representation; keep allowlists as CIDR objects, not display strings. Dual-stack frontends should parse once and branch on version.
Where bitmask fits
Edge proxies that already store IPs as ints (or can atomically convert) can keep a tiny tuple list (network, mask) beside the ip_network objects used for admin UI. Build bitmasks from ipaddress, never from hand-written hex.
Ops review checklist
Reject PRs that gate admin/VPN access on ip.startswith("10.") alone. Require CIDR lists, tests including 172.31 vs 172.32, and a documented parser (ipaddress or equivalent).
Pitfalls
- Treating RFC1918 as a handful of
startswithstubs. - Re-parsing the same IP string in an inner loop.
- Forgetting IPv4-mapped IPv6 forms.
- Trusting equal hit counts on a lucky synthetic mix.
Reproduce
python3 lab-evidence/119-ipaddress-vs-string-prefix/results/run_lab.py
Evidence: summary.json, summary.txt.
Measurement note
Checks/s counts one allowlist decision per synthetic client IP against four CIDRs (first match wins). Absolute rates move with CIDR count and hit position — put frequent office/VPN ranges earlier in the list after you measure production histograms.
Limits
One Linux box. IPv4 only. Four static CIDRs. Not radix/trie multi-million prefix tables.
Takeaway
Pre-parsed ipaddress ~2589147 checks/s; bitmask ~6853718 if you need more speed. Keep string prefixes out of production CIDR checks — correctness beats a lucky microbench tie.
Lab evidence
What I found running this
Ran the supplied ipaddress allowlist benchmark on the Linux localhost fixture and checked the rendered body against the source. The correctness footgun was the important result: a lucky equal hit count does not make a string prefix safe for non-aligned CIDRs. I verified the comparison table and related links render as intended.