99 lines
4.4 KiB
Markdown
99 lines
4.4 KiB
Markdown
# 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.
|