The 519-line handler split into 12 independent, named check functions (checks.js), the bogon-detection nested functions pulled out on their own (bogon.js, no I/O, easiest to verify), and a slim orchestrator (index.js) that runs Phase 1 fetch, dispatches all checks via Promise.race+allSettled (same 5s-cap/10s-total behavior as before), scores, and assembles the response -- each piece under 50 lines except the orchestrator's main function itself. 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/validate's own response -- the highest-value confirmation this decomposition preserved behavior exactly).
63 lines
3.0 KiB
JavaScript
63 lines
3.0 KiB
JavaScript
// 11. Bogon Detection (local check, no external API calls)
|
|
function checkBogonPrefix(prefix) {
|
|
var bogonV4 = [
|
|
{ net: "0.0.0.0", mask: 8 }, { net: "10.0.0.0", mask: 8 },
|
|
{ net: "100.64.0.0", mask: 10 }, { net: "127.0.0.0", mask: 8 },
|
|
{ net: "169.254.0.0", mask: 16 }, { net: "172.16.0.0", mask: 12 },
|
|
{ net: "192.0.2.0", mask: 24 }, { net: "192.168.0.0", mask: 16 },
|
|
{ net: "198.51.100.0", mask: 24 }, { net: "203.0.113.0", mask: 24 },
|
|
{ net: "240.0.0.0", mask: 4 },
|
|
];
|
|
if (prefix.includes(":")) return { prefix: prefix, is_bogon: false, reason: "IPv6 bogon check skipped" };
|
|
var split = prefix.split("/");
|
|
var addr = split[0];
|
|
var mask = parseInt(split[1] || "0");
|
|
var parts = addr.split(".").map(Number);
|
|
var ip = ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;
|
|
for (var bi = 0; bi < bogonV4.length; bi++) {
|
|
var b = bogonV4[bi];
|
|
var bParts = b.net.split(".").map(Number);
|
|
var bIp = ((bParts[0] << 24) | (bParts[1] << 16) | (bParts[2] << 8) | bParts[3]) >>> 0;
|
|
var bMask = (~((1 << (32 - b.mask)) - 1)) >>> 0;
|
|
if ((ip & bMask) === (bIp & bMask) && mask >= b.mask) {
|
|
return { prefix: prefix, is_bogon: true, reason: "Matches bogon " + b.net + "/" + b.mask };
|
|
}
|
|
}
|
|
return { prefix: prefix, is_bogon: false };
|
|
}
|
|
|
|
function checkBogonAsn(asnNum) {
|
|
if (asnNum === 0 || asnNum === 23456 || asnNum === 65535) return true;
|
|
if (asnNum >= 64496 && asnNum <= 64511) return true;
|
|
if (asnNum >= 64512 && asnNum <= 65534) return true;
|
|
return false;
|
|
}
|
|
|
|
// "pass" must mean prefixes/neighbours were actually fetched and checked clean --
|
|
// not that the upstream source failed and left nothing to check. A source is
|
|
// unavailable if it's falsy (fetchRipeStatCached network failure) OR if it's a
|
|
// truthy object with status:"error" (local-db-client.js's DB-error signal --
|
|
// see the 2026-07-16 audit; that object is NOT null, so a plain `!x` check
|
|
// alone misses this case and would show "pass" on 0 checked prefixes).
|
|
function isSourceUnavailable(x) { return !x || x.status === "error"; }
|
|
|
|
function computeBogonResult(allPrefixes, neighbours, prefixData, neighbourData) {
|
|
var bogonPrefixResults = allPrefixes.map(checkBogonPrefix);
|
|
var bogonPrefixes = bogonPrefixResults.filter(function(r) { return r.is_bogon; });
|
|
var asnInPaths = neighbours.map(function(n) { return n.asn; });
|
|
var bogonAsns = asnInPaths.filter(checkBogonAsn);
|
|
var prefixesUnavailable = isSourceUnavailable(prefixData);
|
|
var neighboursUnavailable = isSourceUnavailable(neighbourData);
|
|
var bogonDataUnavailable = prefixesUnavailable && neighboursUnavailable;
|
|
return {
|
|
status: bogonDataUnavailable ? "info" : (bogonPrefixes.length === 0 && bogonAsns.length === 0 ? "pass" : "fail"),
|
|
bogon_prefixes: bogonPrefixes,
|
|
bogon_asns_in_paths: bogonAsns,
|
|
total_prefixes_checked: allPrefixes.length,
|
|
prefixes_source_unavailable: prefixesUnavailable,
|
|
neighbours_source_unavailable: neighboursUnavailable,
|
|
};
|
|
}
|
|
|
|
module.exports = { computeBogonResult };
|