mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
- cfradar `view=asn` keeps the RPKI route counts from routes/stats. - New cfradar `view=bgp-prefixes`: the AS's announced prefixes (pfx2as) plus IPv4 country shares resolved against the local MaxMind database. - New /api/asrank: CAIDA AS Rank lookup (rank, customer cone, degree). Misses answer 200 with null fields and stay out of the edge cache via the new `cacheIf` option on `cacheable`. - /api/asn-connectivity adds `neighbours`: provider / peer / customer counts and name lists from the local AS-relationship snapshot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
205 lines
8.1 KiB
JavaScript
205 lines
8.1 KiB
JavaScript
// Unit tests for the pure topology walk and neighbour lists in
|
|
// api/asn-connectivity.js. Both take an injected adjacency API, so fixtures
|
|
// need no snapshot on disk.
|
|
import assert from 'node:assert/strict';
|
|
import { describe, it } from 'node:test';
|
|
|
|
import { buildTopology, buildNeighbours } from '../api/asn-connectivity.js';
|
|
|
|
// Build a rel API from plain objects; ASNs 101+ are the Tier 1 clique.
|
|
const makeRel = ({ providers = {}, peers = {}, customers = {}, tier1s = [], customerCounts = {} }) => {
|
|
const t1 = new Set(tier1s);
|
|
return {
|
|
providersOf: (asn) => providers[asn] || [],
|
|
peersOf: (asn) => peers[asn] || [],
|
|
customersOf: (asn) => customers[asn] || [],
|
|
isTier1: (asn) => t1.has(asn),
|
|
customerCountOf: (asn) => customerCounts[asn] || 0,
|
|
};
|
|
};
|
|
|
|
const edgeKey = (e) => `${e.from}->${e.to}:${e.kind}`;
|
|
const sortedEdges = (graph) => graph.edges.map(edgeKey).sort();
|
|
const nodeType = (graph, asn) => graph.nodes.find(n => n.asn === asn)?.type;
|
|
|
|
describe('buildTopology', () => {
|
|
it('walks transit chains up to Tier 1s and types nodes', () => {
|
|
// 10 buys from 20; 20 buys from 101 (T1) and 30; 30 buys from 101.
|
|
const rel = makeRel({
|
|
providers: { 10: [20], 20: [101, 30], 30: [101] },
|
|
tier1s: [101],
|
|
});
|
|
const graph = buildTopology(10, rel);
|
|
assert.deepEqual(sortedEdges(graph), [
|
|
'10->20:transit',
|
|
'20->101:transit',
|
|
'20->30:transit',
|
|
'30->101:transit',
|
|
]);
|
|
assert.equal(nodeType(graph, 10), 'origin');
|
|
assert.equal(nodeType(graph, 20), 'intermediate');
|
|
assert.equal(nodeType(graph, 101), 'tier1');
|
|
});
|
|
|
|
it('records Tier-1 peers as terminal peering edges', () => {
|
|
// 10's provider 20 reaches the backbone by peering, not transit.
|
|
const rel = makeRel({
|
|
providers: { 10: [20] },
|
|
peers: { 20: [101, 102, 40] }, // 40 is a non-T1 peer — ignored
|
|
tier1s: [101, 102],
|
|
});
|
|
const graph = buildTopology(10, rel);
|
|
assert.deepEqual(sortedEdges(graph), [
|
|
'10->20:transit',
|
|
'20->101:peering',
|
|
'20->102:peering',
|
|
]);
|
|
assert.equal(graph.nodes.some(n => n.asn === 40), false);
|
|
});
|
|
|
|
it('drops non-Tier-1 providers of a node peering with the clique', () => {
|
|
// 5 T1 peers hit the peering bar → misinferred "provider" 60 vanishes.
|
|
const rel = makeRel({
|
|
providers: { 99: [101, 60], 60: [103] },
|
|
peers: { 99: [102, 103, 104, 105, 106] },
|
|
tier1s: [101, 102, 103, 104, 105, 106],
|
|
});
|
|
const graph = buildTopology(99, rel);
|
|
assert.deepEqual(sortedEdges(graph), [
|
|
'99->101:transit',
|
|
'99->102:peering',
|
|
'99->103:peering',
|
|
'99->104:peering',
|
|
'99->105:peering',
|
|
'99->106:peering',
|
|
]);
|
|
assert.equal(graph.nodes.some(n => n.asn === 60), false);
|
|
});
|
|
|
|
it('keeps a multihomed hoster below both trusted bars expanding', () => {
|
|
// 5 T1 transits + 2 T1 peers is under both bars.
|
|
const rel = makeRel({
|
|
providers: { 10: [101, 102, 103, 104, 105, 20], 20: [106] },
|
|
peers: { 10: [105, 106] },
|
|
tier1s: [101, 102, 103, 104, 105, 106],
|
|
});
|
|
const graph = buildTopology(10, rel);
|
|
assert.ok(graph.edges.some(e => edgeKey(e) === '10->20:transit'));
|
|
assert.ok(graph.edges.some(e => edgeKey(e) === '20->106:transit'));
|
|
});
|
|
|
|
it('drops non-Tier-1 providers on high combined Tier-1 adjacency', () => {
|
|
// 8 T1 transits, no T1 peers: only the combined bar catches it.
|
|
const rel = makeRel({
|
|
providers: { 99: [101, 102, 103, 104, 105, 106, 107, 108, 60], 60: [101] },
|
|
tier1s: [101, 102, 103, 104, 105, 106, 107, 108],
|
|
});
|
|
const graph = buildTopology(99, rel);
|
|
assert.equal(graph.nodes.some(n => n.asn === 60), false);
|
|
assert.equal(graph.edges.length, 8);
|
|
assert.ok(graph.edges.every(e => e.kind === 'transit'));
|
|
});
|
|
|
|
it('gives a Tier-1 origin its peering edges into the clique', () => {
|
|
const rel = makeRel({
|
|
peers: { 101: [102, 103] },
|
|
tier1s: [101, 102, 103],
|
|
});
|
|
const graph = buildTopology(101, rel);
|
|
assert.equal(nodeType(graph, 101), 'origin-tier1');
|
|
assert.deepEqual(sortedEdges(graph), [
|
|
'101->102:peering',
|
|
'101->103:peering',
|
|
]);
|
|
});
|
|
|
|
it('caps non-Tier-1 providers at the top 3 by customer count', () => {
|
|
const rel = makeRel({
|
|
providers: { 10: [21, 22, 23, 24, 25] },
|
|
customerCounts: { 21: 500, 22: 400, 23: 300, 24: 200, 25: 100 },
|
|
// Providers reach a T1 so pruning doesn't hide the cap result.
|
|
peers: { 21: [101], 22: [101], 23: [101], 24: [101], 25: [101] },
|
|
tier1s: [101],
|
|
});
|
|
const graph = buildTopology(10, rel);
|
|
const kept = graph.edges.filter(e => e.from === 10).map(e => e.to).sort();
|
|
assert.deepEqual(kept, [21, 22, 23]);
|
|
});
|
|
|
|
it('prunes intermediate chains that never reach a Tier 1', () => {
|
|
// 10 → 20 → 30, nobody touches the clique → single-node graph.
|
|
const rel = makeRel({
|
|
providers: { 10: [20], 20: [30] },
|
|
tier1s: [101],
|
|
});
|
|
const graph = buildTopology(10, rel);
|
|
assert.deepEqual(graph.nodes.map(n => n.asn), [10]);
|
|
assert.deepEqual(graph.edges, []);
|
|
});
|
|
|
|
it('keeps one edge per pair, transit winning over peering', () => {
|
|
const rel = makeRel({
|
|
providers: { 10: [101] },
|
|
peers: { 10: [101] },
|
|
tier1s: [101],
|
|
});
|
|
const graph = buildTopology(10, rel);
|
|
assert.deepEqual(sortedEdges(graph), ['10->101:transit']);
|
|
});
|
|
});
|
|
|
|
describe('buildNeighbours', () => {
|
|
const names = { 20: 'Upstream One', 21: 'Upstream Two', 40: 'Peer Net' };
|
|
const lookupName = (asn) => names[asn] || null;
|
|
|
|
it('lists providers, peers and customers with names and counts', () => {
|
|
const rel = makeRel({
|
|
providers: { 10: [20, 21] },
|
|
peers: { 10: [40] },
|
|
customers: { 10: [50] },
|
|
});
|
|
assert.deepEqual(buildNeighbours(10, rel, lookupName), {
|
|
counts: { providers: 2, peers: 1, customers: 1 },
|
|
providers: [{ asn: 20, name: 'Upstream One' }, { asn: 21, name: 'Upstream Two' }],
|
|
peers: [{ asn: 40, name: 'Peer Net' }],
|
|
customers: [{ asn: 50, name: null }],
|
|
});
|
|
});
|
|
|
|
it('ranks by customer count, ASN breaking ties', () => {
|
|
const rel = makeRel({
|
|
providers: { 10: [23, 21, 22, 24] },
|
|
customerCounts: { 21: 5, 22: 900, 23: 5 },
|
|
});
|
|
assert.deepEqual(buildNeighbours(10, rel, lookupName).providers.map((n) => n.asn), [22, 21, 23, 24]);
|
|
});
|
|
|
|
it('caps each list at 50 while counts stay complete', () => {
|
|
const many = Array.from({ length: 120 }, (_, i) => 1000 + i);
|
|
const rel = makeRel({ customers: { 10: many }, peers: { 10: many.map((asn) => asn + 1000) } });
|
|
const result = buildNeighbours(10, rel, lookupName);
|
|
assert.equal(result.counts.customers, 120);
|
|
assert.equal(result.customers.length, 50);
|
|
assert.equal(result.counts.peers, 120);
|
|
assert.equal(result.peers.length, 50);
|
|
});
|
|
|
|
it('counts a pair seen as both transit and peering as transit only', () => {
|
|
const rel = makeRel({
|
|
providers: { 10: [20] },
|
|
customers: { 10: [50] },
|
|
peers: { 10: [20, 50, 40, 40] },
|
|
});
|
|
const result = buildNeighbours(10, rel, lookupName);
|
|
assert.deepEqual(result.counts, { providers: 1, peers: 1, customers: 1 });
|
|
assert.deepEqual(result.peers, [{ asn: 40, name: 'Peer Net' }]);
|
|
});
|
|
|
|
it('returns empty lists for an ASN unknown to the snapshot', () => {
|
|
assert.deepEqual(buildNeighbours(10, makeRel({}), lookupName), {
|
|
counts: { providers: 0, peers: 0, customers: 0 },
|
|
providers: [], peers: [], customers: [],
|
|
});
|
|
});
|
|
});
|