diff --git a/app/app.py b/app/app.py index 8071d15..48811c4 100644 --- a/app/app.py +++ b/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)})