fix(rvs-clients): RVS_SNI via getaddrinfo-Override statt host=/port= (HTTP-200-Fix)

Symptom mit RVS_SNI: 'server rejected WebSocket connection: HTTP 200'. Ursache:
die IP stand in der URI -> HTTP-Host-Header = IP -> Caddy findet keinen RVS-Site-
Block (routet nach Host) und liefert eine Default-200-Seite statt WS-Upgrade. Der
vorige Fix korrigierte nur SNI/Cert (server_hostname), nicht den Host-Header.

Zudem: host=/port= als websockets.connect-kwargs kollidieren in der Legacy-API
mit dem aus der URI abgeleiteten Host (TypeError).

Loesung (host-agent, satellite, f5tts/whisper/voxtral/llm-adapter): die URI nutzt
den HOSTNAMEN (RVS_SNI) -> Host-Header + SNI + Cert stimmen; ein In-Process-
getaddrinfo-Override mappt RVS_SNI -> RVS_HOST (IP) fuer den TCP-Connect.
Versionsunabhaengig, nicht-blockierend, nur aktiv wenn RVS_SNI gesetzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 17:54:38 +02:00
co-authored by Claude Opus 4.8
parent 4125ab8160
commit df5cd3be99
6 changed files with 102 additions and 44 deletions
+21 -10
View File
@@ -105,6 +105,18 @@ RVS_TOKEN = os.environ.get("RVS_TOKEN", "")
# RVS_SNI=<zert-name>. Leer = SNI = RVS_HOST (Normalfall).
RVS_SNI = os.environ.get("RVS_SNI", "").strip()
# In-Process-DNS-Override: wenn RVS_SNI gesetzt ist, verbindet die URI ueber den
# HOSTNAMEN (Host-Header + SNI + Cert stimmen), waehrend getaddrinfo den Namen auf
# die echte IP in RVS_HOST aufloest. Versionsunabhaengig — host=/port= kollidiert
# in der Legacy-websockets-API mit dem aus der URI abgeleiteten Host.
if RVS_TLS and RVS_SNI:
import socket as _socket
_orig_getaddrinfo = _socket.getaddrinfo
def _sni_getaddrinfo(host, *a, **k):
return _orig_getaddrinfo(RVS_HOST if host == RVS_SNI else host, *a, **k)
_socket.getaddrinfo = _sni_getaddrinfo
HOST_ID = (os.environ.get("HOST_ID") or _default_id()).strip()
HOST_NAME = (os.environ.get("HOST_NAME") or HOST_ID).strip()
@@ -405,20 +417,19 @@ class HostAgent:
logger.error("RVS_HOST und RVS_TOKEN sind Pflicht (siehe .env.example).")
return
backoff = 1
# SNI-/Cert-Override: auf eine IP verbinden, aber den Namen im TLS-
# Handshake praesentieren (Caddy-Cert gilt fuer den Namen).
# RVS_SNI gesetzt (Verbindung auf eine interne IP): die URI nutzt den
# HOSTNAMEN, damit Host-Header + SNI + Cert-Pruefung stimmen (sonst weist
# Caddy die Verbindung ab -> HTTP 200 statt WS-Upgrade). Der TCP-Connect
# wird per host=/port= auf die IP in RVS_HOST umgebogen.
proto = "wss" if RVS_TLS else "ws"
uri_host = RVS_SNI if (RVS_TLS and RVS_SNI) else RVS_HOST
url = f"{proto}://{uri_host}:{RVS_PORT}?token={RVS_TOKEN}"
connect_kwargs = {"max_size": 16 * 1024 * 1024,
"ping_interval": 20, "ping_timeout": 20}
if RVS_TLS and RVS_SNI:
import ssl
connect_kwargs["ssl"] = ssl.create_default_context()
connect_kwargs["server_hostname"] = RVS_SNI
while True:
proto = "wss" if RVS_TLS else "ws"
url = f"{proto}://{RVS_HOST}:{RVS_PORT}?token={RVS_TOKEN}"
try:
logger.info("Verbinde mit RVS %s://%s:%s%s …", proto, RVS_HOST, RVS_PORT,
f" (SNI {RVS_SNI})" if RVS_SNI else "")
logger.info("Verbinde mit RVS %s://%s:%s%s …", proto, uri_host, RVS_PORT,
f" (TCP {RVS_HOST})" if (RVS_TLS and RVS_SNI) else "")
async with websockets.connect(url, **connect_kwargs) as ws:
self.ws = ws
backoff = 1