4.6 KiB
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:
- UDP (schnell, bevorzugt)
- TURN über TCP
- 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
- TCP Listener: src/tcp.rs
- TLS Listener: src/tls.rs
- Allocation + Relay: src/alloc.rs
Schlüsselidee: ClientSink
Damit der Relay-Loop Peer-Pakete an unterschiedliche Client-Transporte schicken kann, gibt es in src/alloc.rs eine Abstraktion:
ClientSink::Udp { sock, addr }→ sendet Peer-Daten perudp.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):
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-Bits01) - 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).
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)
- Client verbindet sich per TCP oder TLS.
- 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
- Peer sendet UDP an die Relay-Adresse.
- Relay-Loop leitet die Bytes an den
ClientSinkweiter:- bei Stream:
tx.send(bytes)→ Writer-Task schreibt Data-Indication oder ChannelData zurück auf denselben Stream
- bei 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.