geoblock: match_original_dport (conntrack --ctorigdstport) fuer Dienste hinter DNAT
Bei Portweiterleitung/DNAT schreibt der Kernel den Zielport in PREROUTING um, BEVOR der Filter laeuft -- eine Regel auf den oeffentlichen Port (z.B. 1194) matcht dann nie, weil im Filter der interne Port (z.B. 6666/6667) ankommt. Genau der Fall auf Endian-Appliances (public 1194 -> intern 6666-6667, 443 -> 30443). Neue Option 'match_original_dport = true': matcht per 'conntrack --ctorigdstport' den urspruenglichen Zielport (vor DNAT). Dann in 'ports' die OEFFENTLICHEN Ports eintragen -- immun gegen den internen Remap. - build_rule_variants: bei orig-dport eine Regel pro (proto, port), da conntrack --ctorigdstport einwertig ist (kein multiport). _variant_port_args nutzt dann conntrack statt multiport. - load_config: match_original_dport (bool, default false). - show_status zeigt den Match-Modus an. - Doku: geoblock.ini.example + README-Abschnitt "Dienst hinter Portweiterleitung/DNAT". 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
af2a75c443
commit
161d90de45
+35
-10
@@ -178,6 +178,12 @@ def load_config(path):
|
||||
chains_raw = sec.get("chain", "INPUT").strip()
|
||||
chains = [c.strip() for c in chains_raw.split(",") if c.strip()] or ["INPUT"]
|
||||
|
||||
# match_original_dport: bei Diensten hinter einer Portweiterleitung/DNAT matcht
|
||||
# 'ports' sonst den UMGESCHRIEBENEN Port (nach DNAT), nicht den, den der Client
|
||||
# gewaehlt hat. Mit true wird per conntrack der ORIGINAL-Zielport gematcht -> in
|
||||
# 'ports' die oeffentlichen Ports (z.B. 1194, 443) eintragen, immun gegen Remap.
|
||||
match_orig_dport = sec.getboolean("match_original_dport", fallback=False)
|
||||
|
||||
return {
|
||||
"countries": countries,
|
||||
"chains": chains,
|
||||
@@ -188,6 +194,7 @@ def load_config(path):
|
||||
"log_file": sec.get("log_file", "").strip(),
|
||||
"ports": ports,
|
||||
"protocols": protocols,
|
||||
"match_orig_dport": match_orig_dport,
|
||||
"proxy": sec.get("proxy", "").strip(),
|
||||
}
|
||||
|
||||
@@ -206,14 +213,25 @@ def build_rule_variants(cfg):
|
||||
"""Baut die Liste der (proto, dports)-Kombinationen fuer iptables-Regeln.
|
||||
|
||||
Ohne 'ports' in der .ini: eine Variante ohne Proto-/Port-Einschraenkung
|
||||
(= klassisches Geoblocking, alle Ports/Protokolle). Mit 'ports': eine
|
||||
Variante pro konfiguriertem Protokoll (multiport braucht -p tcp ODER -p udp,
|
||||
nicht beides gleichzeitig).
|
||||
(= klassisches Geoblocking, alle Ports/Protokolle).
|
||||
|
||||
Mit 'ports' und match_original_dport=False (Standard): eine Variante pro
|
||||
Protokoll, jeweils mit allen Ports als multiport --dports.
|
||||
|
||||
Mit 'ports' und match_original_dport=True: gematcht wird der URSPRUENGLICHE
|
||||
Zielport (vor DNAT) per 'conntrack --ctorigdstport'. Das ist einwertig (kein
|
||||
multiport), daher eine Variante pro (Protokoll, Port). Fuer Dienste hinter
|
||||
einer Portweiterleitung/DNAT (z.B. Endian: public 1194/udp -> intern 6666),
|
||||
wo im Filter der umgeschriebene Port ankommt -- so bleibt die Config auf dem
|
||||
oeffentlichen Port und ist immun gegen den internen Remap.
|
||||
"""
|
||||
if not cfg["ports"]:
|
||||
return [{"proto": None, "dports": None}]
|
||||
return [{"proto": None, "dports": None, "orig": False}]
|
||||
if cfg.get("match_orig_dport"):
|
||||
return [{"proto": proto, "dports": port, "orig": True}
|
||||
for proto in cfg["protocols"] for port in cfg["ports"]]
|
||||
dports = ",".join(cfg["ports"])
|
||||
return [{"proto": proto, "dports": dports} for proto in cfg["protocols"]]
|
||||
return [{"proto": proto, "dports": dports, "orig": False} for proto in cfg["protocols"]]
|
||||
|
||||
|
||||
def _variant_match_args(variant):
|
||||
@@ -224,15 +242,19 @@ def _variant_match_args(variant):
|
||||
|
||||
|
||||
def _variant_port_args(variant):
|
||||
if variant["dports"]:
|
||||
return ["-m", "multiport", "--dports", variant["dports"]]
|
||||
return []
|
||||
if not variant["dports"]:
|
||||
return []
|
||||
if variant.get("orig"):
|
||||
# conntrack matcht den ORIGINAL-Zielport (vor DNAT); einwertig, kein multiport.
|
||||
return ["-m", "conntrack", "--ctorigdstport", variant["dports"]]
|
||||
return ["-m", "multiport", "--dports", variant["dports"]]
|
||||
|
||||
|
||||
def _variant_label(variant):
|
||||
if not variant["dports"]:
|
||||
return "alle Ports"
|
||||
return f"{variant['proto']}/{variant['dports']}"
|
||||
base = f"{variant['proto']}/{variant['dports']}"
|
||||
return base + " [orig-dport]" if variant.get("orig") else base
|
||||
|
||||
|
||||
def _build_url_opener(proxy):
|
||||
@@ -524,7 +546,10 @@ def show_status(cfg):
|
||||
|
||||
print(f"Konfigurierte Laender: {', '.join(c.upper() for c in cfg['countries'])}")
|
||||
if cfg["ports"]:
|
||||
print(f"Port-Einschraenkung: {', '.join(cfg['ports'])} ({'/'.join(cfg['protocols'])})")
|
||||
modus = "Original-Zielport vor DNAT (conntrack)" if cfg.get("match_orig_dport") \
|
||||
else "Zielport (dports)"
|
||||
print(f"Port-Einschraenkung: {', '.join(cfg['ports'])} ({'/'.join(cfg['protocols'])}) "
|
||||
f"-- gematcht: {modus}")
|
||||
else:
|
||||
print("Port-Einschraenkung: keine (alle Ports/Protokolle werden geblockt)")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user