Agent index (llms.txt) — fetch this first, then the page it names.
Stealth TURN operations
A TLS-level SNI router that lets Orama serve TURN-over-TLS on :443,
sharing the port with Caddy HTTPS. From a network observer's
perspective, TURN traffic is indistinguishable from ordinary HTTPS —
useful for users in regions that block standard VoIP ports (UAE, Saudi
Arabia, China, Iran).
Architecture
Internet
│
▼
TCP :443
│
┌─────────┴─────────┐
│ orama-sni-router │ peeks SNI, forwards bytes
└─────────┬─────────┘
│
┌───────────────┼────────────────┐
▼ ▼
cdn-<hash>.<base> *.<base>, <base>
turn.ns-<ns>.<base> (everything else)
│ │
▼ ▼
Pion TURN-TLS Caddy
127.0.0.1:<tls-port> 127.0.0.1:8443
(per namespace) (moved from :443)The router does not terminate TLS. It reads the unencrypted TLS ClientHello (first ~5 KB), inspects the SNI extension, and dials the matching backend. Encrypted bytes pass through verbatim.
The stealth hostname is cdn-<12-hex-of-sha256(namespace)>.<base-domain>
(turn.StealthHostForNamespace). The label deliberately does NOT contain
the namespace — an SNI string like cdn.ns-anchat.… would hand DPI the
exact app to block. It's deterministic, so the cluster manager, gateway,
SNI router, and DNS all derive the same value with no coordination. Each
namespace also gets a human-readable turn.ns-<namespace>.<base> alias
for operator UX.
The plain-TURN host (turn: on 3478) is turn.ns-<namespace>.<base-domain>. The TURNS host a client uses on 5349 is the single-label turn-<namespace>.<base-domain>, which the cluster's *.<base-domain> wildcard certificate covers. The stealth host is also one label under the base domain, for the same reason.
Components
- +Library:
pkg/sniproxy/— ClientHello parser, route table, TCP server, config-file hot-reload (FileRouteReloader), and namespace auto-discovery (TURNRouteDiscoverer) - +Binary:
cmd/sni-router/(built asbin/orama-sni-routerbyorama maint build, installed to/opt/orama/bin/orama-sni-router) - +Installer:
pkg/install/installers/sni_router.go— writes the config + systemd unit and drives the unit lifecycle - +Systemd unit:
orama-namespace-sni-router@index(templatecore/systemd/orama-namespace-sni-router@.service). Runs as theoramauser withCAP_NET_BIND_SERVICE, orderedBefore=orama-namespace-caddy@index. Leftoverorama-sni-router.serviceis disabled;orama-nodestarts the@indexunit whensni_router.enabledis true. - +Config:
<oramaDir>/configs/sni-router.yaml(e.g./opt/orama/.orama/configs/sni-router.yaml) — generated by the installer; re-running install/upgrade overwrites it
Enabling the router on a node
The router is an operator opt-in per node, controlled by the
top-level sni_router block in the node's node.yaml
(<oramaDir>/configs/node.yaml):
sni_router: enabled: true
Set the flag, then re-run the normal node install/upgrade flow. The orchestrator does the whole cutover — there are no manual steps:
- +Moves Caddy's HTTPS listener from
:443to:8443(CaddyHTTPSPortBehindSNI) via thehttps_portglobal option in the generated Caddyfile. Plain HTTP (:80) is unaffected. - +Writes
sni-router.yaml(listen:443, fallback Caddy127.0.0.1:8443,turn_discoveryscanning the node's namespaces dir). - +
orama-nodestartsorama-namespace-sni-router@indexafter the index gateway is up and before Caddy, so the router owns:443.
The flag is carried forward across config regenerations, so an upgrade
never silently disables the router. When the flag is off (or removed),
the supervisor stops orama-namespace-sni-router@index and Caddy reclaims
:443 — that is also the rollback path.
Route discovery — no static routes
The generated config emits routes: []. Every TURNS route is
auto-discovered: the router rescans
<oramaDir>/data/namespaces/*/configs/turn-*.yaml every 30 seconds
(turn_discovery.rescan_interval) and, for each namespace with a TURNS
listener, installs two routes to 127.0.0.1:<tls-port>:
- +
cdn-<hash>.<base-domain>(the stealth host) - +
turn.ns-<namespace>.<base-domain>(operator alias)
Route tables are swapped atomically (Router.Replace) while connections
are in flight — new namespaces appear without a router restart.
Operator-set static routes in the config file win on an SNI conflict. A
failed scan keeps the previously-installed routes (a filesystem hiccup
never blackholes live :443 traffic).
Config values written by the installer: client_hello_timeout: 5s,
backend_dial_timeout: 5s, max_concurrent_conns: 10000.
Enabling stealth for a namespace
Per-namespace stealth is toggled via the CLI (requires WebRTC to already be enabled for the namespace):
orama namespace enable webrtc-stealth --namespace myapp orama namespace disable webrtc-stealth --namespace myapp
This drives POST /v1/namespace/webrtc/stealth/{enable|disable}, which:
- +Creates DNS A records for the stealth host pointing at the namespace's TURN nodes.
- +Flips
namespace_webrtc_config.stealth_enabled(migration030_webrtc_stealth.sql). - +Re-spawns the namespace's TURN servers with the stealth domain,
served with the cluster's
*.<base>certificate: the stealth host is one label under the base domain, so no certificate is obtained for it (see WebRTC). On failure the enable is rolled back so the board never claims a stealth endpoint that doesn't terminate TLS. - +Refreshes the namespace gateways so
turn.credentialsadvertisesturns:<stealth-host>:443as the final rung of the ICE URI ladder.
A gateway restart returns only once the restarted gateway answers
/v1/health, so the request does not complete while turn.credentials still
refuses connections (HTTP 503 from the namespace proxy).
Disabling keeps TURN and the baseline ladder (udp/tcp 3478, turns:5349)
running — only the :443 rung is removed. Disabling stealth that is already
off is idempotent (200, "already disabled"); enabling it twice, or toggling it
on a namespace without WebRTC, answers 409; an unknown namespace answers 404.
The gateway side is config-driven (Config.StealthCDNDomain in
pkg/gateway/config.go) — no code edit is needed to advertise the
stealth URI.
TLS certificates
TURN serves both its primary cert and the stealth-host cert through
hot-reloading GetCertificate callbacks (selection by SNI ServerName),
polling the cert files every 60 seconds (pkg/turn/cert_reloader.go).
Caddy-renewed certs are picked up in-process — no TURN restart, so
active relays are never dropped by a renewal.
Monitoring
sudo orama node logs orama-namespace-sni-router@index -f sudo orama node logs caddy -f
Watch for:
- +
Connection limit reachedwarnings (bumpmax_concurrent_conns) - +
backend dial failedwarnings (Caddy isn't listening on:8443, or the namespace's TURNS listener is down) - +
ClientHello peek faileddebugs (curious clients sending non-TLS to:443— usually port scanners) - +
TURN route discovery failedwarnings (namespaces dir unreadable — router keeps serving its current routes)
Rollback
Set sni_router.enabled: false in the node's node.yaml and re-run the
node upgrade flow. The supervisor stops orama-namespace-sni-router@index
and regenerates the Caddyfile so Caddy reclaims :443 — the fastest way
back to the previous topology.
What clients see
Once enabled, the credentials response gains one entry:
{
"username": "...",
"password": "...",
"ttl": 600,
"uris": [
"turn:turn.example.com:3478?transport=udp",
"turn:turn.example.com:3478?transport=tcp",
"turns:turn.example.com:5349",
"turns:cdn-1a2b3c4d5e6f.example.com:443"
]
}Browsers iterate ICE candidates; users in restricted regions will silently
succeed via the :443 URI when others fail. No client-side change is
required.