niom-turn/README.md

6.9 KiB

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) implemented.
  • STUN message parser + builder in src/stun.rs.
  • CredentialStore trait + in-memory implementation in src/auth.rs.
  • Minimal logic: on any STUN request, server replies with a 401 challenge (REALM + NONCE).

Design

  • Modules
    • stun.rs - STUN/TURN message parsing and builders.
    • auth.rs - CredentialStore trait and an InMemoryStore impl. Use the trait to swap for DB-backed stores later.
    • main.rs - Bootstraps UDP listener, parses requests, and emits challenges for auth.

CredentialStore interface

  • CredentialStore is an async trait with get_password(username) -> Option<String>.
  • The default InMemoryStore is provided for tests and local dev. Swap in a production store by implementing the trait.

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-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 in-repo long-term auth implementation is intentionally minimal for the MVP and uses legacy constructs (A1/MD5 derivation + HMAC-SHA1 MESSAGE-INTEGRITY). MD5 is not recommended for new secure systems — this is present for RFC compatibility and testing only. We will replace this with a secure credential workflow (ephemeral/REST credentials, PBKDF/KDF storage, or mTLS) before any production deployment. See src/auth.rs for the current simple store and helpers.

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 EnablementCreatePermission, 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:
    • CreatePermission handling and permission tracking
    • 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:

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 &
  1. 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
  1. 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:

{
  "server": {
    "bind": "0.0.0.0:3478",
    "tls_cert": null,
    "tls_key": null
  },
  "credentials": [
    {
      "username": "testuser",
      "password": "secretpassword"
    }
  ]
}

Wenn appsettings.json vorhanden ist, verwendet der Server die server.bind Adresse und befüllt den anfänglichen Credential-Store aus dem credentials-Array. Falls die Datei fehlt, verwendet der Server die internen Defaults (Bind 0.0.0.0:3478 und Demo-Cred testuser).

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.