This commit is contained in:
kastov 2026-03-30 17:32:44 +03:00
commit 814dc63735
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
9 changed files with 668 additions and 0 deletions

61
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,61 @@
name: Build ASN Index
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build asn-prefixes.json
run: bash scripts/build.sh
- name: Build LMDB database
run: node scripts/build-lmdb.mjs
- name: Print stats
run: |
echo "=== ASN Index Stats ==="
ASN_COUNT=$(jq 'keys | length' ./dist/asn-prefixes.json)
echo "ASNs indexed: ${ASN_COUNT}"
echo ""
echo "Artifact sizes:"
ls -lh ./dist/asn-prefixes.json ./dist/asn-prefixes-lmdb.tar.gz
- name: Force-update latest tag
run: |
git tag -f latest
git push origin latest --force
- name: Create or update release
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: Latest ASN Index
body: |
Auto-generated ASN prefix index, updated daily from [ipverse/as-ip-blocks](https://github.com/ipverse/as-ip-blocks).
**Last updated:** ${{ github.run_id }} — see workflow run for details.
files: |
./dist/asn-prefixes.json
./dist/asn-prefixes-lmdb.tar.gz
make_latest: true
prerelease: false

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
dist/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Remnawave
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

83
README.md Normal file
View file

@ -0,0 +1,83 @@
# asn-index
Daily-updated index of ASN → IP prefix mappings, available in JSON and LMDB formats. Built from [ipverse/as-ip-blocks](https://github.com/ipverse/as-ip-blocks) and published as GitHub Release assets.
## Downloads
| Format | URL |
|--------|-----|
| JSON | https://github.com/remnawave/asn-index/releases/latest/download/asn-prefixes.json |
| LMDB (tar.gz) | https://github.com/remnawave/asn-index/releases/latest/download/asn-prefixes-lmdb.tar.gz |
## Data format
### asn-prefixes.json
A single JSON object where each key is an ASN number (as a string) and the value contains IPv4 and IPv6 prefix lists:
```json
{
"13335": {
"ipv4": ["1.0.0.0/24", "1.1.1.0/24"],
"ipv6": ["2400:cb00::/32", "2606:4700::/32"]
},
"15169": {
"ipv4": ["8.8.8.0/24"],
"ipv6": ["2001:4860::/32"]
}
}
```
### asn-prefixes.lmdb
An LMDB database with:
- **Key**: ASN number (`number`)
- **Value**: `{ ipv4: string[], ipv6: string[] }` (MessagePack-serialized via lmdb-js)
## Usage
### JSON
```js
import { readFileSync } from 'node:fs';
const data = JSON.parse(readFileSync('asn-prefixes.json', 'utf8'));
// Direct object lookup
const cloudflare = data['13335'];
console.log(cloudflare.ipv4); // ["1.0.0.0/24", ...]
// Convert to Map for repeated lookups
const asnMap = new Map(Object.entries(data));
console.log(asnMap.get('13335').ipv6);
```
### LMDB
```js
import { open } from 'lmdb';
// Extract asn-prefixes-lmdb.tar.gz first, then:
const db = open({
path: './asn-prefixes.lmdb',
encoding: 'msgpack',
readOnly: true,
});
const cloudflare = db.get(13335);
console.log(cloudflare.ipv4); // ["1.0.0.0/24", ...]
db.close();
```
## Update frequency
The index is rebuilt **daily at 04:00 UTC** via a scheduled GitHub Actions workflow. Each run downloads the latest snapshot from ipverse/as-ip-blocks and overwrites the `latest` release assets.
## Data source
IP prefix data is sourced from [ipverse/as-ip-blocks](https://github.com/ipverse/as-ip-blocks), which aggregates routing data from public BGP sources.
## License
MIT

292
package-lock.json generated Normal file
View file

@ -0,0 +1,292 @@
{
"name": "asn-index",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "asn-index",
"version": "1.0.0",
"dependencies": {
"lmdb": "^3.5.2"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@harperfast/extended-iterable": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz",
"integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==",
"license": "Apache-2.0"
},
"node_modules/@lmdb/lmdb-darwin-arm64": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.2.tgz",
"integrity": "sha512-ZTEuSwB3QHOA6Jal4bi0oxAV1MK3xtzS3oUMq5OK3HSXNN4A79f9dhieZA0hgvwOTaGzEWmTd8Fg9XSkBIhAWw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@lmdb/lmdb-darwin-x64": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.2.tgz",
"integrity": "sha512-Zs+mdB6gNqpPK6ybNbqFoSU+DCIdhE8tqeaAzRs+hNJt8V43PRvTVxu1UPBHwK2917FnQ4dL5/OIoqHDa+9Dpw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@lmdb/lmdb-linux-arm": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.2.tgz",
"integrity": "sha512-GhdC4huGWDzcbZWfS+G3dW4/TopNUnO+/E7aVdfWIhslSs1FI2+sVo94040S9BPJ7lNpnf1zVxaBlLmqZpKhcw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@lmdb/lmdb-linux-arm64": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.2.tgz",
"integrity": "sha512-hT6JPw5hDCXzppBgpIFS/cQp4v2LqNMgd5nuo4U9H5/wnbMS7Prh0twu5IbDvzYZf2a/xPTXtTDRuUiFc39lEw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@lmdb/lmdb-linux-x64": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.2.tgz",
"integrity": "sha512-aTBBxTQGdgKcqZD6ywIVCIbCIJ3fJ28OhzCxgl3zGQzzJwkDt5TSIuBtMt4oKZMgDSjuRBjtID9TOUvSRg8IQA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@lmdb/lmdb-win32-arm64": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.2.tgz",
"integrity": "sha512-mqfNN5zb3z3QnHEPaV4Zv5zd3BhlcL+uqPNF7kGRkmCaRHuh6T9N5g/4ZqOiNHNPWglv3g8Ut15XxCKZjf6jHw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@lmdb/lmdb-win32-x64": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.2.tgz",
"integrity": "sha512-JhPxlA8sIxPIdS78e4LeNfTlkF+2I/r98jKXf90pf+yhMCzyLkphcvbnWv7YL8yckp32c1uKZ1vf/JqcSiplHg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/lmdb": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.2.tgz",
"integrity": "sha512-od5AWh1MNylIOeX7MB7TV627MM9tzyOUn8U8FZOeWKpWFnMU5FS9pu5t41pS4+pi7OxHRyk5QVRhuUimHjfkmg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@harperfast/extended-iterable": "^1.0.3",
"msgpackr": "^1.11.2",
"node-addon-api": "^6.1.0",
"node-gyp-build-optional-packages": "5.2.2",
"ordered-binary": "^1.5.3",
"weak-lru-cache": "^1.2.2"
},
"bin": {
"download-lmdb-prebuilds": "bin/download-prebuilds.js"
},
"optionalDependencies": {
"@lmdb/lmdb-darwin-arm64": "3.5.2",
"@lmdb/lmdb-darwin-x64": "3.5.2",
"@lmdb/lmdb-linux-arm": "3.5.2",
"@lmdb/lmdb-linux-arm64": "3.5.2",
"@lmdb/lmdb-linux-x64": "3.5.2",
"@lmdb/lmdb-win32-arm64": "3.5.2",
"@lmdb/lmdb-win32-x64": "3.5.2"
}
},
"node_modules/msgpackr": {
"version": "1.11.9",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz",
"integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==",
"license": "MIT",
"optionalDependencies": {
"msgpackr-extract": "^3.0.2"
}
},
"node_modules/msgpackr-extract": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build-optional-packages": "5.2.2"
},
"bin": {
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
},
"optionalDependencies": {
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
}
},
"node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"license": "MIT"
},
"node_modules/node-gyp-build-optional-packages": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.1"
},
"bin": {
"node-gyp-build-optional-packages": "bin.js",
"node-gyp-build-optional-packages-optional": "optional.js",
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
"node_modules/ordered-binary": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz",
"integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==",
"license": "MIT"
},
"node_modules/weak-lru-cache": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz",
"integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==",
"license": "MIT"
}
}
}

18
package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name": "asn-index",
"version": "1.0.0",
"description": "Daily-updated ASN prefix index in JSON and LMDB formats",
"type": "module",
"private": true,
"scripts": {
"build:json": "bash scripts/build.sh",
"build:lmdb": "node scripts/build-lmdb.mjs",
"build": "npm run build:json && npm run build:lmdb"
},
"dependencies": {
"lmdb": "^3.5.2"
},
"engines": {
"node": ">=24"
}
}

44
scripts/build-lmdb.mjs Normal file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env node
import { open } from 'lmdb';
import { readFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { resolve } from 'node:path';
const DIST_DIR = resolve('./dist');
const JSON_PATH = resolve(DIST_DIR, 'asn-prefixes.json');
const LMDB_PATH = resolve(DIST_DIR, 'asn-prefixes.lmdb');
const TAR_PATH = resolve(DIST_DIR, 'asn-prefixes-lmdb.tar.gz');
console.log('=== LMDB Builder ===');
console.log(`Reading ${JSON_PATH}...`);
const data = JSON.parse(readFileSync(JSON_PATH, 'utf8'));
const entries = Object.entries(data);
console.log(`Loaded ${entries.length} ASN entries.`);
console.log(`Opening LMDB at ${LMDB_PATH}...`);
const db = open({
path: LMDB_PATH,
mapSize: 256 * 1024 * 1024, // 256 MB
encoding: 'msgpack',
});
console.log('Writing entries...');
await db.transaction(() => {
for (const [asn, prefixes] of entries) {
db.put(Number(asn), prefixes);
}
});
const count = db.getCount();
console.log(`Written ${count} entries to LMDB.`);
await db.close();
console.log('LMDB closed.');
console.log(`Creating tarball: ${TAR_PATH}...`);
execSync(
`tar -czf "${TAR_PATH}" -C "${DIST_DIR}" asn-prefixes.lmdb`,
{ stdio: 'inherit' },
);
console.log('Done.');

59
scripts/build.sh Normal file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
SOURCE_URL="https://github.com/ipverse/as-ip-blocks/releases/latest/download/as-ip-blocks.tar.gz"
DIST_DIR="./dist"
OUTPUT_FILE="${DIST_DIR}/asn-prefixes.json"
TMPDIR="$(mktemp -d)"
cleanup() {
echo "Cleaning up temp directory: ${TMPDIR}"
rm -rf "${TMPDIR}"
}
trap cleanup EXIT
echo "=== ASN Index Builder ==="
echo "Source: ${SOURCE_URL}"
mkdir -p "${DIST_DIR}"
echo "Downloading as-ip-blocks archive..."
curl -fsSL --retry 3 --retry-delay 5 -o "${TMPDIR}/as-ip-blocks.tar.gz" "${SOURCE_URL}"
echo "Download complete."
echo "Extracting archive..."
tar -xzf "${TMPDIR}/as-ip-blocks.tar.gz" -C "${TMPDIR}"
echo "Extraction complete."
AS_DIR="${TMPDIR}/as"
if [[ ! -d "${AS_DIR}" ]]; then
echo "ERROR: Expected 'as/' directory not found in archive." >&2
exit 1
fi
echo "Building ${OUTPUT_FILE}..."
# Build the combined JSON object using jq
# Each per-ASN file is named like <asn>.json and contains:
# { "asn": 13335, "handle": "...", "description": "...", "subnets": { "ipv4": [...], "ipv6": [...] } }
find "${AS_DIR}" -name 'aggregated.json' -print0 \
| sort -z \
| xargs -0 jq -c '
select(
((.prefixes.ipv4 // []) | length) > 0
or ((.prefixes.ipv6 // []) | length) > 0
)
| {
key: (.asn | tostring),
value: {
ipv4: (.prefixes.ipv4 // []),
ipv6: (.prefixes.ipv6 // [])
}
}
' \
| jq -s 'from_entries' \
> "${OUTPUT_FILE}"
ASN_COUNT=$(jq 'keys | length' "${OUTPUT_FILE}")
echo "Done. ${ASN_COUNT} ASNs written to ${OUTPUT_FILE}."

88
scripts/verify.mjs Normal file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env node
/**
* Local verification script not used in CI, just for sanity-checking the build.
* Usage: node scripts/verify.mjs
*/
import { open } from 'lmdb';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const DIST_DIR = resolve('./dist');
// ── 1. Verify JSON ──────────────────────────────────────────────────────────
console.log('── JSON verification ──────────────────────────────────────────');
const json = JSON.parse(readFileSync(resolve(DIST_DIR, 'asn-prefixes.json'), 'utf8'));
const jsonKeys = Object.keys(json);
console.log(`Total ASNs in JSON : ${jsonKeys.length}`);
const noData = jsonKeys.filter(k => json[k].ipv4.length === 0 && json[k].ipv6.length === 0);
console.log(`ASNs with no prefixes (should be 0): ${noData.length}`);
// Spot-check well-known ASNs
const SPOT = [
{ asn: '13335', name: 'Cloudflare', expectedIpv4: '1.1.1.0/24' },
{ asn: '15169', name: 'Google', expectedIpv4: '8.8.8.0/24' },
{ asn: '16509', name: 'Amazon AWS', expectedIpv4: null },
];
for (const { asn, name, expectedIpv4 } of SPOT) {
const entry = json[asn];
if (!entry) {
console.log(` [FAIL] ${name} (AS${asn}) — not found`);
continue;
}
const ipv4ok = expectedIpv4 ? entry.ipv4.includes(expectedIpv4) : entry.ipv4.length > 0;
const status = ipv4ok ? 'OK ' : 'WARN';
console.log(` [${status}] ${name} (AS${asn}) — ${entry.ipv4.length} IPv4, ${entry.ipv6.length} IPv6 prefixes${expectedIpv4 ? ` (${expectedIpv4}: ${ipv4ok ? 'present' : 'MISSING'})` : ''}`);
}
// ── 2. Verify LMDB ──────────────────────────────────────────────────────────
console.log('\n── LMDB verification ──────────────────────────────────────────');
const db = open({
path: resolve(DIST_DIR, 'asn-prefixes.lmdb'),
encoding: 'msgpack',
readOnly: true,
});
const dbCount = db.getCount();
console.log(`Total entries in LMDB: ${dbCount}`);
console.log(`Match JSON count: ${dbCount === jsonKeys.length ? 'YES' : `NO (delta: ${dbCount - jsonKeys.length})`}`);
for (const { asn, name, expectedIpv4 } of SPOT) {
const entry = db.get(Number(asn));
if (!entry) {
console.log(` [FAIL] ${name} (AS${asn}) — not found`);
continue;
}
const ipv4ok = expectedIpv4 ? entry.ipv4.includes(expectedIpv4) : entry.ipv4.length > 0;
const status = ipv4ok ? 'OK ' : 'WARN';
console.log(` [${status}] ${name} (AS${asn}) — ${entry.ipv4.length} IPv4, ${entry.ipv6.length} IPv6 prefixes`);
}
// Verify a random sample from JSON matches LMDB
console.log('\nRandom sample cross-check (10 entries):');
const sampleKeys = jsonKeys.sort(() => Math.random() - 0.5).slice(0, 10);
let mismatches = 0;
for (const k of sampleKeys) {
const fromJson = json[k];
const fromLmdb = db.get(Number(k));
if (!fromLmdb) { mismatches++; console.log(` [FAIL] AS${k} missing in LMDB`); continue; }
const match =
JSON.stringify(fromJson.ipv4.sort()) === JSON.stringify(fromLmdb.ipv4.sort()) &&
JSON.stringify(fromJson.ipv6.sort()) === JSON.stringify(fromLmdb.ipv6.sort());
if (!match) { mismatches++; console.log(` [FAIL] AS${k} data mismatch`); }
else console.log(` [OK ] AS${k}`);
}
if (mismatches === 0) console.log('All samples match.');
await db.close();
// ── 3. File sizes ────────────────────────────────────────────────────────────
console.log('\n── Artifact sizes ─────────────────────────────────────────────');
import { statSync } from 'node:fs';
const fmt = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
for (const name of ['asn-prefixes.json', 'asn-prefixes-lmdb.tar.gz']) {
const { size } = statSync(resolve(DIST_DIR, name));
console.log(` ${name}: ${fmt(size)}`);
}
console.log('\nVerification complete.');