- PHP 68.6%
- JSON 16.5%
- Markdown 14%
- XML 0.9%
Geo & ASN Data for Kipchak
Bundled IP-to-country and IP-to-ASN databases in MaxMind DB (MMDB) format, plus a small helper for locating them and normalising the records they return.
This package carries data only — it does no lookups itself. Subashi Pro consumes it for its geo_country and asn firewall conditions. It replaces the previous dependency on the 7x geolocation API, so geo and ASN matching now happen entirely in-process with no network call on the request path.
It is part of the Kipchak Enterprise offering. It is not sold separately: a licence that includes Subashi Pro includes this package.
Composer Package
kipchak/data-geo-asn, from the Kipchak Enterprise repository at https://php.pkgs.1x.ax. Composer installs it with Subashi Pro, so it is rarely required directly.
What's bundled
Two databases back each lookup type. They are consulted in order: if the first has no record for an address, the second is tried; if neither does, the lookup returns null and the caller is expected to treat that as "unknown" rather than as a match.
| Lookup | Order | Database | Provider | Licence | Upstream cadence | Size |
|---|---|---|---|---|---|---|
| Country | 1st | dbip-country.mmdb | DB-IP IP to Country Lite | CC BY 4.0 | Monthly | 8.0 MB |
| Country | 2nd | iptoasn-country.mmdb | iptoasn.com | PDDL 1.0 | Daily | 5.0 MB |
| ASN | 1st | iptoasn-asn.mmdb | iptoasn.com | PDDL 1.0 | Daily | 9.2 MB |
| ASN | 2nd | dbip-asn.mmdb | DB-IP IP to ASN Lite | CC BY 4.0 | Monthly | 9.1 MB |
iptoasn leads on ASN because it is derived from BGP announcements and rebuilt daily, so it reflects what is actually routed. DB-IP leads on country because it places addresses by observed location rather than by registration — for anycast prefixes the difference is visible (DB-IP puts 1.1.1.1 in AU, iptoasn in US).
MaxMind GeoLite2 is deliberately not bundled. Including GeoLite2 in a commercial product requires a Commercial Redistribution License from MaxMind. Both databases here are freely redistributable.
data/manifest.json records the version, build time, source URL, upstream build date, node count, size, SHA-256 and licence of every file.
Versioning
CalVer, YYYY.MM.DD, matching the day the build ran. A weekly Concourse job publishes a new version. Depend on it with a lower bound only, so composer update always picks up the newest build:
"kipchak/data-geo-asn": ">=2026.9"
Do not use a caret here. ^2026.9 stops matching at 2027.01.04, so the data would silently freeze at the last build of the year until somebody noticed and bumped the constraint. There is no API to break — the package is four data files behind a stable helper — so an upper bound buys nothing.
Usage
use Kipchak\Data\GeoAsn\GeoAsnData;
use MaxMind\Db\Reader;
foreach (GeoAsnData::asnDatabases() as $path) {
$reader = new Reader($path);
$asn = GeoAsnData::asnFromRecord($reader->get('212.58.244.20'));
if ($asn !== null) {
break; // AS2818
}
}
The two country databases do not share a record shape — DB-IP uses the GeoLite2 layout (country.iso_code), iptoasn a flat country_code. GeoAsnData::countryFromRecord() accepts either, so callers never need to know which database answered.
| Method | Returns |
|---|---|
GeoAsnData::countryDatabases() | Ordered list of absolute paths to the country databases |
GeoAsnData::asnDatabases() | Ordered list of absolute paths to the ASN databases |
GeoAsnData::path(string $database) | Absolute path to one database by name; throws if unknown or missing |
GeoAsnData::countryFromRecord(mixed $record) | ISO 3166-1 alpha-2 code, or null |
GeoAsnData::asnFromRecord(mixed $record) | AS number as int, or null |
GeoAsnData::organisationFromRecord(mixed $record) | AS organisation name, or null |
GeoAsnData::manifest() | Full build manifest as an array |
GeoAsnData::version() | Build version, e.g. 2026.09.06 |
GeoAsnData::attributions() | Attribution lines required by the bundled licences |
Reading the databases
Any MMDB reader works. maxmind-db/reader is a require-dev here rather than a hard dependency so that consumers choose their own; Subashi Pro requires it directly.
The pure-PHP reader costs roughly 0.1–0.5 ms per lookup. Under FrankenPHP worker mode the Reader should be constructed once at boot and reused across requests — Subashi Pro does this. Where lookup volume justifies it, maxmind-db/reader-ext is a drop-in C extension that is substantially faster.
Do not decompress the databases into memory. They are shipped uncompressed precisely so the reader can seek within the file.
Building
ci/build.php downloads, verifies and manifests the databases. ci/sources.json holds the source URLs, licences and per-database sanity thresholds.
composer install
php ci/build.php # download, verify, rewrite data/manifest.json
php ci/build.php --verify # verify what is already on disk, rewrite the manifest
The build fails, rather than publishing, if any database is older than its cadence allows, has fewer nodes than expected (which is what a truncated download looks like), is missing IPv6 coverage, or returns the wrong answer for a set of known addresses. Every deployment's firewall reads these files, so a silently corrupt build would fail open everywhere at once.
Run ./vendor/bin/phpunit to check the on-disk files against the manifest checksums and the same known-address expectations.
Pipeline
The Concourse pipeline lives in the concourse-pipelines repo as pipelines/mamluk/kipchak-geo-asn.yml:
fly -t mamluk set-pipeline --pipeline kipchak-geo-asn --team mamluk \
--config pipelines/mamluk/kipchak-geo-asn.yml --check-creds
It runs Monday mornings: rebuilds, runs the test suite, commits, and pushes the branch and a YYYY.MM.DD tag through the git resource, then notifies Packeton. ((github.private_key)) needs write access to this repository, which most of the other pipelines' keys do not.
It tags every week, even when the databases come back byte-identical. ci/build.php has already proved the data is fresh and correct by that point, so the tag honestly means "verified good on this date", and git stores identical blobs once — an unchanged week costs a few hundred bytes, not 31 MB.
Bounding the repository
A week where the data genuinely changes adds around 31 MB of largely incompressible blobs, so the pack grows by roughly 1.6 GB a year. Tags pin those blobs, so git gc alone will not reclaim them. Old releases have no value here — nobody wants last spring's geolocation data — so prune them occasionally:
CUTOFF=$(date -u -d '-12 weeks' +%Y.%m.%d)
for TAG in $(git tag -l '20*.*.*'); do
[ "$(printf '%s\n%s\n' "$TAG" "$CUTOFF" | sort | head -1)" = "$TAG" ] || continue
[ "$TAG" = "$CUTOFF" ] && continue
git push origin ":refs/tags/$TAG" && git tag -d "$TAG"
done
git gc --prune=now --aggressive
This is deliberately not in the pipeline: deleting remote refs is outside what the git resource does, and it is not worth breaking the publish path's shape for something that needs running a couple of times a year.
Licences and attribution
The helper code in src/ is proprietary and may be used only under a current Kipchak Enterprise licence (see LICENCE). The databases in data/ keep their upstream licences.
DB-IP data (dbip-country.mmdb, dbip-asn.mmdb) is licensed CC BY 4.0 and requires attribution. Anything that displays results derived from it must carry:
IP geolocation by DB-IP
For a firewall that only accepts or rejects requests there is nothing user-facing to attribute, so this README and NOTICE satisfy the requirement. If you surface country or ASN data in an API response or UI, reproduce the line there too. GeoAsnData::attributions() returns it programmatically.
iptoasn.com data (iptoasn-country.mmdb, iptoasn-asn.mmdb) is PDDL 1.0 — public domain, no attribution required. The MMDB builds come from sapics/ip-location-db.