fix: RIR+Country empty (RIPE Stat .location field), RDAP parallel race (v0.6.7)

This commit is contained in:
Rene Fichtmueller 2026-04-02 23:08:54 +00:00
parent 9be247410c
commit 9012d2931f

View File

@ -2089,7 +2089,7 @@ const server = http.createServer(async (req, res) => {
JSON.stringify({ JSON.stringify({
status, status,
service: "PeerCortex", service: "PeerCortex",
version: "0.6.6", version: "0.6.7",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
uptime_seconds: Math.floor(process.uptime()), uptime_seconds: Math.floor(process.uptime()),
memory_mb: Math.round(mem.heapUsed / 1024 / 1024), memory_mb: Math.round(mem.heapUsed / 1024 / 1024),
@ -3138,10 +3138,13 @@ const server = http.createServer(async (req, res) => {
} }
const pocQuery = netId ? "/poc?net_id=" + netId + "&limit=25" : null; const pocQuery = netId ? "/poc?net_id=" + netId + "&limit=25" : null;
// RDAP: try all 5 RIRs in parallel, take the first valid response (fast race)
const rdapForReg = [ const rdapForReg = [
"https://rdap.db.ripe.net/autnum/" + asn, "https://rdap.db.ripe.net/autnum/" + asn,
"https://rdap.apnic.net/autnum/" + asn,
"https://rdap.arin.net/registry/autnum/" + asn, "https://rdap.arin.net/registry/autnum/" + asn,
"https://rdap.apnic.net/autnum/" + asn,
"https://rdap.lacnic.net/rdap/autnum/" + asn,
"https://rdap.afrinic.net/rdap/autnum/" + asn,
]; ];
const promises = [ const promises = [
@ -3156,15 +3159,15 @@ const server = http.createServer(async (req, res) => {
timedFetch("PeeringDB IXLan", cachedIxlan ? Promise.resolve(cachedIxlan) : fetchPeeringDBWithRetry(ixQuery)), timedFetch("PeeringDB IXLan", cachedIxlan ? Promise.resolve(cachedIxlan) : fetchPeeringDBWithRetry(ixQuery)),
timedFetch("PeeringDB Facilities", cachedFac ? Promise.resolve(cachedFac) : (netId ? fetchPeeringDBWithRetry("/netfac?net_id=" + netId + "&limit=1000") : Promise.resolve(null))), timedFetch("PeeringDB Facilities", cachedFac ? Promise.resolve(cachedFac) : (netId ? fetchPeeringDBWithRetry("/netfac?net_id=" + netId + "&limit=1000") : Promise.resolve(null))),
timedFetch("PeeringDB Contacts", pocQuery ? fetchPeeringDB(pocQuery).catch(() => null) : Promise.resolve(null)), timedFetch("PeeringDB Contacts", pocQuery ? fetchPeeringDB(pocQuery).catch(() => null) : Promise.resolve(null)),
timedFetch("RDAP Registration", (async () => { timedFetch("RDAP Registration", Promise.race([
for (const url of rdapForReg) { // All 5 RIR RDAP endpoints in parallel — first valid wins
try { ...rdapForReg.map(url =>
const d = await fetchJSON(url, { timeout: 5000 }); fetchJSON(url, { timeout: 4000 })
if (d && !d.errorCode && d.handle) return d; .then(d => (d && !d.errorCode && d.handle) ? d : new Promise(() => {}))
} catch(e) {} .catch(() => new Promise(() => {}))
} ),
return null; new Promise(resolve => setTimeout(() => resolve(null), 5000)),
})()), ])),
]; ];
const [prefixData, neighbourData, overviewData, rirData, atlasProbeData, bgpHeData, visibilityData, prefixSizeData, ixlanData, facData, pocData, rdapData] = await Promise.all(promises); const [prefixData, neighbourData, overviewData, rirData, atlasProbeData, bgpHeData, visibilityData, prefixSizeData, ixlanData, facData, pocData, rdapData] = await Promise.all(promises);
@ -3308,14 +3311,45 @@ const server = http.createServer(async (req, res) => {
let rir = ""; let rir = "";
let country = ""; let country = "";
// RIPE Stat rir-stats-country uses 'location' field (not 'country' or 'rir')
if (Array.isArray(rirEntries) && rirEntries.length > 0) { if (Array.isArray(rirEntries) && rirEntries.length > 0) {
country = rirEntries[0]?.location || rirEntries[0]?.country || "";
rir = rirEntries[0]?.rir || ""; rir = rirEntries[0]?.rir || "";
country = rirEntries[0]?.country || "";
} }
if (!rir && rirData?.data) { if (!rir && rirData?.data) {
const rirField = rirData.data.rirs || []; const rirField = rirData.data.rirs || [];
if (rirField.length > 0) rir = rirField[0]?.rir || ""; if (rirField.length > 0) rir = rirField[0]?.rir || "";
} }
// Derive RIR from rdapData.port43 (e.g. "whois.ripe.net" → "RIPE")
if (!rir && rdapData && rdapData.port43) {
const p43 = (rdapData.port43 || "").toLowerCase();
if (p43.includes("ripe")) rir = "RIPE";
else if (p43.includes("arin")) rir = "ARIN";
else if (p43.includes("apnic")) rir = "APNIC";
else if (p43.includes("lacnic")) rir = "LACNIC";
else if (p43.includes("afrinic")) rir = "AFRINIC";
}
// Also derive RIR from RDAP links (URL of the RDAP endpoint that responded)
if (!rir && rdapData && rdapData.links) {
const selfLink = (rdapData.links.find(l => l.rel === "self") || {}).href || "";
if (selfLink.includes("ripe")) rir = "RIPE";
else if (selfLink.includes("arin")) rir = "ARIN";
else if (selfLink.includes("apnic")) rir = "APNIC";
else if (selfLink.includes("lacnic")) rir = "LACNIC";
else if (selfLink.includes("afrinic")) rir = "AFRINIC";
}
// Last resort: derive RIR from country code (common assignments)
if (!rir && country) {
const ARIN_CC = new Set(["US","CA","AI","AG","BS","BB","BZ","VG","KY","DM","DO","GD","GP","HT","JM","MQ","MS","PR","KN","LC","VC","TT","TC","VI","UM"]);
const APNIC_CC = new Set(["AU","NZ","JP","CN","KR","IN","HK","SG","TW","VN","TH","ID","MY","PK","BD","LK","NP","PH","AF","KH","LA","MM","MN","BT","BN","FJ","PG","WS","TO","VU","SB","KI","NR","TV","FM","MH","PW","CK","NU","TK","WF","PF","NC","GU","MP","AS","CC","CX","HM","NF"]);
const LACNIC_CC = new Set(["BR","AR","MX","CO","CL","PE","VE","EC","UY","BO","PY","CU","GT","HN","SV","NI","CR","PA","GY","SR","GF","AW","CW","SX","BQ","AN"]);
const AFRINIC_CC = new Set(["ZA","NG","KE","EG","GH","TZ","UG","MA","CI","SN","ZM","ZW","AO","MZ","CM","ET","SD","MG","DZ","TN","LY","RW","NA","BW","MW","ML","BF","NE","GN","TD","SO","LS","SZ","ER","DJ","GM","SL","LR","TG","BJ","GW","CF","CG","CD","GQ","ST","KM","MR","SC","MU","RE","CV","BU","SS","EH"]);
if (ARIN_CC.has(country)) rir = "ARIN";
else if (APNIC_CC.has(country)) rir = "APNIC";
else if (LACNIC_CC.has(country)) rir = "LACNIC";
else if (AFRINIC_CC.has(country)) rir = "AFRINIC";
else rir = "RIPE"; // Europe + rest = RIPE NCC
}
const duration = Date.now() - start; const duration = Date.now() - start;
@ -3501,7 +3535,7 @@ const server = http.createServer(async (req, res) => {
const result = { const result = {
meta: { meta: {
service: "PeerCortex", service: "PeerCortex",
version: "0.6.6", version: "0.6.7",
query: "AS" + asn, query: "AS" + asn,
duration_ms: duration, duration_ms: duration,
sources: ["PeeringDB", "RIPE Stat", "bgp.he.net", "Cloudflare RPKI", "RIPE RPKI Validator", "Route Views"], sources: ["PeeringDB", "RIPE Stat", "bgp.he.net", "Cloudflare RPKI", "RIPE RPKI Validator", "Route Views"],