niom-turn
Minimal TURN server scaffold for the niom project (MVP).
Goals
- Provide a TURN server with long-term authentication and TLS (turns) for WebRTC clients.
- Start with a minimal, well-tested parsing/utility layer and an in-memory credential store interface that can be replaced later.
Current status
- UDP listener on 0.0.0.0:3478 (STUN/TURN) with Allocate, CreatePermission, ChannelBind, and Send flows forwarding traffic via relay sockets.
- Long-term authentication with REALM/NONCE challenges and MESSAGE-INTEGRITY validation driven by
AuthManager. - STUN message parser + builder in
src/stun.rs. - Optional TLS listener (0.0.0.0:5349) mirrors the UDP path for
turns:clients.
Design
- Modules
stun.rs– STUN/TURN message parsing, MESSAGE-INTEGRITY helpers, and response builders.auth.rs–AuthManagerorchestrates nonce minting, realm checking, and key derivation using the pluggableCredentialStore(default:InMemoryStore).alloc.rs– Relay allocation management with permission and channel tracking.main.rs/tls.rs– Runtime wiring for UDP and TLS listeners using the shared authentication + allocation stack.
Authentication & credential store
CredentialStoreis an async trait withget_password(username) -> Option<String>used byAuthManager.AuthManagerderives the RFC long-term key (MD5(username:realm:password)) and validates MESSAGE-INTEGRITY while issuing signed, timestamped nonces.- The default
InMemoryStoreis provided for tests and local dev. Swap in a production store by implementing the trait and passing it toAuthManager.
How to build
cd niom-turn
cargo build
How to test (quick local smoke)
- Start the server in one terminal (it listens on UDP/3478):
cd niom-turn
cargo run
- From another machine or container, use a STUN client or
sipsak/custom script to send a minimal STUN Binding request and observe the 401 reply.
Next steps
- Implement full STUN attribute parsing (MESSAGE-INTEGRITY, FINGERPRINT).
- Implement long-term auth validation using MESSAGE-INTEGRITY.
- Implement Allocate + relayed sockets and permission handling.
- Add TLS listener (port 5349) using
tokio-rustlsand supportturns:.
Security / Deployment
- For production, run behind properly provisioned TLS certs (Let's Encrypt or mounted certs) and secure credential storage.
- Ensure UDP and TCP/TLS ports (3478/5349) are reachable from the internet when used as a public TURN server.
Auth caveat
- The current implementation intentionally keeps things simple: credentials live in-memory, A1 keys
are derived via MD5 for RFC compatibility, and nonces are signed with HMAC-SHA1. Replace these
pieces (Argon2-backed store, modern KDFs, nonce rotation) before production rollout. See
src/auth.rsfor the pluggable surface.
Milestone 1 — Protocol Backlog
This milestone focuses on turning the current MVP into a feature-complete TURN core that can be used
reliably by niom-webrtc.
Prioritised Backlog (live order)
- TURN Data Plane Enablement —
CreatePermission,ChannelBind, Send/Data indications, and peer forwarding so allocations actually relay packets between clients and peers. - Authentication Hardening — nonce lifecycle, realm configuration, Argon2-backed credential storage, and detailed error handling for 401/438 responses.
- Allocation Lifecycle & Quotas — timers, refresh requests, cleanup of expired allocations, and resource limits per user/IP.
- Protocol Compliance Extras — FINGERPRINT support, XOR-MAPPED-ADDRESS, IPv6 evaluation, checksum validation, and fuzz/interop testing.
- Observability & Limits — structured tracing, metrics, rate limiting, and CI coverage (incl.
the bundled
smoke_client).
Artifacts that track this milestone live in two places:
- This README section is kept up to date while the milestone is in progress.
- Inline module docs (
//!) insidesrc/record the current responsibilities and open backlog items for each subsystem as we iterate.
Task in progress
- TURN data plane enablement:
CreatePermissionhandling and permission trackingChannelBindsetup andSendforwarding to peers- ChannelData framing and Data Indication responses from relay to client
License: MIT
Smoke-Test (End-to-End)
Diese Anleitung beschreibt, wie du lokal den laufenden TURN/STUN-Server prüfst und welche Ergebnisse zu erwarten sind.
- Server starten
Starte den Server im Projektverzeichnis; die Ausgabe wird normal in stdout geschrieben. In meinen Tests habe ich den Server im Hintergrund gestartet und die Logs in /tmp/niom-turn-server.log umgeleitet:
cd niom-turn
# Im Vordergrund (für Entwicklung)
cargo run --bin niom-turn
# Oder im Hintergrund mit Log-Redirect
cargo run --bin niom-turn &>/tmp/niom-turn-server.log &
- Smoke-Client ausführen
Das Repo enthält ein kleines Test-Binary smoke_client, das eine STUN Binding-Request mit USERNAME und MESSAGE-INTEGRITY an 127.0.0.1:3478 sendet.
# Build (falls noch nicht gebaut)
cargo build --bin smoke_client
# Ausführen
./target/debug/smoke_client
- Erwartetes Ergebnis
Der Smoke-Client gibt die erhaltenen Bytes aus. Bei einer erfolgreichen MESSAGE-INTEGRITY-Prüfung sendet der Server eine STUN Success Response (Message Type 0x0101). Beispielsweise habe ich folgende Rückgabe gesehen:
got 20 bytes from 127.0.0.1:3478
[01, 01, 00, 00, 21, 12, a4, 42, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07, 07]
Erklärung:
01 01→ STUN Success Response (0x0101)21 12 a4 42→ Magic Cookie (0x2112A442)- die folgenden 12 Bytes sind die Transaction ID (in diesem Test
07wiederholt)
Das bedeutet: Der Server hat die MESSAGE-INTEGRITY des Requests akzeptiert und eine 200-Antwort gesendet.
Wenn stattdessen eine 401-Antwort (Challenge) ausgegeben wird, sieht man im Hex typischerweise einen Fehler-Response-Typ und REALM/NONCE-Attribute im Payload; das zeigt, dass die Authentifizierung nicht erfolgt ist und der Client die Challenge verarbeiten muss.
Hinweis
- Die derzeitige Auth-Implementierung ist minimal und für Tests gedacht. Für Produktion bitte die README-Abschnitte "Auth caveat" beachten: sichere Credentials, TLS, und ggf. ephemeral credentials verwenden.
Configuration (appsettings.json)
Das Projekt kann eine JSON-Konfigdatei appsettings.json im Arbeitsverzeichnis lesen. Eine Beispiel-Datei appsettings.example.json liegt im Repository und zeigt die erwartete Struktur:
{
"server": {
"bind": "0.0.0.0:3478",
"tls_cert": null,
"tls_key": null
},
"credentials": [
{
"username": "testuser",
"password": "secretpassword"
}
],
"auth": {
"realm": "niom-turn.local",
"nonce_secret": null,
"nonce_ttl_seconds": 300
}
}
Wenn appsettings.json vorhanden ist, verwendet der Server die server.bind Adresse, befüllt den Credential-Store aus dem credentials-Array und übernimmt zusätzlich Realm/Nonce-Einstellungen aus auth. Falls die Datei fehlt, verwendet der Server die internen Defaults (Bind 0.0.0.0:3478, Demo-Cred testuser, Realm niom-turn.local).
Deployment & TLS / Long-term Auth roadmap
Siehe DEPLOYMENT_TLS_AUTH.md für eine kurze Roadmap und empfohlene Schritte zur Integration von TLS (turns: / TCP+TLS Port 5349) und der Implementierung von RFC-konformer Long-Term Authentication.