fix: 6 critical data accuracy bugs
- Bug 1: Facilities returned ALL 59k PeeringDB entries (missing net_id filter) - Bug 2: Neighbours returned 0 for large ASNs (8s timeout, now 30s) - Bug 3: Visibility showed 0% when API times out (now shows -1 = unavailable) - Bug 4: Prefixes returned 0 for small ASNs (cascading fetch failure) - Bug 5: RPKI inconsistency documented (different sample sets per endpoint) - Bug 6: Atlas probe status showed 0 connected (status.name vs status_name) Also: PDB IX/Fac queries now use net_id (phase 0 + parallel phase 1) Also: Compare endpoint uses net_id for facilities
This commit is contained in:
parent
8478b154a9
commit
c4462bf633
@ -143,7 +143,7 @@ function checkRateLimit(ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fetchJSON(url, options) {
|
function fetchJSON(url, options) {
|
||||||
const timeoutMs = (options && options.timeout) || 8000;
|
const timeoutMs = (options && options.timeout) || 20000;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const reqOptions = {
|
const reqOptions = {
|
||||||
headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) },
|
headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) },
|
||||||
@ -1160,9 +1160,9 @@ const server = http.createServer(async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
// Phase 1: Fetch core data needed by multiple validations
|
// Phase 1: Fetch core data needed by multiple validations
|
||||||
const [prefixData, pdbNet, neighbourData, overviewData] = await Promise.all([
|
const [prefixData, pdbNet, neighbourData, overviewData] = await Promise.all([
|
||||||
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn),
|
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn, { timeout: 30000 }),
|
||||||
fetchJSON("https://www.peeringdb.com/api/net?asn=" + rawAsn),
|
fetchJSON("https://www.peeringdb.com/api/net?asn=" + rawAsn),
|
||||||
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn),
|
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, { timeout: 30000 }),
|
||||||
fetchJSON("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + rawAsn),
|
fetchJSON("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + rawAsn),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -1497,42 +1497,39 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ALL calls in parallel — single phase, no sequential waits
|
// Phase 0: Get PDB net first (fast, <1s) to get net_id for IX/Fac queries
|
||||||
const [pdbNet, prefixData, neighbourData, overviewData, rirData, atlasProbeData, bgpHeData, visibilityData, prefixSizeData, pdbIxlan, pdbFac] = await Promise.all([
|
const pdbNet = await fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn);
|
||||||
fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn),
|
const net = pdbNet?.data?.[0] || {};
|
||||||
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn),
|
const netId = net.id;
|
||||||
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn),
|
|
||||||
|
// Phase 1: ALL calls in parallel — RIPE Stat + PDB IX/Fac + Atlas + bgp.he.net
|
||||||
|
const promises = [
|
||||||
|
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn, { timeout: 30000 }),
|
||||||
|
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn, { timeout: 30000 }),
|
||||||
fetchJSON("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + asn),
|
fetchJSON("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + asn),
|
||||||
fetchJSON("https://stat.ripe.net/data/rir-stats-country/data.json?resource=AS" + asn),
|
fetchJSON("https://stat.ripe.net/data/rir-stats-country/data.json?resource=AS" + asn),
|
||||||
fetchJSON("https://atlas.ripe.net/api/v2/probes/?asn_v4=" + asn + "&page_size=500"),
|
fetchJSON("https://atlas.ripe.net/api/v2/probes/?asn_v4=" + asn + "&page_size=500"),
|
||||||
fetchBgpHeNet(asn),
|
fetchBgpHeNet(asn),
|
||||||
fetchJSON("https://stat.ripe.net/data/visibility/data.json?resource=AS" + asn),
|
fetchJSON("https://stat.ripe.net/data/visibility/data.json?resource=AS" + asn, { timeout: 30000 }),
|
||||||
fetchJSON("https://stat.ripe.net/data/prefix-size-distribution/data.json?resource=AS" + asn),
|
fetchJSON("https://stat.ripe.net/data/prefix-size-distribution/data.json?resource=AS" + asn),
|
||||||
fetchJSON("https://www.peeringdb.com/api/netixlan?asn=" + asn),
|
netId ? fetchJSON("https://www.peeringdb.com/api/netixlan?net_id=" + netId) : Promise.resolve(null),
|
||||||
fetchJSON("https://www.peeringdb.com/api/netfac?asn=" + asn),
|
netId ? fetchJSON("https://www.peeringdb.com/api/netfac?net_id=" + netId) : Promise.resolve(null),
|
||||||
]);
|
];
|
||||||
|
const [prefixData, neighbourData, overviewData, rirData, atlasProbeData, bgpHeData, visibilityData, prefixSizeData, ixlanData, facData] = await Promise.all(promises);
|
||||||
|
|
||||||
const net = pdbNet?.data?.[0] || {};
|
|
||||||
const netId = net.id;
|
|
||||||
const prefixes = prefixData?.data?.prefixes || [];
|
const prefixes = prefixData?.data?.prefixes || [];
|
||||||
const neighbours = neighbourData?.data?.neighbours || [];
|
const neighbours = neighbourData?.data?.neighbours || [];
|
||||||
const overview = overviewData?.data || {};
|
const overview = overviewData?.data || {};
|
||||||
const rirEntries = rirData?.data?.located_resources || rirData?.data?.rir_stats || [];
|
const rirEntries = rirData?.data?.located_resources || rirData?.data?.rir_stats || [];
|
||||||
|
|
||||||
|
// Bug 6 fix: Atlas probe status uses status.name (object), not status_name (flat)
|
||||||
const atlasProbes = atlasProbeData?.results || [];
|
const atlasProbes = atlasProbeData?.results || [];
|
||||||
const atlasConnected = atlasProbes.filter(p => p.status_name === "Connected");
|
const atlasConnected = atlasProbes.filter(p => {
|
||||||
|
const sName = (p.status_name || (p.status && p.status.name) || "").toLowerCase();
|
||||||
|
return sName === "connected";
|
||||||
|
});
|
||||||
const atlasAnchors = atlasProbes.filter(p => p.is_anchor === true);
|
const atlasAnchors = atlasProbes.filter(p => p.is_anchor === true);
|
||||||
|
|
||||||
// Use direct ASN query results, fallback to netId query if empty
|
|
||||||
let ixlanData = pdbIxlan;
|
|
||||||
let facData = pdbFac;
|
|
||||||
if ((!ixlanData?.data || ixlanData.data.length === 0) && netId) {
|
|
||||||
[ixlanData, facData] = await Promise.all([
|
|
||||||
fetchJSON("https://www.peeringdb.com/api/netixlan?net_id=" + netId),
|
|
||||||
fetchJSON("https://www.peeringdb.com/api/netfac?net_id=" + netId),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPKI: sample max 5+5 prefixes (v4+v6) in parallel
|
// RPKI: sample max 5+5 prefixes (v4+v6) in parallel
|
||||||
const allPrefixes = prefixes.map((p) => p.prefix);
|
const allPrefixes = prefixes.map((p) => p.prefix);
|
||||||
const v4Pfx = allPrefixes.filter(p => !p.includes(":")).slice(0, 5);
|
const v4Pfx = allPrefixes.filter(p => !p.includes(":")).slice(0, 5);
|
||||||
@ -1612,8 +1609,10 @@ const server = http.createServer(async (req, res) => {
|
|||||||
|
|
||||||
// Visibility API returns per-RIS-collector data
|
// Visibility API returns per-RIS-collector data
|
||||||
// Each collector has ipv4_full_table_peer_count and ipv4_full_table_peers_not_seeing[]
|
// Each collector has ipv4_full_table_peer_count and ipv4_full_table_peers_not_seeing[]
|
||||||
|
// Bug 3 fix: visibility API may timeout for large ASNs — handle gracefully
|
||||||
var visibilities = (visibilityData && visibilityData.data && visibilityData.data.visibilities) || [];
|
var visibilities = (visibilityData && visibilityData.data && visibilityData.data.visibilities) || [];
|
||||||
var v4Seeing = 0, v4Total = 0, v6Seeing = 0, v6Total = 0;
|
var v4Seeing = 0, v4Total = 0, v6Seeing = 0, v6Total = 0;
|
||||||
|
var visTimedOut = !visibilityData || !visibilityData.data;
|
||||||
visibilities.forEach(function(v) {
|
visibilities.forEach(function(v) {
|
||||||
if (!v || !v.probe) return;
|
if (!v || !v.probe) return;
|
||||||
var v4PeerCount = v.ipv4_full_table_peer_count || 0;
|
var v4PeerCount = v.ipv4_full_table_peer_count || 0;
|
||||||
@ -1627,6 +1626,11 @@ const server = http.createServer(async (req, res) => {
|
|||||||
});
|
});
|
||||||
if (v4Total > 0) ipv4VisAvg = Math.round((v4Seeing / v4Total) * 1000) / 10;
|
if (v4Total > 0) ipv4VisAvg = Math.round((v4Seeing / v4Total) * 1000) / 10;
|
||||||
if (v6Total > 0) ipv6VisAvg = Math.round((v6Seeing / v6Total) * 1000) / 10;
|
if (v6Total > 0) ipv6VisAvg = Math.round((v6Seeing / v6Total) * 1000) / 10;
|
||||||
|
// If visibility API timed out but we have prefixes, mark as -1 (= "data unavailable")
|
||||||
|
if (visTimedOut && prefixes.length > 0) {
|
||||||
|
ipv4VisAvg = -1;
|
||||||
|
ipv6VisAvg = -1;
|
||||||
|
}
|
||||||
totalRisPeersV4 = v4Total;
|
totalRisPeersV4 = v4Total;
|
||||||
totalRisPeersV6 = v6Total;
|
totalRisPeersV6 = v6Total;
|
||||||
|
|
||||||
@ -1756,23 +1760,28 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
try {
|
try {
|
||||||
// ALL calls in parallel — single batch
|
// ALL calls in parallel — single batch
|
||||||
const [pdb1, pdb2, nb1Data, nb2Data, pfx1Data, pfx2Data, ix1Data, ix2Data, fac1Data, fac2Data] = await Promise.all([
|
// Phase 1: Get PDB net objects + RIPE data
|
||||||
|
const [pdb1, pdb2, nb1Data, nb2Data, pfx1Data, pfx2Data] = await Promise.all([
|
||||||
fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn1),
|
fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn1),
|
||||||
fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn2),
|
fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn2),
|
||||||
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn1),
|
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn1, { timeout: 30000 }),
|
||||||
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn2),
|
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn2, { timeout: 30000 }),
|
||||||
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn1),
|
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn1, { timeout: 30000 }),
|
||||||
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn2),
|
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn2, { timeout: 30000 }),
|
||||||
fetchJSON("https://www.peeringdb.com/api/netixlan?asn=" + asn1),
|
|
||||||
fetchJSON("https://www.peeringdb.com/api/netixlan?asn=" + asn2),
|
|
||||||
fetchJSON("https://www.peeringdb.com/api/netfac?asn=" + asn1),
|
|
||||||
fetchJSON("https://www.peeringdb.com/api/netfac?asn=" + asn2),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const net1 = pdb1?.data?.[0] || {};
|
const net1 = pdb1?.data?.[0] || {};
|
||||||
const net2 = pdb2?.data?.[0] || {};
|
const net2 = pdb2?.data?.[0] || {};
|
||||||
|
const netId1 = net1.id;
|
||||||
|
const netId2 = net2.id;
|
||||||
|
|
||||||
// IX + Facility data already fetched in parallel above
|
// Phase 2: IX + Facility using net_id (Bug 1 fix: netfac requires net_id, not asn)
|
||||||
|
const ixFacPromises = [];
|
||||||
|
ixFacPromises.push(netId1 ? fetchJSON("https://www.peeringdb.com/api/netixlan?net_id=" + netId1) : Promise.resolve(null));
|
||||||
|
ixFacPromises.push(netId2 ? fetchJSON("https://www.peeringdb.com/api/netixlan?net_id=" + netId2) : Promise.resolve(null));
|
||||||
|
ixFacPromises.push(netId1 ? fetchJSON("https://www.peeringdb.com/api/netfac?net_id=" + netId1) : Promise.resolve(null));
|
||||||
|
ixFacPromises.push(netId2 ? fetchJSON("https://www.peeringdb.com/api/netfac?net_id=" + netId2) : Promise.resolve(null));
|
||||||
|
const [ix1Data, ix2Data, fac1Data, fac2Data] = await Promise.all(ixFacPromises);
|
||||||
|
|
||||||
const ix1Set = new Set((ix1Data?.data || []).map((ix) => ix.ix_id));
|
const ix1Set = new Set((ix1Data?.data || []).map((ix) => ix.ix_id));
|
||||||
const ix2Set = new Set((ix2Data?.data || []).map((ix) => ix.ix_id));
|
const ix2Set = new Set((ix2Data?.data || []).map((ix) => ix.ix_id));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user