173 lines
7.4 KiB
Markdown
173 lines
7.4 KiB
Markdown
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` – `AuthManager` orchestrates nonce minting, realm checking, and key derivation using the pluggable `CredentialStore` (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
|
||
- `CredentialStore` is an async trait with `get_password(username) -> Option<String>` used by `AuthManager`.
|
||
- `AuthManager` derives the RFC long-term key (`MD5(username:realm:password)`) and validates MESSAGE-INTEGRITY while issuing signed, timestamped nonces.
|
||
- The default `InMemoryStore` is provided for tests and local dev. Swap in a production store by implementing the trait and passing it to `AuthManager`.
|
||
|
||
How to build
|
||
|
||
```bash
|
||
cd niom-turn
|
||
cargo build
|
||
```
|
||
|
||
How to test (quick local smoke)
|
||
- Start the server in one terminal (it listens on UDP/3478):
|
||
|
||
```bash
|
||
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-rustls` and support `turns:`.
|
||
|
||
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.rs` for 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)**
|
||
1. **TURN Data Plane Enablement** — `CreatePermission`, `ChannelBind`, Send/Data indications, and
|
||
peer forwarding so allocations actually relay packets between clients and peers.
|
||
2. **Authentication Hardening** — nonce lifecycle, realm configuration, Argon2-backed credential
|
||
storage, and detailed error handling for 401/438 responses.
|
||
3. **Allocation Lifecycle & Quotas** — timers, refresh requests, cleanup of expired allocations,
|
||
and resource limits per user/IP.
|
||
4. **Protocol Compliance Extras** — FINGERPRINT support, XOR-MAPPED-ADDRESS, IPv6 evaluation,
|
||
checksum validation, and fuzz/interop testing.
|
||
5. **Observability & Limits** — structured tracing, metrics, rate limiting, and CI coverage (incl.
|
||
the bundled `smoke_client`).
|
||
|
||
Artifacts that track this milestone live in two places:
|
||
|
||
1. This README section is kept up to date while the milestone is in progress.
|
||
2. Inline module docs (`//!`) inside `src/` record the current responsibilities and open backlog
|
||
items for each subsystem as we iterate.
|
||
|
||
**Task in progress**
|
||
- TURN data plane enablement:
|
||
- [x] `CreatePermission` handling and permission tracking
|
||
- [x] `ChannelBind` setup and `Send` forwarding 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.
|
||
|
||
1) 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:
|
||
|
||
```bash
|
||
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 &
|
||
```
|
||
|
||
2) 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.
|
||
|
||
```bash
|
||
# Build (falls noch nicht gebaut)
|
||
cargo build --bin smoke_client
|
||
|
||
# Ausführen
|
||
./target/debug/smoke_client
|
||
```
|
||
|
||
3) 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 `07` wiederholt)
|
||
|
||
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:
|
||
|
||
```json
|
||
{
|
||
"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.
|
||
|