Working tree was 3+ months ahead of git in uncommitted local edits, sitting on a detached HEAD at d3611a8 (2026-04-09), never reconciled with main. Committing as-is on its own branch, without touching main or the detached HEAD, to stop these files existing only on this one server: - public/index.html + server.js: PDF export add-on gated to client Izzy, full Adoption Tracker overlay (ASPA + IPv6 tabs) -- neither exists anywhere in git history on any branch until now - audit/: daily/rotating audit scripts + email report sender, previously untracked - scripts/: tunnel-cleanup.sh, refresh-peeringdb.sh, previously untracked - deploy-from-scp.sh, webhook-subs.json, hijack-alerts.json, aspa-adoption-history.json: previously untracked runtime/deploy state - public/public/: duplicate mirror of the HTML variants found alongside the real public/ dir, preserved as found No reconciliation with main attempted here -- that needs a deliberate pass, not a side effect of a backup commit.
177 lines
7.0 KiB
Python
Executable File
177 lines
7.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
PeerCortex Final Quality Audit v4
|
|
Tests 103 ASNs against v0.6.7 (running, stable)
|
|
Uses cached responses where available, skips ASNs that don't respond in 15s
|
|
Cross-validates: prefix counts, RPKI math, IX dedup, facilities, RIR, country
|
|
"""
|
|
import json, urllib.request, time, sys
|
|
from datetime import datetime, timezone
|
|
|
|
def fetch(url, timeout=15):
|
|
try:
|
|
req = urllib.request.Request(url, headers={"User-Agent": "PeerCortex-Audit/4.0"})
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
return json.loads(r.read())
|
|
except Exception as e:
|
|
return None
|
|
|
|
ASNS = [
|
|
1, 174, 714, 1299, 2497, 2516, 2906, 3257, 3303, 3320, 3356,
|
|
4134, 4766, 5089, 5483, 5511, 6057, 6128, 6147, 6453, 6695,
|
|
6762, 6830, 6939, 7018, 7473, 7545, 7922, 8075, 8151, 8220,
|
|
8422, 8708, 9121, 9304, 9318, 9498, 10429, 10474, 10796, 12479,
|
|
12714, 12956, 13237, 13335, 14080, 15133, 15169, 15600, 15704,
|
|
16276, 16509, 17676, 18403, 19281, 20115, 20562, 20932, 20940,
|
|
21928, 22773, 22822, 23106, 24115, 24429, 24940, 24961, 25091,
|
|
29049, 32934, 33891, 34465, 34549, 35369, 37100, 38001, 39122,
|
|
41327, 42, 42739, 43350, 43996, 44574, 45899, 47692, 48159,
|
|
50629, 51088, 54113, 56630, 57695, 58057, 59947, 60068, 60501,
|
|
62240, 64271, 132335, 136907, 137409, 139070, 197540, 199121,
|
|
]
|
|
|
|
results = {"ok": 0, "warn": 0, "crit": 0, "skip": 0, "total": len(ASNS)}
|
|
all_issues = []
|
|
responded = []
|
|
|
|
print(f"PeerCortex Final Audit v4 — {datetime.now(timezone.utc).isoformat()}")
|
|
print(f"Target: http://localhost:3101 | ASNs: {len(ASNS)}")
|
|
print("=" * 75)
|
|
|
|
for i, asn in enumerate(ASNS):
|
|
sys.stdout.write(f"\r [{i+1:3d}/{len(ASNS)}] AS{asn}... ")
|
|
sys.stdout.flush()
|
|
|
|
d = fetch(f"http://localhost:3101/api/lookup?asn={asn}", timeout=15)
|
|
if not d or "network" not in d or d.get("error"):
|
|
results["skip"] += 1
|
|
continue
|
|
|
|
responded.append(asn)
|
|
n = d.get("network", {})
|
|
p = d.get("prefixes", {})
|
|
r = d.get("rpki", {})
|
|
ix = d.get("ix_presence", {})
|
|
fac = d.get("facilities", {})
|
|
meta= d.get("meta", {})
|
|
reg = d.get("registration", {})
|
|
|
|
issues = []
|
|
|
|
# ── Math checks ─────────────────────────────────────────
|
|
if p.get("ipv4", 0) + p.get("ipv6", 0) != p.get("total", -1):
|
|
issues.append(("CRITICAL", "prefix_math",
|
|
f"v4({p.get('ipv4')})+v6({p.get('ipv6')})={p.get('ipv4',0)+p.get('ipv6',0)} ≠ total({p.get('total')})"))
|
|
|
|
rpki_sum = r.get("valid",0) + r.get("invalid",0) + r.get("not_found",0)
|
|
if rpki_sum != r.get("checked", 0):
|
|
issues.append(("CRITICAL", "rpki_sum",
|
|
f"{rpki_sum} ≠ checked({r.get('checked')})"))
|
|
|
|
if r.get("checked", 0) > 0:
|
|
exp = round(r.get("valid",0) / r.get("checked") * 100)
|
|
if abs(exp - r.get("coverage_percent", 0)) > 1:
|
|
issues.append(("CRITICAL", "rpki_pct",
|
|
f"expected {exp}% got {r.get('coverage_percent')}%"))
|
|
|
|
conns = ix.get("connections", [])
|
|
dedup = len(set(c.get("ix_id") for c in conns if c.get("ix_id")))
|
|
if dedup != ix.get("unique_ixps", 0):
|
|
issues.append(("CRITICAL", "ix_dedup",
|
|
f"dedup={dedup} ≠ unique_ixps={ix.get('unique_ixps')}"))
|
|
|
|
if len(conns) != ix.get("total_connections", 0):
|
|
issues.append(("WARNING", "ix_total",
|
|
f"len={len(conns)} ≠ total_connections={ix.get('total_connections')}"))
|
|
|
|
if len(fac.get("list",[])) != fac.get("total", 0):
|
|
issues.append(("WARNING", "fac_total",
|
|
f"len={len(fac.get('list',[]))} ≠ total={fac.get('total')}"))
|
|
|
|
# ── Mandatory fields ─────────────────────────────────────
|
|
if not n.get("name") or n.get("name") == "Unknown":
|
|
issues.append(("CRITICAL", "name_empty", f"name={n.get('name')!r}"))
|
|
|
|
if not n.get("asn"):
|
|
issues.append(("CRITICAL", "asn_empty", "ASN field missing"))
|
|
|
|
if not n.get("rir") and not reg.get("rir"):
|
|
issues.append(("WARNING", "rir_empty", f"rir empty, country={n.get('country')!r}"))
|
|
|
|
if not n.get("country"):
|
|
issues.append(("WARNING", "country_empty", "country field empty"))
|
|
|
|
if meta.get("version") != "0.6.7":
|
|
issues.append(("WARNING", "version", f"got {meta.get('version')}"))
|
|
|
|
# ── Categorize ───────────────────────────────────────────
|
|
crits = [x for x in issues if x[0] == "CRITICAL"]
|
|
warns = [x for x in issues if x[0] == "WARNING"]
|
|
|
|
if crits:
|
|
results["crit"] += 1
|
|
elif warns:
|
|
results["warn"] += 1
|
|
else:
|
|
results["ok"] += 1
|
|
|
|
if issues:
|
|
all_issues.append((asn, n.get("name","?"), issues,
|
|
p.get("total"), r.get("coverage_percent"),
|
|
ix.get("total_connections"), fac.get("total"),
|
|
n.get("rir"), n.get("country")))
|
|
|
|
print(f"\r Done. {len(responded)} responded, {results['skip']} skipped. ")
|
|
|
|
print()
|
|
print("=" * 75)
|
|
print(f"FINAL AUDIT SUMMARY — {len(responded)} ASNs responded ({results['skip']} timed out)")
|
|
print("=" * 75)
|
|
print(f" ✓ PERFECT (zero issues): {results['ok']:3d}")
|
|
print(f" ⚠ WARNING only: {results['warn']:3d}")
|
|
print(f" ✗ CRITICAL: {results['crit']:3d}")
|
|
print(f" ⏭ Skipped (no response): {results['skip']:3d}")
|
|
|
|
if results["crit"] == 0:
|
|
print("\n ✅ NO CRITICAL DATA ERRORS IN ANY RESPONDED ASN")
|
|
|
|
# Per-category counts
|
|
warn_counts = {}
|
|
crit_counts = {}
|
|
for _, _, issues, *_ in all_issues:
|
|
for sev, field, _ in issues:
|
|
if sev == "CRITICAL":
|
|
crit_counts[field] = crit_counts.get(field, 0) + 1
|
|
else:
|
|
warn_counts[field] = warn_counts.get(field, 0) + 1
|
|
|
|
if crit_counts:
|
|
print("\n CRITICAL by field:")
|
|
for f, c in sorted(crit_counts.items(), key=lambda x: -x[1]):
|
|
print(f" {f:35s}: {c} ASNs")
|
|
|
|
if warn_counts:
|
|
print("\n WARNING by field:")
|
|
for f, c in sorted(warn_counts.items(), key=lambda x: -x[1]):
|
|
print(f" {f:35s}: {c} ASNs")
|
|
|
|
# Detail for any issues
|
|
if all_issues:
|
|
print("\n DETAILS:")
|
|
for asn, name, issues, pfx, rpki_pct, ix_cnt, fac_cnt, rir, cc in all_issues:
|
|
for sev, field, msg in issues:
|
|
print(f" [{sev[:4]}] AS{asn:>7} | {field:30s} | {msg[:60]}")
|
|
|
|
print()
|
|
print(" SAMPLE DATA (responded ASNs):")
|
|
print(f" {'ASN':>8} {'Name':24s} {'RIR':>6} {'CC':>3} {'Pfx':>5} {'RPKI%':>6} {'IX':>4} {'Fac':>4}")
|
|
print(f" {'-'*70}")
|
|
for asn in responded[:25]:
|
|
d = fetch(f"http://localhost:3101/api/lookup?asn={asn}", timeout=5)
|
|
if not d: continue
|
|
n=d.get("network",{}); p=d.get("prefixes",{}); r=d.get("rpki",{}); ix=d.get("ix_presence",{}); fac=d.get("facilities",{})
|
|
print(f" AS{asn:>6} {n.get('name','?')[:22]:24s} {n.get('rir','?'):>6} {n.get('country','?'):>3} {p.get('total',0):>5} {r.get('coverage_percent',0):>5}% {ix.get('total_connections',0):>4} {fac.get('total',0):>4}")
|
|
|
|
print()
|
|
print(f"Audit complete: {datetime.now(timezone.utc).isoformat()}")
|