diff --git a/public/index.html b/public/index.html
index f3fe104..21791bc 100644
--- a/public/index.html
+++ b/public/index.html
@@ -847,7 +847,7 @@ async function doLookup() {
$('sourcesCard').classList.remove('hidden');
// Load peering recommendations
- if (d.ix_presence && d.ix_presence.connections) loadPeeringRecommendations(currentAsn, d.ix_presence.connections);
+ if (d.ix_presence && d.ix_presence.connections) loadPeeringRecommendations(currentAsn, d.ix_presence.connections, d);
// Load ASPA and bgproutes.io data asynchronously
loadHealthReport(raw);
@@ -2676,8 +2676,9 @@ function renderHealthReport(d) {
}
-function loadPeeringRecommendations(asn, ixConnections) {
+function loadPeeringRecommendations(asn, ixConnections, lookupData) {
if (!ixConnections || ixConnections.length === 0) return;
+ lookupData = lookupData || {};
$('peeringRecCard').classList.remove('hidden');
// Get the IXPs this network is on
@@ -2685,6 +2686,13 @@ function loadPeeringRecommendations(asn, ixConnections) {
var myIxNames = {};
ixConnections.forEach(function(ix) { myIxNames[ix.ix_id] = ix.ix_name; });
+ // Get existing BGP neighbours (to filter out already-established peering)
+ var existingPeers = new Set();
+ var nb = lookupData.neighbours || {};
+ (nb.upstreams || []).forEach(function(n) { existingPeers.add(n.asn); });
+ (nb.downstreams || []).forEach(function(n) { existingPeers.add(n.asn); });
+ (nb.peers || []).forEach(function(n) { existingPeers.add(n.asn); });
+
// Top networks to check peering potential with
var topNets = [13335, 15169, 32934, 16509, 8075, 20940, 6939, 174, 1299, 2914, 3356, 3257, 714, 36459, 13414, 46489, 14618, 54113, 396982, 2906];
@@ -2702,17 +2710,20 @@ function loadPeeringRecommendations(asn, ixConnections) {
}).catch(function() { return null; });
})).then(function(results) {
results = results.filter(function(r) { return r && r.asn !== parseInt(asn); });
- // Sort by common IXPs descending
results.sort(function(a, b) { return b.common_ixps.length - a.common_ixps.length; });
- var h = '';
- var withCommon = results.filter(function(r) { return r.common_ixps.length > 0; });
+ // Split into 3 categories: established, potential new, no shared IXP
+ var established = results.filter(function(r) { return r.common_ixps.length > 0 && existingPeers.has(r.asn); });
+ var potential = results.filter(function(r) { return r.common_ixps.length > 0 && !existingPeers.has(r.asn); });
var without = results.filter(function(r) { return r.common_ixps.length === 0; });
- if (withCommon.length > 0) {
- h += '
\u2705 Peering possible at shared IXPs (' + withCommon.length + ')
';
+ var h = '';
+
+ // NEW PEERING OPPORTUNITIES (not yet peering, shared IXPs exist)
+ if (potential.length > 0) {
+ h += '\uD83D\uDE80 New Peering Opportunities (' + potential.length + ')
';
h += '';
- withCommon.forEach(function(r) {
+ potential.forEach(function(r) {
h += '
';
h += '
AS' + r.asn + '' + r.common_ixps.length + ' shared IXPs
';
h += '
' + escHtml(r.name) + '
';
@@ -2726,16 +2737,32 @@ function loadPeeringRecommendations(asn, ixConnections) {
h += '
';
}
- if (without.length > 0) {
- h += '
\u26a0\ufe0f No shared IXP (' + without.length + ')
';
- h += '
';
- without.forEach(function(r) {
- h += '
' + escHtml(r.name) + ' (AS' + r.asn + ')';
+ // ALREADY ESTABLISHED (peering exists + shared IXPs)
+ if (established.length > 0) {
+ h += '
\u2705 Already Peering (' + established.length + ')
';
+ h += '
';
+ established.forEach(function(r) {
+ h += '' + escHtml(r.name) + ' (AS' + r.asn + ') — ' + r.common_ixps.length + ' IXPs';
});
h += '
';
}
- h += '
Compared with top 20 global networks by traffic volume
';
+ // NO SHARED IXP
+ if (without.length > 0) {
+ h += '
\u26a0\ufe0f No Shared IXP (' + without.length + ')
';
+ h += '
';
+ without.forEach(function(r) {
+ var alreadyPeer = existingPeers.has(r.asn) ? ' \u2714 peered via transit' : '';
+ h += '' + escHtml(r.name) + ' (AS' + r.asn + ')' + alreadyPeer + '';
+ });
+ h += '
';
+ }
+
+ if (potential.length === 0 && established.length > 0) {
+ h += '
\u2705 Already peering with all top networks at shared IXPs
';
+ }
+
+ h += '
Compared with top 20 global networks — existing peering detected via BGP neighbour data
';
$('peeringRecContent').innerHTML = h;
});
}
diff --git a/server.js b/server.js
index 4bb60ca..12460e5 100644
--- a/server.js
+++ b/server.js
@@ -726,8 +726,8 @@ async function fetchBgpHeNet(asn) {
const result = {};
const titleMatch = html.match(/
([^<]+)<\/title>/i);
if (titleMatch) result.title = titleMatch[1].trim();
- const peerMatch = html.match(/Observed\s+Peers[^<]*<[^>]*>\s*(\d+)/i) || html.match(/(\d+)\s+Peers/i);
- if (peerMatch) result.peer_count = parseInt(peerMatch[1]);
+ 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();
const lgMatch = html.match(/Looking\s+Glass[^<]*<[^>]*href="([^"]+)"/i);
@@ -736,10 +736,13 @@ async function fetchBgpHeNet(asn) {
if (descMatch) result.description = descMatch[1].trim();
const irrMatch = html.match(/IRR\s+Record[^<]*<[^>]*>[^<]*<[^>]*>([^<]+)/i);
if (irrMatch) result.irr_record = irrMatch[1].trim();
- const v4Match = html.match(/Prefixes\s+v4[^<]*<[^>]*>\s*(\d+)/i) || html.match(/IPv4\s+Prefixes[^<]*<[^>]*>\s*(\d+)/i);
- if (v4Match) result.prefixes_v4 = parseInt(v4Match[1]);
- const v6Match = html.match(/Prefixes\s+v6[^<]*<[^>]*>\s*(\d+)/i) || html.match(/IPv6\s+Prefixes[^<]*<[^>]*>\s*(\d+)/i);
- if (v6Match) result.prefixes_v6 = parseInt(v6Match[1]);
+ // bgp.he.net format: "Prefixes Originated (v4): 147
" or "Prefixes v4 ... 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) {
@@ -940,14 +943,8 @@ const server = http.createServer(async (req, res) => {
return res.end();
}
- let url, reqPath;
- try {
- url = new URL(req.url, "http://localhost");
- reqPath = url.pathname;
- } catch (_) {
- res.writeHead(400);
- return res.end("Bad Request");
- }
+ const url = new URL(req.url, "http://localhost");
+ const reqPath = url.pathname;
// Serve static files
if (reqPath === "/" || reqPath === "/index.html") {
|