Client for TURN: allocate a relay address and send/receive through it.
What TURN solves
When direct UDP (and sometimes TCP) paths between two peers fail -- strict NATs, symmetric NAT combinations, corporate firewalls -- TURN (Traversal Using Relays around NAT) provides a relay on a publicly reachable server. Your peer sends to the relay; the relay forwards to the far side.
This module speaks TURN over STUN (XMediaLib.Stun) without depending on the
xturn server application. It covers Allocate / Refresh / Delete,
CreatePermission, ChannelBind, Send Indication, and ChannelData -- enough
for XGW to gather relay candidates and keep allocations alive.
RFCs
- RFC 8656 -- TURN (Allocate, Refresh, CreatePermission, ChannelBind, indications, ChannelData)
- RFC 8489 -- STUN long-term credentials and MESSAGE-INTEGRITY
- RFC 5766 -- ChannelData layout still used on the wire (channel number range)
Summary
Types
Active TURN allocation state for refresh and teardown.
Functions
Performs TURN Allocate with optional 401 challenge handling.
Unauthenticated TURN Allocate request wire.
Binds TURN channel channel (0x4000..0x7FFF) to peer_address on the allocation.
Builds a ChannelBind request binding channel to peer_address.
Creates a TURN permission toward peer_address ({ip, port}).
Builds a CreatePermission request for peer_address on allocation.
Decodes an RFC 5766 ChannelData message.
Deletes a TURN allocation (Refresh with lifetime 0).
Encodes an RFC 5766 ChannelData message (4-byte header + application data + pad).
Long-term credential MESSAGE-INTEGRITY key (MD5 of username:realm:password).
Parses host[:port] from a STUN/TURN URI body (after the scheme prefix).
Parses a TURN server URI (turn:host:port) to {ip, port}.
Sends TURN Refresh to extend or delete an allocation.
REST shared-secret credentials (expiry:user_id + Base64 HMAC-SHA1 password).
Sends a Send Indication with data toward peer_address through the allocation.
Types
@type allocation() :: %{ turn_server: {:inet.ip_address(), :inet.port_number()}, relay: {:inet.ip_address(), :inet.port_number()}, lifetime_s: pos_integer(), username: String.t(), realm: String.t(), nonce: String.t(), integrity_key: binary() }
Active TURN allocation state for refresh and teardown.
Fields
:turn_server- TURN server{ip, port}used for Allocate:relay- allocated relay{ip, port}advertised as a local candidate:lifetime_s- allocation lifetime from the success response:username/:realm/:nonce- long-term credential challenge state:integrity_key- MESSAGE-INTEGRITY key for subsequent TURN requests
Functions
@spec allocate( (binary(), {:inet.ip_address(), :inet.port_number()} -> {:ok, binary()} | {:error, term()}), {:inet.ip_address(), :inet.port_number()}, keyword() ) :: {:ok, allocation()} | {:error, term()}
Performs TURN Allocate with optional 401 challenge handling.
cred_opts keyword: :turn_username, :turn_password, :turn_realm,
:turn_secret, :turn_user_id, :turn_ttl_s.
Unauthenticated TURN Allocate request wire.
@spec channel_bind( (binary(), {:inet.ip_address(), :inet.port_number()} -> {:ok, binary()} | {:error, term()}), allocation(), {:inet.ip_address(), :inet.port_number()}, 16384..32767 ) :: :ok | {:error, term()}
Binds TURN channel channel (0x4000..0x7FFF) to peer_address on the allocation.
@spec channel_bind_request( allocation(), {:inet.ip_address(), :inet.port_number()}, 16384..32767 ) :: binary()
Builds a ChannelBind request binding channel to peer_address.
@spec create_permission( (binary(), {:inet.ip_address(), :inet.port_number()} -> {:ok, binary()} | {:error, term()}), allocation(), {:inet.ip_address(), :inet.port_number()} ) :: :ok | {:error, term()}
Creates a TURN permission toward peer_address ({ip, port}).
@spec create_permission_request( allocation(), {:inet.ip_address(), :inet.port_number()} ) :: binary()
Builds a CreatePermission request for peer_address on allocation.
Decodes an RFC 5766 ChannelData message.
Returns {:ok, channel, app} or :error.
Examples
iex> XGW.TURN.Client.decode_channel_data(<<0x40, 0x00, 0, 2, "hi", 0, 0>>)
{:ok, 0x4000, "hi"}
iex> XGW.TURN.Client.decode_channel_data(<<1, 2, 3>>)
:error
@spec delete( (binary(), {:inet.ip_address(), :inet.port_number()} -> {:ok, binary()} | {:error, term()}), allocation() ) :: :ok | {:error, term()}
Deletes a TURN allocation (Refresh with lifetime 0).
Encodes an RFC 5766 ChannelData message (4-byte header + application data + pad).
Examples
iex> bin = XGW.TURN.Client.encode_channel_data(0x4000, "hi")
iex> {:ok, 0x4000, "hi"} = XGW.TURN.Client.decode_channel_data(bin)
iex> true
true
Long-term credential MESSAGE-INTEGRITY key (MD5 of username:realm:password).
Examples
iex> key = XGW.TURN.Client.long_term_key("user", "example.com", "secret")
iex> key == :crypto.hash(:md5, "user:example.com:secret")
true
@spec parse_server_host(String.t(), term()) :: {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, term()}
Parses host[:port] from a STUN/TURN URI body (after the scheme prefix).
@spec parse_turn_server(String.t()) :: {:ok, {:inet.ip_address(), :inet.port_number()}} | {:error, term()}
Parses a TURN server URI (turn:host:port) to {ip, port}.
Examples
iex> XGW.TURN.Client.parse_turn_server("turn:127.0.0.1:3478")
{:ok, {{127, 0, 0, 1}, 3478}}
iex> XGW.TURN.Client.parse_turn_server("turns:127.0.0.1:5349")
{:error, :unsupported_turns}
@spec refresh( (binary(), {:inet.ip_address(), :inet.port_number()} -> {:ok, binary()} | {:error, term()}), allocation(), non_neg_integer() ) :: {:ok, allocation()} | {:error, term()}
Sends TURN Refresh to extend or delete an allocation.
@spec rest_credentials(String.t(), binary(), pos_integer()) :: {String.t(), String.t()}
REST shared-secret credentials (expiry:user_id + Base64 HMAC-SHA1 password).
Time-bound username form used by many hosted TURN providers. The password is
not stored long-term; it is derived from secret and the username string.
Examples
iex> {user, pass} = XGW.TURN.Client.rest_credentials("alice", "s3cret", 3600)
iex> String.contains?(user, ":alice") and byte_size(pass) > 0
true
@spec send_indication( allocation(), {:inet.ip_address(), :inet.port_number()}, binary() ) :: binary()
Sends a Send Indication with data toward peer_address through the allocation.