Important
This project is no longer maintained and the repository is archived.
The code and the published dataset are still here and still usable. The last
GeoLite2 refresh was GeoLite2-City-CSV_20260220, and there will not be
another one, so lookups will drift further from reality over time.
The repository can be unarchived, but only for a named maintainer who commits to keeping it up. If that is you, reach out to the IPFS Foundation.
GeoIP lookup over IPFS
npm install --save ipfs-geoipInstead of a local installation (and browserification) you may request a specific
version N.N.N as a remote copy from jsDelivr:
<script type="module">
import { lookup } from 'https://cdn.jsdelivr.net/npm/ipfs-geoip@N.N.N/dist/index.min.js';
const gateway = 'https://trustless-gateway.link'
console.log(await lookup(gateway, '66.6.44.4'))
</script>The response in the console should look similar to:
{
"country_name": "USA",
"country_code": "US",
"region_code": "VA",
"city": "Ashburn",
"postal_code": "20149",
"latitude": 39.0469,
"longitude": -77.4903,
"planet": "Earth"
}If gateways is a string or array of strings with public gateway URLs, it will be used for
fetching IPFS blocks as application/vnd.ipld.raw
and parsing them as DAG-CBOR locally via @ipld/dag-cbor:
const geoip = require('ipfs-geoip')
const exampleIp = '66.6.44.4'
const gateways = ['https://trustless-gateway.link', 'https://example.com']
try {
const result = await geoip.lookup(gateways, exampleIp)
console.log('Result: ', result)
} catch (err) {
console.log('Error: ' + err)
}
try {
const result = await geoip.lookupPretty(gateways, '/ip4/' + exampleIp)
console.log('Pretty result: %s', result.formatted)
} catch (err) {
console.log('Error: ' + err)
}It is also possible to use it with local or remote IPFS node by passing block getter function, e.g., one that exposes
ipfs.block.get Core JS API:
const geoip = require('ipfs-geoip')
const exampleIp = '66.6.44.4'
const ipfs = require('ipfs-http-client')()
try {
const getBlock = (cid) => ipfs.block.get(cid)
const result = await geoip.lookup(getBlock, exampleIp)
console.log('Result: ', result)
} catch (err) {
console.log('Error: ' + err)
}Returns a promise that resolves to an object of the form
{
"country_code": "US",
"country_name": "USA",
"region_code": "CA",
"city": "Mountain View",
"postal_code": "94040",
"latitude": 37.3860,
"longitude": -122.0838,
"planet": "Earth"
}Provides the same results as lookup with the addition of
a formatted property that looks like this: Mountain View, CA, United States, Earth.
The lookup dataset is stored as DAG-CBOR blocks on IPFS.
Both IPv4 and IPv6 addresses are supported. IPv4 addresses are mapped into
IPv4-mapped IPv6 space (::ffff:a.b.c.d) so that all keys are 128-bit.
A root metadata node ties everything together:
{
version: 2, // data format version
indexRoot: CID, // root of the prolly tree index
locationTableRoot: CID, // root of the sharded location table
entryCount: Number, // total index entries (IPv4 + IPv6)
locationCount: Number, // unique locations
pageSize: 256 // entries per location page
}IP-to-location mapping is stored in a prolly tree
(a deterministically-chunked search tree). Each entry maps a 128-bit IP key
(the start of a CIDR range, as a 16-byte Uint8Array) to a value of
[location_id, end_key] where end_key is the last IP in the CIDR range.
Lookup traverses the tree from root to leaf, doing a floor search (greatest
key <= the queried IP) at each level. After finding a match, the queried IP
is checked against end_key to verify it falls within the CIDR range.
IPs that fall in gaps between ranges get an "Unmapped range" error.
Block format:
- Branch:
{ branch: [distance, [[key, cid], ...]], closed } - Leaf:
{ leaf: [[key, value], ...], closed }
A typical lookup fetches 3-4 blocks: root metadata, 1-2 branch nodes, and a leaf.
Locations are stored in a flat array split into pages of 256 entries each.
The locationTableRoot block contains an array of CIDs, one per page.
A location_id maps to page floor(id / 256), offset id % 256.
Each location entry is an array:
[country_name, country_code, region_code, city, postal_code, latitude, longitude]A lookup fetches two additional blocks: the page CID array and the specific page.
The current root hash for lookups is defined under GEOIP_ROOT in src/lookup.js.
It is generated from GeoLite2 CSV source files fetched from the DATA_HASH
directory defined in src/generate/index.js.
Updating the dataset is a two-step process: first fetch fresh CSV files from MaxMind, then rebuild the lookup index.
Requires a free MaxMind GeoLite2 account and a running IPFS daemon.
$ MAXMIND_ACCOUNT_ID=<id> MAXMIND_LICENSE_KEY=<key> npm run update-datasetThis downloads the latest GeoLite2-City-CSV zip, extracts the three needed
CSV files, adds them to IPFS (CIDv1, 1 MiB chunks), exports a
geolite2-city-csv.car, and updates DATA_HASH in src/generate/index.js.
$ npm run generateThis reads the CSV files from the DATA_HASH directory on IPFS, builds the
prolly tree index and sharded location table, and writes all blocks as DAG-CBOR
into ipfs-geoip.car.
The root CID is printed to the terminal. It should then be imported to IPFS,
pinned in multiple locations, and stored as the new GEOIP_ROOT in
src/lookup.js.
It is possible to run tests against a local gateway by passing IPFS_GATEWAY:
$ IPFS_GATEWAY="http://127.0.0.1:8080" npm testYou can find an example of how to use this in example/lookup.js, which you can use like this:
$ export IPFS_GATEWAY="http://127.0.0.1:8080"
$ node example/lookup.js 66.6.44.4
Result: {
"country_name": "USA",
"country_code": "US",
"region_code": "NY",
"city": "New York",
"postal_code": "10004",
"latitude": 40.7126,
"longitude": -74.0066,
"planet": "Earth"
}
Pretty result: New York, NY, USA, EarthThis repository is archived and is not accepting contributions. Drive-by patches will not reopen it. Unarchiving requires someone willing to be named as maintainer and to keep the dataset refreshed.
If that is you, contact the IPFS Foundation. Forking is fine too; the Maintenance section above documents how to refresh the dataset.
The IPFS Code of Conduct still applies.
ipfs-geoip is MIT licensed.
This library includes GeoLite2 data created by MaxMind, available from maxmind.com.
