Routes & HTTPS
Überblick
GateControl spricht nicht selbst HTTP nach aussen — die öffentliche Terminierung übernimmt Caddy. Der Node-Prozess baut aus seiner Datenbank eine Caddy-JSON-Konfiguration und lädt sie über die Caddy-Admin-API neu. Es gibt zwei Route-Typen (http, l4) und zwei Ziel-Arten (peer, gateway). Diese beiden Dimensionen ergeben vier Kombinationen, die im Config-Builder unterschiedlich gerendert werden.
Die zentrale Erkenntnis, ohne die vieles nicht verständlich ist: Alle HTTP-Routen landen in einem einzigen Caddy-Server srv0 mit listen: [':443', ':80']. Das pro-Route-listen-Feld wird bei der Assembly verworfen. Caddys Automatic-HTTPS stellt Zertifikate für jede Domain in srv0 aus, unabhängig vom https_enabled-Flag der Route. Dieses Flag wirkt nur noch als Hinweis für Redirects.
Routing-Kern lebt in src/services/caddyConfig.js (~900 Zeilen) und src/services/l4.js. Der CRUD-Teil der Routes-Tabelle sitzt in src/services/routes.js und delegiert die Caddy-Seite komplett an caddyConfig.syncToCaddy.
Architektur
Client ──HTTPS──▶ Caddy (srv0, :443/:80)
│
├──▶ reverse_proxy → Peer-IP:Port (target_kind=peer)
│
├──▶ reverse_proxy → Gateway-IP:8080 (target_kind=gateway)
│ mit Headers X-Gateway-Target=…
│ │
│ ▼
│ Gateway-HTTP-Proxy → LAN-Host:Port
│
└──▶ apps.layer4 (eigene L4-Server pro Port)
│
├──▶ proxy → Peer-IP:Port
└──▶ proxy → Gateway-IP:ListenPort
│
▼
Gateway-TcpProxy → LAN-Host:Port
Route-Typen und Ziel-Arten
route_type |
target_kind |
Upstream in Caddy | Backend |
|---|---|---|---|
http |
peer |
peer.allowed_ips:route.target_port |
Peer-WG-IP, direkter TCP |
http |
gateway |
gateway.allowed_ips:8080 |
Gateway-Container über WG-Tunnel, HTTP-Proxy |
l4 |
peer |
peer.allowed_ips:route.target_port |
Peer-WG-IP, L4-Proxy |
l4 |
gateway |
gateway.allowed_ips:l4_listen_port |
Gateway-Container, eigene L4-Listener |
Die Spalte target_kind in routes steuert den Upstream-Build; target_peer_id zeigt bei Gateway-Routen auf den Gateway-Peer, target_lan_host + target_lan_port liegen als Instruktion für den Gateway im Header.
Datenmodell (Auszug)
routes (
id, domain, route_type, -- 'http' | 'l4'
enabled, https_enabled,
-- Peer-Routen
peer_id, -- FK peers.id, Upstream-Peer
target_ip, target_port,
-- Gateway-Routen
target_kind, -- 'peer' | 'gateway'
target_peer_id, -- FK peers.id, Gateway-Peer
target_lan_host, target_lan_port, -- LAN-Adresse HINTER dem Gateway
-- HTTP-Features
basic_auth_enabled, basic_auth_user, basic_auth_password_hash,
acl_enabled, ip_filter_enabled,
rate_limit_enabled, rate_limit_requests, rate_limit_window,
compress_enabled, retry_enabled, retry_count,
circuit_breaker_enabled, circuit_breaker_status,
bot_blocker_enabled, bot_blocker_mode, bot_blocker_config,
backend_https, -- nur peer-target
custom_headers, mirror_enabled, mirror_targets,
sticky_enabled, backends, -- Load-Balancing
-- L4-Felder
l4_protocol, l4_listen_port, l4_tls_mode,
-- WoL (hauptsächlich Gateway-Kontext)
wol_enabled, wol_mac
)
HTTP-Routing-Flow
buildCaddyConfig() (in caddyConfig.js) iteriert alle aktivierten Routen und baut pro Domain ein Caddy-Route-Objekt. Schritte pro HTTP-Route:
-
Upstream bestimmen
target_kind='gateway'→ Upstream istgatewayPeerIp:8080.- Mehrere
backends→ Array für Load-Balancing. - Sonst Peer-IP oder
target_ip+target_port.
-
Gateway-Offline-Fallback — wenn der Gateway offline ist (Flag
gateway_offline), wird stattreverse_proxyeinstatic_responsemit 502 + Maintenance-HTML gerendert (Templategateway-offline.njk). SiehepatchGatewayRouteHandlersfür Runtime-Patches via Admin-API. -
Gateway-Headers — bei
target_kind='gateway'werden in diereverse_proxy-Headers gesetzt:X-Gateway-Target: target_lan_host:target_lan_portX-Gateway-Target-Domain: route.domain
Der Gateway-Container interpretiert diese Header und forwardet an die LAN-Adresse. Siehe home-gateway.md.
-
Backend-HTTPS —
backend_https=trueaktiviertinsecure_skip_verifyals Transport. Nur beitarget_kind='peer', nie bei Gateway-Routen: zum Gateway-Port 8080 geht immer HTTP, TLS würde einen 502 auslösen. -
Handler-Chain aufbauen (in Reihenfolge):
bot_blocker (defender) → trace (debug) → custom_headers (request) → rate_limit → mirror → compress → reverse_proxy -
Circuit-Breaker — wenn
status='open', wird die gesamte Chain durch einenstatic_response503 ersetzt (mitRetry-After-Header). -
Peer-ACL —
match.remote_ip.rangesaus derroute_peer_acl-Tabelle. Nur Peers in der Allow-List dürfen durch. -
Route-Auth / Forward-Auth — siehe unten.
-
Routen-Config einhängen unter
caddyRoutes[route.domain].
Route-Auth (Forward-Auth)
Route-Auth fügt VOR der Haupt-Chain eine zweite Route ein:
caddyRoutes[domain] = {
routes: [
routeAuthProxy, // match /route-auth/* → Node-Process (Login-UI + /verify)
routeConfig // match anything else → forward_auth-Subrequest → upstream
]
}
Der forwardAuthSubrequest ruft 127.0.0.1:3000/route-auth/verify mit den Request-Metadaten als Header auf. Bei 2xx wird die Request fortgesetzt; bei 4xx/5xx gibt Caddy einen 302 auf /route-auth/login?route=…&redirect=… zurück. Nach dem Login setzt der Node-Server einen Session-Cookie, den die nächste Subrequest wieder akzeptiert.
Nur /route-auth/* wird intercepted — frühere Versionen haben auch /css, /js, /fonts durch das Login-Asset-Bundle beansprucht; das hat mit Upstream-Panels (Synology, Speedport, TR-064) kollidiert. Siehe Kommentar in caddyConfig.js ab Zeile ~420.
Load-Balancing
backends ist ein JSON-Array mit {peer_id, port, weight}. Beim Build werden Peer-IPs aufgelöst und disabled Peers gefiltert.
| Modus | Policy |
|---|---|
sticky_enabled=true |
cookie + konfigurierbarer Cookie-Name + TTL |
| Alle Weights gleich | round_robin |
| Unterschiedliche Weights | weighted_round_robin |
retry_enabled erhöht zusätzlich load_balancing.retries = retry_count.
Server-Assembly
Nach dem Pro-Route-Build werden alle Domains in einen einzigen Server srv0 zusammengeführt:
caddyConfig.apps.http.servers.srv0 = {
listen: [':443', ':80'],
routes: serverRoutes, // [{match: {host: [domain]}, handle: [...]}]
protocols: ['h1', 'h2'],
}
Einzelne Routen werden per {match: {host: [domain]}, handle: [...], terminal: true} eingehängt. Compound-Routen (Route-Auth) werden in ein subroute-Handler gewickelt, damit die interne Route-Liste erhalten bleibt.
Die Management-UI-Route (aus GC_BASE_URL) wird ebenfalls automatisch eingefügt mit 127.0.0.1:{config.app.port} als Upstream — der Node-Prozess bedient also seine eigene Admin-Oberfläche über Caddy.
Konsequenz für TLS
Da srv0 auf :443 lauscht und alle Host-Matcher darin enthalten sind, provisioniert Caddys Automatic-HTTPS für jede Domain ein Zertifikat, unabhängig vom https_enabled-Flag der einzelnen Route. Das Flag dient im Client-UI nur noch als Hinweis, ob der Admin einen TLS-Endpunkt erwartet — es steuert keinen Listener.
L4-Routing
L4-Routes liegen unter apps.layer4.servers.<name> (Caddy-L4-Plugin). buildL4Servers in src/services/l4.js gruppiert Routen nach (protocol, listen_port, tls_mode) und erzeugt pro Gruppe einen Server:
servers['l4-tls-8443'] = {
listen: ['tcp/:8443'],
routes: [
{ match: [{tls: {sni: ['app.example.com']}}], handle: [{handler: 'tls'}, proxyHandler] },
...
]
}
TLS-Modi
l4_tls_mode |
Verhalten |
|---|---|
none |
Rohes TCP/UDP durchschleifen, kein match, kein TLS |
passthrough |
Match via SNI, aber Caddy terminiert NICHT — Zertifikat liegt am Backend |
terminate |
Match via SNI, Caddy terminiert TLS (Zertifikat wird provisioniert), Backend sieht Klartext |
Port-Konflikte
validatePortConflicts() prüft:
- Reservierte Ports (via
isPortBlocked) — z.B. 22, 53, 443, 51820. - Doppelte TLS-None-Routen auf demselben Port.
- Überlappende Port-Ranges (
8000-8100vs8050-8150).
Konflikte werfen und brechen den Config-Push ab.
Gateway-L4
Bei target_kind='gateway' + route_type='l4' wird der Upstream auf gateway-peer-ip:l4_listen_port gesetzt. Der Gateway-Container lauscht selbst auf diesem Port (über seinen TcpProxyManager) und forwardet zum LAN-Host. Caddy auf dem Server reicht nur die TCP-Verbindung zum Gateway weiter.
Sync-Flow und Self-Healing
syncToCaddy() in caddyConfig.js:
- Previous-Config via
GET /config/holen (für Rollback). - Neue Config bauen mit
buildCaddyConfig(). - runtime.json schreiben nach
/data/caddy/runtime.json(atomic rename). Diese Datei ist Source-of-Truth beim Caddy-Boot durchentrypoint.sh. - Live-Reload via
POST /load. - Verify via
GET /config/— Config muss angekommen sein. - TLS-Canary —
_verifyLocalTls(managementHost)öffnet eine TLS-Verbindung zu127.0.0.1:443mit der Management-UI-Domain als SNI. Wenn das scheitert, obwohl/loadok war, ist Caddys Listener-State korrupt. Dann: - Caddy neustart via
pkill -TERM -x caddy. Supervisord startet Caddy neu ausruntime.json. - Bei Verify-Fehler ohne TLS-Issue: Rollback auf
previousConfig.
Der TLS-Canary adressiert einen konkreten Bug: /load kann in bestimmten Situationen (Listener-Rebind, neuer Cert) erfolgreich antworten, während der Server trotzdem jeden TLS-Handshake mit internal error abwürgt. Supervisord-Restart resolved den Zustand zuverlässig.
Partial-Patches für Gateway-State
patchGatewayRouteHandlers({peerId, offline, ...}) wird vom gatewayHealth-Statemachine aufgerufen, wenn ein Gateway von online nach offline (oder zurück) wechselt. Jede Route bekommt ein @id-Marker (gc_route_<id>) bei der Assembly, sodass PATCH /id/gc_route_<id>/handle den Handler im Live-Config austauschen kann — ohne kompletten /load-Round-Trip.
offline=true→ Handler aufstatic_response502 + Maintenance-HTML.offline=false→/revert(Handler zurück auf Original).
Siehe auch
- home-gateway.md — Gateway-Architektur, Config-Hash, Heartbeat.
- licensing.md — welche Routing-Features hinter welchen Flags liegen.
- rdp-routes.md — wie RDP-Gateway-Mode eine L4-Route auto-erzeugt.
- ../HTTP-VS-L4-ROUTING.md — User-seitige Entscheidungshilfe HTTP vs L4.
- ../API.md —
/api/v1/routes-Endpoints und Payload-Schema.
Quelldateien
src/services/caddyConfig.js— Config-Builder, Sync, Self-Heal, Partial-Patches.src/services/l4.js—buildL4Servers,buildL4Route,validatePortConflicts.src/services/routes.js— CRUD, delegiert Sync ancaddyConfig.syncToCaddy.src/services/routeAuth.js— Route-Auth-Lookup-Helper für den Forward-Auth-Handler.templates/gateway-offline.njk— Maintenance-Page für offline Gateways.entrypoint.sh— Caddy-Boot aus/data/caddy/runtime.json.