PeerCortex/server/routes/lookup/cross-check.js
Rene Fichtmueller cba7680de0 refactor: decompose /api/lookup into server/routes/lookup/{index,facilities,enrich-neighbours,rir-country,routing-info,cross-check}.js
The 662-line handler (the largest in the file) split into one function
per data-source/merge stage: facility+IX-location coordinate resolution
(facilities.js), neighbour name-resolution + threat-intel enrichment
(enrich-neighbours.js), RIR/country derivation cascade (rir-country.js),
visibility/prefix-size computation (routing-info.js), multi-source
cross-checks + data-quality scoring (cross-check.js), and a slim
orchestrator (index.js) running Phase 0/1 fetch and final result assembly.

Verified via node --check, eslint no-undef (2 known bugs remain: compare,
webhooks), and smoke-test harness (28/28 match, including a byte-for-byte
match on /api/lookup's own large response body -- the strongest possible
confirmation this, the most complex handler in the file, was decomposed
without changing behavior).

server.js: 2917 -> 1734 lines (6838 originally -- 75% reduction so far).
2026-07-17 07:52:22 +02:00

91 lines
5.3 KiB
JavaScript

const { crossCheckRpki } = require("../../services/rpki");
// Multi-source cross-checks (RPKI vs RIPE Validator, prefix/neighbour counts
// vs bgp.he.net) plus the overall data-quality summary derived from them.
async function computeCrossChecks(asn, allPrefixes, rpkiStatuses, prefixes, neighbours, bgpHeData) {
let rpkiCrossCheck = { cloudflare_valid: 0, ripe_valid: 0, agreement_pct: null, disagreements: [], sample_size: 0, comparable_size: 0 };
let prefixCrossCheck = { ripe_stat: prefixes.length, bgp_he_net: null, agreement: null, note: "" };
let neighbourCrossCheck = { ripe_stat_total: neighbours.length, bgp_he_net_total: null };
try {
// RPKI cross-check: sample up to 5 prefixes against RIPE Validator (with 8s total timeout)
const rpkiCrossPromise = crossCheckRpki(asn, allPrefixes, rpkiStatuses);
const rpkiCrossResult = await Promise.race([
rpkiCrossPromise,
new Promise((resolve) => setTimeout(() => resolve(null), 8000)),
]);
if (rpkiCrossResult) rpkiCrossCheck = rpkiCrossResult;
} catch (_e) { /* cross-check failed, keep defaults */ }
// bgp.he.net fetching is deliberately disabled (see the hardcoded
// Promise.resolve(null) in index.js's timedFetch("bgp.he.net", ...)) --
// bgpHeData is therefore always null and this whole branch, along with the
// prefix/neighbour cross-checks below, never actually runs. Left in place
// (not deleted) in case bgp.he.net fetching is re-enabled later, but
// overall_confidence below must not claim a 2-source agreement that never
// happened -- see the sources:1/2 handling further down.
// Prefix count cross-check: compare RIPE Stat vs bgp.he.net
if (bgpHeData) {
const heV4 = bgpHeData.prefixes_v4 || 0;
const heV6 = bgpHeData.prefixes_v6 || 0;
const heTotal = heV4 + heV6;
if (heTotal > 0) {
prefixCrossCheck.bgp_he_net = heTotal;
const ripeStat = prefixes.length;
if (ripeStat > 0 && heTotal > 0) {
const ratio = Math.min(ripeStat, heTotal) / Math.max(ripeStat, heTotal);
prefixCrossCheck.agreement = ratio >= 0.9;
const diff = Math.abs(ripeStat - heTotal);
prefixCrossCheck.note = diff === 0
? "Exact match"
: "Difference of " + diff + " prefixes (" + Math.round((1 - ratio) * 100) + "% divergence)";
}
} else {
prefixCrossCheck.note = "bgp.he.net prefix count unavailable";
}
// Neighbour cross-check: compare RIPE Stat vs bgp.he.net peer_count
if (bgpHeData.peer_count != null) {
neighbourCrossCheck.bgp_he_net_total = bgpHeData.peer_count;
}
} else {
prefixCrossCheck.note = "bgp.he.net data unavailable";
}
// Compute overall data quality. Only push a score for a cross-check that
// actually ran (agreement_pct !== null) -- an unrun/inconclusive check must
// not silently count as a perfect 100 in the average, and confidence must
// reflect "nothing was actually cross-checked" rather than defaulting high.
const crossCheckScores = [];
if (rpkiCrossCheck.agreement_pct !== null) crossCheckScores.push(rpkiCrossCheck.agreement_pct);
// Prefix agreement: convert to percentage
if (prefixCrossCheck.bgp_he_net != null && prefixes.length > 0) {
const pfxRatio = Math.min(prefixes.length, prefixCrossCheck.bgp_he_net) / Math.max(prefixes.length, prefixCrossCheck.bgp_he_net);
crossCheckScores.push(Math.round(pfxRatio * 100));
}
// Neighbour agreement
if (neighbourCrossCheck.bgp_he_net_total != null && neighbours.length > 0) {
const nbrRatio = Math.min(neighbours.length, neighbourCrossCheck.bgp_he_net_total) / Math.max(neighbours.length, neighbourCrossCheck.bgp_he_net_total);
crossCheckScores.push(Math.round(nbrRatio * 100));
}
const avgAgreement = crossCheckScores.length > 0
? Math.round(crossCheckScores.reduce((a, b) => a + b, 0) / crossCheckScores.length)
: null;
const overallConfidence = avgAgreement === null ? "unknown" : avgAgreement > 90 ? "high" : avgAgreement >= 70 ? "medium" : "low";
const dataQuality = {
sources_queried: ["PeeringDB", "RIPE Stat", "bgp.he.net", "Cloudflare RPKI", "RIPE RPKI Validator"],
cross_checks: {
rpki: { sources: rpkiCrossCheck.agreement_pct !== null ? 2 : 1, agreement_pct: rpkiCrossCheck.agreement_pct, sample_size: rpkiCrossCheck.sample_size, comparable_size: rpkiCrossCheck.comparable_size, disagreements: rpkiCrossCheck.disagreements },
prefixes: { sources: prefixCrossCheck.bgp_he_net != null ? 2 : 1, agreement_pct: prefixCrossCheck.bgp_he_net != null ? Math.round((Math.min(prefixes.length, prefixCrossCheck.bgp_he_net) / Math.max(prefixes.length, prefixCrossCheck.bgp_he_net || 1)) * 100) : null, ripe_stat: prefixCrossCheck.ripe_stat, bgp_he_net: prefixCrossCheck.bgp_he_net, note: prefixCrossCheck.note },
neighbours: { sources: neighbourCrossCheck.bgp_he_net_total != null ? 2 : 1, agreement_pct: neighbourCrossCheck.bgp_he_net_total != null && neighbours.length > 0 ? Math.round((Math.min(neighbours.length, neighbourCrossCheck.bgp_he_net_total) / Math.max(neighbours.length, neighbourCrossCheck.bgp_he_net_total)) * 100) : null, ripe_stat_total: neighbourCrossCheck.ripe_stat_total, bgp_he_net_total: neighbourCrossCheck.bgp_he_net_total },
},
overall_confidence: overallConfidence,
overall_agreement_pct: avgAgreement,
};
return { rpkiCrossCheck, prefixCrossCheck, neighbourCrossCheck, dataQuality };
}
module.exports = { computeCrossChecks };