Skip to content

Commit

Permalink
Bugfixes and config improvements (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
LVala authored Aug 22, 2023
1 parent 5c93b51 commit 9530fe0
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 93 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
env:
GF_SECURITY_ADMIN_PASSWORD: ${{ secrets.GF_SECURITY_ADMIN_PASSWORD }}
GF_SECURITY_ADMIN_USER: ${{ secrets.GF_SECURITY_ADMIN_USER }}
DOMAIN: ${{ secrets.DOMAIN }}
REALM: ${{ secrets.REALM }}
DIR_NAME: ${{ secrets.DIR_NAME }}
TAG: ${{ github.ref_name }}
with:
Expand All @@ -65,7 +65,7 @@ jobs:
rm -rf $DIR_NAME; mkdir $DIR_NAME
cd $DIR_NAME
git clone -b $TAG --depth 1 https://github.com/${{ github.repository }} .
echo "DOMAIN=$DOMAIN
echo "REALM=$REALM
GF_SECURITY_ADMIN_PASSWORD=$GF_SECURITY_ADMIN_PASSWORD
GF_SECURITY_ADMIN_USER=$GF_SECURITY_ADMIN_USER
TAG=${TAG#v}" > .env
Expand Down
67 changes: 10 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
TURN server in pure Elixir.

Aims to implement:
- [RFC 5766](https://datatracker.ietf.org/doc/html/rfc5766)
- [RFC 6156](https://datatracker.ietf.org/doc/html/rfc6156#autoid-7)
- RFC 5389: [Session Traversal Utilities for NAT (STUN)](https://datatracker.ietf.org/doc/html/rfc5389)
- RFC 5766: [Traversal Using Relays around NAT (TURN): Relay Extensions to Session Traversal Utilities for NAT (STUN)](https://datatracker.ietf.org/doc/html/rfc5766)
- RFC 6156: [Traversal Using Relays around NAT (TURN) Extension for IPv6](https://datatracker.ietf.org/doc/html/rfc6156#autoid-7)

This project is in early stage of development and some of the features described in the RFCs might be missing.
Expect breaking changes.
Expand Down Expand Up @@ -43,7 +44,7 @@ pc = new RTCPeerConnection({
});
```

## Installation
## Installation and running

1. From source

Expand All @@ -57,63 +58,15 @@ mix run --no-halt
2. In Docker

```console
docker run ghcr.io/webrtc-elixir/rel:latest
docker run --network=host ghcr.io/elixir-webrtc/rel:latest
```

## Features and configuration

Currently, Rel is configured via environment variables.
Rel exposes Prometheus metrics endpoint (by default `http://127.0.0.1:9568/metrics`).

### TURN server

Rel by default listens on `0.0.0.0:3478/UDP` for TURN traffic. This can be configured via `LISTEN_IP` and `LISTEN_PORT`.

```console
LISTEN_IP=0.0.0.0
LISTEN_PORT=3478
```

`EXTERNAL_LISTEN_IP` is the IP address at which Rel is visible to clients. By default, Rel will try to guess the address
based on active network interfaces, but this must be set explicitly when e.g. using Docker without `--network host`.

```console
EXTERNAL_LISTEN_IP=167.235.241.140
```

By default, Rel will use the same addresses (`RELAY_IP == LISTEN_IP and EXTERNAL_RELAY_IP == EXTERNAL_LISTEN_IP`) to open allocations, but this
can be set to something else:

```console
RELAY_IP=0.0.0.0
EXTERNAL_RELAY_IP=167.235.241.140
```

Remember to use the `DOMAIN` variable specific to your deployment. It's used in e.g. `REALM` STUN attributes.

```console
DOMAIN=my-amazing-turn.com
```

### Auth

Auth Provider is an HTTP endpoint that provides credentials required by *A REST API For Access To TURN Services*.
By default it is available at `http://127.0.0.1:4000/`, but the address, encryption and CORS can be configured:

```console
AUTH_PROVIDER_IP=127.0.0.1
AUTH_PROVIDER_PORT=4000
AUTH_PROVIDER_USE_TLS=false
KEY_FILE_PAHT=./rel.key
CERT_FILE_PATH./rel.cert
AUTH_PROVIDER_ALLOW_CORS=false
```

### Metrics

By default, Rel provides Prometheus metrics at `http://127.0.0.1:9578/metrics`. The address can be configured:

```console
METRICS_IP=127.0.0.1
METRICS_PORT=9568
```
Rel supports authentication described in [A REST API For Access To TURN Services](https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-turn-rest-00#section-2.2).
By default available under `http://127.0.0.1:4000/`. Example request would be `POST http://127.0.0.1:40000/?service=turn&username=johnsmith`.
Key query parameter currently is not supported.

Rel is configured via environment variables. All of the possible options are described in [sample env file](./sample.env).
38 changes: 22 additions & 16 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule ConfigUtils do

def parse_port(port) do
case Integer.parse(port, 10) do
{val, _rem} when val in 0..49_151 ->
{val, _rem} when val in 0..65_535 ->
val

_other ->
Expand Down Expand Up @@ -77,11 +77,11 @@ defmodule ConfigUtils do
end

# HTTPS for AuthProvider
use_tls? = System.get_env("AUTH_PROVIDER_USE_TLS", "false") |> ConfigUtils.is_truthy?()
keyfile = System.get_env("KEY_FILE_PATH")
certfile = System.get_env("CERT_FILE_PATH")
auth_use_tls? = System.get_env("AUTH_USE_TLS", "false") |> ConfigUtils.is_truthy?()
auth_keyfile = System.get_env("AUTH_KEYFILE")
auth_certfile = System.get_env("AUTH_CERTFILE")

if use_tls? and (is_nil(keyfile) or is_nil(certfile)) do
if auth_use_tls? and (is_nil(auth_keyfile) or is_nil(auth_certfile)) do
raise "Both KEY_FILE_PATH and CERT_FILE_PATH must be set is TLS is used"
end

Expand All @@ -101,11 +101,17 @@ relay_ip =
end

external_relay_ip =
case System.fetch_env("EXTERNAL_LISTEN_IP") do
case System.fetch_env("EXTERNAL_RELAY_IP") do
{:ok, addr} -> ConfigUtils.parse_ip_address(addr)
:error -> external_listen_ip
end

relay_port_start = System.get_env("RELAY_PORT_START", "49152") |> ConfigUtils.parse_port()
relay_port_end = System.get_env("RELAY_PORT_END", "65535") |> ConfigUtils.parse_port()

if relay_port_start > relay_port_end,
do: raise("RELAY_PORT_END must be greater or equal to RELAY_PORT_END")

listener_count =
case System.fetch_env("LISTENER_COUNT") do
{:ok, count} ->
Expand All @@ -119,23 +125,23 @@ listener_count =

# AuthProvider/credentials configuration
config :rel,
auth_provider_ip:
System.get_env("AUTH_PROVIDER_IP", "127.0.0.1") |> ConfigUtils.parse_ip_address(),
auth_provider_port: System.get_env("AUTH_PROVIDER_PORT", "4000") |> ConfigUtils.parse_port(),
auth_provider_allow_cors?:
System.get_env("AUTH_PROVIDER_ALLOW_CORS", "false") |> ConfigUtils.is_truthy?(),
auth_provider_use_tls?: use_tls?,
keyfile: keyfile,
certfile: certfile
auth_ip: System.get_env("AUTH_IP", "127.0.0.1") |> ConfigUtils.parse_ip_address(),
auth_port: System.get_env("AUTH_PORT", "4000") |> ConfigUtils.parse_port(),
auth_allow_cors?: System.get_env("AUTH_ALLOW_CORS", "false") |> ConfigUtils.is_truthy?(),
auth_use_tls?: auth_use_tls?,
auth_keyfile: auth_keyfile,
auth_certfile: auth_certfile

# TURN server configuration
config :rel,
listen_ip: listen_ip,
external_listen_ip: external_listen_ip,
relay_ip: relay_ip,
external_relay_ip: external_relay_ip,
listen_port: System.get_env("UDP_LISTEN_PORT", "3478") |> ConfigUtils.parse_port(),
domain: System.get_env("DOMAIN", "example.com")
listen_port: System.get_env("LISTEN_PORT", "3478") |> ConfigUtils.parse_port(),
realm: System.get_env("REALM", "example.com"),
relay_port_start: relay_port_start,
relay_port_end: relay_port_end

# Metrics endpoint configuration
config :rel,
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
restart: on-failure
network_mode: host
environment:
DOMAIN: "${DOMAIN}"
REALM: "${REALM}"

node-exporter:
image: prom/node-exporter:v1.6.1
Expand Down
2 changes: 1 addition & 1 deletion lib/rel/auth_provider.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule Rel.AuthProvider do
def init(_opts), do: []

def call(conn, _opts) do
allow? = Application.fetch_env!(:rel, :auth_provider_allow_cors?)
allow? = Application.fetch_env!(:rel, :auth_allow_cors?)

if allow? do
CORSPlug.call(conn, CORSPlug.init([]))
Expand Down
10 changes: 6 additions & 4 deletions lib/rel/listener.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ defmodule Rel.Listener do
alias ExSTUN.Message.Type
alias ExSTUN.Message.Attribute.{Username, XORMappedAddress}

@buf_size 2 * 1024
@default_alloc_ports MapSet.new(49_152..65_535)
@buf_size 2 * 1024 * 1024

@spec start_link(term()) :: {:ok, pid()}
def start_link(args) do
Expand Down Expand Up @@ -198,7 +197,7 @@ defmodule Rel.Listener do
# origin_alloc_state. In most cases, this shouldn't be a problem as
# client is encouraged to refresh its allocation one minute
# before its deadline
:ok = :gen_udp.send(socket, c_ip, c_port, origin_response)
:ok = :socket.sendto(socket, origin_response, %{family: :inet, addr: c_ip, port: c_port})

{:error, :allocation_exists, _alloc_origin_state} ->
handle_error.(:allocation_exists, socket, c_ip, c_port, msg)
Expand Down Expand Up @@ -322,7 +321,10 @@ defmodule Rel.Listener do
|> Enum.map(fn alloc_origin_state -> Map.fetch!(alloc_origin_state, :alloc_port) end)
|> MapSet.new()

available_alloc_ports = MapSet.difference(@default_alloc_ports, used_alloc_ports)
relay_port_start = Application.fetch_env!(:rel, :relay_port_start)
relay_port_end = Application.fetch_env!(:rel, :relay_port_end)
default_alloc_ports = MapSet.new(relay_port_start..relay_port_end)
available_alloc_ports = MapSet.difference(default_alloc_ports, used_alloc_ports)

if MapSet.size(available_alloc_ports) == 0 do
{:error, :out_of_ports}
Expand Down
4 changes: 2 additions & 2 deletions lib/rel/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ defmodule Rel.Utils do
@spec build_error(atom(), integer(), Method.t()) ::
{response :: binary(), log_msg :: String.t()}
def build_error(reason, t_id, method) do
domain = Application.fetch_env!(:rel, :domain)
realm = Application.fetch_env!(:rel, :realm)
{log_msg, code, with_attrs?} = translate_error(reason)
error_type = %Type{class: :error_response, method: method}

attrs = [%ErrorCode{code: code}]

attrs =
if with_attrs? do
attrs ++ [%Nonce{value: build_nonce()}, %Realm{value: domain}]
attrs ++ [%Nonce{value: build_nonce()}, %Realm{value: realm}]
else
attrs
end
Expand Down
20 changes: 10 additions & 10 deletions lib/rel_app.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ defmodule Rel.App do
def start(_, _) do
Logger.info("Starting Rel v#{@version}")

auth_ip = Application.fetch_env!(:rel, :auth_provider_ip)
auth_port = Application.fetch_env!(:rel, :auth_provider_port)
use_tls? = Application.fetch_env!(:rel, :auth_provider_use_tls?)
keyfile = Application.fetch_env!(:rel, :keyfile)
certfile = Application.fetch_env!(:rel, :certfile)
auth_ip = Application.fetch_env!(:rel, :auth_ip)
auth_port = Application.fetch_env!(:rel, :auth_port)
auth_use_tls? = Application.fetch_env!(:rel, :auth_use_tls?)
auth_keyfile = Application.fetch_env!(:rel, :auth_keyfile)
auth_certfile = Application.fetch_env!(:rel, :auth_certfile)

auth_opts =
if use_tls? do
if auth_use_tls? do
[
scheme: :https,
certfile: certfile,
keyfile: keyfile
certfile: auth_certfile,
keyfile: auth_keyfile
]
else
[scheme: :http]
Expand All @@ -33,7 +33,7 @@ defmodule Rel.App do

metrics_ip = Application.fetch_env!(:rel, :metrics_ip)
metrics_port = Application.fetch_env!(:rel, :metrics_port)
metrics_opts = [metrics: metrics(), plug_cowboy_opts: [ip: metrics_ip, port: metrics_port]]
metrics_opts = [metrics: metrics(), port: metrics_port, plug_cowboy_opts: [ip: metrics_ip]]

children = [
Rel.ListenerSupervisor,
Expand All @@ -46,7 +46,7 @@ defmodule Rel.App do
metrics_endpoint = "http://#{:inet.ntoa(metrics_ip)}:#{metrics_port}/metrics"
Logger.info("Starting Prometheus metrics endpoint at: #{metrics_endpoint}")

scheme = if(use_tls?, do: "https", else: "http")
scheme = if(auth_use_tls?, do: "https", else: "http")
auth_endpoint = "#{scheme}://#{:inet.ntoa(auth_ip)}:#{auth_port}/"
Logger.info("Starting credentials endpoint at: #{auth_endpoint}")

Expand Down
53 changes: 53 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Rel config env variables

# Values presented in this example file are used by default
# except where explicitly specified otherwise

## TURN

# Server address and port on which Rel listens for TURN/STUN requests
LISTEN_IP=0.0.0.0
LISTEN_PORT=3478

# Server address as seen from the client
# By default it is equal to LISTEN_PORT or (if LISTEN_PORT == 0.0.0.0) Rel
# will try to guess the address based on host's network interfaces
# It must be explicitly set when e.g. running in Docker without `--network=host`
# EXTERNAL_LISTEN_IP=167.235.241.140

# Address and port range where relay address will be allocated
RELAY_IP=0.0.0.0
RELAY_PORT_START=49152
RELAY_PORT_END=65535

# Relay address as seen from peers
# Behave the same way as EXTERNAL_LISTEN_IP
# EXTERNAL_RELAY_IP=167.235.241.140

# Values used in REALM STUN attribute, see https://datatracker.ietf.org/doc/html/rfc5389#section-15.7
REALM=example.com

# Number of running listener processes. By default equal to number of running Erlang VM schedulers
# LISTENER_COUNT=8

## AUTH PROVIDER

# Auth provider is available under http(s)://$AUTH_IP:$AUTH_PORT/
AUTH_IP=127.0.0.1
AUTH_PORT=4000

# whether to use HTTP or HTTPS
# If true, AUTH_KEYFILE and AUTH_CERFILE must be explicitly set
AUTH_USE_TLS=false
# AUTH_KEYFILE=./rel.key
# AUTH_CERTFILE=./rel.cert

# Whether to allos Cross-Origin Resource Sharing
# May be useful when requesting credentials via JavaScript in the browser
AUTH_ALLOW_CORS=false

## METRICS

# Prometheus metrics are served on http://$METRICS_IP:$METRICS_PORT/metrics
METRICS_IP=127.0.0.1
METRICS_PORT=9568

0 comments on commit 9530fe0

Please sign in to comment.