Fast, offline IPv4/IPv6 to ISO 3166-1 alpha-2 country code lookup. One Rust core with Python and WASM bindings, plus a CLI.
Important
iptocc 3.0 is a complete Rust rewrite. Versions 2.x and earlier were pure Python on top of pandas. 3.x is a standalone Rust crate with Python and WASM bindings. The Python public API stays compatible for most uses (see Migrating from 2.x).
Note
Country codes reflect the country assigned by a Regional Internet Registry (RIR) to each IP block, not where the block is being used. RIR data agrees with MaxMind for ~95% of IPv4 addresses and has minimal discrepancies for IPv6 (Zander, 2012).
- Offline lookup, no external API calls.
- IPv4 and IPv6 in a single call.
- Accepts a single address or a batch of addresses.
- Lookup data embedded in the binary; no runtime file I/O.
- ~1-13 ns on native Rust (typed input); ~44-141 ns through Python, ~200-300 ns through WASM.
iptoccCLI installed viapip,cargo, ornpm.- Data refreshed nightly from the five Regional Internet Registries.
pip install iptocccargo add iptocc # library
cargo install iptocc # CLI binarynpm install @roniemartinez/iptoccfrom iptocc import country_code
# Single lookup
country_code("8.8.8.8") # "US"
country_code("2001:4860:4860::8888") # "US"
country_code("10.0.0.0") # None
# Batch lookup (any iterable of strings)
country_code(["8.8.8.8", "1.0.16.1", "10.0.0.0"])
# ["US", "JP", None]use iptocc::{country_code, country_codes};
let cc = country_code("8.8.8.8");
assert_eq!(cc, Some("US"));
let codes = country_codes(["8.8.8.8", "1.0.16.1", "10.0.0.0"]);
assert_eq!(codes, vec![Some("US"), Some("JP"), None]);const { country_code } = require("@roniemartinez/iptocc");
country_code("8.8.8.8"); // "US"
country_code(["8.8.8.8", "1.0.16.1"]); // ["US", "JP"]$ iptocc 8.8.8.8
US
$ iptocc 8.8.8.8 1.0.16.1 10.0.0.0 193.0.6.139
8.8.8.8 US
1.0.16.1 JP
10.0.0.0 -
193.0.6.139 NLCompared below against ip_to_country and the legacy iptocc 2.x line.
| Feature | iptocc 2.x (legacy) | ip_to_country | iptocc 3.x |
|---|---|---|---|
| IPv4 | ✅ | ✅ | ✅ |
| IPv6 | ✅ | ❌ | ✅ |
| Offline lookup | ✅ | ✅ | ✅ |
| Batch API | ❌ | ❌ | ✅ |
| CLI | ❌ | ❌ | ✅ |
| Python | ✅ | ✅ | ✅ |
| Rust | ❌ | ❌ | ✅ |
| WASM | ❌ | ❌ | ✅ |
| Nightly data refresh | ❌ | ✅ | ✅ |
Apple M3 Pro, per-call latency on a single v4 hit. time.perf_counter() for Python, Criterion for the Rust core.
| Library | 1 lookup | vs baseline |
|---|---|---|
| ip_to_country (Python bisect) | ~1.2 us | 1x |
| iptocc 2.1.2 (legacy, pandas filter) | ~78 ms | ~65,000x slower |
| iptocc 3.x Python | ~100 ns | ~12x faster |
| iptocc Rust | ~1.3 ns | ~900x faster |
The 2.1.2 row is for historical context. The Python binding gets most of the way to the Rust core's speed; the remaining gap is per-call FFI overhead.
See BENCHMARK.md for the full per-RIR breakdown and the Rust-core / WASM numbers.
The legacy iptocc.get_country_code(ip) is now iptocc.country_code(ip). The return value is still str | None on a hit or miss.
# 2.x
from iptocc import get_country_code, CountryCodeNotFound
try:
cc = get_country_code("8.8.8.8")
except CountryCodeNotFound:
cc = None
# 3.x
from iptocc import country_code
cc = country_code("8.8.8.8") # returns None on miss, no exceptionLookups are based on the delegated extended statistics published by the five Regional Internet Registries:
| RIR | URL |
|---|---|
| AFRINIC | https://ftp.afrinic.net/stats/afrinic/delegated-afrinic-extended-latest |
| ARIN | https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest |
| APNIC | https://ftp.apnic.net/public/apnic/stats/apnic/delegated-apnic-extended-latest |
| LACNIC | https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest |
| RIPE NCC | https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest |
A nightly GitHub Action fetches fresh data, rebuilds the lookup tables, and publishes new releases automatically.
Thanks goes to these wonderful people (emoji key):
Ronie Martinez 💻 📖 🚇 🚧 👀 |
Tate Barber 💻 📖 🚇 |
mathgeek12 🐛 |
James Dolan 🤔 |
This project follows the all-contributors specification. Contributions of any kind welcome!