diff --git a/host-agent/.env.example b/host-agent/.env.example index f9c0de5..c9eecd1 100644 --- a/host-agent/.env.example +++ b/host-agent/.env.example @@ -6,6 +6,7 @@ RVS_HOST=rvs.example.de RVS_PORT=443 RVS_TLS=true +RVS_TLS_FALLBACK=true # bei TLS-Fehlschlag einmal auf ws:// zurueckfallen RVS_TOKEN= # RVS_SNI: nur noetig, wenn RVS_HOST eine IP ist (Agent im selben Netz wie der # RVS, direkt auf die interne IP). Dann hier den Zertifikats-/Hostnamen angeben, diff --git a/host-agent/host_agent.py b/host-agent/host_agent.py index e1ead61..a9898ba 100644 --- a/host-agent/host_agent.py +++ b/host-agent/host_agent.py @@ -98,6 +98,9 @@ def _default_id() -> str: RVS_HOST = os.environ.get("RVS_HOST", "") RVS_PORT = int(os.environ.get("RVS_PORT", "443") or "443") RVS_TLS = _env_bool("RVS_TLS", True) +# Bei TLS-Fehlschlag einmal auf ws:// zurueckfallen (wie die Compute-Bridges). +# Hilft nur, wenn der RVS plaintext erreichbar ist; gegen Caddy-TLS bleibt wss. +RVS_TLS_FALLBACK = _env_bool("RVS_TLS_FALLBACK", True) RVS_TOKEN = os.environ.get("RVS_TOKEN", "") # TLS-Hostname (SNI + Zertifikatspruefung), falls RVS_HOST eine IP ist — z.B. der # Agent laeuft im selben Netz wie der RVS und verbindet direkt auf die interne IP, @@ -417,22 +420,26 @@ class HostAgent: logger.error("RVS_HOST und RVS_TOKEN sind Pflicht (siehe .env.example).") return backoff = 1 - # 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}" + # use_tls kann bei Fehlschlag einmal auf ws:// fallen (RVS_TLS_FALLBACK), + # danach wieder zurueck auf RVS_TLS (kein Sticky-Fallback). + use_tls = RVS_TLS + tls_fallback_tried = False connect_kwargs = {"max_size": 16 * 1024 * 1024, "ping_interval": 20, "ping_timeout": 20} while True: + proto = "wss" if use_tls else "ws" + # Bei TLS + RVS_SNI: URI nutzt den HOSTNAMEN (Host-Header/SNI/Cert), + # getaddrinfo mappt ihn auf die IP in RVS_HOST. Bei ws:// direkt die IP. + uri_host = RVS_SNI if (use_tls and RVS_SNI) else RVS_HOST + url = f"{proto}://{uri_host}:{RVS_PORT}?token={RVS_TOKEN}" + fallback = False try: 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 "") + f" (TCP {RVS_HOST})" if (use_tls and RVS_SNI) else "") async with websockets.connect(url, **connect_kwargs) as ws: self.ws = ws backoff = 1 + tls_fallback_tried = False await self._hello(log=True) hb = asyncio.create_task(self._heartbeat()) try: @@ -442,10 +449,19 @@ class HostAgent: hb.cancel() except Exception as exc: logger.warning("RVS-Verbindung verloren: %s", exc) + if use_tls and RVS_TLS_FALLBACK and not tls_fallback_tried: + logger.info("TLS fehlgeschlagen — Fallback auf ws://") + use_tls = False + tls_fallback_tried = True + fallback = True finally: self.ws = None + if fallback: + continue # sofort erneut mit ws:// await asyncio.sleep(backoff) backoff = min(backoff * 2, 30) + use_tls = RVS_TLS # kein Sticky-Fallback + tls_fallback_tried = False def main() -> None: diff --git a/satellite/.env.example b/satellite/.env.example index 9b84a33..1772093 100644 --- a/satellite/.env.example +++ b/satellite/.env.example @@ -6,6 +6,7 @@ RVS_HOST=rvs.example.de RVS_PORT=443 RVS_TLS=true +RVS_TLS_FALLBACK=true # bei TLS-Fehlschlag einmal auf ws:// zurueckfallen RVS_TOKEN= # RVS_SNI: nur noetig, wenn RVS_HOST eine IP ist (Satellit im selben Netz wie der # RVS, direkt auf die interne IP). Dann hier den Zertifikats-/Hostnamen angeben, diff --git a/satellite/satellite.py b/satellite/satellite.py index 81a213b..8c03613 100644 --- a/satellite/satellite.py +++ b/satellite/satellite.py @@ -106,6 +106,8 @@ def _default_id() -> str: RVS_HOST = os.environ.get("RVS_HOST", "") RVS_PORT = int(os.environ.get("RVS_PORT", "443") or "443") RVS_TLS = _env_bool("RVS_TLS", True) +# Bei TLS-Fehlschlag einmal auf ws:// zurueckfallen (wie die Compute-Bridges). +RVS_TLS_FALLBACK = _env_bool("RVS_TLS_FALLBACK", True) RVS_TOKEN = os.environ.get("RVS_TOKEN", "") # TLS-Hostname (SNI + Zertifikatspruefung), falls RVS_HOST eine IP ist — z.B. ein # Satellit im selben Netz wie der RVS, der direkt auf die interne IP verbindet @@ -1332,22 +1334,26 @@ class Satellite: _creds_load() asyncio.create_task(self._periodic_scan()) backoff = 1 - # 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}" + # use_tls kann bei Fehlschlag einmal auf ws:// fallen (RVS_TLS_FALLBACK), + # danach wieder zurueck auf RVS_TLS (kein Sticky-Fallback). Bei TLS+RVS_SNI + # nutzt die URI den HOSTNAMEN (Host-Header/SNI/Cert), getaddrinfo mappt ihn + # auf die IP in RVS_HOST. + use_tls = RVS_TLS + tls_fallback_tried = False connect_kwargs = {"max_size": 8 * 1024 * 1024, "ping_interval": 20, "ping_timeout": 20} while True: + proto = "wss" if use_tls else "ws" + uri_host = RVS_SNI if (use_tls and RVS_SNI) else RVS_HOST + url = f"{proto}://{uri_host}:{RVS_PORT}?token={RVS_TOKEN}" + fallback = False try: 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 "") + f" (TCP {RVS_HOST})" if (use_tls and RVS_SNI) else "") async with websockets.connect(url, **connect_kwargs) as ws: self.ws = ws backoff = 1 + tls_fallback_tried = False await self._hello(log=True) hb = asyncio.create_task(self._heartbeat()) try: @@ -1357,10 +1363,19 @@ class Satellite: hb.cancel() except Exception as exc: logger.warning("RVS-Verbindung verloren: %s", exc) + if use_tls and RVS_TLS_FALLBACK and not tls_fallback_tried: + logger.info("TLS fehlgeschlagen — Fallback auf ws://") + use_tls = False + tls_fallback_tried = True + fallback = True finally: self.ws = None + if fallback: + continue await asyncio.sleep(backoff) backoff = min(backoff * 2, 30) + use_tls = RVS_TLS + tls_fallback_tried = False def main() -> None: