92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
|
|
interface IrrAuditQuery {
|
|
asn?: string;
|
|
}
|
|
|
|
async function fetchWithRetry(url: string, retries = 1, timeout = 20000): Promise<any> {
|
|
for (let i = 0; i <= retries; i++) {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
try {
|
|
const response = await fetch(url, { signal: controller.signal });
|
|
clearTimeout(timeoutId);
|
|
if (!response.ok) {
|
|
if (i === retries) throw new Error(`HTTP ${response.status}`);
|
|
continue;
|
|
}
|
|
return await response.json();
|
|
} catch (e) {
|
|
clearTimeout(timeoutId);
|
|
if (i === retries) throw e;
|
|
await new Promise(r => setTimeout(r, 1500));
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function irrAuditRoutes(fastify: FastifyInstance): Promise<void> {
|
|
fastify.get<{ Querystring: IrrAuditQuery }>(
|
|
'/irr-audit',
|
|
async (request: FastifyRequest<{ Querystring: IrrAuditQuery }>, reply: FastifyReply) => {
|
|
let asnStr = request.query.asn || '';
|
|
const asn = asnStr.replace(/[^0-9]/g, '');
|
|
|
|
reply.header('Access-Control-Allow-Origin', '*');
|
|
reply.header('Cache-Control', 'public, max-age=1800');
|
|
|
|
if (!asn) {
|
|
return reply.status(400).send({ error: 'asn required' });
|
|
}
|
|
|
|
try {
|
|
const nlnogData = await fetchWithRetry(`https://irrexplorer.nlnog.net/api/prefixes/asn/AS${asn}`);
|
|
const prefixes = (nlnogData && nlnogData.directOrigin) || [];
|
|
|
|
let irrRoutes: string[] = [];
|
|
let irrDetails: any[] = [];
|
|
let goodCount = 0;
|
|
let warnCount = 0;
|
|
let errorCount = 0;
|
|
|
|
for (const pfx of prefixes) {
|
|
const hasIrr = pfx.irrRoutes && Object.keys(pfx.irrRoutes).length > 0;
|
|
const sources = hasIrr ? Object.keys(pfx.irrRoutes) : [];
|
|
const cat = pfx.categoryOverall || 'unknown';
|
|
|
|
if (hasIrr) irrRoutes.push(pfx.prefix);
|
|
|
|
irrDetails.push({
|
|
prefix: pfx.prefix,
|
|
irr_sources: sources,
|
|
rpki_status: pfx.rpkiRoutes && pfx.rpkiRoutes.length ? pfx.rpkiRoutes[0].rpkiStatus : 'not-found',
|
|
category: cat,
|
|
messages: (pfx.messages || []).map((m: any) => m.text)
|
|
});
|
|
|
|
if (cat === 'success') goodCount++;
|
|
else if (cat === 'warning') warnCount++;
|
|
else errorCount++;
|
|
}
|
|
|
|
const actualPfx = prefixes.map((p: any) => p.prefix);
|
|
const inBgpNotIrr = actualPfx.filter((p: string) => !irrRoutes.includes(p));
|
|
const score = actualPfx.length ? Math.round((irrRoutes.length / actualPfx.length) * 100) : 0;
|
|
|
|
return reply.status(200).send({
|
|
asn: asn,
|
|
irr_routes: irrRoutes,
|
|
actual_prefixes: actualPfx,
|
|
in_irr_not_bgp: [],
|
|
in_bgp_not_irr: inBgpNotIrr,
|
|
score: score,
|
|
details: irrDetails,
|
|
summary: { good: goodCount, warning: warnCount, error: errorCount, total: prefixes.length },
|
|
source: 'NLNOG IRR Explorer'
|
|
});
|
|
} catch (e: any) {
|
|
return reply.status(500).send({ error: e.message });
|
|
}
|
|
}
|
|
);
|
|
}
|