Update: Documentation, README.md
This commit is contained in:
parent
295bac11e3
commit
70ad89e867
@ -1,72 +0,0 @@
|
||||
# TLS and Long-Term Authentication — Integration Notes
|
||||
|
||||
Dieses Dokument beschreibt empfohlene Schritte, Abhängigkeiten und eine kurze Roadmap zur Integration von TLS (turns) und RFC-konformer Long-Term Authentication für den `niom-turn` MVP.
|
||||
|
||||
Ziele
|
||||
- TLS (turns) auf TCP/TLS Port 5349 (tokio-rustls / rustls)
|
||||
- Long-Term Authentication (RFC 5389 / RFC 5766): Benutzerverwaltung + MESSAGE-INTEGRITY-Prüfung
|
||||
- Sichere Speicherung von Credentials (keine Klartext-Passwörter in Produktion)
|
||||
|
||||
Empfohlene Bibliotheken
|
||||
- rustls + tokio-rustls — TLS für Tokio-basierte Server
|
||||
- rcgen — optional für lokale self-signed certs während Tests
|
||||
- ring / argon2 / scrypt — für sichere Passwort-Hashing (statt MD5/A1 in Produktion)
|
||||
|
||||
Schritte (high-level)
|
||||
1. TLS Listener
|
||||
- Ergänze einen TCP-Listener auf Port 5349.
|
||||
- Wrapping der TCP-Streams mit tokio-rustls using a configured ServerConfig (cert + private key).
|
||||
- Handhabe ALPN falls nötig (z. B. `webrtc` client expectations).
|
||||
|
||||
2. Socket Multiplexing und Protocols
|
||||
- Behalte den UDP-Listener auf 3478 für STUN/TURN (UDP). Für TLS/TCP setze eine separate Task auf.
|
||||
- Gemeinsame STUN/TURN-Nachrichtenverarbeitung (Parsing/Antworten) ist wiederverwendbar — benutze die Bibliotheks-Module (`stun.rs`, `alloc.rs`).
|
||||
|
||||
3. Long-Term Auth (Credentials)
|
||||
- Implementiere ein `PersistentCredentialStore`-Impl der `CredentialStore`-Trait, welches z. B. eine SQLite/Postgres-Tabelle oder Vault/Secrets-Backend nutzt.
|
||||
- Verifiziere MESSAGE-INTEGRITY gemäß RFC: Client sendet `USERNAME`, `REALM`, `NONCE` und `MESSAGE-INTEGRITY`. Der Server berechnet die HMAC-SHA1 über den Nachrichtenteil entsprechend RFC und vergleicht.
|
||||
- Verwende sichere Passwortspeicherung: speichere gehashte Passwörter (Argon2/Scrypt) und validiere beim Anmelden. Langfristige TURN-Konten sind oft server-seitig in Klartext/A1 gesetzt; für Produktion empfiehlt sich ein server-seitiges Secret, mit dem kurzfristige Credentials erzeugt werden (REST API zum Erstellen ephemerer Creds).
|
||||
|
||||
4. Nonce / Replay-Protektion
|
||||
- Implementiere Nonce-Generierung mit ausreichender Entropie und begrenzter Lebenszeit. Optional: zentrale Nonce-Store oder HMAC-gebundene Nonces.
|
||||
|
||||
5. Tests und Fallbacks
|
||||
- Automatisiere Tests: Unit-Tests für MESSAGE-INTEGRITY, Integrationstests mit `smoke_client` gegen UDP und TCP/TLS.
|
||||
- Fallback-Mode: if TLS configs not present, start UDP-only server but clearly log the insecure state.
|
||||
|
||||
Code-Snippets / Hinweise
|
||||
- Beispiel (TLS-Server-Setup sketch):
|
||||
|
||||
```rust
|
||||
// load cert/key
|
||||
let certs = load_certs("/path/to/cert.pem")?;
|
||||
let key = load_private_key("/path/to/key.pem")?;
|
||||
let mut cfg = rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)?;
|
||||
let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(cfg));
|
||||
|
||||
let listener = TcpListener::bind("0.0.0.0:5349").await?;
|
||||
loop {
|
||||
let (socket, peer) = listener.accept().await?;
|
||||
let acceptor = tls_acceptor.clone();
|
||||
tokio::spawn(async move {
|
||||
match acceptor.accept(socket).await {
|
||||
Ok(stream) => {
|
||||
// read STUN messages from the TLS stream, process same as UDP
|
||||
}
|
||||
Err(e) => tracing::error!("TLS accept failed: {:?}", e),
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Prioritäten / Phasen
|
||||
- Phase 1: Config-driven TLS listener + integration tests (low risk)
|
||||
- Phase 2: Implement Persistent CredentialStore and nonce lifecycle
|
||||
- Phase 3: Replace MD5/A1 usage with secure KDF-backed credential workflow (Argon2, PBKDF2)
|
||||
- Phase 4: Hardening, monitoring, and deployment automation (cert renewal)
|
||||
|
||||
Weiteres
|
||||
- Wenn du möchtest, kann ich die TLS listener-Integration direkt als PR implementieren (schrittweise: TLS listener -> tests -> credential-store wiring). Ich kann auch Beispiel-Implementierungen für `PersistentCredentialStore` mit SQLite/SQLx anbieten.
|
||||
216
README.md
216
README.md
@ -1,210 +1,58 @@
|
||||
niom-turn
|
||||
=========
|
||||
# niom-turn
|
||||
|
||||
Minimal TURN server scaffold for the niom project (MVP).
|
||||
A minimal TURN/STUN server in Rust (Tokio), including long-term authentication (REALM/NONCE + MESSAGE-INTEGRITY) and optional TLS (`turns:`).
|
||||
|
||||
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.
|
||||
## Features
|
||||
|
||||
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.
|
||||
- STUN Binding (basic)
|
||||
- TURN Allocate / Refresh
|
||||
- CreatePermission, ChannelBind, Send
|
||||
- UDP relay + return path as Data Indication or ChannelData
|
||||
- TCP (`turn:`) and TLS (`turns:`) control plane (stream framing)
|
||||
- TURN REST credentials (optional)
|
||||
- Basic limits (allocations/permissions/channel bindings + rate limits)
|
||||
|
||||
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
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
cd niom-turn
|
||||
cargo build
|
||||
cargo test
|
||||
```
|
||||
|
||||
How to test (quick local smoke)
|
||||
- Start the server in one terminal (it listens on UDP/3478):
|
||||
Local start (loads `appsettings.json` from the current working directory):
|
||||
|
||||
```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
|
||||
- [x] 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)
|
||||
cp appsettings.example.json appsettings.json
|
||||
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
|
||||
## Configuration
|
||||
|
||||
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.
|
||||
The current schema is shown in `appsettings.example.json`.
|
||||
Important: `niom-turn` currently **always** loads `appsettings.json` from the **working directory**.
|
||||
|
||||
```bash
|
||||
# Build (falls noch nicht gebaut)
|
||||
cargo build --bin smoke_client
|
||||
Details: `docs/config/runtime.md`.
|
||||
|
||||
# Ausführen
|
||||
./target/debug/smoke_client
|
||||
```
|
||||
## Deployment
|
||||
|
||||
3) Erwartetes Ergebnis
|
||||
See `docs/deployment.md` for:
|
||||
- systemd unit (including `WorkingDirectory=/etc/niom-turn`)
|
||||
- TLS certificate/key paths
|
||||
- NAT / `relay.advertised_ip`
|
||||
- debugging with `journalctl`
|
||||
|
||||
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:
|
||||
## Protocol / Interop notes
|
||||
|
||||
```
|
||||
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]
|
||||
```
|
||||
- `CHANNEL-BIND` can implicitly create the permission for that peer (interop with common clients).
|
||||
- Responses are signed using an MI mode that matches the accepted request variant.
|
||||
|
||||
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)
|
||||
See `docs/turn_end_to_end_flow.md`.
|
||||
|
||||
Das bedeutet: Der Server hat die MESSAGE-INTEGRITY des Requests akzeptiert und eine 200-Antwort gesendet.
|
||||
## Docs
|
||||
|
||||
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.
|
||||
Entry point: `docs/index.md`.
|
||||
|
||||
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.
|
||||
## License
|
||||
|
||||
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",
|
||||
"udp_bind": null,
|
||||
"tcp_bind": null,
|
||||
"tls_bind": "0.0.0.0:5349",
|
||||
"enable_udp": true,
|
||||
"enable_tcp": true,
|
||||
"enable_tls": true,
|
||||
"tls_cert": null,
|
||||
"tls_key": null
|
||||
},
|
||||
"relay": {
|
||||
"relay_port_min": null,
|
||||
"relay_port_max": null,
|
||||
"relay_bind_ip": "0.0.0.0",
|
||||
"advertised_ip": null
|
||||
},
|
||||
"logging": {
|
||||
"default_directive": "warn,niom_turn=info"
|
||||
},
|
||||
"limits": {
|
||||
"max_allocations_per_ip": null,
|
||||
"max_permissions_per_allocation": null,
|
||||
"max_channel_bindings_per_allocation": null,
|
||||
|
||||
"unauth_rps": null,
|
||||
"unauth_burst": null,
|
||||
"binding_rps": null,
|
||||
"binding_burst": null
|
||||
},
|
||||
"credentials": [
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "secretpassword"
|
||||
}
|
||||
],
|
||||
"auth": {
|
||||
"realm": "niom-turn.local",
|
||||
"nonce_secret": null,
|
||||
"nonce_ttl_seconds": 300,
|
||||
"rest_secret": null,
|
||||
"rest_max_ttl_seconds": 600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Wenn `appsettings.json` vorhanden ist, befüllt der Server den Credential-Store aus `credentials` und übernimmt Realm/Nonce/REST-Einstellungen aus `auth`.
|
||||
|
||||
Listener-Binds:
|
||||
- `server.bind` ist der Legacy-Default (wird genutzt, wenn `udp_bind`/`tcp_bind` nicht gesetzt sind).
|
||||
- TCP/TLS nutzen denselben TURN-over-Stream Handler; `turns:` läuft auf `server.tls_bind`.
|
||||
- Wenn `server.enable_tls=true`, aber `tls_cert`/`tls_key` fehlen, wird der TLS-Listener übersprungen.
|
||||
|
||||
Relay/NAT:
|
||||
- Hinter NAT sollte `relay.advertised_ip` auf die öffentliche IP gesetzt werden, damit Clients in XOR-RELAYED-ADDRESS eine erreichbare Adresse erhalten.
|
||||
- Für Firewalls ist ein fixer Relay-Port-Range (`relay_port_min/max`) sinnvoll.
|
||||
|
||||
Details und Runbook: siehe `docs/config/runtime.md`.
|
||||
|
||||
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.
|
||||
MIT
|
||||
|
||||
|
||||
@ -1,22 +1,32 @@
|
||||
# Packet Flow & Allocation Handling
|
||||
|
||||
## Komponenten
|
||||
- **UDP Listener** (`udp_reader_loop`): Empfängt STUN/TURN Nachrichten auf `bind` Adresse.
|
||||
- **AllocationManager**: Verwaltet Relay-Sockets je Client (`allocate_for`).
|
||||
- **TLS Listener** (`tls::serve_tls`): Optional, Wrappt dieselbe Logik über TCP/TLS.
|
||||
This page describes the high-level data flow through `niom-turn` (control plane and data plane).
|
||||
|
||||
## Ablauf (UDP)
|
||||
1. `UdpSocket::recv_from` liest Paket, `parse_message` prüft STUN-Header.
|
||||
2. Enthält `MESSAGE-INTEGRITY` → Username wird aus CredentialStore geladen und verifiziert.
|
||||
3. Bei `ALLOCATE` → `AllocationManager` erstellt Relay-Port, sendet `XOR-RELAYED-ADDRESS` zurück.
|
||||
4. Ohne gültige Credentials → Antwort `401 Unauthorized` mit `NONCE`.
|
||||
## Components
|
||||
|
||||
## Ablauf (Relay)
|
||||
- Für jedes Allocation wird ein Task gespawnt, der Pakete vom Relay-Socket liest und sie zurück zum Client sendet.
|
||||
- TODO: Channel-Bindings und PeerData-Handling implementieren.
|
||||
- **UDP listener**: receives STUN/TURN as well as ChannelData on UDP/3478.
|
||||
- **TCP listener**: STUN/TURN over TCP/3478.
|
||||
- **TLS listener**: STUN/TURN over TLS/5349 ("turns").
|
||||
- **AuthManager**: REALM/NONCE challenges, long-term auth (MI), optional TURN REST.
|
||||
- **AllocationManager**: manages allocations including relay sockets, permissions, and channel bindings.
|
||||
|
||||
## TODOs
|
||||
- Allocation Timeout & Refresh Requests.
|
||||
- Permission Handling (CreatePermission).
|
||||
- Bandbreitenlimits, Statistik (Prometheus).
|
||||
- Shared Secret Mechanismus für Long-Term Credentials (RFC 5389/5766).
|
||||
## Flow (control plane)
|
||||
|
||||
1. The request is parsed as STUN/TURN (or recognized as ChannelData).
|
||||
2. Auth is checked:
|
||||
- without valid credentials → `401` (challenge) or `438` (stale nonce)
|
||||
- with valid credentials → the request is processed; responses include `MESSAGE-INTEGRITY` + `FINGERPRINT`
|
||||
3. Allocate/Refresh/Permission/ChannelBind update the allocation state.
|
||||
|
||||
## Flow (relay/data plane)
|
||||
|
||||
- On `ALLOCATE`, a UDP relay socket is bound and a relay loop is started.
|
||||
- Packets from the peer are received by the relay and sent back to the client as **Data Indication** or **ChannelData**.
|
||||
- For interop, a `CHANNEL-BIND` may implicitly create the permission for the peer.
|
||||
|
||||
## Lifecycle / Housekeeping
|
||||
|
||||
- Allocations are periodically checked for expiry (housekeeping) so relay tasks can end even while idle.
|
||||
- The server periodically logs a compact metrics snapshot.
|
||||
|
||||
See also: `docs/turn_end_to_end_flow.md` and `docs/tcp_tls_data_plane.md`.
|
||||
|
||||
98
docs/architecture/tcp_tls_data_plane.md
Normal file
98
docs/architecture/tcp_tls_data_plane.md
Normal file
@ -0,0 +1,98 @@
|
||||
# TCP/TLS Data Plane (TURN over TCP/TLS) in niom-turn
|
||||
|
||||
This document explains **why** a TCP/TLS data plane matters for TURN and **how** `niom-turn` currently implements it.
|
||||
|
||||
## Why is this needed?
|
||||
|
||||
TURN has two kinds of traffic:
|
||||
|
||||
- **Control plane**: STUN/TURN requests/responses (Allocate, CreatePermission, ChannelBind, Refresh, …)
|
||||
- **Data plane**: application data between client and peer flowing through the relay (Send/Data Indication or ChannelData)
|
||||
|
||||
In many real-world networks, **UDP is restricted or fully blocked** (corporate Wi‑Fi, mobile APNs, captive portals, proxy environments). WebRTC/ICE typically tries:
|
||||
|
||||
1. UDP (fast, preferred)
|
||||
2. TURN over TCP
|
||||
3. TURN over TLS ("turns:") as the last, but often working option
|
||||
|
||||
For TURN over TCP/TLS to be usable, not only the control plane must run over the stream, but also the **return path peer → client** (data plane) must arrive over the **same TCP/TLS connection**.
|
||||
|
||||
## What does niom-turn implement?
|
||||
|
||||
- Client ↔ server transport can be **UDP**, **TCP**, or **TLS**.
|
||||
- The relay to the peer remains **UDP** (classic TURN UDP relay).
|
||||
- For **TCP/TLS**, the server delivers the data-plane return path back to the client over the **stream**.
|
||||
|
||||
This matches the common WebRTC fallback: “client↔server over TCP/TLS, peer transport over UDP”.
|
||||
|
||||
## Architecture in code
|
||||
|
||||
- Stream-Handler (TCP/TLS): [src/turn_stream.rs](../src/turn_stream.rs)
|
||||
- TCP Listener: [src/tcp.rs](../src/tcp.rs)
|
||||
- TLS Listener: [src/tls.rs](../src/tls.rs)
|
||||
- Allocation + Relay: [src/alloc.rs](../src/alloc.rs)
|
||||
|
||||
### Key idea: `ClientSink`
|
||||
|
||||
So the relay loop can send peer packets back over different client transports, [src/alloc.rs](../src/alloc.rs) uses an abstraction:
|
||||
|
||||
- `ClientSink::Udp { sock, addr }` → sends peer data via `udp.send_to(..., addr)`
|
||||
- `ClientSink::Stream { tx }` → queues bytes into a writer task that writes onto the TCP/TLS stream
|
||||
|
||||
When a client allocates over TCP/TLS, the allocation is created with a `ClientSink::Stream`.
|
||||
|
||||
## Framing: STUN vs. ChannelData on a byte stream
|
||||
|
||||
On UDP you receive datagrams; on TCP/TLS you receive a **continuous byte stream**. TURN over TCP/TLS multiplexes:
|
||||
|
||||
- STUN/TURN messages (control plane)
|
||||
- ChannelData frames (data plane, client → server)
|
||||
|
||||
`niom-turn` therefore parses the stream in a loop as “next frame” (see `try_pop_next_frame(...)` in [src/turn_stream.rs](../src/turn_stream.rs)):
|
||||
|
||||
### STUN Message
|
||||
|
||||
- Header is 20 bytes
|
||||
- The Length field is the body length
|
||||
- Total length: $20 + length$
|
||||
|
||||
### ChannelData Frame
|
||||
|
||||
- 4-byte header: `CHANNEL-NUMBER` (2) + `LENGTH` (2)
|
||||
- Channel numbers are in the range `0x4000..=0x7FFF` (top bits `01`)
|
||||
- Total length: $4 + length$
|
||||
|
||||
Important: for TCP/TLS, **no padding** may remain as “extra bytes” in the stream. Therefore `niom-turn` builds ChannelData as exactly `4 + len` bytes (see [src/stun.rs](../src/stun.rs)).
|
||||
|
||||
### Hardening: resync & limits
|
||||
|
||||
Because TCP/TLS is a byte stream, broken or malicious clients can otherwise easily “desynchronise” the parser.
|
||||
`niom-turn` therefore implements in the stream parser:
|
||||
|
||||
- **Magic cookie check** for STUN: invalid cookies trigger byte-wise resync (instead of waiting for huge lengths).
|
||||
- **Frame size limits** (STUN body and ChannelData) to limit memory/DoS risk.
|
||||
- **Max buffer limit** per connection: if the input buffer grows too large, the connection is closed.
|
||||
|
||||
## Data flow (TCP/TLS)
|
||||
|
||||
1. Client connects over TCP or TLS.
|
||||
2. The stream handler reads frames:
|
||||
- STUN/TURN requests → processed like the UDP path (auth, allocation, permission, channel bind, send, refresh)
|
||||
- ChannelData (client→peer) → forwarded to the peer via the UDP relay
|
||||
3. Peer sends UDP to the relay address.
|
||||
4. The relay loop forwards bytes to the `ClientSink`:
|
||||
- for streams: `tx.send(bytes)` → a writer task writes Data Indication or ChannelData back onto the same stream
|
||||
|
||||
## Limitations / not implemented
|
||||
|
||||
- No TCP relay to the peer (TURN TCP allocations / CONNECT methods like RFC6062).
|
||||
- Focus is: client↔server transport over TCP/TLS + UDP relay.
|
||||
- Full IPv6 operational coverage is not the focus of the MVP.
|
||||
|
||||
## Tests
|
||||
|
||||
- TCP Stream Data-Plane: `tests/tcp_turn.rs`
|
||||
- TLS Stream Data-Plane: `tests/tls_data_plane.rs`
|
||||
- Gemeinsames Framing (STUN + ChannelData): `tests/support/stream.rs`
|
||||
|
||||
Wenn du willst, kann ich als nächsten Schritt die Doku um eine kurze Interop-Checkliste (WebRTC/ICE Verhalten, Candidate-Types, typische Fehlerbilder) ergänzen.
|
||||
145
docs/architecture/turn_end_to_end_flow.md
Normal file
145
docs/architecture/turn_end_to_end_flow.md
Normal file
@ -0,0 +1,145 @@
|
||||
# End-to-End TURN Flow (UDP + TLS)
|
||||
|
||||
This document describes the **currently implemented** end-to-end flow in `niom-turn`, based on the current MVP code:
|
||||
|
||||
- UDP control plane + UDP data plane: [src/server.rs](../src/server.rs), [src/alloc.rs](../src/alloc.rs), [src/stun.rs](../src/stun.rs), [src/auth.rs](../src/auth.rs)
|
||||
- TLS control plane ("turns") with STUN framing: [src/tls.rs](../src/tls.rs)
|
||||
- Test builders (how requests are constructed): [tests/support/stun_builders.rs](../tests/support/stun_builders.rs)
|
||||
|
||||
## Terms
|
||||
|
||||
- **Client**: TURN client (e.g. a WebRTC ICE agent)
|
||||
- **Server**: `niom-turn` (this project)
|
||||
- **Peer**: the remote endpoint to relay to (typically another WebRTC endpoint)
|
||||
- **Allocation**: server-side session providing a **UDP relay socket**
|
||||
- **Permission**: permission to send to a peer / accept peer packets
|
||||
- **Channel binding**: mapping `channel-number -> peer` to enable ChannelData
|
||||
|
||||
---
|
||||
|
||||
## UDP: sequence diagram (happy path)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant C as Client (TURN)
|
||||
participant S as niom-turn (UDP:3478)
|
||||
participant R as Relay Socket (UDP:ephemeral)
|
||||
participant P as Peer
|
||||
|
||||
Note over C,S: 1) Allocate without auth → 401 challenge
|
||||
C->>S: STUN Allocate Request (no MI)
|
||||
S->>C: STUN Error Response 401 + REALM + NONCE
|
||||
|
||||
Note over C,S: 2) Allocate with long-term auth
|
||||
C->>S: STUN Allocate Request + USERNAME/REALM/NONCE + MESSAGE-INTEGRITY
|
||||
S->>R: bind("0.0.0.0:0"), spawn Relay-Loop
|
||||
S->>C: Allocate Success + XOR-RELAYED-ADDRESS + LIFETIME (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
|
||||
Note over C,S: 3) Permission (CreatePermission optional)
|
||||
C->>S: CreatePermission + XOR-PEER-ADDRESS (+ Auth + MI)
|
||||
S->>C: Success (200) (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
|
||||
Note over C,S: 4) Send (client→peer via relay)
|
||||
C->>S: Send + XOR-PEER-ADDRESS + DATA (+ Auth + MI)
|
||||
S->>R: relay.send_to(DATA, Peer)
|
||||
R->>P: UDP payload (source = relay_addr)
|
||||
|
||||
Note over P,C: 5) Return path (peer→client)
|
||||
P->>R: UDP payload (dest = relay_addr)
|
||||
alt Channel binding exists
|
||||
R->>S: recv_from(Peer)
|
||||
S->>C: ChannelData(channel, payload)
|
||||
else No channel binding
|
||||
R->>S: recv_from(Peer)
|
||||
S->>C: Data Indication (METHOD_DATA|INDICATION) + XOR-PEER-ADDRESS + DATA
|
||||
end
|
||||
|
||||
Note over C,S: 6) Optional: ChannelBind + ChannelData
|
||||
C->>S: ChannelBind + CHANNEL-NUMBER + XOR-PEER-ADDRESS (+ Auth + MI)
|
||||
Note over C,S: Interop: ChannelBind may implicitly create the permission for this peer
|
||||
S->>C: Success (200) (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
C->>S: ChannelData(channel, payload)
|
||||
S->>R: relay.send_to(payload, Peer)
|
||||
R->>P: UDP payload
|
||||
|
||||
Note over C,S: 7) Refresh
|
||||
C->>S: Refresh + LIFETIME (+ Auth + MI)
|
||||
S->>C: Success + LIFETIME(applied) (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
```
|
||||
|
||||
### What the server does (concretely)
|
||||
|
||||
- Entry point: `udp_reader_loop` in [src/server.rs](../src/server.rs)
|
||||
- Early branch: if `parse_channel_data(...)` succeeds, the packet is **not** parsed as STUN; ChannelData is forwarded directly (only if allocation + binding + permission checks pass).
|
||||
- STUN/TURN requests are parsed via `parse_message(...)` in [src/stun.rs](../src/stun.rs).
|
||||
|
||||
### RFC interop note: FINGERPRINT
|
||||
|
||||
- All STUN messages built by the server append `FINGERPRINT` as the last attribute.
|
||||
- If a client includes `FINGERPRINT`, it is validated; invalid fingerprints cause the message to be dropped (no response).
|
||||
|
||||
### Auth decisions and common error codes
|
||||
|
||||
Auth policy is centralised in `AuthManager::authenticate` in [src/auth.rs](../src/auth.rs).
|
||||
|
||||
- **401 Unauthorized**: when `MESSAGE-INTEGRITY` is missing → challenge with `REALM` + `NONCE`.
|
||||
- **438 Stale Nonce**: when `NONCE` is expired/invalid → new challenge.
|
||||
- **437 Allocation Mismatch**: when CreatePermission/Send/ChannelBind/Refresh arrives without an allocation.
|
||||
- **403 Peer Not Permitted**: when a peer is not (or no longer) permitted.
|
||||
- **400 Missing/Invalid ...**: when required attributes are missing or XOR-PEER-ADDRESS cannot be decoded.
|
||||
|
||||
### Interop note: MESSAGE-INTEGRITY in responses
|
||||
|
||||
Some clients expect responses to be signed using the same “MESSAGE-INTEGRITY variant” that was accepted for the request.
|
||||
`niom-turn` therefore derives the mode from the authenticated request and uses it for all responses within the same transaction.
|
||||
|
||||
---
|
||||
|
||||
## TLS (turns): sequence diagram (control plane)
|
||||
|
||||
Important: the TLS implementation in [src/tls.rs](../src/tls.rs) reuses the same TURN handler as TCP and implements a real **stream data plane**.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant C as Client (turns)
|
||||
participant T as niom-turn (TLS:5349)
|
||||
participant R as Relay Socket (UDP:ephemeral)
|
||||
participant P as Peer
|
||||
|
||||
Note over C,T: STUN framing over TCP/TLS: read → chunk by (len+20)
|
||||
C->>T: STUN Allocate (no MI)
|
||||
T->>C: 401 + REALM + NONCE (over TLS)
|
||||
|
||||
C->>T: STUN Allocate + Auth + MI
|
||||
T->>R: allocate_for(peer, stream-sink)
|
||||
T->>C: Allocate Success + XOR-RELAYED-ADDRESS + LIFETIME (over TLS)
|
||||
|
||||
C->>T: CreatePermission/Send/Refresh/ChannelBind (over TLS)
|
||||
T->>C: Success/Error (over TLS)
|
||||
|
||||
Note over P,C: Peer data returns over the TLS stream
|
||||
P->>R: UDP payload an relay_addr
|
||||
R->>T: recv_from(Peer)
|
||||
T->>C: Data Indication / ChannelData (over TLS)
|
||||
```
|
||||
|
||||
### Consequence
|
||||
|
||||
- Control plane over TLS works (Allocate/Refresh/… are answered over TLS).
|
||||
- The **data-plane return path** (peer → client) also runs over the TLS stream (relay → `ClientSink::Stream`).
|
||||
|
||||
More details: [docs/tcp_tls_data_plane.md](tcp_tls_data_plane.md)
|
||||
|
||||
---
|
||||
|
||||
## Mini checklist: minimal flow (practical)
|
||||
|
||||
1. `ALLOCATE` without MI → `401` + `REALM` + `NONCE`
|
||||
2. `ALLOCATE` with `USERNAME/REALM/NONCE` + `MESSAGE-INTEGRITY` → `XOR-RELAYED-ADDRESS` + `LIFETIME`
|
||||
3. Optional `CREATE_PERMISSION` for the peer → `200`
|
||||
4. `SEND` with `DATA` → server sends to the peer via relay
|
||||
5. Peer sends back to the relay → server delivers to the client as `DATA-INDICATION` or `CHANNEL-DATA`
|
||||
6. Optional `CHANNEL_BIND` + ChannelData for a more efficient data plane (may implicitly create the permission)
|
||||
7. `REFRESH` to extend, or `LIFETIME=0` to release
|
||||
75
docs/architecture/turn_rest_credentials.md
Normal file
75
docs/architecture/turn_rest_credentials.md
Normal file
@ -0,0 +1,75 @@
|
||||
# TURN REST Credentials (Ephemeral) — Usage & Operations
|
||||
|
||||
This document explains the **TURN REST credential** strategy for `niom-turn` (MVP → production-capable path).
|
||||
|
||||
## Why TURN REST?
|
||||
|
||||
- You want to issue *short-lived* TURN logins for WebRTC (e.g. 5–10 minutes).
|
||||
- Your TURN server does not store per-user passwords.
|
||||
- Your backend can mint tokens later; until then you can generate tokens locally/ops-side.
|
||||
|
||||
## Scheme (compatible with common WebRTC stacks)
|
||||
|
||||
**Username**: `<expiry_unix_seconds>` or `<expiry_unix_seconds>:<opaque_user_id>`
|
||||
|
||||
- Example: `1735412345:alice`
|
||||
- `expiry_unix_seconds` is a Unix timestamp in seconds.
|
||||
|
||||
**Credential (password)**:
|
||||
|
||||
$$\n\text{credential} = \text{base64}(\text{HMAC-SHA1}(\text{secret}, \text{username}))\n$$
|
||||
|
||||
This `credential` is then used as the TURN password in your ICE server configuration.
|
||||
|
||||
## Server side (`niom-turn`)
|
||||
|
||||
Configuration in [appsettings.example.json](../appsettings.example.json):
|
||||
|
||||
- `auth.rest_secret`: shared secret (must stay secret)
|
||||
- `auth.rest_max_ttl_seconds`: maximum acceptable TTL window into the future. If a token is too far in the future, it is rejected (safety measure).
|
||||
|
||||
Important:
|
||||
- The server accepts TURN REST credentials as a fallback **only if** the username is not found in the credential store.
|
||||
- Expiry/TTL rules:
|
||||
- `expiry` must be >= `now`
|
||||
- and `expiry - now <= rest_max_ttl_seconds`
|
||||
|
||||
## Generate tokens locally (without a backend)
|
||||
|
||||
This repo includes a small CLI:
|
||||
|
||||
- Binary: [src/bin/turn_rest_cred.rs](../src/bin/turn_rest_cred.rs)
|
||||
|
||||
Examples:
|
||||
|
||||
1) Simple (stdout as env-like lines)
|
||||
|
||||
`cargo run --bin turn_rest_cred -- --secret "SUPER_SECRET" --user alice --ttl 600`
|
||||
|
||||
2) JSON output
|
||||
|
||||
`cargo run --bin turn_rest_cred -- --secret "SUPER_SECRET" --user alice --ttl 600 --json`
|
||||
|
||||
You get:
|
||||
- `username` → use as `iceServers[].username` in WebRTC
|
||||
- `credential` → use as `iceServers[].credential` in WebRTC
|
||||
|
||||
## WebRTC example (frontend)
|
||||
|
||||
In your app you typically set:
|
||||
|
||||
- URLs (at least UDP + TLS):
|
||||
- `turn:your-domain:3478?transport=udp`
|
||||
- `turn:your-domain:3478?transport=tcp`
|
||||
- `turns:your-domain:5349?transport=tcp`
|
||||
|
||||
and then:
|
||||
- `username`: from the generator/backend
|
||||
- `credential`: from the generator/backend
|
||||
|
||||
## Operational notes
|
||||
|
||||
- **Secret rotation**: plan rotation (e.g. two secrets in parallel) or in-flight tokens will break.
|
||||
- **Keep TTL small**: 5–10 minutes is typical.
|
||||
- **Logs**: never log the `secret` or full credentials.
|
||||
- **Rate limits/quotas**: add them to avoid open-relay abuse.
|
||||
@ -1,10 +1,10 @@
|
||||
# Runtime Configuration
|
||||
|
||||
## Dateien
|
||||
- `appsettings.example.json`: Beispiel mit Server-Bind und Test-Credentials.
|
||||
- `appsettings.json`: Produktiverstellung (bind, TLS, Credentials).
|
||||
## Files
|
||||
- `appsettings.example.json`: example config with server binds and test credentials.
|
||||
- `appsettings.json`: production config (binds, TLS, credentials).
|
||||
|
||||
## Struktur
|
||||
## Schema
|
||||
```
|
||||
Config {
|
||||
server: ServerOptions {
|
||||
@ -44,35 +44,35 @@ Config {
|
||||
}
|
||||
```
|
||||
|
||||
## Default-Fallbacks (siehe `main.rs`)
|
||||
- Bind: `0.0.0.0:3478`
|
||||
- Single Test Credential: `testuser` / `secretpassword`
|
||||
## Default fallbacks (see `main.rs`)
|
||||
- bind: `0.0.0.0:3478`
|
||||
- single test credential: `testuser` / `secretpassword`
|
||||
|
||||
## Runbook (Start & Betrieb)
|
||||
## Runbook (start & operations)
|
||||
|
||||
### 1) Konfiguration anlegen
|
||||
### 1) Create the configuration
|
||||
|
||||
- Der Server lädt **immer** `appsettings.json` aus dem aktuellen Working Directory (siehe `Config::load_default()`).
|
||||
- Als Basis kannst du `appsettings.example.json` nach `appsettings.json` kopieren und anpassen.
|
||||
- The server currently **always** loads `appsettings.json` from the current working directory (see `Config::load_default()`).
|
||||
- As a starting point, copy `appsettings.example.json` to `appsettings.json` and adjust it.
|
||||
|
||||
### 2) Server starten
|
||||
### 2) Start the server
|
||||
|
||||
```bash
|
||||
# im Repo-Root
|
||||
# from the repo root
|
||||
cargo run --bin niom-turn
|
||||
```
|
||||
|
||||
Optional kannst du das Log-Level überschreiben:
|
||||
Optionally override the log level:
|
||||
|
||||
```bash
|
||||
RUST_LOG=info cargo run --bin niom-turn
|
||||
```
|
||||
|
||||
Hinweise:
|
||||
- Wenn `server.enable_tls=true`, aber `server.tls_cert`/`server.tls_key` fehlen, wird der TLS-Listener **übersprungen** (Info-Log).
|
||||
- Beim Start loggt der Server, welche Listener aktiv sind und welche Relay-Optionen gelten.
|
||||
Notes:
|
||||
- If `server.enable_tls=true` but `server.tls_cert`/`server.tls_key` are missing, the TLS listener is **skipped** (info log).
|
||||
- On startup, the server logs which listeners are enabled and which relay options are in effect.
|
||||
|
||||
### 3) Minimal-Konfig: UDP-only (einfachster Start)
|
||||
### 3) Minimal config: UDP-only (simplest start)
|
||||
|
||||
```json
|
||||
{
|
||||
@ -116,7 +116,7 @@ Hinweise:
|
||||
}
|
||||
```
|
||||
|
||||
### 4) Minimal-Konfig: UDP + TCP + TLS (für maximale WebRTC-Kompatibilität)
|
||||
### 4) Minimal config: UDP + TCP + TLS (best WebRTC compatibility)
|
||||
|
||||
```json
|
||||
{
|
||||
@ -154,19 +154,19 @@ Hinweise:
|
||||
}
|
||||
```
|
||||
|
||||
Betriebs-Checkliste (kurz):
|
||||
- Firewall öffnen: UDP/3478, TCP/3478, TCP/5349 sowie den UDP-Relay-Portbereich (`relay_port_min/max`).
|
||||
- Hinter NAT: `relay.advertised_ip` auf die öffentliche IP setzen.
|
||||
- Für TURN REST Credentials: `auth.rest_secret` setzen und ggf. feste `credentials` leer lassen.
|
||||
Operations checklist (short):
|
||||
- open firewall: UDP/3478, TCP/3478, TCP/5349 and the UDP relay port range (`relay_port_min/max`).
|
||||
- behind NAT: set `relay.advertised_ip` to the public IP.
|
||||
- for TURN REST credentials: set `auth.rest_secret` and optionally leave static `credentials` empty.
|
||||
|
||||
## TODOs
|
||||
- Shared Secret / REST API zur Credential-Verwaltung.
|
||||
- Health-Port (HTTP) für Monitoring.
|
||||
- Rate-Limits/Quota (pro Username) und Relay-Port-Range produktiv setzen.
|
||||
- Shared secret / REST API for credential management.
|
||||
- Health endpoint (HTTP) for monitoring.
|
||||
- Rate limits/quotas (per username) and production relay port range settings.
|
||||
|
||||
## Hinweise für produktiven Betrieb
|
||||
## Production notes
|
||||
|
||||
- Wenn der Server hinter NAT läuft, setze `relay.advertised_ip` auf die öffentliche IP, damit Clients in XOR-RELAYED-ADDRESS eine erreichbare Adresse erhalten.
|
||||
- Für Firewalls ist ein fester Relay-Port-Range sinnvoll (`relay_port_min/max`), damit nur dieser UDP-Bereich geöffnet werden muss.
|
||||
- If the server runs behind NAT, set `relay.advertised_ip` to the public IP so clients receive a reachable address in XOR-RELAYED-ADDRESS.
|
||||
- A fixed relay port range (`relay_port_min/max`) is recommended so you only need to open that UDP range.
|
||||
|
||||
- Allocations werden auch ohne Traffic periodisch auf Expiry geprüft und bereinigt (Housekeeping), damit Relay-Sockets/Tasks nicht dauerhaft hängen bleiben.
|
||||
- Allocations are periodically checked for expiry and pruned (housekeeping) even without traffic, so relay sockets/tasks do not linger indefinitely.
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
# Deployment: TLS (turns) in Proxmox LXC
|
||||
|
||||
This document describes a minimal, practical approach to deploy `niom-turn` as a TURN/TLS
|
||||
(`turns:`) server inside a Proxmox LXC container for an initial Internet-facing MVP.
|
||||
|
||||
Goals
|
||||
- Run `niom-turn` with a TLS listener (TCP/TLS 5349) and UDP listener (3478).
|
||||
- Ensure TLS certs are provisioned securely (Let's Encrypt or mounted files).
|
||||
- Harden minimal attack surface (firewall, user, systemd supervision).
|
||||
|
||||
Prerequisites
|
||||
- Proxmox VE host with public IPv4 address and a reserved port mapping for the LXC.
|
||||
- LXC template based on Debian/Ubuntu (minimal), with rust runtime or built binary copied in.
|
||||
|
||||
Ports
|
||||
- UDP 3478 — STUN/TURN (required for many clients).
|
||||
- TCP 5349 — TLS/TURN (turns).
|
||||
|
||||
Networking notes for LXC
|
||||
- Use a bridged interface so the LXC has a routable IP (recommended).
|
||||
- Alternatively, NAT the required ports from the Proxmox host to the LXC.
|
||||
|
||||
Certificates
|
||||
- Recommended: use Let's Encrypt with a reverse proxy or certbot on the host and mount the
|
||||
certs into the container at `/etc/ssl/niom-turn/` (e.g. `fullchain.pem` and `privkey.pem`).
|
||||
- Alternatively, copy PEM certs into the LXC (owner root only). For testing, you can generate
|
||||
a self-signed cert with `rcgen` or `openssl`.
|
||||
|
||||
Systemd service (example)
|
||||
|
||||
Create `/etc/systemd/system/niom-turn.service` inside the container:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=niom-turn TURN server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=niom
|
||||
Group=niom
|
||||
WorkingDirectory=/opt/niom-turn
|
||||
ExecStart=/opt/niom-turn/niom-turn --config /opt/niom-turn/appsettings.json
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Sample `appsettings.json` (mount next to binary)
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"bind": "0.0.0.0:3478",
|
||||
"tls_cert": "/etc/ssl/niom-turn/fullchain.pem",
|
||||
"tls_key": "/etc/ssl/niom-turn/privkey.pem"
|
||||
},
|
||||
"credentials": [
|
||||
{ "username": "testuser", "password": "secretpassword" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Firewall
|
||||
- Allow UDP 3478 and TCP 5349. If using UFW on the container host:
|
||||
|
||||
```bash
|
||||
ufw allow proto udp from any to any port 3478
|
||||
ufw allow proto tcp from any to any port 5349
|
||||
```
|
||||
|
||||
Security recommendations (MVP)
|
||||
- Run the server as an unprivileged user (e.g. `niom`).
|
||||
- Mount TLS certs read-only and restrict permissions to the service user.
|
||||
- Monitor logs and open ports; add basic rate-limiting on host if available.
|
||||
- For production: use ephemeral credential minting and avoid long-lived plain passwords.
|
||||
|
||||
Validation steps
|
||||
1. Start server via systemd: `systemctl start niom-turn` and check `journalctl -u niom-turn`.
|
||||
2. From a client (or the host), run the included `smoke_client` to test the UDP path.
|
||||
3. For TLS: use `openssl s_client -connect <your-lxc-ip>:5349 -servername <your-hostname>` to verify
|
||||
the cert chain and that TCP connections are accepted.
|
||||
|
||||
If you want, I can prepare a `systemd` unit, a small packaging script, and CI steps that build
|
||||
and copy the binary into a release tarball suitable for deploying into the LXC. Let me know
|
||||
which level of automation you prefer.
|
||||
@ -1,136 +1,151 @@
|
||||
# Deployment Guide (niom-turn)
|
||||
# Deployment (niom-turn)
|
||||
|
||||
This guide assumes a fresh Debian LXC (e.g., 10.0.0.22), Fritzbox port forwards are in place, and you want TURN reachable on 3478/udp+tcp and 5349/tcp with a UDP relay range (e.g., 49152-49200).
|
||||
This page describes a pragmatic deployment of `niom-turn` as a systemd service.
|
||||
|
||||
## 1) Install dependencies
|
||||
TL;DR:
|
||||
- TURN/STUN: UDP + TCP on port 3478
|
||||
- TURN over TLS (`turns:`): TCP/TLS on port 5349
|
||||
- Relay ports: optional UDP port range (e.g. 49152-49200)
|
||||
|
||||
## Important pitfall: config path
|
||||
|
||||
The server currently **always** loads `appsettings.json` from the **current working directory** (`Config::load_default()`).
|
||||
|
||||
For systemd, that means: set `WorkingDirectory=` to the directory that contains `appsettings.json`.
|
||||
|
||||
## 1) Provide the binary
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential pkg-config libssl-dev curl git systemd
|
||||
# Rust toolchain (stable)
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
```
|
||||
|
||||
## 2) Clone and build
|
||||
|
||||
```bash
|
||||
cd /opt
|
||||
sudo mkdir -p niom-turn && sudo chown "$USER":"$USER" niom-turn
|
||||
cd /opt/niom-turn
|
||||
git clone https://github.com/<your-repo>/niom-turn.git .
|
||||
cargo build --release
|
||||
# Binary: target/release/niom-turn
|
||||
ls -lah target/release/niom-turn
|
||||
```
|
||||
|
||||
## 3) Configuration
|
||||
Typical layout (example):
|
||||
- binary: `/opt/niom-turn/target/release/niom-turn`
|
||||
- config: `/etc/niom-turn/appsettings.json`
|
||||
- TLS: `/etc/niom-turn/fullchain.pem`, `/etc/niom-turn/privkey.pem`
|
||||
|
||||
Create config dir and place TLS cert/key (exported from NPM) and config:
|
||||
## 2) Create the configuration
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/niom-turn
|
||||
sudo chown "$USER":"$USER" /etc/niom-turn
|
||||
# place /etc/niom-turn/fullchain.pem and /etc/niom-turn/privkey.pem
|
||||
sudo cp appsettings.example.json /etc/niom-turn/appsettings.json
|
||||
sudoedit /etc/niom-turn/appsettings.json
|
||||
```
|
||||
|
||||
Example `/etc/niom-turn/appsettings.json` (adjust realm, WAN IP, secrets):
|
||||
Example `appsettings.json` (schema matches `src/config.rs`):
|
||||
|
||||
```json
|
||||
{
|
||||
"logging": { "level": "info" },
|
||||
"auth": {
|
||||
"realm": "turn.example.com",
|
||||
"nonce_ttl_seconds": 600,
|
||||
"rest_secret": "CHANGE_ME_REST_SECRET",
|
||||
"rest_max_ttl_seconds": 86400
|
||||
},
|
||||
"listeners": {
|
||||
"udp": "0.0.0.0:3478",
|
||||
"tcp": "0.0.0.0:3478",
|
||||
"tls": {
|
||||
"addr": "0.0.0.0:5349",
|
||||
"cert_file": "/etc/niom-turn/fullchain.pem",
|
||||
"key_file": "/etc/niom-turn/privkey.pem"
|
||||
}
|
||||
"server": {
|
||||
"bind": "0.0.0.0:3478",
|
||||
"udp_bind": null,
|
||||
"tcp_bind": null,
|
||||
"tls_bind": "0.0.0.0:5349",
|
||||
"enable_udp": true,
|
||||
"enable_tcp": true,
|
||||
"enable_tls": true,
|
||||
"tls_cert": "/etc/niom-turn/fullchain.pem",
|
||||
"tls_key": "/etc/niom-turn/privkey.pem"
|
||||
},
|
||||
"relay": {
|
||||
"bind_addr": "0.0.0.0",
|
||||
"public_addr": "YOUR_WAN_IP",
|
||||
"port_range": "49152-49200"
|
||||
"relay_port_min": 49152,
|
||||
"relay_port_max": 49200,
|
||||
"relay_bind_ip": "0.0.0.0",
|
||||
"advertised_ip": "YOUR_PUBLIC_IP_OR_HOSTNAME"
|
||||
},
|
||||
"rate_limits": {
|
||||
"enabled": true,
|
||||
"max_allocations_per_ip": 10,
|
||||
"max_permissions_per_allocation": 10,
|
||||
"max_channels_per_allocation": 10
|
||||
"auth": {
|
||||
"realm": "turn.example.com",
|
||||
"nonce_secret": null,
|
||||
"nonce_ttl_seconds": 300,
|
||||
"rest_secret": null,
|
||||
"rest_max_ttl_seconds": 600
|
||||
},
|
||||
"credentials": [
|
||||
{ "username": "testuser", "password": "secretpassword" }
|
||||
],
|
||||
"logging": {
|
||||
"default_directive": "warn,niom_turn=info"
|
||||
},
|
||||
"limits": {
|
||||
"max_allocations_per_ip": null,
|
||||
"max_permissions_per_allocation": null,
|
||||
"max_channel_bindings_per_allocation": null,
|
||||
|
||||
"unauth_rps": null,
|
||||
"unauth_burst": null,
|
||||
"binding_rps": null,
|
||||
"binding_burst": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `public_addr` must be your public WAN IP (not the LXC IP).
|
||||
- `rest_secret` is used for TURN REST credentials (time-based user/pass).
|
||||
### NAT / public IP / hostnames
|
||||
|
||||
## 4) Systemd service
|
||||
- If the server runs behind NAT, set `relay.advertised_ip` to the public IP so clients receive a reachable address in `XOR-RELAYED-ADDRESS`.
|
||||
- As a workaround, `relay.relay_bind_ip` and `relay.advertised_ip` can also be hostnames; they are resolved **once at startup**.
|
||||
|
||||
Install binary and user:
|
||||
## 3) TLS certificates
|
||||
|
||||
You need PEM files:
|
||||
- `fullchain.pem` (certificate chain)
|
||||
- `privkey.pem` (private key)
|
||||
|
||||
Set restrictive permissions, e.g.:
|
||||
|
||||
```bash
|
||||
sudo cp /opt/niom-turn/target/release/niom-turn /usr/local/bin/niom-turn
|
||||
sudo useradd --system --no-create-home --shell /usr/sbin/nologin niomturn
|
||||
sudo chown root:root /usr/local/bin/niom-turn
|
||||
sudo chmod 0755 /usr/local/bin/niom-turn
|
||||
sudo chown -R niomturn:niomturn /etc/niom-turn
|
||||
sudo chown root:root /etc/niom-turn/fullchain.pem /etc/niom-turn/privkey.pem
|
||||
sudo chmod 0644 /etc/niom-turn/fullchain.pem
|
||||
sudo chmod 0600 /etc/niom-turn/privkey.pem
|
||||
```
|
||||
|
||||
Create `/etc/systemd/system/niom-turn.service`:
|
||||
## 4) systemd unit
|
||||
|
||||
```
|
||||
Example: `/etc/systemd/system/niom-turn.service`
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=niom-turn
|
||||
Description=niom-turn TURN server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=niomturn
|
||||
Group=niomturn
|
||||
ExecStart=/usr/local/bin/niom-turn --config /etc/niom-turn/appsettings.json
|
||||
Environment=RUST_LOG=debug,niom_turn=debug
|
||||
|
||||
# Important: appsettings.json is loaded from WorkingDirectory
|
||||
WorkingDirectory=/etc/niom-turn
|
||||
ExecStart=/opt/niom-turn/target/release/niom-turn
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
# Optional: LimitNOFILE=65535
|
||||
RestartSec=2
|
||||
|
||||
# Optional: enable temporarily for debugging
|
||||
# Environment=RUST_LOG=debug,niom_turn=debug
|
||||
# Environment=NIOM_TURN_DEBUG_CONFIG=1
|
||||
# Environment=NIOM_TURN_DEBUG_CONFIG_JSON=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable/start:
|
||||
Enable:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now niom-turn
|
||||
```
|
||||
|
||||
## 5) Firewall (LXC)
|
||||
## 5) Firewall / port forwarding
|
||||
|
||||
Allow inbound: UDP 3478, TCP 3478, TCP 5349, UDP relay range (49152-49200). Outbound allow all.
|
||||
Open/forward at least:
|
||||
- UDP 3478
|
||||
- TCP 3478
|
||||
- TCP 5349
|
||||
- UDP relay range (if `relay_port_min/max` is set)
|
||||
|
||||
## 6) Quick checks
|
||||
## 6) Debugging / checks
|
||||
|
||||
- Listener ports: `ss -tulpen | grep -E '3478|5349'`
|
||||
- Logs: `journalctl -u niom-turn -f`
|
||||
- External TCP reachability (from Hotspot): `nc -vz turn.example.com 3478` and `nc -vz turn.example.com 5349`
|
||||
- STUN/TURN test: `stunclient turn.example.com 3478 -u user -p pass` (or REST creds)
|
||||
- WebRTC: open webrtc-internals / about:webrtc; ensure relay candidates show your WAN IP + ports in 49152-49200.
|
||||
- Ports: `ss -tulpen | grep -E '3478|5349'`
|
||||
- Logs: `journalctl -u niom-turn -f -o cat`
|
||||
- Nur letzte Logs: `journalctl -u niom-turn -n 200 --no-pager -o cat`
|
||||
|
||||
## 7) Fritzbox / Port forwards (reference)
|
||||
|
||||
- UDP 3478 → 10.0.0.22:3478
|
||||
- TCP 3478 → 10.0.0.22:3478
|
||||
- TCP 5349 → 10.0.0.22:5349
|
||||
- UDP 49152-49200 → 10.0.0.22:49152-49200
|
||||
Test from external network (Hotspot), not from LAN (avoid NAT loopback assumptions).
|
||||
|
||||
## 8) Tuning / next steps
|
||||
|
||||
- For more logs temporarily set `RUST_LOG=trace,niom_turn=trace` in the service env.
|
||||
- Consider JSON logging + metrics export if you need richer observability.
|
||||
- Keep certs renewed via NPM and re-export to the LXC.
|
||||
|
||||
@ -1,17 +1,28 @@
|
||||
# niom-turn Docs
|
||||
|
||||
Dokumentationsübersicht für den TURN/STUN-Server.
|
||||
Short entry point for the documentation.
|
||||
|
||||
- [`architecture/data_flow.md`](architecture/data_flow.md) – UDP/TLS-Loop, Allocation Manager.
|
||||
- [`config/runtime.md`](config/runtime.md) – Appsettings & Credentials.
|
||||
- [`turn_end_to_end_flow.md`](turn_end_to_end_flow.md) – Sequenzgrafik: Allocate → Permission → Send/ChannelData → Rückweg (UDP + TLS).
|
||||
- [`tcp_tls_data_plane.md`](tcp_tls_data_plane.md) – Warum TCP/TLS Data-Plane wichtig ist und wie sie implementiert ist.
|
||||
- [`mvp_gaps_and_rfc_notes.md`](mvp_gaps_and_rfc_notes.md) – MVP-Lücken, RFC-Notizen und Auswirkungen.
|
||||
- [`turn_rest_credentials.md`](turn_rest_credentials.md) – TURN REST Credentials: Algorithmus, CLI-Tool und Betrieb.
|
||||
- [`testing.md`](testing.md) – Testübersicht: was abgedeckt ist und wie man es ausführt.
|
||||
- [`testing_todo.md`](testing_todo.md) – Vorschläge für zusätzliche Tests (Roadmap).
|
||||
- Bereits vorhanden: `deploy_tls_lxc.md`, RFC-Referenzen (STUN/TURN Specs).
|
||||
## Start here
|
||||
|
||||
## Zielsetzung
|
||||
- Production-ready TURN mit Authentifizierung, Lebenszeitverwaltung und Monitoring.
|
||||
- Optionales TLS (TURN over TLS) für restriktive Netzwerke.
|
||||
- [`deployment.md`](deployment.md) – systemd deployment, TLS, NAT/`advertised_ip`, debugging.
|
||||
- [`config/runtime.md`](config/runtime.md) – `appsettings.json` schema and defaults.
|
||||
|
||||
## Protocol flows
|
||||
|
||||
- [`architecture/turn_end_to_end_flow.md`](architecture/turn_end_to_end_flow.md) – Happy path: Allocate → (Permission) → Send/ChannelData → return path.
|
||||
- [`architecture/turn_rest_credentials.md`](architecture/turn_rest_credentials.md) – TURN REST Credentials + CLI.
|
||||
|
||||
## Architecture
|
||||
|
||||
- [`architecture/data_flow.md`](architecture/data_flow.md) – components and data flow.
|
||||
- [`tcp_tls_data_plane.md`](architecture/tcp_tls_data_plane.md) – stream framing and TCP/TLS data plane.
|
||||
|
||||
## Testing
|
||||
|
||||
- [`testing.md`](testing.md) – test overview and commands.
|
||||
|
||||
## Notes & specs
|
||||
|
||||
- [`mvp_gaps_and_rfc_notes.md`](mvp_gaps_and_rfc_notes.md) – MVP gaps and RFC notes.
|
||||
- [`specs/rfc5389-stun.txt`](specs/rfc5389-stun.txt) – STUN RFC (text copy).
|
||||
- [`specs/rfc5766-turn.txt`](specs/rfc5766-turn.txt) – TURN RFC (text copy).
|
||||
|
||||
@ -1,114 +1,114 @@
|
||||
# MVP-Lücken & RFC-Notizen (STUN/TURN)
|
||||
# MVP Gaps & RFC Notes (STUN/TURN)
|
||||
|
||||
Dieses Dokument listet **bewusst vereinfachte/fehlende Teile** im aktuellen `niom-turn` MVP auf, jeweils mit kurzer Auswirkung und Code-Anker.
|
||||
This document lists **intentionally simplified / missing pieces** in the current `niom-turn` MVP, with a short impact note and a code anchor for each.
|
||||
|
||||
> Ziel: Klarheit, was schon interoperabel ist, und wo (für Production/Interop) noch Arbeit nötig ist.
|
||||
> Goal: clarity on what is already interoperable, and what still needs work for production / broader interop.
|
||||
|
||||
---
|
||||
|
||||
## 1) STUN Binding ist minimal
|
||||
|
||||
**Ist-Zustand**
|
||||
- `METHOD_BINDING` beantwortet der Server als "Success" und enthält `XOR-MAPPED-ADDRESS` (IPv4+IPv6): [src/stun.rs](../src/stun.rs)
|
||||
**Current state**
|
||||
- The server answers `METHOD_BINDING` with "Success" and includes `XOR-MAPPED-ADDRESS` (IPv4 + IPv6): [src/stun.rs](../src/stun.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- Für STUN-Diagnose/ICE-NAT-Discovery ist das damit deutlich interoperabler.
|
||||
**Impact**
|
||||
- This improves interop for STUN diagnostics / ICE NAT discovery.
|
||||
|
||||
---
|
||||
|
||||
## 2) IPv6 wird nicht unterstützt (historisch)
|
||||
## 2) IPv6 support (historical note)
|
||||
|
||||
**Ist-Zustand**
|
||||
- XOR-Address Encoding/Decoding unterstützt IPv4 **und** IPv6 (XOR-Key: Magic Cookie + Transaction ID): [src/stun.rs](../src/stun.rs)
|
||||
**Current state**
|
||||
- XOR-Address encoding/decoding supports IPv4 **and** IPv6 (XOR key: magic cookie + transaction ID): [src/stun.rs](../src/stun.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- TURN/STUN kann IPv6-Adressen in XOR-ADDRESS Attributen korrekt verarbeiten.
|
||||
**Impact**
|
||||
- TURN/STUN can correctly handle IPv6 addresses in XOR-ADDRESS attributes.
|
||||
|
||||
---
|
||||
|
||||
## 3) TURN Allocate ist stark vereinfacht
|
||||
## 3) TURN Allocate is heavily simplified
|
||||
|
||||
**Ist-Zustand**
|
||||
- `allocate_for` bindet immer ein UDP-Relay auf `0.0.0.0:0`: [src/alloc.rs](../src/alloc.rs)
|
||||
- `REQUESTED-TRANSPORT` wird nicht ausgewertet (es gibt nur UDP-Relay).
|
||||
- Weitere RFC-TURN Optionen (EVEN-PORT, RESERVATION-TOKEN, DONT-FRAGMENT, etc.) sind nicht implementiert.
|
||||
**Current state**
|
||||
- `allocate_for` always binds a UDP relay on `0.0.0.0:0`: [src/alloc.rs](../src/alloc.rs)
|
||||
- `REQUESTED-TRANSPORT` is not evaluated (there is only UDP relay).
|
||||
- Additional RFC TURN options (EVEN-PORT, RESERVATION-TOKEN, DONT-FRAGMENT, etc.) are not implemented.
|
||||
|
||||
**Auswirkung**
|
||||
- Das MVP deckt den typischen UDP-Relay-Fall ab, aber nicht die volle TURN-Feature-Matrix.
|
||||
**Impact**
|
||||
- The MVP covers the common UDP relay case, but not the full TURN feature matrix.
|
||||
|
||||
---
|
||||
|
||||
## 4) Data Plane: UDP-relay ja, TCP-relay nein
|
||||
## 4) Data plane: UDP relay yes, TCP relay no
|
||||
|
||||
**Ist-Zustand**
|
||||
- Relay-Datenpfad ist UDP-only (Relay-Socket ist `tokio::net::UdpSocket`): [src/alloc.rs](../src/alloc.rs)
|
||||
- TLS-Listener re-used Control-Plane Logik, aber kein TCP-Relay: [src/tls.rs](../src/tls.rs)
|
||||
**Current state**
|
||||
- The relay data path is UDP-only (the relay socket is `tokio::net::UdpSocket`): [src/alloc.rs](../src/alloc.rs)
|
||||
- The TLS listener reuses the control-plane logic; there is no separate TCP relay: [src/tls.rs](../src/tls.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- `turns:` (TLS) kann Control-Plane, aber der Rückweg der relayed Daten läuft derzeit weiterhin über UDP an die Client-UDP-Adresse.
|
||||
- Für echte TURN-over-TCP/TLS Data Plane (Interoperabilität mit restriktiven Netzwerken) fehlt ein eigener TCP-Relay-Pfad.
|
||||
**Impact**
|
||||
- `turns:` (TLS) supports the control plane, and for stream clients the **return path** (server → client) is delivered over the stream as Data Indications / ChannelData.
|
||||
- However, the actual relay socket towards the peer is still UDP-only. A true TURN-over-TCP/TLS data plane with a dedicated TCP relay path is not implemented.
|
||||
|
||||
---
|
||||
|
||||
## 5) Allocation- und Timer-Verhalten ist MVP-artig
|
||||
## 5) Allocation and timer behaviour is MVP-style
|
||||
|
||||
**Ist-Zustand**
|
||||
- Allocation-Lifetime wird über `refresh_allocation` gesetzt/geklemmt: [src/alloc.rs](../src/alloc.rs)
|
||||
- `remove_allocation` entfernt den Eintrag.
|
||||
- Der Relay-Task (spawn in `allocate_for`) läuft jedoch weiter und prüft nur noch, ob Allocation existiert: [src/alloc.rs](../src/alloc.rs)
|
||||
**Current state**
|
||||
- Allocation lifetime is set/clamped via `refresh_allocation`: [src/alloc.rs](../src/alloc.rs)
|
||||
- `remove_allocation` removes the entry.
|
||||
- The relay task (spawned in `allocate_for`) can continue running and primarily checks whether the allocation still exists: [src/alloc.rs](../src/alloc.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- Bei vielen Allocations kann das zu unnötigen Hintergrund-Tasks führen (Resource-Management/Backpressure fehlt).
|
||||
**Impact**
|
||||
- With many allocations, this can lead to unnecessary background tasks (resource management / backpressure is minimal).
|
||||
|
||||
---
|
||||
|
||||
## 6) Permissions & ChannelBindings sind minimal
|
||||
## 6) Permissions & channel bindings are minimal
|
||||
|
||||
**Ist-Zustand**
|
||||
- Permission TTL ist statisch (300s) und wird nur durch erneutes `CreatePermission` erneuert: [src/alloc.rs](../src/alloc.rs)
|
||||
- ChannelBindings nutzen denselben TTL-Wert (`PERMISSION_LIFETIME`): [src/alloc.rs](../src/alloc.rs)
|
||||
**Current state**
|
||||
- Permission TTL is static (300s) and is only renewed by sending `CreatePermission` again: [src/alloc.rs](../src/alloc.rs)
|
||||
- Channel bindings use the same TTL value (`PERMISSION_LIFETIME`): [src/alloc.rs](../src/alloc.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- Funktional ok für MVP, aber nicht unbedingt exakt RFC-getreu im Detail (separate Lifetime-Regeln/Refresh-Strategien sind üblich).
|
||||
**Impact**
|
||||
- Functionally OK for an MVP, but not necessarily RFC-exact in all details (separate lifetime rules / refresh strategies are common).
|
||||
|
||||
---
|
||||
|
||||
## 7) MESSAGE-INTEGRITY/FINGERPRINT
|
||||
|
||||
**Ist-Zustand**
|
||||
- `MESSAGE-INTEGRITY` wird RFC-konform geprüft (HMAC-SHA1 über die Message bis inkl. MI-Attribut; Header-Länge wird dafür auf „Ende von MI“ gesetzt, d.h. kompatibel mit nachfolgenden Attributen wie `FINGERPRINT`): [src/stun.rs](../src/stun.rs)
|
||||
- Für TURN-Responses nach erfolgreicher Authentisierung hängt der Server `MESSAGE-INTEGRITY` an und setzt `FINGERPRINT` als letztes Attribut: [src/server.rs](../src/server.rs), [src/turn_stream.rs](../src/turn_stream.rs)
|
||||
- `FINGERPRINT` wird bei allen vom Server gebauten STUN-Nachrichten als letztes Attribut angehängt und bei eingehenden Nachrichten (falls vorhanden) validiert: [src/stun.rs](../src/stun.rs)
|
||||
**Current state**
|
||||
- `MESSAGE-INTEGRITY` is validated in an RFC-compatible way (HMAC-SHA1 over the message up to and including the MI attribute; the header length is set to the “end of MI”, which is compatible with subsequent attributes like `FINGERPRINT`): [src/stun.rs](../src/stun.rs)
|
||||
- For TURN responses after successful authentication, the server appends `MESSAGE-INTEGRITY` and sets `FINGERPRINT` as the last attribute: [src/server.rs](../src/server.rs), [src/turn_stream.rs](../src/turn_stream.rs)
|
||||
- `FINGERPRINT` is appended as the last attribute on all STUN messages built by the server, and validated on inbound messages (if present): [src/stun.rs](../src/stun.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- Bessere Browser/ICE-Interop und leichtes Hardening (Messages mit ungültigem `FINGERPRINT` werden verworfen).
|
||||
**Impact**
|
||||
- Better browser/ICE interop and mild hardening (messages with invalid `FINGERPRINT` are dropped).
|
||||
|
||||
---
|
||||
|
||||
## 8) Observability / Limits / Hardening fehlen (noch)
|
||||
## 8) Observability / limits / hardening (still missing)
|
||||
|
||||
**Ist-Zustand**
|
||||
- Keine Quotas pro User/IP, keine Rate-Limits, keine Bandbreitenlimits pro Allocation.
|
||||
- Credential Store ist In-Memory (Test/Dev): [src/auth.rs](../src/auth.rs), Trait: [src/traits/credential_store.rs](../src/traits/credential_store.rs)
|
||||
**Current state**
|
||||
- No quotas per user/IP, no rate limits, no bandwidth limits per allocation.
|
||||
- Credential store is in-memory (test/dev): [src/auth.rs](../src/auth.rs), trait: [src/traits/credential_store.rs](../src/traits/credential_store.rs)
|
||||
|
||||
**Auswirkung**
|
||||
- Für Production braucht es Limits, persistente Credentials, Monitoring/Metrics und härteres Error-Handling.
|
||||
**Impact**
|
||||
- For production you likely want limits, persistent credentials, monitoring/metrics, and stricter error handling.
|
||||
|
||||
---
|
||||
|
||||
## 9) Weitere RFC-Ecken (nicht implementiert)
|
||||
## 9) More RFC corners (not implemented)
|
||||
|
||||
Typische Punkte, die im MVP fehlen/noch offen sind:
|
||||
- Full attribute coverage (z.B. UNKNOWN-ATTRIBUTES, SOFTWARE, etc.)
|
||||
- Vollständige STUN/TURN Class/Method Encoding nach RFC (hier bewusst vereinfacht über `METHOD | CLASS_*`): [src/constants.rs](../src/constants.rs)
|
||||
- IPv6, Hairpinning-Sonderfälle, NAT-bezogene Interop-Edge-Cases
|
||||
Typical items that are missing / still open in the MVP:
|
||||
- Full attribute coverage (e.g. UNKNOWN-ATTRIBUTES, SOFTWARE, etc.)
|
||||
- Fully RFC-accurate STUN/TURN class/method encoding (here intentionally simplified via `METHOD | CLASS_*`): [src/constants.rs](../src/constants.rs)
|
||||
- IPv6 hairpinning edge cases, NAT-related interop edge cases
|
||||
|
||||
---
|
||||
|
||||
## Was bereits gut abgedeckt ist
|
||||
## What is already covered well
|
||||
|
||||
- End-to-end UDP TURN Core: `Allocate` → `CreatePermission` → `Send`/ChannelData → Rückweg als Data Indication/ChannelData.
|
||||
- Long-term Auth (Realm/Nonce + MI) mit klaren 401/438/437/403 Pfaden.
|
||||
- TLS Listener (Control-Plane) mit STUN-Framing über TCP/TLS.
|
||||
- End-to-end UDP TURN core: `Allocate` → `CreatePermission` → `Send`/ChannelData → return path as Data Indication/ChannelData.
|
||||
- Long-term auth (realm/nonce + MI) with clear 401/438/437/403 paths.
|
||||
- TLS listener (control plane) with STUN framing over TCP/TLS.
|
||||
|
||||
Siehe auch die Integrationstests: [tests/udp_turn.rs](../tests/udp_turn.rs) und [tests/tls_turn.rs](../tests/tls_turn.rs).
|
||||
See also the integration tests: [tests/udp_turn.rs](../tests/udp_turn.rs) and [tests/tls_turn.rs](../tests/tls_turn.rs).
|
||||
|
||||
Binary file not shown.
@ -1,98 +0,0 @@
|
||||
# TCP/TLS Data-Plane (TURN over TCP/TLS) in niom-turn
|
||||
|
||||
Dieses Dokument erklärt **wofür** eine TCP/TLS Data-Plane bei TURN gebraucht wird und **wie** `niom-turn` sie (aktuell) implementiert.
|
||||
|
||||
## Wofür wird das benötigt?
|
||||
|
||||
TURN hat zwei Arten von Traffic:
|
||||
|
||||
- **Control-Plane**: STUN/TURN Requests/Responses (Allocate, CreatePermission, ChannelBind, Refresh, …)
|
||||
- **Data-Plane**: Nutzdaten zwischen Client und Peer, die über das Relay laufen (Send/Data-Indication oder ChannelData)
|
||||
|
||||
In vielen realen Netzen ist **UDP eingeschränkt oder komplett blockiert** (Corporate WLAN, Mobilfunk-APNs, Captive Portals, Proxy-Umgebungen). WebRTC/ICE versucht deshalb typischerweise:
|
||||
|
||||
1. UDP (schnell, bevorzugt)
|
||||
2. TURN über TCP
|
||||
3. TURN über TLS ("turns:") als letzte, aber oft funktionierende Option
|
||||
|
||||
Damit TURN über TCP/TLS wirklich nutzbar ist, muss nicht nur die Control-Plane über den Stream laufen, sondern auch der **Rückweg Peer → Client** (Data-Plane) über **dieselbe TCP/TLS Verbindung** beim Client ankommen.
|
||||
|
||||
## Was implementiert niom-turn konkret?
|
||||
|
||||
- Client ↔ Server Transport kann **UDP**, **TCP** oder **TLS** sein.
|
||||
- Das Relay zum Peer ist weiterhin **UDP** (klassisches TURN UDP-Relay).
|
||||
- Bei **TCP/TLS** liefert der Server die Data-Plane zurück an den Client über den **Stream** (statt über UDP an die Client-Adresse zu senden).
|
||||
|
||||
Das entspricht dem üblichen WebRTC-Fallback: "Client-Server über TCP/TLS, Peer-Transport über UDP".
|
||||
|
||||
## Architektur im Code
|
||||
|
||||
- Stream-Handler (TCP/TLS): [src/turn_stream.rs](../src/turn_stream.rs)
|
||||
- TCP Listener: [src/tcp.rs](../src/tcp.rs)
|
||||
- TLS Listener: [src/tls.rs](../src/tls.rs)
|
||||
- Allocation + Relay: [src/alloc.rs](../src/alloc.rs)
|
||||
|
||||
### Schlüsselidee: `ClientSink`
|
||||
|
||||
Damit der Relay-Loop Peer-Pakete an unterschiedliche Client-Transporte schicken kann, gibt es in [src/alloc.rs](../src/alloc.rs) eine Abstraktion:
|
||||
|
||||
- `ClientSink::Udp { sock, addr }` → sendet Peer-Daten per `udp.send_to(..., addr)`
|
||||
- `ClientSink::Stream { tx }` → queued Bytes in einen Writer-Task, der auf den TCP/TLS Stream schreibt
|
||||
|
||||
Wenn ein Client über TCP/TLS allocatet, wird die Allocation mit einem `ClientSink::Stream` erzeugt.
|
||||
|
||||
## Framing: STUN vs. ChannelData auf einem Byte-Stream
|
||||
|
||||
Auf UDP bekommt man Datagramme; auf TCP/TLS bekommt man einen **kontinuierlichen Byte-Stream**. TURN over TCP/TLS multiplexed:
|
||||
|
||||
- STUN/TURN Messages (Control-Plane)
|
||||
- ChannelData Frames (Data-Plane, Client → Server)
|
||||
|
||||
`niom-turn` parst daher im Stream in einer Schleife "nächstes Frame" (siehe `try_pop_next_frame(...)` in [src/turn_stream.rs](../src/turn_stream.rs)):
|
||||
|
||||
### STUN Message
|
||||
|
||||
- Header ist 20 Bytes
|
||||
- Length-Feld ist die Body-Länge
|
||||
- Gesamtlänge ist: $20 + length$
|
||||
|
||||
### ChannelData Frame
|
||||
|
||||
- 4 Byte Header: `CHANNEL-NUMBER` (2) + `LENGTH` (2)
|
||||
- Channel-Nummern liegen im Bereich `0x4000..=0x7FFF` (Top-Bits `01`)
|
||||
- Gesamtlänge ist: $4 + length$
|
||||
|
||||
Wichtig: Bei TCP/TLS darf **kein Padding** als "separate Bytes" im Stream verbleiben. Deshalb baut `niom-turn` ChannelData als exakt `4 + len` Bytes (siehe [src/stun.rs](../src/stun.rs)).
|
||||
|
||||
### Hardening: Resync & Limits
|
||||
|
||||
Da TCP/TLS ein Byte-Stream ist, können kaputte oder bösartige Clients den Parser sonst leicht „desynchronisieren“.
|
||||
`niom-turn` implementiert daher im Stream-Parser:
|
||||
|
||||
- **Magic-Cookie Check** für STUN: Ungültige Cookies führen zu einem Byte-weisen Resync (statt auf riesige Längen zu warten).
|
||||
- **Frame-Size Limits** (STUN-Body und ChannelData), um Speicher-/DoS-Risiken zu begrenzen.
|
||||
- **Max Buffer Limit** pro Verbindung: wenn der Eingangspuffer zu groß wird, wird die Verbindung geschlossen.
|
||||
|
||||
## Datenfluss (TCP/TLS)
|
||||
|
||||
1. Client verbindet sich per TCP oder TLS.
|
||||
2. Der Stream-Handler liest Frames:
|
||||
- STUN/TURN Requests → verarbeitet wie UDP-Pfad (Auth, Allocation, Permission, ChannelBind, Send, Refresh)
|
||||
- ChannelData (Client→Peer) → wird über das UDP-Relay an den Peer geschickt
|
||||
3. Peer sendet UDP an die Relay-Adresse.
|
||||
4. Relay-Loop leitet die Bytes an den `ClientSink` weiter:
|
||||
- bei Stream: `tx.send(bytes)` → Writer-Task schreibt Data-Indication oder ChannelData zurück auf denselben Stream
|
||||
|
||||
## Grenzen / Noch nicht implementiert
|
||||
|
||||
- Kein TCP-Relay zum Peer (TURN TCP allocations / CONNECT-Methoden wie in RFC6062).
|
||||
- Fokus liegt auf: Client-Server Transport über TCP/TLS + UDP-Relay.
|
||||
- IPv6 ist im aktuellen MVP noch nicht vollständig umgesetzt.
|
||||
|
||||
## Tests
|
||||
|
||||
- TCP Stream Data-Plane: `tests/tcp_turn.rs`
|
||||
- TLS Stream Data-Plane: `tests/tls_data_plane.rs`
|
||||
- Gemeinsames Framing (STUN + ChannelData): `tests/support/stream.rs`
|
||||
|
||||
Wenn du willst, kann ich als nächsten Schritt die Doku um eine kurze Interop-Checkliste (WebRTC/ICE Verhalten, Candidate-Types, typische Fehlerbilder) ergänzen.
|
||||
@ -1,60 +1,60 @@
|
||||
# Testing
|
||||
|
||||
Dieses Projekt ist so aufgebaut, dass sich die wichtigsten TURN-Pfade als **Unit- und Integrationstests** ausführen lassen.
|
||||
This project is structured so the most important TURN paths can be exercised via **unit and integration tests**.
|
||||
|
||||
## Schnellstart
|
||||
## Quick start
|
||||
|
||||
- Alle Tests: `cargo test`
|
||||
- Mit weniger Output: `cargo test -q`
|
||||
- Mit Logs (Beispiele):
|
||||
- All tests: `cargo test`
|
||||
- Less output: `cargo test -q`
|
||||
- With logs (examples):
|
||||
- `RUST_LOG=warn,niom_turn=info cargo test -- --nocapture`
|
||||
|
||||
Hinweis: Die Integrationstests initialisieren `tracing` über die Helpers in `tests/support`.
|
||||
Note: integration tests initialise `tracing` via helpers in `tests/support`.
|
||||
|
||||
## Was wird getestet?
|
||||
## What is covered?
|
||||
|
||||
### STUN RFC-Interop (FINGERPRINT)
|
||||
|
||||
- Unit-Tests prüfen, dass der Server `FINGERPRINT` an Responses anhängt und dass die CRC32/XOR Validierung fehlschlägt, wenn die Nachricht manipuliert wird.
|
||||
- Unit tests verify that the server appends `FINGERPRINT` to responses and that CRC32/XOR validation fails when the message is tampered with.
|
||||
|
||||
Siehe: Unit-Tests in [src/stun.rs](../src/stun.rs).
|
||||
See: unit tests in [src/stun.rs](../src/stun.rs).
|
||||
|
||||
### STUN RFC-Interop (MESSAGE-INTEGRITY)
|
||||
|
||||
- Unit-Tests prüfen `MESSAGE-INTEGRITY` Validierung (inkl. Fall „MI + nachfolgendes FINGERPRINT“).
|
||||
- UDP-Integrationstests prüfen, dass Responses nach erfolgreicher Authentisierung `MESSAGE-INTEGRITY` enthalten und validierbar sind.
|
||||
- Unit tests verify `MESSAGE-INTEGRITY` validation (including the “MI + trailing FINGERPRINT” case).
|
||||
- UDP integration tests verify that responses after successful authentication contain `MESSAGE-INTEGRITY` and are verifiable.
|
||||
|
||||
### UDP (turn:)
|
||||
|
||||
- Auth-Challenge (401 + NONCE) und erfolgreicher Allocate
|
||||
- Refresh mit `LIFETIME=0` entfernt Allocation
|
||||
- CreatePermission + Send → Peer erhält UDP Payload über Relay
|
||||
- Auth challenge (401 + NONCE) and successful allocate
|
||||
- Refresh with `LIFETIME=0` releases the allocation
|
||||
- CreatePermission + Send → peer receives UDP payload via the relay
|
||||
|
||||
Siehe: `tests/udp_turn.rs` sowie die thematischen Ordner in `tests/`.
|
||||
See: `tests/udp_turn.rs` and the topic folders under `tests/`.
|
||||
|
||||
### TLS (turns:) / Stream-basierte Data-Plane
|
||||
### TLS (turns:) / stream-based data plane
|
||||
|
||||
- Allocate/Refresh über TLS-Stream
|
||||
- (Neu) Data-Plane Rückweg Peer→Client über denselben TLS-Stream (Data-Indication oder ChannelData)
|
||||
- Allocate/Refresh over the TLS stream
|
||||
- Data-plane return path peer→client over the same TLS stream (Data Indication or ChannelData)
|
||||
|
||||
Siehe: `tests/tls_turn.rs` und `tests/tls_data_plane.rs`.
|
||||
See: `tests/tls_turn.rs` and `tests/tls_data_plane.rs`.
|
||||
|
||||
### TCP (turn:?transport=tcp) / Stream-basierte Data-Plane
|
||||
### TCP (turn:?transport=tcp) / stream-based data plane
|
||||
|
||||
- (Neu) Allocate/CreatePermission/Send über TCP
|
||||
- (Neu) Peer→Client Rückweg als STUN Data Indication über TCP
|
||||
- (Neu) ChannelBind + ChannelData in beide Richtungen
|
||||
- Allocate/CreatePermission/Send over TCP
|
||||
- Peer→client return path as STUN Data Indication over TCP
|
||||
- ChannelBind + ChannelData in both directions
|
||||
|
||||
Siehe: `tests/tcp_turn.rs`.
|
||||
See: `tests/tcp_turn.rs`.
|
||||
|
||||
## Test-Hilfen
|
||||
## Test helpers
|
||||
|
||||
- STUN/TURN Builder: `tests/support/stun_builders.rs`
|
||||
- Stream-Framing (STUN + ChannelData über TCP/TLS): `tests/support/stream.rs`
|
||||
- TLS Test-Certs: `tests/support/tls.rs`
|
||||
- STUN/TURN builder: `tests/support/stun_builders.rs`
|
||||
- Stream framing (STUN + ChannelData over TCP/TLS): `tests/support/stream.rs`
|
||||
- TLS test certs: `tests/support/tls.rs`
|
||||
|
||||
## Erweiterungsideen (nächste sinnvolle Abdeckung)
|
||||
## Expansion ideas (useful next coverage)
|
||||
|
||||
- Split-Reads/Writes (Frames in mehreren TCP Reads) als Regressionstest
|
||||
- IPv6 Encode/Decode Tests für XOR-ADDRESS Varianten
|
||||
- Negative Tests: Peer nicht permitted, Channel ohne Bind, Allocation Timeout
|
||||
- Split reads/writes (frames spanning multiple TCP reads) as regression tests
|
||||
- IPv6 encode/decode tests for XOR-ADDRESS variants
|
||||
- Negative tests: peer not permitted, channel without binding, allocation timeout
|
||||
|
||||
@ -1,59 +1,57 @@
|
||||
# Test-ToDo (Vorschläge)
|
||||
# Testing TODO (ideas)
|
||||
|
||||
Dieses Dokument sammelt **konkrete** Test-Ideen, die den sicheren/stabilen Betrieb (insb. unter Last/Fehlverhalten) absichern sollen.
|
||||
This document collects **concrete** test ideas to increase safety/stability (especially under load and misbehaviour).
|
||||
|
||||
## Stream (TCP/TLS) Robustheit
|
||||
## Stream (TCP/TLS) robustness
|
||||
|
||||
- Split-Reads: STUN Header (20B) in 2 Reads, Body in mehreren Reads
|
||||
- Split-Reads: ChannelData Header (4B) und Payload getrennt
|
||||
- Mixed Frames: STUN → ChannelData → STUN in einem Read (und in mehreren Reads)
|
||||
- Oversize Frames:
|
||||
- STUN Length > Max → Verbindung wird geschlossen (oder Frame gedroppt, je nach Policy)
|
||||
- ChannelData Length > Max → Verbindung wird geschlossen (oder Frame gedroppt)
|
||||
- Garbage Resync:
|
||||
- Zufallsbytes vor gültigem STUN (bereits abgedeckt)
|
||||
- Zufallsbytes zwischen gültigen Frames
|
||||
- Split reads: STUN header (20B) in 2 reads, body in multiple reads
|
||||
- Split reads: ChannelData header (4B) and payload separated
|
||||
- Mixed frames: STUN → ChannelData → STUN in a single read (and across multiple reads)
|
||||
- Oversize frames:
|
||||
- STUN length > max → close connection (or drop frame, depending on policy)
|
||||
- ChannelData length > max → close connection (or drop frame)
|
||||
- Garbage resync:
|
||||
- random bytes before valid STUN (already covered)
|
||||
- random bytes between valid frames
|
||||
|
||||
## TURN Flows (Happy + Negative)
|
||||
## TURN flows (happy + negative)
|
||||
|
||||
- Negative pro Methode (UDP/TCP/TLS jeweils):
|
||||
- ohne Allocation → 437 Allocation Mismatch
|
||||
- ohne Permission → 403 Peer Not Permitted
|
||||
- ChannelData ohne ChannelBind → drop + optional log counter
|
||||
- Stale Nonce → 438
|
||||
- falsches MI → 401/403 je nach Policy
|
||||
- Negative cases per method (for UDP/TCP/TLS each):
|
||||
- without allocation → 437 Allocation Mismatch
|
||||
- without permission → 403 Peer Not Permitted
|
||||
- ChannelData without ChannelBind → drop + optional log counter
|
||||
- stale nonce → 438
|
||||
- wrong MI → 401/403 depending on policy
|
||||
|
||||
## Auth
|
||||
|
||||
- TURN REST:
|
||||
- abgelaufener Username → reject
|
||||
- Username zu weit in der Zukunft (max TTL) → reject
|
||||
- falsches HMAC/base64 → reject
|
||||
- „user exists in store“ vs. „REST fallback“ Priorität
|
||||
- expired username → reject
|
||||
- username too far in the future (max TTL) → reject
|
||||
- wrong HMAC/base64 → reject
|
||||
- priority: “user exists in store” vs. “REST fallback”
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- Allocation expiry:
|
||||
- Refresh verkürzt/verlängert, Min/Max Lifetime
|
||||
- Expiry entfernt Allocation und beendet Relay-Task (keine Task-Leaks)
|
||||
- Refresh shortens/extends; min/max lifetime
|
||||
- Expiry removes allocation and ends relay task (no task leaks)
|
||||
- Permission expiry:
|
||||
- Peer wird nach Ablauf verworfen
|
||||
- peer is dropped after expiry
|
||||
- Channel binding expiry:
|
||||
- Rückweg fällt auf Data Indication zurück, wenn Binding abläuft
|
||||
- return path falls back to Data Indication when the binding expires
|
||||
|
||||
## Abuse-/DoS Prevention (sobald Limits implementiert sind)
|
||||
## Abuse/DoS prevention (once limits are implemented)
|
||||
|
||||
- Rate limit: auth failures pro IP/Username
|
||||
- Max allocations pro IP
|
||||
- Max permissions/channels pro allocation
|
||||
- Bandwidth caps (bytes/s) pro allocation
|
||||
- Backpressure: Writer-Queue voll → Verhalten definieren (drop/close)
|
||||
- Rate limit: auth failures per IP/username
|
||||
- Max allocations per IP
|
||||
- Max permissions/channels per allocation
|
||||
- Bandwidth caps (bytes/s) per allocation
|
||||
- Backpressure: writer queue full → define behaviour (drop/close)
|
||||
|
||||
## Interop (manuell reproduzierbar, aber dokumentiert)
|
||||
## Interop (manually reproducible, but documented)
|
||||
|
||||
- Browser Plan:
|
||||
- Trickle ICE / webrtc-internals: forced relay
|
||||
- UDP-only block: erwarte TCP/TLS fallback
|
||||
- `turns:` mit self-signed vs. valid cert
|
||||
|
||||
Wenn du willst, kann ich als nächsten Schritt aus diesen Punkten eine priorisierte Test-Roadmap machen (P0/P1/P2) und direkt die nächsten P0-Tests implementieren.
|
||||
- Browser plan:
|
||||
- trickle ICE / webrtc-internals: forced relay
|
||||
- UDP-only block: expect TCP/TLS fallback
|
||||
- `turns:` with self-signed vs. valid cert
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
# End-to-End TURN Flow (UDP + TLS)
|
||||
|
||||
Dieses Dokument beschreibt den **konkret implementierten** End-to-End Ablauf in `niom-turn` anhand des aktuellen Codes (MVP):
|
||||
|
||||
- UDP-Control-Plane und UDP-Data-Plane: [src/server.rs](../src/server.rs), [src/alloc.rs](../src/alloc.rs), [src/stun.rs](../src/stun.rs), [src/auth.rs](../src/auth.rs)
|
||||
- TLS-Control-Plane ("turns") mit STUN-Framing: [src/tls.rs](../src/tls.rs)
|
||||
- Test-Builder (wie Requests gebaut werden): [tests/support/stun_builders.rs](../tests/support/stun_builders.rs)
|
||||
|
||||
## Begriffe
|
||||
|
||||
- **Client**: TURN-Client (z.B. WebRTC ICE Agent)
|
||||
- **Server**: `niom-turn` (dieses Projekt)
|
||||
- **Peer**: Gegenstelle, zu der relayed werden soll (typischerweise anderer WebRTC-Endpunkt)
|
||||
- **Allocation**: Server-seitige Sitzung, die ein **Relay-UDP-Socket** bereitstellt
|
||||
- **Permission**: Erlaubnis, zu einem Peer zu senden/Peer-Pakete zu akzeptieren
|
||||
- **Channel Binding**: Zuordnung `channel-number -> peer`, um ChannelData nutzen zu können
|
||||
|
||||
---
|
||||
|
||||
## UDP: Sequenzgrafik (Happy Path)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant C as Client (TURN)
|
||||
participant S as niom-turn (UDP:3478)
|
||||
participant R as Relay Socket (UDP:ephemeral)
|
||||
participant P as Peer
|
||||
|
||||
Note over C,S: 1) Allocate ohne Auth → 401 Challenge
|
||||
C->>S: STUN Allocate Request (ohne MI)
|
||||
S->>C: STUN Error Response 401 + REALM + NONCE
|
||||
|
||||
Note over C,S: 2) Allocate mit Long-Term Auth
|
||||
C->>S: STUN Allocate Request + USERNAME/REALM/NONCE + MESSAGE-INTEGRITY
|
||||
S->>R: bind("0.0.0.0:0"), spawn Relay-Loop
|
||||
S->>C: Allocate Success + XOR-RELAYED-ADDRESS + LIFETIME (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
|
||||
Note over C,S: 3) CreatePermission (Pflicht vor Send/ChannelBind)
|
||||
C->>S: CreatePermission + XOR-PEER-ADDRESS (+ Auth + MI)
|
||||
S->>C: Success (200) (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
|
||||
Note over C,S: 4) Send (Client→Peer via Relay)
|
||||
C->>S: Send + XOR-PEER-ADDRESS + DATA (+ Auth + MI)
|
||||
S->>R: relay.send_to(DATA, Peer)
|
||||
R->>P: UDP payload (source = relay_addr)
|
||||
|
||||
Note over P,C: 5) Rückweg (Peer→Client)
|
||||
P->>R: UDP payload (dest = relay_addr)
|
||||
alt Channel Binding existiert
|
||||
R->>S: recv_from(Peer)
|
||||
S->>C: ChannelData(channel, payload)
|
||||
else Kein Channel Binding
|
||||
R->>S: recv_from(Peer)
|
||||
S->>C: Data Indication (METHOD_DATA|INDICATION) + XOR-PEER-ADDRESS + DATA
|
||||
end
|
||||
|
||||
Note over C,S: 6) Optional: ChannelBind + ChannelData
|
||||
C->>S: ChannelBind + CHANNEL-NUMBER + XOR-PEER-ADDRESS (+ Auth + MI)
|
||||
S->>C: Success (200) (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
C->>S: ChannelData(channel, payload)
|
||||
S->>R: relay.send_to(payload, Peer)
|
||||
R->>P: UDP payload
|
||||
|
||||
Note over C,S: 7) Refresh
|
||||
C->>S: Refresh + LIFETIME (+ Auth + MI)
|
||||
S->>C: Success + LIFETIME(applied) (+ MESSAGE-INTEGRITY + FINGERPRINT)
|
||||
```
|
||||
|
||||
### Was der Server dabei **genau** macht
|
||||
|
||||
- Eingangspunkt: `udp_reader_loop` in [src/server.rs](../src/server.rs)
|
||||
- Frühe Abzweigung: Wenn `parse_channel_data(...)` erfolgreich ist, wird **kein STUN** geparst, sondern ChannelData direkt weitergeleitet (nur wenn Allocation+Binding+Permission passt).
|
||||
- STUN/TURN Requests werden mit `parse_message(...)` in [src/stun.rs](../src/stun.rs) geparst.
|
||||
|
||||
### RFC-Interop Hinweis: FINGERPRINT
|
||||
|
||||
- Alle vom Server gebauten STUN-Nachrichten enthalten `FINGERPRINT` als letztes Attribut.
|
||||
- Wenn ein Client `FINGERPRINT` mitsendet, wird es validiert; bei ungültigem `FINGERPRINT` wird die Nachricht verworfen (kein Response).
|
||||
|
||||
### Auth-Entscheidungen und typische Error-Codes
|
||||
|
||||
Die Auth-Policy ist zentral in `AuthManager::authenticate` in [src/auth.rs](../src/auth.rs).
|
||||
|
||||
- **401 Unauthorized**: Wenn `MESSAGE-INTEGRITY` fehlt → Challenge mit `REALM` + `NONCE`.
|
||||
- **438 Stale Nonce**: Wenn `NONCE` abgelaufen/ungültig ist → neue Challenge.
|
||||
- **437 Allocation Mismatch**: Wenn CreatePermission/Send/ChannelBind/Refresh ohne Allocation kommt.
|
||||
- **403 Peer Not Permitted**: Wenn ein Peer nicht (mehr) permitted ist.
|
||||
- **400 Missing/Invalid ...**: Wenn Attribute fehlen oder XOR-PEER-ADDRESS nicht dekodierbar ist.
|
||||
|
||||
---
|
||||
|
||||
## TLS (turns): Sequenzgrafik (Control-Plane)
|
||||
|
||||
Wichtig: Die TLS-Implementierung in [src/tls.rs](../src/tls.rs) nutzt denselben TURN-Handler wie TCP und implementiert eine echte **Stream-Data-Plane**.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant C as Client (turns)
|
||||
participant T as niom-turn (TLS:5349)
|
||||
participant R as Relay Socket (UDP:ephemeral)
|
||||
participant P as Peer
|
||||
|
||||
Note over C,T: STUN framing über TCP/TLS: read → chunk by (len+20)
|
||||
C->>T: STUN Allocate (ohne MI)
|
||||
T->>C: 401 + REALM + NONCE (über TLS)
|
||||
|
||||
C->>T: STUN Allocate + Auth + MI
|
||||
T->>R: allocate_for(peer, stream-sink)
|
||||
T->>C: Allocate Success + XOR-RELAYED-ADDRESS + LIFETIME (über TLS)
|
||||
|
||||
C->>T: CreatePermission/Send/Refresh/ChannelBind (über TLS)
|
||||
T->>C: Success/Error (über TLS)
|
||||
|
||||
Note over P,C: Peer-Daten kommen über den TLS-Stream zurück
|
||||
P->>R: UDP payload an relay_addr
|
||||
R->>T: recv_from(Peer)
|
||||
T->>C: Data Indication / ChannelData (über TLS)
|
||||
```
|
||||
|
||||
### Konsequenz
|
||||
|
||||
- Control-Plane über TLS funktioniert (Allocate/Refresh/… werden über TLS beantwortet).
|
||||
- Der **Data-Plane Rückweg** (Peer → Client) läuft ebenfalls über den TLS-Stream (Relay → `ClientSink::Stream`).
|
||||
|
||||
Mehr Details dazu: [docs/tcp_tls_data_plane.md](tcp_tls_data_plane.md)
|
||||
|
||||
---
|
||||
|
||||
## Mini-Checkliste: Minimaler Ablauf (praktisch)
|
||||
|
||||
1. `ALLOCATE` ohne MI → `401` + `REALM` + `NONCE`
|
||||
2. `ALLOCATE` mit `USERNAME/REALM/NONCE` + `MESSAGE-INTEGRITY` → `XOR-RELAYED-ADDRESS` + `LIFETIME`
|
||||
3. `CREATE_PERMISSION` für Peer → `200`
|
||||
4. `SEND` mit `DATA` → Server sendet via Relay an Peer
|
||||
5. Peer sendet zurück an Relay → Server liefert an Client als `DATA-INDICATION` oder `CHANNEL-DATA`
|
||||
6. Optional `CHANNEL_BIND` + ChannelData für effizientere Data-Plane
|
||||
7. `REFRESH` zum Verlängern oder `LIFETIME=0` zum Freigeben
|
||||
@ -1,75 +0,0 @@
|
||||
# TURN REST Credentials (Ephemeral) – Nutzung & Betrieb
|
||||
|
||||
Dieses Dokument erklärt die **TURN REST Credential** Strategie für `niom-turn` (MVP → production-fähiger Pfad).
|
||||
|
||||
## Warum TURN REST?
|
||||
|
||||
- Du willst für WebRTC *kurzlebige* TURN-Logins ausstellen (z.B. 5–10 Minuten).
|
||||
- Dein TURN-Server speichert keine User-Passwörter pro Nutzer.
|
||||
- Dein Backend (später) kann Tokens ausstellen; bis dahin kannst du lokal/ops-seitig Tokens generieren.
|
||||
|
||||
## Verfahren (kompatibel zu gängigen WebRTC-Stacks)
|
||||
|
||||
**Username**: `<expiry_unix_seconds>` oder `<expiry_unix_seconds>:<opaque_user_id>`
|
||||
|
||||
- Beispiel: `1735412345:alice`
|
||||
- `expiry_unix_seconds` ist ein Unix-Timestamp in Sekunden.
|
||||
|
||||
**Credential (Password)**:
|
||||
|
||||
$$\n\text{credential} = \text{base64}(\text{HMAC-SHA1}(\text{secret}, \text{username}))\n$$
|
||||
|
||||
Dieses `credential` wird dann ganz normal als TURN-Passwort im ICE-Server-Config verwendet.
|
||||
|
||||
## Server-Seite (`niom-turn`)
|
||||
|
||||
Konfiguration in [appsettings.example.json](../appsettings.example.json):
|
||||
|
||||
- `auth.rest_secret`: Shared Secret (muss geheim bleiben)
|
||||
- `auth.rest_max_ttl_seconds`: Maximal akzeptiertes Zeitfenster in die Zukunft. Wenn ein Token zu weit in der Zukunft liegt, wird es abgewiesen (Sicherheitsmaßnahme).
|
||||
|
||||
Wichtig:
|
||||
- Der Server akzeptiert TURN REST Credentials als Fallback **nur wenn** der Username nicht im CredentialStore gefunden wird.
|
||||
- Ablauf/TTL:
|
||||
- `expiry` muss >= `now` sein
|
||||
- und `expiry - now <= rest_max_ttl_seconds`
|
||||
|
||||
## Lokal Tokens generieren (ohne Backend)
|
||||
|
||||
Das Repo enthält ein kleines CLI:
|
||||
|
||||
- Binary: [src/bin/turn_rest_cred.rs](../src/bin/turn_rest_cred.rs)
|
||||
|
||||
Beispiele:
|
||||
|
||||
1) Einfach (stdout als ENV-Zeilen)
|
||||
|
||||
`cargo run --bin turn_rest_cred -- --secret "SUPER_SECRET" --user alice --ttl 600`
|
||||
|
||||
2) JSON Ausgabe
|
||||
|
||||
`cargo run --bin turn_rest_cred -- --secret "SUPER_SECRET" --user alice --ttl 600 --json`
|
||||
|
||||
Du erhältst:
|
||||
- `username` → in WebRTC als `iceServers[].username`
|
||||
- `credential` → in WebRTC als `iceServers[].credential`
|
||||
|
||||
## WebRTC Beispiel (Frontend)
|
||||
|
||||
Du setzt in deiner App typischerweise:
|
||||
|
||||
- URLs (mindestens UDP + TLS):
|
||||
- `turn:your-domain:3478?transport=udp`
|
||||
- `turn:your-domain:3478?transport=tcp`
|
||||
- `turns:your-domain:5349?transport=tcp`
|
||||
|
||||
und dann:
|
||||
- `username`: vom Generator/Backend
|
||||
- `credential`: vom Generator/Backend
|
||||
|
||||
## Betriebshinweise
|
||||
|
||||
- **Secret Rotation**: Plane Rotation (z.B. zwei Secrets parallel) – sonst brechen Tokens im Umlauf.
|
||||
- **TTL klein halten**: 5–10 Minuten ist typisch.
|
||||
- **Logs**: Niemals `secret` oder vollständige Credentials loggen.
|
||||
- **Rate Limits/Quotas**: Unbedingt ergänzen (Open-Relay/Abuse vermeiden).
|
||||
Loading…
x
Reference in New Issue
Block a user