Bug fix (agreed pre-existing bug #2 of 3): /api/compare called fetchRPKIPerPrefix(asn, prefix), never defined anywhere in the codebase -- threw ReferenceError whenever either ASN had >=1 sample prefix, caught by the outer try/catch, returning 500. Replaced both call sites with validateRPKIWithCache(asn, prefix), the existing single-prefix RPKI check used everywhere else in the codebase -- its return shape ({status, ...}) is exactly what the surrounding code already expects. Verified via node --check, eslint no-undef (only 1 known bug remains: webhooks POST's `secret`), and smoke-test harness (28/28 match). The compare fix itself isn't exercised by the smoke test's fixed test ASNs (both return empty prefix samples locally), but the fix is a direct, same-shape substitution of an already-proven function. server.js: 1736 -> 989 lines.
107 lines
3.7 KiB
JavaScript
107 lines
3.7 KiB
JavaScript
const { fetchPeeringDB } = require("../services/peeringdb");
|
|
|
|
// Peer Matching endpoint: /api/peers/find?ix=NAME&policy=open&min_speed=10000
|
|
async function handlePeersFind(req, res, url) {
|
|
const ixName = url.searchParams.get("ix") || "";
|
|
const policy = url.searchParams.get("policy") || "";
|
|
const minSpeed = parseInt(url.searchParams.get("min_speed") || "0");
|
|
const netType = url.searchParams.get("type") || "";
|
|
|
|
if (!ixName) {
|
|
res.writeHead(400);
|
|
return res.end(JSON.stringify({ error: "Missing ix parameter (IX name)" }));
|
|
}
|
|
|
|
const start = Date.now();
|
|
try {
|
|
// Search for IX by name
|
|
const ixSearch = await fetchPeeringDB("/ix?name__contains=" + encodeURIComponent(ixName));
|
|
const ixResults = ixSearch?.data || [];
|
|
if (ixResults.length === 0) {
|
|
return res.end(JSON.stringify({ error: "No IX found matching: " + ixName, matches: [] }));
|
|
}
|
|
|
|
// Use first matching IX
|
|
const ix = ixResults[0];
|
|
const ixId = ix.id;
|
|
|
|
// Get ixlan for this IX
|
|
const ixlanData = await fetchPeeringDB("/ixlan?ix_id=" + ixId);
|
|
const ixlans = ixlanData?.data || [];
|
|
if (ixlans.length === 0) {
|
|
return res.end(JSON.stringify({ ix: { id: ixId, name: ix.name }, matches: [] }));
|
|
}
|
|
|
|
const ixlanId = ixlans[0].id;
|
|
|
|
// Get all networks at this IX
|
|
const netixlanData = await fetchPeeringDB("/netixlan?ixlan_id=" + ixlanId);
|
|
const netixlans = netixlanData?.data || [];
|
|
|
|
// Get unique net_ids
|
|
const netIds = [...new Set(netixlans.map(n => n.net_id))];
|
|
|
|
// Fetch network details in batches
|
|
const networks = [];
|
|
const batchSize = 20;
|
|
for (let i = 0; i < Math.min(netIds.length, 200); i += batchSize) {
|
|
const batch = netIds.slice(i, i + batchSize);
|
|
const batchResults = await Promise.all(
|
|
batch.map(nid => fetchPeeringDB("/net/" + nid))
|
|
);
|
|
batchResults.forEach(r => {
|
|
if (r?.data?.[0]) networks.push(r.data[0]);
|
|
});
|
|
}
|
|
|
|
// Filter and rank
|
|
let filtered = networks.map(net => {
|
|
const nix = netixlans.filter(n => n.net_id === net.id);
|
|
const maxSpeed = Math.max(...nix.map(n => n.speed || 0));
|
|
return {
|
|
asn: net.asn,
|
|
name: net.name || "",
|
|
policy: net.policy_general || "",
|
|
type: net.info_type || "",
|
|
speed_mbps: maxSpeed,
|
|
speed_gbps: maxSpeed >= 1000 ? (maxSpeed / 1000) + " Gbps" : maxSpeed + " Mbps",
|
|
traffic: net.info_traffic || "",
|
|
website: net.website || "",
|
|
peeringdb_id: net.id,
|
|
ipv4: nix[0]?.ipaddr4 || null,
|
|
ipv6: nix[0]?.ipaddr6 || null,
|
|
};
|
|
});
|
|
|
|
// Apply filters
|
|
if (policy) {
|
|
filtered = filtered.filter(n => n.policy.toLowerCase().includes(policy.toLowerCase()));
|
|
}
|
|
if (minSpeed > 0) {
|
|
filtered = filtered.filter(n => n.speed_mbps >= minSpeed);
|
|
}
|
|
if (netType) {
|
|
filtered = filtered.filter(n => n.type.toLowerCase().includes(netType.toLowerCase()));
|
|
}
|
|
|
|
// Sort by speed desc
|
|
filtered.sort((a, b) => b.speed_mbps - a.speed_mbps);
|
|
|
|
// Also find common IXPs for each match (check if they share other IXPs)
|
|
const duration = Date.now() - start;
|
|
return res.end(JSON.stringify({
|
|
meta: { duration_ms: duration, timestamp: new Date().toISOString() },
|
|
ix: { id: ixId, name: ix.name, ixlan_id: ixlanId },
|
|
total_members: netixlans.length,
|
|
filtered_count: filtered.length,
|
|
matches: filtered.slice(0, 50),
|
|
}, null, 2));
|
|
} catch (err) {
|
|
res.writeHead(500);
|
|
return res.end(JSON.stringify({ error: "Peer matching failed", message: err.message }));
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = { handlePeersFind };
|