CallMeTechie
EN Anmelden
Home Produkte Blog Über mich Kontakt

Gateway-Routen auf Layer 4 (TCP/UDP-Port-Forwarding)

⚙️ Fortgeschritten · Updated vor 1 Monat

Seit v1.42.0 funktionieren L4-Routen mit target_kind=gateway end-to-end. Damit lässt sich jeder TCP- oder UDP-Port eines LAN-Geräts hinter dem Gateway-Container über den öffentlichen Server veröffentlichen — unabhängig vom Application-Layer-Protokoll.

Typische Use-Cases:

  • RDP auf Port 3389 (Windows-Server, Workstation)
  • SSH auf Port 22 (Linux-Server, Switches)
  • Datenbanken (MySQL 3306, PostgreSQL 5432, MSSQL 1433)
  • Mailserver (IMAPS 993, SMTP 587)
  • Proprietäre Protokolle (MQTT 1883, Modbus 502, Industrie-Steuerungen)
  • Game-Server (Minecraft 25565, Valheim 2456, etc.)
  • Medienserver (Plex 32400, Jellyfin 8096 via TCP statt Reverse-Proxy)
  • Alles was kein HTTP ist und trotzdem ins LAN soll

Datenfluss

Internet-Client
    │
    ├─ TCP-connect → Server-Public-IP:3389
    ▼
Caddy Layer-4-Plugin (VPS)
    │
    │ Layer-4 Proxy (kein HTTP-Parsing, einfach Byte-Stream)
    │ Upstream: gateway-tunnel-IP:3389
    ▼
WireGuard-Tunnel
    │
    ▼
Gateway-Container TcpProxyManager (Heimnetz, tunnel_ip:3389)
    │
    │ Forwarded zu target_lan_host:target_lan_port
    ▼
LAN-Gerät (z.B. 192.168.2.100:3389 = Windows-RDP)

Der Listen-Port (an dem der Server im Internet lauscht) und der Port den der Gateway-Container auf der Tunnel-IP öffnet sind identisch — 1:1-Mapping. Der LAN-Host und der LAN-Port dahinter sind frei wählbar und können sich unterscheiden (z.B. Listen 2222 → LAN 192.168.2.100:22, falls dir nicht danach ist, den SSH-Port des Servers mit Port 22 zu belegen).

Voraussetzungen

  • Pro-Lizenz: Das Feature-Flag gateway_tcp_routing ist im Community-Fallback auf false. Ohne Pro-Key blockt der API-Handler den Route-Create mit 403 gateway_tcp_routing not licensed.
  • Listen-Port darf nicht auf der Blocklist stehen. Default: 80, 443, 2019, 3000, 51820 (HTTP, HTTPS, Caddy-Admin-API, Node-App, WireGuard). Konfigurierbar via Env GC_L4_BLOCKED_PORTS.
  • Listen-Port darf nicht zweimal mit derselben TLS-Einstellung belegt sein. Konflikte werden bei buildCaddyConfig() erkannt und als Error geworfen (Route-Create rollt zurück).
  • Der Gateway-Container muss erreichbar sein — heißt WG-Tunnel up und Handshake aktiv. Ohne Tunnel kein L4-Forwarding.

Anlegen im Admin-UI

  1. Routes → Neue Route (oder existierende bearbeiten)
  2. Type auf L4 umschalten (der HTTP/L4-Toggle oben im Create-Form)
  3. Ziel-Typ auf Home Gateway (LAN) setzen
  4. Home Gateway auswählen (der Gateway-Peer durch den geroutet wird)
  5. LAN-Host eintragen (die IP des Zielgeräts im Heimnetz, z.B. 192.168.2.100)
  6. LAN-Port eintragen (z.B. 3389 für RDP)
  7. L4-Protokoll wählen: TCP oder UDP
  8. L4-Listen-Port eintragen. Meist identisch zum LAN-Port (z.B. 3389). Kann aber abweichen, wenn der Server-Port bereits belegt ist oder du bewusst eine andere Port-Nummer nach außen geben willst.
  9. L4-TLS-Mode:
    • None: Caddy reicht den Stream durch ohne jede TLS-Behandlung. Passt für RDP, SSH, DB-Ports, alles was nativ kein TLS spricht ODER seine eigene TLS-Terminierung macht.
    • Passthrough: Caddy liest nur den SNI aus dem Client-Hello zum Routing und leitet den kompletten TLS-Handshake durch. Passt wenn das LAN-Ziel HTTPS macht und du das Cert aufs Ziel-Gerät auslagern willst. Erfordert eine Domain.
    • Terminate: Caddy terminiert TLS auf dem Server und spricht plain TCP zum Gateway. ACME-Cert via DNS/HTTP-01. Erfordert eine Domain.
  10. Domain (nur bei TLS-Modes ≠ None): FQDN der per SNI zum Routing genutzt wird, z.B. rdp.example.com.
  11. Speichern

Der Gateway-Container bekommt die Änderung via Config-Sync sofort (Push-Notification vom Server → /api/config-changed → Gateway pollt → TcpProxyManager.setRoutes() startet den neuen Listener).

Port-Ranges

Wenn du einen zusammenhängenden Port-Bereich brauchst (z.B. FTP Passive-Mode, oder mehrere aufeinanderfolgende Game-Server-Ports), trägst du den Bereich als L4-Listen-Port ein:

L4-Listen-Port: 2000-2099
LAN-Port:       2000

Caddy öffnet dann alle 100 Ports auf dem Server und forwarded jeden 1:1 an den Gateway. Der Gateway startet einen einzelnen Listener der alle eingehenden Connections an die LAN-Zieladresse weitergibt.

Beispiel Game-Server mit vielen Worker-Ports:

L4-Listen-Port: 27015-27020
LAN-Host:       192.168.2.50
LAN-Port:       27015
L4-Protokoll:   UDP

Wildcard "alle Ports eines Geräts" ist nicht möglich

Das ist eine bewusste Design-Entscheidung:

  • Der Server selbst braucht seine eigenen Ports (TLS, Admin-UI, SSH, WireGuard). Ein „schluck alles" würde die Servermaschine selbst unerreichbar machen.
  • Port-Konflikte mit anderen L4-Routen (z.B. zu einem zweiten Gateway) wären nicht mehr auflösbar.
  • DDoS-Exposure wäre enorm.

Stattdessen: explizit die Ports aufzählen, die du wirklich brauchst. Jeder Port = eine Route = ein Firewall-Regel-Äquivalent.

Mehrere Gateways, gleicher Port

Wenn du zwei Gateways hast die beide z.B. SSH (Port 22) freigeben wollen, musst du unterschiedliche Listen-Ports vergeben:

gateway-a.domain:  Listen-Port 2222 → Gateway-A tunnel-IP:22 → LAN-A:22
gateway-b.domain:  Listen-Port 2223 → Gateway-B tunnel-IP:22 → LAN-B:22

Für HTTP/S-Routen läuft das über SNI (Domain-basiertes Routing auf 443). Für L4 ohne TLS-Mode hat Caddy keine Möglichkeit den Stream auseinanderzuhalten — deshalb pro Server-Listen-Port exakt ein Ziel.

Alternativ: L4 mit TLS-Mode passthrough oder terminate — dann kann Caddy per SNI zwischen mehreren Diensten auf demselben Listen-Port unterscheiden (z.B. rdp1.example.com und rdp2.example.com beide auf 3389, aber der SNI unterscheidet).

Wake-on-LAN

L4-Gateway-Routen unterstützen den WoL-Flag genauso wie HTTP-Gateway- Routen. Wenn der Gateway beim Connect-Versuch ECONNREFUSED vom LAN-Target bekommt und der WoL-Flag auf der Route gesetzt ist, schickt er ein Magic-Packet an die konfigurierte MAC. Kombiniert mit dem automatischen Retry des Clients (TCP SYN-Retransmission) kommt die Verbindung meist im zweiten Anlauf durch — das Zielgerät ist dann aus dem Standby aufgewacht.

Blockierte Ports

Server-Ports die nicht als L4-Listen-Port genutzt werden können (weil der Server sie selbst belegt oder sie sicherheitskritisch sind):

Port Zweck
22 SSH-Daemon des VPS (fast immer belegt)
53 systemd-resolved / host-dnsmasq (mit network_mode: host geteilt)
80 HTTP Auto-HTTPS (Caddy)
443 HTTPS (Caddy)
2019 Caddy Admin-API (intern)
3000 Node-App (interne Admin-API)
51820 WireGuard Server-Endpoint

Überschreibbar via Env: GC_L4_BLOCKED_PORTS="22,53,80,443,2019,3000,51820".

Praktischer Tipp: Wenn du einen LAN-Dienst auf einem blockierten Standard-Port hast (z.B. SSH auf 22), nimm einen anderen Listen-Port nach außen (z.B. 2222) und halte den LAN-Port gleich:

Listen-Port:  2222   (extern, frei wählbar)
LAN-Port:     22     (SSH auf dem Ziel-Gerät)

Der Client ruft dann ssh -p 2222 user@example.com auf. Nicht empfohlen, die Defaults aufzuweichen — Port-Konflikt mit der eigenen Infrastruktur wäre die Folge.

TLS-Modes im Detail

none (TCP Passthrough)

Einfachster Fall. Caddy ist pure Byte-Forwarder. Kein TLS-Handling. Domain-Feld optional (kein SNI-Routing). Nur ein Ziel pro Listen-Port erlaubt (keine Mehrdeutigkeit auflösbar).

Client ──TCP──▶ Server:3389 ──TCP──▶ Gateway:3389 ──TCP──▶ LAN:3389

passthrough (SNI-Sniffing ohne Termination)

Caddy liest das Client-Hello um den SNI zu extrahieren und routet basierend darauf. Der komplette TLS-Handshake + verschlüsselter Traffic laufen zum LAN-Ziel. TLS-Cert liegt auf dem LAN-Gerät.

Mehrere Ziele auf demselben Listen-Port möglich, wenn sie unterschiedliche Domains nutzen.

Client ──TLS+SNI──▶ Caddy (liest SNI, Byte-Forward den Rest) ──▶ LAN

terminate (TLS-Termination auf dem Server)

Caddy terminiert TLS, forwarded den entschlüsselten Stream an Gateway/LAN. ACME-Cert wird auf dem Server via DNS-/HTTP-01-Challenge ausgestellt.

Client ──TLS──▶ Caddy (decrypt) ──TCP──▶ Gateway ──TCP──▶ LAN

Passt, wenn das LAN-Ziel kein HTTPS beherrscht oder du die Cert-Verwaltung zentral lassen willst.

Compat / Hashing

L4-Routen sind schon im Config-Hash seit der ersten Gateway-Version. Der Bugfix in v1.42.0 ändert nur die Caddy-Config auf dem Server — das Gateway selbst bekommt denselben L4-Routen-Payload wie zuvor. Kein Hash-Bump, keine Breaking-Changes, keine Migration nötig.

Relevante Dateien

Server

  • src/services/l4.js — baut Caddy-Layer-4-Config (buildL4Servers, buildL4Route, validatePortConflicts)
  • src/services/caddyConfig.js — Pre-Processing: setzt für Gateway-L4-Routen target_ip = <gateway-tunnel-ip> und target_port = <l4_listen_port> bevor buildL4Servers läuft
  • src/services/gateways.jsgetGatewayConfig() liefert l4_routes[] an den Gateway
  • src/routes/api/routes.js — License-Gate gateway_tcp_routing im POST + PUT Handler

Gateway

  • src/proxy/tcp.jsTcpProxyManager startet Listener auf tunnel_ip:listen_port und forwarded zu target_lan_host: target_lan_port, Dual-Bind-Overlap bei Port-Änderungen
  • src/bootstrap.jstcpMgr.setRoutes(cfg.l4_routes) im config-change Event-Handler

Tests

  • tests/caddyConfig_gateway.test.js pinnt das neue Verhalten: „L4 gateway route forwards to gateway-peer-ip:listen_port (not the 127.0.0.1 placeholder)"
  • tests/gateways_getConfig.test.js deckt die L4-Sync-Payload ab
  • Gateway: tests/proxy_tcp.test.js testet den TcpProxyManager

Troubleshooting

Route zeigt beim Speichern gateway_tcp_routing not licensed

Du hast keine Pro-Lizenz oder der Community-Fallback ist aktiv. Feature ist Pro-only.

Client bekommt Connection-Refused

  • Gateway offline? Check docker ps und sudo docker logs gatecontrol-gateway
  • WG-Tunnel down? Auf dem Server wg show wg0 | grep handshake — die Zeile fürs Gateway-Peer sollte <2 Minuten alt sein
  • LAN-Target offline? Gateway checkt das nicht vorab — er versucht die TCP-Verbindung erst beim Client-Connect. WoL auf der Route aktivieren wenn es sich ums Aufwecken handelt.

„Port conflict" beim Save

Ein anderer L4-Route benutzt denselben Listen-Port schon. Server listet beide IDs im Error. Entweder: anderen Listen-Port wählen, oder — falls beide Routen TLS nutzen — sicherstellen dass sie unterschiedliche Domains per SNI haben.

Verbindung klappt von draußen, aber nicht aus dem VPN

Client ist schon im selben WG-Subnet wie der Gateway — nimm den LAN-Pfad direkt zum Zielgerät statt den Umweg über das öffentliche Caddy. Dafür sind Split-Horizon-DNS + direktes WG-Peering da.

„tls: first record does not look like a TLS handshake"

Du hast TLS-Mode auf terminate gesetzt, aber das LAN-Ziel will selbst TLS terminieren (spricht also nur HTTPS). Wechsle auf passthrough oder none.

Cookie Settings

Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Essentielle Cookies sind immer aktiv.

Datenschutzerklärung
ESC
↑↓ navigate open esc close