ipfs-geoip
v9.3.0
Published
Library for ipfs geoip lookups
Readme
IPFS GeoIP
GeoIP lookup over IPFS
Table of Contents
Install
NPM
npm install --save ipfs-geoipCDN
Instead 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/[email protected]/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"
}Usage
With public gateways (default)
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)
}With custom block getter function
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)
}API
lookup(ipfs, ip)
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"
}lookupPretty(ipfs, multiaddrs)
Provides the same results as lookup with the addition of
a formatted property that looks like this: Mountain View, CA, United States, Earth.
Data Structures
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
}Prolly tree index
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.
Sharded location table
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.
Maintenance
CIDs of the lookup dataset
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 GeoLite2 dataset
Updating the dataset is a two-step process: first fetch fresh CSV files from MaxMind, then rebuild the lookup index.
1. Fetch new CSV data
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.
2. Rebuild the lookup index
$ 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.
Testing in CLI
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, EarthContribute
Feel free to join in. All welcome. Open an issue!
This repository falls under the IPFS Code of Conduct.
Want to hack on IPFS?
License
ipfs-geoip is MIT licensed.
This library includes GeoLite2 data created by MaxMind, available from maxmind.com.

