diff --git a/deploy/server.js b/deploy/server.js index c0f24da..37cab07 100644 --- a/deploy/server.js +++ b/deploy/server.js @@ -1233,7 +1233,7 @@ function checkRateLimit(ip) { } function fetchJSON(url, options) { - const timeoutMs = (options && options.timeout) || 20000; + const timeoutMs = (options && options.timeout) || 8000; return new Promise((resolve) => { const reqOptions = { headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) }, @@ -2390,24 +2390,29 @@ const server = http.createServer(async (req, res) => { res.writeHead(400); return res.end(JSON.stringify({ error: "Missing or invalid ASN parameter" })); } + const cachedVerify = resultCacheGet(aspaResultCache, "verify:" + rawAsn); + if (cachedVerify !== undefined) { + res.writeHead(200, { "Content-Type": "application/json" }); + return res.end(JSON.stringify(cachedVerify)); + } const targetAsn = parseInt(rawAsn); const start = Date.now(); try { // Fetch neighbour and prefix data first const [neighbourData, prefixData] = await Promise.all([ - fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn), - fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn), + fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, { timeout: 5000 }), + fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn, { timeout: 5000 }), ]); // Use looking-glass with actual prefixes to get BGP paths const announcedPrefixes = prefixData?.data?.prefixes || []; - const samplePrefixes = announcedPrefixes.slice(0, 5).map((p) => p.prefix); + const samplePrefixes = announcedPrefixes.slice(0, 3).map((p) => p.prefix); // reduced 5→3 - // Fetch looking-glass data for up to 5 prefixes in parallel + // Fetch looking-glass data for up to 3 prefixes in parallel (3s timeout each) const lgResults = await Promise.all( samplePrefixes.map((pfx) => - fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx)) + fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx), { timeout: 3000 }).catch(() => null) ) ); @@ -2590,51 +2595,46 @@ const server = http.createServer(async (req, res) => { }); const duration = Date.now() - start; - - return res.end( - JSON.stringify( - { - meta: { - query: "AS" + rawAsn, - duration_ms: duration, - timestamp: new Date().toISOString(), - paths_analyzed: pathResults.length, - total_paths_seen: allPaths.length, - }, - asn: targetAsn, - readiness_score: readinessScore, - aspa_object_exists: aspaObjectExists, - detected_providers: detectedProviders.map((p) => ({ - ...p, - frequency: providerFrequency.get(p.asn) || 0, - frequency_pct: allPaths.length > 0 - ? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100) - : 0, - })), - provider_audit: { - declared_count: aspaDeclaredProviders.length, - detected_count: detectedProviders.length, - completeness_pct: providerCompleteness, - missing_from_aspa: missingFromAspa, - extra_in_aspa: extraInAspa, - }, - path_verification: { - total: pathResults.length, - valid: validPaths, - invalid: invalidPaths, - unknown: unknownPaths, - as_set_flagged: asSetPaths, - valley_detected: valleyPaths, - valid_pct: pathValidPct, - not_invalid_pct: pathNotInvalidPct, - results: pathResults, - }, - rpki_coverage: rpkiCoverage, - }, - null, - 2 - ) - ); + const verifyResult = { + meta: { + query: "AS" + rawAsn, + duration_ms: duration, + timestamp: new Date().toISOString(), + paths_analyzed: pathResults.length, + total_paths_seen: allPaths.length, + }, + asn: targetAsn, + readiness_score: readinessScore, + aspa_object_exists: aspaObjectExists, + detected_providers: detectedProviders.map((p) => ({ + ...p, + frequency: providerFrequency.get(p.asn) || 0, + frequency_pct: allPaths.length > 0 + ? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100) + : 0, + })), + provider_audit: { + declared_count: aspaDeclaredProviders.length, + detected_count: detectedProviders.length, + completeness_pct: providerCompleteness, + missing_from_aspa: missingFromAspa, + extra_in_aspa: extraInAspa, + }, + path_verification: { + total: pathResults.length, + valid: validPaths, + invalid: invalidPaths, + unknown: unknownPaths, + as_set_flagged: asSetPaths, + valley_detected: valleyPaths, + valid_pct: pathValidPct, + not_invalid_pct: pathNotInvalidPct, + results: pathResults, + }, + rpki_coverage: rpkiCoverage, + }; + resultCacheSet(aspaResultCache, "verify:" + rawAsn, verifyResult); + return res.end(JSON.stringify(verifyResult, null, 2)); } catch (err) { res.writeHead(500); return res.end(JSON.stringify({ error: "ASPA verification failed", message: err.message })); diff --git a/server.js b/server.js index c0f24da..37cab07 100644 --- a/server.js +++ b/server.js @@ -1233,7 +1233,7 @@ function checkRateLimit(ip) { } function fetchJSON(url, options) { - const timeoutMs = (options && options.timeout) || 20000; + const timeoutMs = (options && options.timeout) || 8000; return new Promise((resolve) => { const reqOptions = { headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) }, @@ -2390,24 +2390,29 @@ const server = http.createServer(async (req, res) => { res.writeHead(400); return res.end(JSON.stringify({ error: "Missing or invalid ASN parameter" })); } + const cachedVerify = resultCacheGet(aspaResultCache, "verify:" + rawAsn); + if (cachedVerify !== undefined) { + res.writeHead(200, { "Content-Type": "application/json" }); + return res.end(JSON.stringify(cachedVerify)); + } const targetAsn = parseInt(rawAsn); const start = Date.now(); try { // Fetch neighbour and prefix data first const [neighbourData, prefixData] = await Promise.all([ - fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn), - fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn), + fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, { timeout: 5000 }), + fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn, { timeout: 5000 }), ]); // Use looking-glass with actual prefixes to get BGP paths const announcedPrefixes = prefixData?.data?.prefixes || []; - const samplePrefixes = announcedPrefixes.slice(0, 5).map((p) => p.prefix); + const samplePrefixes = announcedPrefixes.slice(0, 3).map((p) => p.prefix); // reduced 5→3 - // Fetch looking-glass data for up to 5 prefixes in parallel + // Fetch looking-glass data for up to 3 prefixes in parallel (3s timeout each) const lgResults = await Promise.all( samplePrefixes.map((pfx) => - fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx)) + fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx), { timeout: 3000 }).catch(() => null) ) ); @@ -2590,51 +2595,46 @@ const server = http.createServer(async (req, res) => { }); const duration = Date.now() - start; - - return res.end( - JSON.stringify( - { - meta: { - query: "AS" + rawAsn, - duration_ms: duration, - timestamp: new Date().toISOString(), - paths_analyzed: pathResults.length, - total_paths_seen: allPaths.length, - }, - asn: targetAsn, - readiness_score: readinessScore, - aspa_object_exists: aspaObjectExists, - detected_providers: detectedProviders.map((p) => ({ - ...p, - frequency: providerFrequency.get(p.asn) || 0, - frequency_pct: allPaths.length > 0 - ? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100) - : 0, - })), - provider_audit: { - declared_count: aspaDeclaredProviders.length, - detected_count: detectedProviders.length, - completeness_pct: providerCompleteness, - missing_from_aspa: missingFromAspa, - extra_in_aspa: extraInAspa, - }, - path_verification: { - total: pathResults.length, - valid: validPaths, - invalid: invalidPaths, - unknown: unknownPaths, - as_set_flagged: asSetPaths, - valley_detected: valleyPaths, - valid_pct: pathValidPct, - not_invalid_pct: pathNotInvalidPct, - results: pathResults, - }, - rpki_coverage: rpkiCoverage, - }, - null, - 2 - ) - ); + const verifyResult = { + meta: { + query: "AS" + rawAsn, + duration_ms: duration, + timestamp: new Date().toISOString(), + paths_analyzed: pathResults.length, + total_paths_seen: allPaths.length, + }, + asn: targetAsn, + readiness_score: readinessScore, + aspa_object_exists: aspaObjectExists, + detected_providers: detectedProviders.map((p) => ({ + ...p, + frequency: providerFrequency.get(p.asn) || 0, + frequency_pct: allPaths.length > 0 + ? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100) + : 0, + })), + provider_audit: { + declared_count: aspaDeclaredProviders.length, + detected_count: detectedProviders.length, + completeness_pct: providerCompleteness, + missing_from_aspa: missingFromAspa, + extra_in_aspa: extraInAspa, + }, + path_verification: { + total: pathResults.length, + valid: validPaths, + invalid: invalidPaths, + unknown: unknownPaths, + as_set_flagged: asSetPaths, + valley_detected: valleyPaths, + valid_pct: pathValidPct, + not_invalid_pct: pathNotInvalidPct, + results: pathResults, + }, + rpki_coverage: rpkiCoverage, + }; + resultCacheSet(aspaResultCache, "verify:" + rawAsn, verifyResult); + return res.end(JSON.stringify(verifyResult, null, 2)); } catch (err) { res.writeHead(500); return res.end(JSON.stringify({ error: "ASPA verification failed", message: err.message }));