fetchBgpHeNet moved as-is, not removed, despite being confirmed dead code in practice (its only call site in /api/lookup is hardcoded to Promise.resolve(null)) -- that's a "disabled pending re-enable" state, a bigger behavioral decision than this extraction pass should make alone. Also widened the smoke-test harness's volatile-path exclusions: the live Cloudflare RPKI feed's ASPA object count ticks between runs (independent of any code change here), and the field appears under three different names (aspa_map.entries, aspa_adoption.total_objects, plus the derived delta_from_previous) that weren't all covered by the existing aspa_objects exclusion. Re-captured baseline accordingly. Verified via smoke-test harness (28/28 match). server.js: 4827 -> 4575 lines.
54 lines
3.0 KiB
JavaScript
54 lines
3.0 KiB
JavaScript
const { fetchHTML } = require("./services/http-helpers");
|
|
|
|
// Feature 24: bgp.he.net Integration.
|
|
// NOTE: currently dead code in practice -- /api/lookup's only call site
|
|
// (`timedFetch("bgp.he.net", ...)`) is hardcoded to `Promise.resolve(null)`
|
|
// rather than actually invoking this. Moved as-is (not removed) since that's
|
|
// a "disabled pending re-enable" state, not a provably-unreachable one --
|
|
// removing a whole scraping feature is a bigger decision than this
|
|
// behavior-preserving extraction pass should make unilaterally.
|
|
async function fetchBgpHeNet(asn) {
|
|
try {
|
|
const html = await fetchHTML("https://bgp.he.net/AS" + asn);
|
|
if (!html) return null;
|
|
const result = {};
|
|
const titleMatch = html.match(/<title>([^<]+)<\/title>/i);
|
|
if (titleMatch) result.title = titleMatch[1].trim();
|
|
const peerMatch = html.match(/BGP\s+Peers\s+Observed\s*\(all\)\s*:\s*(\d[\d,]*)/i) || html.match(/Observed\s+Peers[^<]*<[^>]*>\s*(\d+)/i);
|
|
if (peerMatch) result.peer_count = parseInt(peerMatch[1].replace(/,/g, ''));
|
|
const countryMatch = html.match(/Country[^<]*<[^>]*>[^<]*<[^>]*>\s*<[^>]*>([^<]+)/i);
|
|
if (countryMatch) result.country = countryMatch[1].trim();
|
|
// Extract 2-letter country code from href="/country/XX"
|
|
const ccMatch = html.match(/href="\/country\/([A-Z]{2})"/i);
|
|
if (ccMatch) result.country_code = ccMatch[1].toUpperCase();
|
|
// Extract clean AS name from title: "AS12345 Some Name - bgp.he.net" → "Some Name"
|
|
if (titleMatch) {
|
|
const rawTitle = titleMatch[1].trim();
|
|
const nameFromTitle = rawTitle.replace(/^AS\d+\s+/i, '').replace(/\s+-\s+bgp\.he\.net.*$/i, '').trim();
|
|
if (nameFromTitle && !nameFromTitle.toLowerCase().includes('bgp.he.net')) {
|
|
result.name_from_title = nameFromTitle;
|
|
}
|
|
}
|
|
|
|
const lgMatch = html.match(/Looking\s+Glass[^<]*<[^>]*href="([^"]+)"/i);
|
|
if (lgMatch) result.looking_glass = lgMatch[1];
|
|
const descMatch = html.match(/AS\s+Name[^<]*<[^>]*>[^<]*<[^>]*>([^<]+)/i);
|
|
if (descMatch) result.description = descMatch[1].trim();
|
|
const irrMatch = html.match(/IRR\s+Record[^<]*<[^>]*>[^<]*<[^>]*>([^<]+)/i);
|
|
if (irrMatch) result.irr_record = irrMatch[1].trim();
|
|
// bgp.he.net format: "Prefixes Originated (v4): 147<br/>" or "Prefixes v4 ... <td>147"
|
|
const v4Match = html.match(/Prefixes\s+Originated\s*\(v4\)\s*:\s*(\d[\d,]*)/i) || html.match(/Prefixes\s+v4[^<]*<[^>]*>\s*(\d+)/i);
|
|
if (v4Match) result.prefixes_v4 = parseInt(v4Match[1].replace(/,/g, ''));
|
|
const v6Match = html.match(/Prefixes\s+Originated\s*\(v6\)\s*:\s*(\d[\d,]*)/i) || html.match(/Prefixes\s+v6[^<]*<[^>]*>\s*(\d+)/i);
|
|
if (v6Match) result.prefixes_v6 = parseInt(v6Match[1].replace(/,/g, ''));
|
|
const allMatch = html.match(/Prefixes\s+Originated\s*\(all\)\s*:\s*(\d[\d,]*)/i);
|
|
if (allMatch) result.prefixes_all = parseInt(allMatch[1].replace(/,/g, ''));
|
|
result.source_url = "https://bgp.he.net/AS" + asn;
|
|
return result;
|
|
} catch (_e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
module.exports = { fetchBgpHeNet };
|