mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
Fix(asn): report unloaded local data as an error, not a cacheable empty
- PeeringDB is 'disabled' only without a Cloudflare key; configured but not loaded (still downloading, or the download failed) is an 'error', so the ASN Profile isn't cached for a week without it. - Connectivity without an as-rel snapshot is an 'error' too, not an empty graph; a missing as2org still only blocks while it downloads, as RIPEstat names the nodes. - A CAIDA as2org / as-rel file that parses to nothing (corrupt) no longer counts as loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
02476aec7e
commit
d84fefbbea
4 files changed
+60
-17
No files matched your search
+23
-10
@@ -20,12 +20,27 @@ import {
|
||||
} from '../common/asn-profile.js';
|
||||
import { getAsnConnectivity } from './asn-connectivity.js';
|
||||
|
||||
// A section whose local dataset is still downloading at boot fails (status
|
||||
// 'error'): left out of the answer and keeping it uncached, while every
|
||||
// other section answers as usual.
|
||||
const unlessLoading = (checks, label, fn) => (...args) => {
|
||||
if (isStillLoading(...checks)) throw new Error(`${label} still loading`);
|
||||
return fn(...args);
|
||||
// A section whose local data isn't there fails (status 'error'): left out
|
||||
// of the answer and keeping it uncached, while every other section answers
|
||||
// as usual — never an 'empty' / 'disabled' that would be cached for a week.
|
||||
|
||||
// Connectivity needs the as-rel snapshot (without it every graph is empty);
|
||||
// as2org only names the nodes, RIPEstat covers a failed download, so it
|
||||
// counts only while still downloading. Exported for tests.
|
||||
export const connectivityReadiness = ({ relLoaded = isAsRelLoaded, orgLoaded = isAsOrgLoaded, fetch = getAsnConnectivity } = {}) =>
|
||||
(...args) => {
|
||||
if (!relLoaded()) throw new Error('CAIDA as-rel snapshot not loaded');
|
||||
if (isStillLoading(orgLoaded)) throw new Error('CAIDA as2org snapshot still loading');
|
||||
return fetch(...args);
|
||||
};
|
||||
|
||||
// PeeringDB is 'disabled' only where it isn't configured (no Cloudflare key,
|
||||
// so never downloaded). Configured but not loaded — still downloading, or
|
||||
// the download failed — is an error. Exported for tests.
|
||||
export const peeringdbReadiness = ({ configured = hasRadarApiKey, loaded = isPeeringdbLoaded } = {}) => () => {
|
||||
if (!configured()) return false;
|
||||
if (!loaded()) throw new Error('PeeringDB index not loaded');
|
||||
return true;
|
||||
};
|
||||
|
||||
export default async (req, res) => {
|
||||
@@ -36,15 +51,13 @@ export default async (req, res) => {
|
||||
fetchRadarAsn: (n) => loadAsnSummary(String(n)),
|
||||
fetchRadarPrefixes: (n) => RADAR_VIEWS['bgp-prefixes'].fetch({ asn: String(n) }),
|
||||
isPartialPrefixes: (body) => !isCompleteRadarAnswer(body),
|
||||
getConnectivity: unlessLoading([isAsRelLoaded, isAsOrgLoaded], 'CAIDA snapshot', getAsnConnectivity),
|
||||
getConnectivity: connectivityReadiness(),
|
||||
rdapAutnum: (n) => rdapAutnum(n, { timeoutMs: SOURCE_TIMEOUTS.autnum }),
|
||||
isAutnumMissing,
|
||||
queryAsRank: (n) => queryAsRank(n, { timeoutMs: SOURCE_TIMEOUTS.rank }),
|
||||
// Private-API pass-through: the caller's headers go upstream.
|
||||
requestReputation: (n) => requestAsnReputation(n, req.headers, { timeoutMs: SOURCE_TIMEOUTS.reputation }),
|
||||
// The index only downloads with the Cloudflare key; without it the
|
||||
// section is 'disabled', never loading.
|
||||
isPeeringdbLoaded: unlessLoading([() => !hasRadarApiKey() || isPeeringdbLoaded()], 'PeeringDB index', isPeeringdbLoaded),
|
||||
isPeeringdbLoaded: peeringdbReadiness(),
|
||||
lookupPeeringdb,
|
||||
});
|
||||
const body = await composeAsnProfile(asn, loaders);
|
||||
|
||||
@@ -72,6 +72,13 @@ function loadDatabase() {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
asnToOrgName.clear();
|
||||
parsePipeText(text);
|
||||
// The parser skips what it can't read, so a corrupt file parses to
|
||||
// nothing rather than throwing: that is not a loaded snapshot.
|
||||
if (asnToOrgName.size === 0) {
|
||||
loadedFrom = null;
|
||||
logger.warn({ path: filePath }, '⚠️ CAIDA as2org snapshot holds no ASNs; treating it as missing');
|
||||
return;
|
||||
}
|
||||
loadedFrom = path.basename(filePath);
|
||||
logger.info(`📦 CAIDA as2org loaded (${loadedFrom}) — ${asnToOrgName.size} ASNs in ${Date.now() - start}ms`);
|
||||
} catch (error) {
|
||||
|
||||
@@ -114,6 +114,13 @@ function loadDatabase() {
|
||||
customerCount.clear();
|
||||
parsePipeText(text);
|
||||
rebuildTier1Set();
|
||||
// A corrupt file parses to nothing rather than throwing: that is not
|
||||
// a loaded snapshot.
|
||||
if (providersIndex.size === 0 && peersIndex.size === 0) {
|
||||
loadedFrom = null;
|
||||
logger.warn({ path: filePath }, '⚠️ CAIDA as-rel snapshot holds no relationships; treating it as missing');
|
||||
return;
|
||||
}
|
||||
loadedFrom = path.basename(filePath);
|
||||
logger.info(`📦 CAIDA as-rel loaded (${loadedFrom}) — ${providersIndex.size} customers, ${customerCount.size} providers, ${peersIndex.size} peering ASes, ${tier1Set.size} Tier 1s in ${Date.now() - start}ms`);
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,8 +22,8 @@ import invisibilityHandler from '../api/invisibility-test.js';
|
||||
import macCheckerHandler from '../api/mac-checker.js';
|
||||
import githubStarsHandler from '../api/github-stars.js';
|
||||
import personaEvaluateHandler from '../api/persona.js';
|
||||
import asnProfileHandler from '../api/asn-profile.js';
|
||||
import { isPeeringdbLoaded } from '../common/peeringdb-db.js';
|
||||
import asnProfileHandler, { peeringdbReadiness, connectivityReadiness } from '../api/asn-profile.js';
|
||||
import { isAsRelLoaded } from '../common/as-rel-db.js';
|
||||
import { isCompleteProfile } from '../common/asn-profile.js';
|
||||
import updateAchievementHandler from '../api/update-user-achievement.js';
|
||||
import ipcheckIngHandler from '../api/ipcheck-ing.js';
|
||||
@@ -741,13 +741,29 @@ describe('asn-profile handler', () => {
|
||||
assert.equal(res.body.status.reputation, 'disabled');
|
||||
assert.equal(res.body.status.whois, 'error');
|
||||
assert.equal(res.body.status.rank, 'error');
|
||||
assert.ok(['ok', 'empty'].includes(res.body.status.connectivity));
|
||||
// Local: ok / empty with an as-rel snapshot, an error (never a
|
||||
// cacheable empty) without one — a fresh checkout.
|
||||
if (isAsRelLoaded()) assert.ok(['ok', 'empty'].includes(res.body.status.connectivity));
|
||||
else assert.equal(res.body.status.connectivity, 'error');
|
||||
assert.equal(res.body.whois, null);
|
||||
assert.equal(res.body.rank, null);
|
||||
// Local and never an error: disabled without an index file (the
|
||||
// checkout default), ok / empty once the updater has built one.
|
||||
if (isPeeringdbLoaded()) assert.ok(['ok', 'empty'].includes(res.body.status.peeringdb));
|
||||
else assert.equal(res.body.status.peeringdb, 'disabled');
|
||||
// No Cloudflare key: PeeringDB isn't configured here.
|
||||
assert.equal(res.body.status.peeringdb, 'disabled');
|
||||
});
|
||||
|
||||
it('PeeringDB: disabled only when unconfigured; configured but not loaded is an error', () => {
|
||||
assert.equal(peeringdbReadiness({ configured: () => false, loaded: () => false })(), false);
|
||||
assert.equal(peeringdbReadiness({ configured: () => true, loaded: () => true })(), true);
|
||||
assert.throws(() => peeringdbReadiness({ configured: () => true, loaded: () => false })(), /not loaded/);
|
||||
});
|
||||
|
||||
it('connectivity: no as-rel snapshot is an error; as2org matters only while loading', async () => {
|
||||
const fetch = async () => 'graph';
|
||||
const missingRel = connectivityReadiness({ relLoaded: () => false, orgLoaded: () => true, fetch });
|
||||
assert.throws(() => missingRel(13335), /as-rel snapshot not loaded/);
|
||||
// as2org failed after boot: RIPEstat names the nodes, the graph is served.
|
||||
const noOrg = connectivityReadiness({ relLoaded: () => true, orgLoaded: () => false, fetch });
|
||||
assert.equal(await noOrg(13335), 'graph');
|
||||
});
|
||||
|
||||
it('shows a partial Radar summary but flags it incomplete (not cacheable)', async () => {
|
||||
|
||||
Reference in new issue
Block a user