From 9038e280fa2668fc3571aa65242cb30368ea7a47 Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Fri, 3 Apr 2026 01:42:56 +0200 Subject: [PATCH] fix: bgp.he.net name+country fallback for unregistered ASNs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For ASNs with no PeeringDB entry and no RIPE Stat holder (e.g. reserved or unannounced ASNs), extract name from bgp.he.net page title and country code from the /country/XX href. Eliminates the last 2 CRITICAL audit failures (AS34465 → 'RIPE NCC ASN block'/GB, AS59947 → 'LLHOST INC. SRL'/RO). Audit result: 80/82 PERFECT, 0 CRITICAL. v0.6.8. --- CHANGELOG.md | 9 +++++++++ server.js | 22 +++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6ad8a5..0f8e673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,3 +118,12 @@ All notable changes to PeerCortex are documented here. ### Added - **Name search with autocomplete**: Type any network or organization name in the search bar to get live suggestions. Results are sourced from both RIPE Stat and PeeringDB — covering thousands of registered networks worldwide. Use arrow keys to navigate, Enter or click to select. + +## [0.6.8] — 2026-04-03 + +### Fixed +- **Name fallback via bgp.he.net title**: ASNs without a PeeringDB entry and no RIPE Stat holder + now extract their name from bgp.he.net page title (e.g. LLHOST INC. SRL, RIPE NCC ASN block) +- **Country code fallback via bgp.he.net**: ASNs with no country in rir-stats-country + now derive their 2-letter country code from bgp.he.net href (e.g. /country/RO, /country/GB) +- Zero CRITICAL data errors across 82 of 103 audited ASNs (21 large Tier-1 ASNs exceed 15s cold-cache) diff --git a/server.js b/server.js index 4a67a78..4d050d5 100644 --- a/server.js +++ b/server.js @@ -1546,6 +1546,18 @@ async function fetchBgpHeNet(asn) { 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); @@ -2089,7 +2101,7 @@ const server = http.createServer(async (req, res) => { JSON.stringify({ status, service: "PeerCortex", - version: "0.6.7", + version: "0.6.8", timestamp: new Date().toISOString(), uptime_seconds: Math.floor(process.uptime()), memory_mb: Math.round(mem.heapUsed / 1024 / 1024), @@ -3338,6 +3350,10 @@ const server = http.createServer(async (req, res) => { else if (selfLink.includes("lacnic")) rir = "LACNIC"; else if (selfLink.includes("afrinic")) rir = "AFRINIC"; } + // bgp.he.net country_code fallback (for unannounced/reserve ASNs) + if (!country && bgpHeData && bgpHeData.country_code) { + country = bgpHeData.country_code; + } // Last resort: derive RIR from country code (common assignments) if (!rir && country) { const ARIN_CC = new Set(["US","CA","AI","AG","BS","BB","BZ","VG","KY","DM","DO","GD","GP","HT","JM","MQ","MS","PR","KN","LC","VC","TT","TC","VI","UM"]); @@ -3535,7 +3551,7 @@ const server = http.createServer(async (req, res) => { const result = { meta: { service: "PeerCortex", - version: "0.6.7", + version: "0.6.8", query: "AS" + asn, duration_ms: duration, sources: ["PeeringDB", "RIPE Stat", "bgp.he.net", "Cloudflare RPKI", "RIPE RPKI Validator", "Route Views"], @@ -3545,7 +3561,7 @@ const server = http.createServer(async (req, res) => { }, network: { asn: parseInt(asn), - name: net.name || overview?.holder || "Unknown", + name: net.name || overview?.holder || (bgpHeData && bgpHeData.name_from_title) || "Unknown", aka: net.aka || "", org_name: (net.org && net.org.name) ? net.org.name : "", website: net.website || "",