geoblock: mehrere iptables-Chains unterstuetzen (chain = INPUT, FORWARD)

'chain' akzeptiert jetzt eine komma-getrennte Liste. Noetig, wenn der geschuetzte
Dienst hinter einer Firewall/einem Router liegt (Portweiterleitung/DNAT): solcher
Traffic laeuft durch die FORWARD-Chain und wird von einer reinen INPUT-Regel nie
gesehen. "chain = INPUT, FORWARD" deckt beide Wege ab.

- load_config: cfg["chain"] -> cfg["chains"] (Liste), Default ["INPUT"].
- apply/purge/set_iptables_rules/reassert/status/Watchdog arbeiten ueber alle
  konfigurierten Chains. geoblock_rules_on_top aggregiert (on_top nur, wenn in
  JEDER Chain oben; present, wenn in mindestens einer vorhanden) -- neuer Helper
  _chain_rules_position() prueft je Chain. --status zeigt Position pro Chain.
- Doku: geoblock.ini.example + README-Abschnitt "INPUT vs FORWARD" (inkl.
  DNAT-Diagnose und Endian-Hinweis).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-07-23 12:02:30 +02:00
co-authored by Claude Opus 4.8
parent 867a8a1c6f
commit da83f351d7
3 changed files with 121 additions and 50 deletions
+47 -1
View File
@@ -297,6 +297,49 @@ die Regel-Reihenfolge auch ohne den Watchdog stabil. Ohne bestehendes ipset
(z.B. bevor je ein `--apply` lief) macht `--reassert` bewusst nichts; das
Befuellen uebernimmt der taegliche `geoblock.timer`.
## INPUT vs FORWARD (Dienst hinter Firewall/Router)
`chain` entscheidet, welchen Traffic Geoblocking ueberhaupt zu sehen bekommt:
- **`INPUT`** — Pakete, die **an diesen Host selbst** gerichtet sind. Richtig,
wenn der geschuetzte Dienst (Webserver, VPN, …) **auf demselben Host** laeuft,
auf dem auch `geoblock.py` die Regeln setzt.
- **`FORWARD`** — Pakete, die dieser Host **weiterleitet/routet**, statt sie
selbst anzunehmen. Noetig, wenn `geoblock.py` auf einer **Firewall/einem
Router** laeuft und der eigentliche Dienst auf einem **dahinterliegenden Host**
sitzt (klassisch: Portweiterleitung/DNAT von der Firewall auf einen internen
Server). Solche Pakete laufen durch die FORWARD-Chain und werden von einer
INPUT-Regel **nie** gesehen.
**Typisches Symptom fuer den FORWARD-Fall:** Set stimmt (`ipset test` = is in),
die DROP-Regel steht in INPUT ganz oben — und trotzdem kommt die Verbindung
durch, weil das Paket gar nicht ueber INPUT laeuft. `tcpdump` zeigt dann als
Ziel eine **interne/private IP** (der Backend-Host), nicht die des Firewall-Hosts
selbst. Gegencheck auf dem Firewall-Host:
```bash
sudo iptables -t nat -L PREROUTING -n -v | grep <port> # DNAT-Portweiterleitung vorhanden?
sudo iptables -L FORWARD -n -v --line-numbers # laeuft der Traffic hier durch?
```
Gibt es eine DNAT-Weiterleitung, muss die Chain auf `FORWARD` (oder zur
Sicherheit beides):
```ini
[geoblock]
chain = INPUT, FORWARD
```
Bei mehreren Chains legt `--apply` die Regeln in **jeder** an, `--remove` raeumt
in jeder auf, und `--reassert`/`--status`/der Watchdog pruefen/korrigieren jede
Chain einzeln. Der `src`-Match (Quell-IP = echter Client) funktioniert in FORWARD
genauso wie in INPUT, weil DNAT nur das Ziel umschreibt, nicht die Quelle.
> **Endian & Co.:** Laeuft der Dienst (z.B. der OpenVPN-Server) hinter der
> Appliance und wird per Portweiterleitung erreicht, ist FORWARD der richtige
> Ort. Kombiniere das am besten mit dem Reassert-Watchdog (Abschnitt oben), da
> die Appliance auch die FORWARD-Chain bei jedem Reload neu baut.
## Verbindungstest (Test-Webserver)
`geoblock_testserver.py` ist ein winziger HTTP-Server ohne
@@ -412,7 +455,10 @@ inkl. Perfdata (Alter in Sekunden, Anzahl geblockter IP-Bereiche).
Siehe Kommentare in der Datei selbst — kurz:
- `countries`: Komma-Liste ISO-3166-1-alpha-2 Codes (z.B. `RU, CN, KP, IR`)
- `chain`: iptables-Chain (`INPUT` = eingehender Traffic zu diesem Host)
- `chain`: iptables-Chain(s), eine oder komma-getrennt mehrere. `INPUT` =
Traffic zu diesem Host (Standard); `FORWARD` = durch diesen Host **gerouteter**
Traffic (Dienst hinter Firewall/Router, per Portweiterleitung/DNAT). Beides
zugleich: `chain = INPUT, FORWARD`. Siehe Abschnitt "INPUT vs FORWARD" unten.
- `interface`: optional, nur ein Interface pruefen
- `log`: iptables-LOG-Eintrag vor dem DROP (dmesg/kern.log) an/aus
- `ipset_name`: Name des ipset-Sets