proxychains-Konfiguration vorab prüfen, alte Versionen unterstützen

proxychains-ng 4.12/4.14 (Debian 11, Ubuntu 20.04) kennt keine IPv6-
localnet-Einträge und bricht beim Dienststart mit "localnet netmask
error" ab, wodurch z.B. Kerio Connect nicht mehr startete.

- jede erzeugte Konfiguration wird vor dem Installieren von der
  installierten Bibliothek eingelesen; bei Ablehnung wird nichts
  installiert (Warnung bzw. Fehler bei 'set')
- IPv6-Fähigkeit der Bibliothek wird erkannt; ohne sie werden die
  IPv6-Ausnahmen weggelassen und deutlich gewarnt (auch in show/test)
- selbst kompilierte Bibliothek unter /usr/local/lib wird bevorzugt
- README: Abschnitt zu alten Versionen und Bauanleitung für 4.17

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-10-05 14:42:36 +02:00
co-authored by Claude Opus 5.5
parent 9270994e82
commit 3862aae27e
2 changed files with 150 additions and 22 deletions
+35 -2
View File
@@ -96,16 +96,49 @@ PROXYCHAINS_CONF_FILE="/etc/svc-proxy/proxychains/<dienst>.conf"
PROXYCHAINS_QUIET_MODE="1"
```
**Sicherheitsprüfung:** proxychains liest seine Konfiguration schon beim Start
des Dienstes. Ein Fehler darin (z.B. `localnet netmask error`) bricht den Dienst ab.
Deshalb lässt svc-proxy jede erzeugte Konfiguration vorher von der installierten
Bibliothek einlesen. Lehnt sie die Konfiguration ab, wird für den Dienst nichts
installiert und eine Warnung ausgegeben. Der Dienst startet dann normal ohne Proxy.
**Direkt (ohne Proxy) verbunden werden immer:**
- localhost (`127.0.0.0/8`, `::1`)
- private Netze: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `fc00::/7`, `fe80::/10`
- localhost (`127.0.0.0/8`, `::1`\*)
- private Netze: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `fc00::/7`\*, `fe80::/10`\*
- DNS (Port 53, jedes Ziel), damit z.B. MX-Abfragen normal funktionieren
- alle **IP-Adressen und Netze** aus der No-Proxy-Liste (z.B. `192.0.2.0/24`).
Hostnamen/Domains aus No-Proxy kann proxychains nicht berücksichtigen.
Hinweise:
\* IPv6-Ausnahmen nur mit proxychains-ng **ab 4.16**, siehe nächster Abschnitt.
#### Ältere proxychains-Versionen (Debian 11, Ubuntu 20.04)
proxychains-ng **4.12/4.14** kennt keine IPv6-Ausnahmen. Mit einer solchen Zeile
bricht es mit `localnet netmask error` ab, und Verbindungen zu `::1` schickt es
über den Proxy. svc-proxy erkennt das: Die IPv6-Zeilen werden weggelassen, und
es gibt eine Warnung (auch in `show` und `test`). IPv4 funktioniert normal. Spricht
der Dienst intern aber über `::1`/localhost via IPv6, schlagen diese Verbindungen fehl.
Lösung: eine aktuelle Version selbst bauen. svc-proxy bevorzugt automatisch die
Bibliothek unter `/usr/local/lib`:
```bash
sudo apt install build-essential git
git clone https://github.com/rofl0r/proxychains-ng.git
cd proxychains-ng
git checkout v4.17
./configure --prefix=/usr/local --sysconfdir=/etc
make
sudo make install # -> /usr/local/lib/libproxychains4.so
sudo svc-proxy apply # Konfigurationen neu erzeugen
sudo svc-proxy show kerio-connect # zeigt Bibliothek und "IPv6-Ausnahmen: ja"
```
Weitere Hinweise:
- Proxy-Typen: `http` (CONNECT), `socks4`, `socks5`. `socks5h` wird wie `socks5`
behandelt. Einen `https`-Proxy kann proxychains nicht.
- proxychains braucht für den Proxy eine **IPv4-Adresse**. Ein Hostname wird beim
+115 -20
View File
@@ -16,12 +16,14 @@ import locale
import os
import pwd
import re
import shutil
import socket
import ssl
import struct
import subprocess
import sys
import tempfile
import textwrap
import time
from glob import glob as glob_paths
from urllib.parse import quote, unquote, urlsplit
@@ -54,23 +56,28 @@ MODE_LABELS = {
}
PROXYCHAINS_TYPES = {"http": "http", "socks4": "socks4", "socks5": "socks5", "socks5h": "socks5"}
PROXYCHAINS_LIB_GLOBS = [
# selbst kompilierte (meist neuere) Version zuerst
"/usr/local/lib/libproxychains4.so",
"/usr/local/lib*/libproxychains4.so",
"/usr/lib/*/libproxychains.so.4",
"/usr/lib/libproxychains.so.4",
"/usr/lib*/libproxychains4.so",
"/usr/lib*/proxychains-ng/libproxychains4.so",
"/usr/local/lib/libproxychains4.so",
]
# Ziele, die im proxychains-Modus immer direkt (ohne Proxy) erreicht werden
PROXYCHAINS_LOCALNETS = [
PROXYCHAINS_LOCALNETS_V4 = [
"127.0.0.0/255.0.0.0",
"::1/128",
"10.0.0.0/255.0.0.0",
"172.16.0.0/255.240.0.0",
"192.168.0.0/255.255.0.0",
"169.254.0.0/255.255.0.0",
"0.0.0.0:53/0.0.0.0",
]
# IPv6-Ausnahmen versteht proxychains-ng erst ab 4.16 (ältere brechen mit "localnet netmask error" ab)
PROXYCHAINS_LOCALNETS_V6 = [
"::1/128",
"fc00::/7",
"fe80::/10",
"0.0.0.0:53/0.0.0.0",
"[::]:53/0",
]
@@ -117,6 +124,11 @@ Proxy laufen lässt. Pro Dienst gibt es zwei Modi:
192.168/16, fc00::/7, Link-Local), DNS (Port 53) und alle IP-/Netz-
Einträge aus der No-Proxy-Liste. Benötigt: apt install proxychains4
Proxy-Typen: http, socks4, socks5 (socks5h wird wie socks5 behandelt).
Jede erzeugte Konfiguration wird vorab mit der installierten Bibliothek
geprüft; lehnt sie sie ab, wird nichts installiert.
IPv6-Ausnahmen (::1 usw.) kann proxychains erst ab Version 4.16
(Debian 11/Ubuntu 20.04 haben 4.14) – dann gibt es eine Warnung.
Eine selbst kompilierte Version unter /usr/local/lib wird bevorzugt.
AUFRUF
{prog} ncurses-Menü starten (wie '{prog} tui')
@@ -417,16 +429,61 @@ def resolve(svc, glob):
return eff, src
_PC_CHECK_CACHE = {}
def proxychains_check_conf(lib, conf_text):
"""Lässt die Bibliothek eine Konfiguration einlesen, so wie beim Dienststart
(proxychains liest sie schon beim Laden). Liefert None oder die Fehlermeldung."""
key = (lib, conf_text)
if key in _PC_CHECK_CACHE:
return _PC_CHECK_CACHE[key]
fd, path = tempfile.mkstemp(prefix="svc-proxy-check-", suffix=".conf")
try:
with os.fdopen(fd, "w") as f:
f.write(conf_text)
env = dict(os.environ, LD_PRELOAD=lib, PROXYCHAINS_CONF_FILE=path, PROXYCHAINS_QUIET_MODE="1")
try:
r = subprocess.run([shutil.which("true") or "/bin/true"], env=env, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=True, timeout=15)
err = r.stderr.strip()
if r.returncode != 0:
result = err or "Exit-Code %d" % r.returncode
elif "preload" in err or "ld.so" in err:
result = err # Bibliothek wurde gar nicht geladen (falsche Architektur o.ä.)
else:
result = None
except (OSError, subprocess.TimeoutExpired) as e:
result = "Prüfung nicht möglich: %s" % e
finally:
os.unlink(path)
_PC_CHECK_CACHE[key] = result
return result
def proxychains_ipv6_ok(lib):
test = "strict_chain\nquiet_mode\nlocalnet ::1/128\n[ProxyList]\nsocks5 127.0.0.1 1080\n"
return proxychains_check_conf(lib, test) is None
def find_proxychains_lib():
"""Pfad der proxychains-Bibliothek; bevorzugt eine Version mit IPv6-Ausnahmen."""
override = os.environ.get("SVC_PROXY_PROXYCHAINS_LIB")
if override:
return override
found = []
for pattern in PROXYCHAINS_LIB_GLOBS:
for path in sorted(glob_paths(pattern)):
if os.path.isfile(path):
return path
raise ProxyError("proxychains ist nicht installiert (Debian/Ubuntu: apt install proxychains4, "
"RHEL/Fedora: dnf install proxychains-ng)")
real = os.path.realpath(path)
if os.path.isfile(real) and real not in [os.path.realpath(f) for f in found]:
found.append(path)
if not found:
raise ProxyError("proxychains ist nicht installiert (Debian/Ubuntu: apt install proxychains4, "
"RHEL/Fedora: dnf install proxychains-ng)")
for path in found:
if proxychains_ipv6_ok(path):
return path
return found[0]
def proxy_ipv4(host):
@@ -447,9 +504,9 @@ def proxy_ipv4(host):
return infos[0][4][0]
def proxychains_localnets(no_proxy):
def proxychains_localnets(no_proxy, ipv6=True):
"""Standard-Ausnahmen plus alle IP-/Netz-Einträge aus der No-Proxy-Liste."""
nets = list(PROXYCHAINS_LOCALNETS)
nets = list(PROXYCHAINS_LOCALNETS_V4) + (list(PROXYCHAINS_LOCALNETS_V6) if ipv6 else [])
for entry in no_proxy.split(","):
entry = entry.strip().strip("[]")
try:
@@ -458,8 +515,10 @@ def proxychains_localnets(no_proxy):
continue # Hostnamen/Domains kann proxychains nicht ausnehmen
if net.version == 4:
item = "%s/%s" % (net.network_address, net.netmask)
else:
elif ipv6:
item = "%s/%d" % (net.network_address, net.prefixlen)
else:
continue
if item not in nets:
nets.append(item)
return nets
@@ -468,8 +527,8 @@ def proxychains_localnets(no_proxy):
def check_mode(eff):
"""Prüft vor dem Speichern, ob der gewählte Modus nutzbar ist."""
if eff["mode"] == "proxychains":
find_proxychains_lib()
proxy_ipv4(eff["host"])
# rendert und prüft die Konfiguration mit der installierten Bibliothek
Config(DEFAULT_CONFIG_DIR, DEFAULT_SYSTEMD_DIR).build_proxychains("check", eff)
def service_user(unit):
@@ -616,7 +675,26 @@ class Config:
lines.append('NO_PROXY="%s"' % eff["no_proxy"])
return "\n".join(lines) + "\n"
def render_proxychains(self, unit, eff):
def build_proxychains(self, unit, eff):
"""Erzeugt die proxychains-Konfiguration und lässt sie von der installierten
Bibliothek prüfen. Liefert (text, warnungen); wirft ProxyError, wenn die
Bibliothek sie ablehnt – dann wird nichts installiert."""
lib = find_proxychains_lib()
ipv6 = proxychains_ipv6_ok(lib)
text = self.render_proxychains(unit, eff, ipv6)
err = proxychains_check_conf(lib, text)
if err:
raise ProxyError("proxychains (%s) lehnt die erzeugte Konfiguration ab: %s" % (lib, err))
warnings = []
if not ipv6:
warnings.append(
"%s: Die installierte proxychains-Version (%s) kann keine IPv6-Ausnahmen "
"(erst ab 4.16). IPv6-Verbindungen – auch zu ::1/localhost – laufen über den Proxy "
"und schlagen dann fehl. Empfehlung: neuere proxychains-ng installieren (siehe README)."
% (unit, lib))
return text, warnings
def render_proxychains(self, unit, eff, ipv6=True):
entry = "%s %s %s" % (PROXYCHAINS_TYPES[eff["type"]], proxy_ipv4(eff["host"]), eff["port"])
if eff["user"]:
entry += " %s %s" % (eff["user"], eff["password"])
@@ -631,7 +709,9 @@ class Config:
"",
"# Diese Ziele werden direkt (ohne Proxy) verbunden:",
]
lines += ["localnet " + n for n in proxychains_localnets(eff["no_proxy"])]
if not ipv6:
lines.append("# ACHTUNG: diese proxychains-Version kennt keine IPv6-Ausnahmen (erst ab 4.16).")
lines += ["localnet " + n for n in proxychains_localnets(eff["no_proxy"], ipv6)]
lines += ["", "[ProxyList]", entry]
return "\n".join(lines) + "\n"
@@ -640,9 +720,10 @@ class Config:
self.ensure_config_dir()
os.makedirs(self.pc_dir, mode=0o755, exist_ok=True)
os.chmod(self.pc_dir, 0o755)
text, warnings = self.build_proxychains(unit, eff)
path = self.pc_file(unit)
secret = bool(eff["user"])
changed = write_if_different(path, self.render_proxychains(unit, eff), 0o600 if secret else 0o644)
changed = write_if_different(path, text, 0o600 if secret else 0o644)
os.chmod(path, 0o600 if secret else 0o644)
warning = None
# Die Bibliothek liest die Datei im Dienst-Prozess, also mit dessen Benutzer.
@@ -661,7 +742,9 @@ class Config:
st = os.stat(path)
if (st.st_uid, st.st_gid) != (uid, gid):
os.chown(path, uid, gid)
return changed, warning
if warning:
warnings.append(warning)
return changed, "\n".join(warnings) or None
def render_dropin(self, unit):
return (
@@ -1094,11 +1177,14 @@ def run_proxy_test(eff, targets, cfg=None, unit=None, timeout=TEST_TIMEOUT):
if eff["mode"] == "proxychains":
try:
conf = (cfg or Config(DEFAULT_CONFIG_DIR, DEFAULT_SYSTEMD_DIR)).render_proxychains(unit or "test", eff)
conf, warnings = (cfg or Config(DEFAULT_CONFIG_DIR, DEFAULT_SYSTEMD_DIR)).build_proxychains(
unit or "test", eff)
except ProxyError as e:
results.append(("fail", "proxychains-Test: %s" % e))
results.append(("fail", "proxychains: %s" % e))
else:
results.append(("info", "Test über proxychains (so verbindet der Dienst):"))
for w in warnings:
results.append(("fail", "proxychains: " + w.split(": ", 1)[-1]))
results += proxychains_selftest(conf, targets, timeout)
return results
@@ -1381,7 +1467,7 @@ class TUI:
self.message("Fehler", [str(e)], error=True)
return
if warnings:
self.message("Hinweis", list(warnings), error=True)
self.message("Hinweis", [l for w in warnings for l in w.split("\n")], error=True)
def edit_service(self, unit):
svc = self.cfg.services.get(unit)
@@ -1469,6 +1555,9 @@ class TUI:
return win, bh, bw
def message(self, title, lines, error=False):
_, w = self.scr.getmaxyx()
width = max(20, min(w - 8, 100))
lines = [part for l in lines for part in (textwrap.wrap(l, width) or [""])]
win, bh, bw = self.box(title, lines)
for i, l in enumerate(lines[:bh - 4]):
put(win, 1 + i, 2, l, self.c_err if error else 0, bw - 4)
@@ -2097,6 +2186,12 @@ def cmd_show(cfg, args):
print("Env-Datei: %s" % cfg.env_file(unit))
if eff["mode"] == "proxychains":
print("proxychains-Konfiguration: %s" % cfg.pc_file(unit))
try:
lib = find_proxychains_lib()
print("proxychains-Bibliothek: %s (IPv6-Ausnahmen: %s)"
% (lib, "ja" if proxychains_ipv6_ok(lib) else "NEIN – Version zu alt, siehe README"))
except ProxyError as e:
print("proxychains-Bibliothek: FEHLER: %s" % e)
return 0