TURN

Overview

This repository contains the TURN relay component of the ERP/1 CHAT monorepo. It packages the eturnal STUN/TURN server as an Erlang/OTP application, wired directly into the RTP signaling plane. When ICE direct-path negotiation fails behind symmetric NATs or enterprise firewalls, TURN allocates a relay port and forwards DTLS-SRTP media between the browser and the GStreamer MCU, maintaining call quality without opening arbitrary firewall holes.

Architecture

TURN operates as a transparent relay between the browser's ICE agent and the GStreamer MCU. All allocation, permission, and channel bindings are handled by eturnal internally. The RTP signaling plane instructs browsers to use the TURN relay via ICE candidates returned during SDP negotiation.

Browser ··► UDP/TCP:3478 STUN/TURN ──► eturnal relay ──► priv/gst (MCU) Browser ··► UDP/TCP:5349 TURNS TLS ──► eturnal relay ──► priv/gst (MCU) rtp_app ──► rtp_token:issue/2 ──► TURN credential (user:pass) ──► browser ICE config

Three roles in the NAT traversal chain:

Directory Blueprint

├── config/ │ └── eturnal.yml eturnal configuration: listen ports, realm, credentials ├── src/ │ └── turn_app.erl OTP Application: starts eturnal under supervision ├── lib/ │ └── turn.ex Elixir shim: Bandit integration for health-check HTTP ├── run/ │ └── run.sh Shell helper: launches the node in embedded mode ├── log/ │ └── eturnal.log Runtime log: STUN/TURN allocation events ├── mix.exs Elixir project and dependency manifest └── rebar.config Rebar3 build configuration

Erlang/OTP Integration

eturnal — RFC 5766 TURN server by ProcessOne, embedded here as an OTP dependency and started under the application supervisor. All TURN authentication uses short-term credentials derived from rtp_token so that credential lifetime matches the WebRTC session lifetime.

rtp_token integration — Each login issues a TURN credential valid for the same configurable TTL (default 180 s, overridable via {rtp, token_ttl, Seconds}). The browser receives the credential in the signaling init message and passes it to RTCPeerConnection as an ICE server entry:

iceServers: [ { urls: "stun:turn.example.com:3478" }, { urls: "turn:turn.example.com:3478", username: "<rtp_token>", credential: "<hmac-sha1-secret>" } ]

Configuration

Edit config/eturnal.yml to adjust listen addresses, realm, and logging:

eturnal: listen: - ip: "0.0.0.0" port: 3478 transport: udp - ip: "0.0.0.0" port: 3478 transport: tcp - ip: "0.0.0.0" port: 5349 transport: tls relay_min_port: 49152 relay_max_port: 65535 secret: "<shared-hmac-secret>" realm: "turn.example.com"

Configuration and Ports

Port Protocol Purpose ────────────────────────────────────────────────────────────────── 3478 UDP/TCP eturnal STUN/TURN — NAT traversal 5349 UDP/TCP eturnal TURNS — STUN/TURN over TLS

ITU-T Standards Alignment

Standard Description System Mapping ──────────────────────────────────────────────────────────────────────────── RFC 5766 TURN protocol eturnal — TURN relay server RFC 5389 STUN protocol eturnal — STUN binding RFC 6062 TCP allocations for TURN eturnal TCP transport RFC 6347 DTLS 1.2 DTLS-SRTP over TURN relay RFC 8656 TURN for IPv6 and long-term creds eturnal relay port range

Getting Started

brew install erlang cd turn mix deps.get iex -S mix
╔════════════════════════════════════════════════════════╗ ║ ERP/1 : TURN Server / STUN Relay ║ ║ STUN : stun://localhost:3478 ║ ║ TURN : turn://localhost:3478 ║ ║ TURNS : turns://localhost:5349 ║ ╚════════════════════════════════════════════════════════╝ Relay ports : 49152–65535 (dynamic allocation) Realm : turn.synrc.com Auth : short-term HMAC-SHA1 (rtp_token)