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()
|
banned_ips, banned_nets = _get_banned_ips()
|
||||||
all_banned = sorted(banned_ips) + sorted(str(n) for n in banned_nets)
|
all_banned = sorted(banned_ips) + sorted(str(n) for n in banned_nets)
|
||||||
|
|
||||||
rep_ips = [b.split("/")[0] for b in all_banned]
|
# ── Étape 1 : reverse-index depuis les fichiers RIPE Stat locaux ──
|
||||||
asn_map = _batch_lookup_ip_asn(rep_ips)
|
# 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 = {}
|
by_asn: dict = {}
|
||||||
for b_entry, rep_ip in zip(all_banned, rep_ips):
|
for entry in all_banned:
|
||||||
info = asn_map.get(rep_ip, {})
|
asn = entry_asn.get(entry, "?")
|
||||||
key = info.get("asn") or "?"
|
det = asn_details.get(asn, {})
|
||||||
if key not in by_asn:
|
if asn not in by_asn:
|
||||||
by_asn[key] = {"asn": info.get("asn", ""), "name": info.get("name", ""),
|
by_asn[asn] = {"asn": asn if asn != "?" else "", "name": det.get("name", ""),
|
||||||
"country": info.get("country", ""), "entries": []}
|
"country": det.get("country", ""), "entries": []}
|
||||||
by_asn[key]["entries"].append(b_entry)
|
by_asn[asn]["entries"].append(entry)
|
||||||
|
|
||||||
groups = sorted(by_asn.values(), key=lambda g: len(g["entries"]), reverse=True)
|
groups = sorted(by_asn.values(), key=lambda g: len(g["entries"]), reverse=True)
|
||||||
return jsonify({"groups": groups, "total": len(all_banned)})
|
return jsonify({"groups": groups, "total": len(all_banned)})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue