geoblock.py: vor Apply/Remove alle bestehenden geoblock-Regeln der Chain purgen
Neue Funktion purge_geoblock_rules() entfernt per 'iptables -S <chain>' ALLE Regeln mit '--match-set <name> src' (LOG wie DROP), unabhaengig von Proto/Port/Variante. Das Loeschkommando wird per shlex direkt aus der -S-Ausgabe rekonstruiert, damit --log-prefix inkl. Leerzeichen exakt passt. - --apply purged jetzt vor dem Neu-Setzen der Regeln, aber erst NACH dem atomaren Befuellen des ipsets -> kein Traffic-Loch waehrend des ipdeny-Downloads. - --remove nutzt denselben Purge statt der bisherigen varianten-basierten Loeschung -> keine verwaisten Regeln mehr, wenn sich ports/protocol zwischen zwei Laeufen geaendert haben. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b5ff887cea
commit
4fce0cd7f0
+50
-25
@@ -65,6 +65,7 @@ import argparse
|
||||
import configparser
|
||||
import datetime
|
||||
import ipaddress
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -286,6 +287,44 @@ def iptables_rule_exists(chain, ipset_name, interface, variant=None, dry_run=Fal
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def _list_chain_rules(chain):
|
||||
"""Gibt die aktuellen Regeln einer iptables-Chain als '-A ...'-Zeilen zurueck
|
||||
(Format von 'iptables -S <chain>', eine Zeile pro Regel)."""
|
||||
result = subprocess.run(["iptables", "-S", chain], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return []
|
||||
return [ln for ln in result.stdout.splitlines() if ln.startswith("-A ")]
|
||||
|
||||
|
||||
def purge_geoblock_rules(cfg, dry_run=False):
|
||||
"""Loescht ALLE Regeln in der Chain, die auf das geoblock-ipset verweisen
|
||||
(--match-set <name> src) -- unabhaengig von Proto/Port/Variante.
|
||||
|
||||
Dadurch werden auch alte Regeln aus frueheren --apply-Laeufen mit anderer
|
||||
ini (z.B. andere Ports/Protokolle) sauber entfernt, die build_rule_variants(cfg)
|
||||
fuer die AKTUELLE Config gar nicht mehr kennt. Wir rekonstruieren das Loesch-
|
||||
Kommando direkt aus 'iptables -S' (per shlex, damit z.B. das --log-prefix mit
|
||||
Leerzeichen erhalten bleibt), statt die Args selbst nachzubauen -- so passt das
|
||||
-D immer exakt zur real existierenden Regel.
|
||||
"""
|
||||
name = cfg["ipset_name"]
|
||||
chain = cfg["chain"]
|
||||
needle = f"--match-set {name} src"
|
||||
removed = 0
|
||||
for line in _list_chain_rules(chain):
|
||||
if needle not in line:
|
||||
continue
|
||||
tokens = shlex.split(line) # z.B. ["-A", "INPUT", "-m", "set", ...]
|
||||
# "-A <chain> <rest>" -> "iptables -D <chain> <rest>"
|
||||
run(["iptables", "-D"] + tokens[1:], dry_run=dry_run, check=False)
|
||||
removed += 1
|
||||
if removed:
|
||||
log(f"{removed} bestehende geoblock-Regel(n) aus Chain '{chain}' entfernt (Set '{name}')")
|
||||
else:
|
||||
log(f"Keine bestehenden geoblock-Regeln in Chain '{chain}' gefunden")
|
||||
return removed
|
||||
|
||||
|
||||
def apply_geoblock(cfg, dry_run=False):
|
||||
name = cfg["ipset_name"]
|
||||
|
||||
@@ -316,7 +355,13 @@ def apply_geoblock(cfg, dry_run=False):
|
||||
log(f"ipset '{name}' befuellt mit {len(all_cidrs)} Bereichen "
|
||||
f"aus Laendern: {', '.join(c.upper() for c in cfg['countries'])}")
|
||||
|
||||
# 4) optionale LOG-Regel(n) + DROP-Regel(n) in iptables, nur wenn noch nicht vorhanden.
|
||||
# 4) Vor dem Neu-Setzen ALLE bestehenden geoblock-Regeln der Chain entfernen
|
||||
# (auch verwaiste aus frueheren Laeufen mit anderer ini, z.B. andere Ports).
|
||||
# Erst JETZT (nach dem Befuellen des Sets), damit waehrend des ipdeny-Downloads
|
||||
# der bestehende Block aktiv bleibt und kein Traffic-Loch entsteht.
|
||||
purge_geoblock_rules(cfg, dry_run=dry_run)
|
||||
|
||||
# 5) LOG-Regel(n) + DROP-Regel(n) frisch setzen.
|
||||
# Eine Variante pro Protokoll wenn 'ports' gesetzt ist, sonst eine Variante (alle Ports).
|
||||
set_match = ["-m", "set", "--match-set", name, "src"]
|
||||
iface_opt = ["-i", cfg["interface"]] if cfg["interface"] else []
|
||||
@@ -353,30 +398,10 @@ def apply_geoblock(cfg, dry_run=False):
|
||||
|
||||
def remove_geoblock(cfg, dry_run=False):
|
||||
name = cfg["ipset_name"]
|
||||
iface_opt = ["-i", cfg["interface"]] if cfg["interface"] else []
|
||||
set_match = ["-m", "set", "--match-set", name, "src"]
|
||||
# Entfernt fuer alle aktuell konfigurierten Varianten. Falls die .ini seit dem letzten
|
||||
# --apply geaendert wurde (z.B. ports entfernt/hinzugefuegt), koennen dadurch alte
|
||||
# Regeln mit anderer Variante stehen bleiben -- im Zweifel per 'iptables -L <chain> -n'
|
||||
# pruefen und manuell aufraeumen.
|
||||
for variant in build_rule_variants(cfg):
|
||||
proto_args = _variant_match_args(variant)
|
||||
port_args = _variant_port_args(variant)
|
||||
|
||||
for _ in range(5):
|
||||
result = run(["iptables", "-D", cfg["chain"]] + iface_opt + proto_args + set_match +
|
||||
port_args + ["-j", "DROP"], dry_run=dry_run, check=False)
|
||||
if dry_run or result is None or result.returncode != 0:
|
||||
break
|
||||
|
||||
if cfg["log"]:
|
||||
for _ in range(5):
|
||||
result = run(["iptables", "-D", cfg["chain"]] + iface_opt + proto_args + set_match +
|
||||
port_args + ["-j", "LOG", "--log-prefix", cfg["log_prefix"] + " "],
|
||||
dry_run=dry_run, check=False)
|
||||
if dry_run or result is None or result.returncode != 0:
|
||||
break
|
||||
|
||||
# Loescht ALLE geoblock-Regeln der Chain (auch verwaiste aus frueheren Laeufen
|
||||
# mit anderer ini), nicht nur die aktuell konfigurierten Varianten -- so bleiben
|
||||
# keine Reste stehen, egal wie sich 'ports'/'protocol' zwischenzeitlich geaendert haben.
|
||||
purge_geoblock_rules(cfg, dry_run=dry_run)
|
||||
run(["ipset", "destroy", name], dry_run=dry_run, check=False)
|
||||
log(f"Geoblock-Regeln entfernt, ipset '{name}' geloescht.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user