const { RIR_DELEGATION_URLS } = require("./data/rir-delegation-urls"); // FEATURE 4: IPv6 Adoption per RIR // In-memory cache: { data, ts } let ipv6StatsCache = null; const IPV6_STATS_TTL = 24 * 60 * 60 * 1000; // 24h // Fetch and parse one RIR delegation file. // Returns { ipv4, ipv6, asns } count objects. function fetchRirDelegation(rir, url) { return new Promise((resolve) => { const result = { rir, ipv4: 0, ipv6: 0, asns: 0, ipv4_countries: new Set(), ipv6_countries: new Set() }; const mod = url.startsWith('https') ? require('https') : require('http'); const req = mod.get(url, { headers: { 'User-Agent': 'PeerCortex/1.0 (+https://peercortex.org)' }, timeout: 20000 }, res => { if (res.statusCode !== 200) { resolve(result); return; } let buf = ''; res.on('data', d => { buf += d; }); res.on('end', () => { for (const line of buf.split('\n')) { if (line.startsWith('#') || line.startsWith(rir.toLowerCase() + '|*') || !line.includes('|')) continue; const parts = line.split('|'); if (parts.length < 7) continue; const type = parts[2]; const status = parts[6].trim(); const cc = parts[1]; if (status !== 'allocated' && status !== 'assigned') continue; if (type === 'ipv4') { result.ipv4++; result.ipv4_countries.add(cc); } else if (type === 'ipv6') { result.ipv6++; result.ipv6_countries.add(cc); } else if (type === 'asn') { result.asns++; } } resolve(result); }); }); req.on('error', () => resolve(result)); req.on('timeout', () => { req.destroy(); resolve(result); }); }); } // Fetch all 5 RIR delegation files and aggregate IPv6 stats. // Cached for 24 hours. async function fetchIpv6AdoptionStats(force) { if (!force && ipv6StatsCache && (Date.now() - ipv6StatsCache.ts) < IPV6_STATS_TTL) { return ipv6StatsCache.data; } console.log('[IPv6] Fetching RIR delegation stats...'); const results = await Promise.all( Object.entries(RIR_DELEGATION_URLS).map(([rir, url]) => fetchRirDelegation(rir, url)) ); const rirs = results.map(r => ({ rir: r.rir, ipv4_records: r.ipv4, ipv6_records: r.ipv6, asn_records: r.asns, ipv6_pct: (r.ipv4 + r.ipv6) > 0 ? Math.round((r.ipv6 / (r.ipv4 + r.ipv6)) * 10000) / 100 : 0, countries_with_ipv6: r.ipv6_countries.size, countries_with_ipv4: r.ipv4_countries.size, })); const total_ipv4 = rirs.reduce((s, r) => s + r.ipv4_records, 0); const total_ipv6 = rirs.reduce((s, r) => s + r.ipv6_records, 0); const data = { fetched_at: new Date().toISOString(), global_ipv6_pct: total_ipv4 + total_ipv6 > 0 ? Math.round((total_ipv6 / (total_ipv4 + total_ipv6)) * 10000) / 100 : 0, total_ipv4_records: total_ipv4, total_ipv6_records: total_ipv6, by_rir: rirs, }; ipv6StatsCache = { data, ts: Date.now() }; console.log(`[IPv6] Done. Global IPv6%: ${data.global_ipv6_pct}% across ${rirs.length} RIRs`); return data; } // Pre-fetch on startup (non-blocking) fetchIpv6AdoptionStats().catch(() => {}); module.exports = { fetchRirDelegation, fetchIpv6AdoptionStats };