Bannis : résolution AS via reverse-index RIPE Stat local + fallback ipinfo.io
- CIDRs bannis via "Ban AS" : résolus depuis as_cache/*.json (0 appel API) + nom/pays récupérés en 1 seule requête PostgreSQL (DISTINCT ON asn) - IPs/CIDRs bannis individuellement : _batch_lookup_ip_asn avec fallback ipinfo.io (résout les cas où ip-api.com retourne vide, ex: 103.51.13.0) - Entrées avec asn='' exclues du cache (AND asn != '') → re-tentée à chaque fois Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e0a3dd42f4
commit
d272b3e8b7
1 changed files with 62 additions and 9 deletions
71
app/app.py
71
app/app.py
|
|
@ -1215,17 +1215,70 @@ def errors_banned_groups():
|
|||
banned_ips, banned_nets = _get_banned_ips()
|
||||
all_banned = sorted(banned_ips) + sorted(str(n) for n in banned_nets)
|
||||
|
||||
rep_ips = [b.split("/")[0] for b in all_banned]
|
||||
asn_map = _batch_lookup_ip_asn(rep_ips)
|
||||
# ── Étape 1 : reverse-index depuis les fichiers RIPE Stat locaux ──
|
||||
# Les CIDRs bannis via "Ban AS" sont tous dans as_cache/AS*.json → zéro appel API
|
||||
prefix_to_asn: dict = {}
|
||||
if _AS_CACHE_DIR.exists():
|
||||
for cf in _AS_CACHE_DIR.glob("AS*.json"):
|
||||
try:
|
||||
d = json.loads(cf.read_text())
|
||||
asn = str(d.get("asn", cf.stem[2:]))
|
||||
for p in d.get("prefixes", []):
|
||||
prefix_to_asn[p] = asn
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ── Étape 2 : classer chaque entrée : connue (prefix cache) ou à chercher ──
|
||||
entry_asn: dict = {} # entry → asn string
|
||||
known_asns: set = set()
|
||||
needs_api: list = [] # entries non trouvées dans prefix cache
|
||||
|
||||
for entry in all_banned:
|
||||
if entry in prefix_to_asn:
|
||||
asn = prefix_to_asn[entry]
|
||||
entry_asn[entry] = asn
|
||||
known_asns.add(asn)
|
||||
else:
|
||||
needs_api.append(entry)
|
||||
|
||||
# ── Étape 3 : nom/pays des AS connus — 1 seule requête PostgreSQL ──
|
||||
asn_details: dict = {} # asn → {name, country}
|
||||
if known_asns:
|
||||
with _pg() as conn:
|
||||
if conn:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT DISTINCT ON (asn) asn, name, country FROM ip_asn_cache "
|
||||
"WHERE asn = ANY(%s) ORDER BY asn, fetched_at DESC",
|
||||
(list(known_asns),)
|
||||
)
|
||||
for row in cur.fetchall():
|
||||
asn_details[row[0]] = {"name": row[1], "country": row[2]}
|
||||
except Exception:
|
||||
try: conn.rollback()
|
||||
except: pass
|
||||
|
||||
# ── Étape 4 : lookup API pour les IPs/CIDRs bannis individuellement ──
|
||||
if needs_api:
|
||||
rep_ips = [e.split("/")[0] for e in needs_api]
|
||||
ip_map = _batch_lookup_ip_asn(rep_ips)
|
||||
for entry, rep_ip in zip(needs_api, rep_ips):
|
||||
info = ip_map.get(rep_ip, {})
|
||||
entry_asn[entry] = info.get("asn") or "?"
|
||||
if info.get("asn"):
|
||||
known_asns.add(info["asn"])
|
||||
asn_details.setdefault(info["asn"], {"name": info.get("name", ""), "country": info.get("country", "")})
|
||||
|
||||
# ── Étape 5 : construire les groupes ──
|
||||
by_asn: dict = {}
|
||||
for b_entry, rep_ip in zip(all_banned, rep_ips):
|
||||
info = asn_map.get(rep_ip, {})
|
||||
key = info.get("asn") or "?"
|
||||
if key not in by_asn:
|
||||
by_asn[key] = {"asn": info.get("asn", ""), "name": info.get("name", ""),
|
||||
"country": info.get("country", ""), "entries": []}
|
||||
by_asn[key]["entries"].append(b_entry)
|
||||
for entry in all_banned:
|
||||
asn = entry_asn.get(entry, "?")
|
||||
det = asn_details.get(asn, {})
|
||||
if asn not in by_asn:
|
||||
by_asn[asn] = {"asn": asn if asn != "?" else "", "name": det.get("name", ""),
|
||||
"country": det.get("country", ""), "entries": []}
|
||||
by_asn[asn]["entries"].append(entry)
|
||||
|
||||
groups = sorted(by_asn.values(), key=lambda g: len(g["entries"]), reverse=True)
|
||||
return jsonify({"groups": groups, "total": len(all_banned)})
|
||||
|
|
|
|||
Loading…
Reference in a new issue