Skip to content

Commit 9530fe0

Browse files
authored
Bugfixes and config improvements (#15)
1 parent 5c93b51 commit 9530fe0

File tree

9 files changed

+107
-93
lines changed

9 files changed

+107
-93
lines changed

.github/workflows/build_deploy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
env:
5555
GF_SECURITY_ADMIN_PASSWORD: ${{ secrets.GF_SECURITY_ADMIN_PASSWORD }}
5656
GF_SECURITY_ADMIN_USER: ${{ secrets.GF_SECURITY_ADMIN_USER }}
57-
DOMAIN: ${{ secrets.DOMAIN }}
57+
REALM: ${{ secrets.REALM }}
5858
DIR_NAME: ${{ secrets.DIR_NAME }}
5959
TAG: ${{ github.ref_name }}
6060
with:
@@ -65,7 +65,7 @@ jobs:
6565
rm -rf $DIR_NAME; mkdir $DIR_NAME
6666
cd $DIR_NAME
6767
git clone -b $TAG --depth 1 https://github.com/${{ github.repository }} .
68-
echo "DOMAIN=$DOMAIN
68+
echo "REALM=$REALM
6969
GF_SECURITY_ADMIN_PASSWORD=$GF_SECURITY_ADMIN_PASSWORD
7070
GF_SECURITY_ADMIN_USER=$GF_SECURITY_ADMIN_USER
7171
TAG=${TAG#v}" > .env

README.md

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
TURN server in pure Elixir.
88

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

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

46-
## Installation
47+
## Installation and running
4748

4849
1. From source
4950

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

5960
```console
60-
docker run ghcr.io/webrtc-elixir/rel:latest
61+
docker run --network=host ghcr.io/elixir-webrtc/rel:latest
6162
```
6263

6364
## Features and configuration
6465

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

67-
### TURN server
68-
69-
Rel by default listens on `0.0.0.0:3478/UDP` for TURN traffic. This can be configured via `LISTEN_IP` and `LISTEN_PORT`.
70-
71-
```console
72-
LISTEN_IP=0.0.0.0
73-
LISTEN_PORT=3478
74-
```
75-
76-
`EXTERNAL_LISTEN_IP` is the IP address at which Rel is visible to clients. By default, Rel will try to guess the address
77-
based on active network interfaces, but this must be set explicitly when e.g. using Docker without `--network host`.
78-
79-
```console
80-
EXTERNAL_LISTEN_IP=167.235.241.140
81-
```
82-
83-
By default, Rel will use the same addresses (`RELAY_IP == LISTEN_IP and EXTERNAL_RELAY_IP == EXTERNAL_LISTEN_IP`) to open allocations, but this
84-
can be set to something else:
85-
86-
```console
87-
RELAY_IP=0.0.0.0
88-
EXTERNAL_RELAY_IP=167.235.241.140
89-
```
90-
91-
Remember to use the `DOMAIN` variable specific to your deployment. It's used in e.g. `REALM` STUN attributes.
92-
93-
```console
94-
DOMAIN=my-amazing-turn.com
95-
```
96-
97-
### Auth
98-
99-
Auth Provider is an HTTP endpoint that provides credentials required by *A REST API For Access To TURN Services*.
100-
By default it is available at `http://127.0.0.1:4000/`, but the address, encryption and CORS can be configured:
101-
102-
```console
103-
AUTH_PROVIDER_IP=127.0.0.1
104-
AUTH_PROVIDER_PORT=4000
105-
AUTH_PROVIDER_USE_TLS=false
106-
KEY_FILE_PAHT=./rel.key
107-
CERT_FILE_PATH./rel.cert
108-
AUTH_PROVIDER_ALLOW_CORS=false
109-
```
110-
111-
### Metrics
112-
113-
By default, Rel provides Prometheus metrics at `http://127.0.0.1:9578/metrics`. The address can be configured:
114-
115-
```console
116-
METRICS_IP=127.0.0.1
117-
METRICS_PORT=9568
118-
```
68+
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).
69+
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`.
70+
Key query parameter currently is not supported.
11971

72+
Rel is configured via environment variables. All of the possible options are described in [sample env file](./sample.env).

config/runtime.exs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule ConfigUtils do
2020

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

2626
_other ->
@@ -77,11 +77,11 @@ defmodule ConfigUtils do
7777
end
7878

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

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

@@ -101,11 +101,17 @@ relay_ip =
101101
end
102102

103103
external_relay_ip =
104-
case System.fetch_env("EXTERNAL_LISTEN_IP") do
104+
case System.fetch_env("EXTERNAL_RELAY_IP") do
105105
{:ok, addr} -> ConfigUtils.parse_ip_address(addr)
106106
:error -> external_listen_ip
107107
end
108108

109+
relay_port_start = System.get_env("RELAY_PORT_START", "49152") |> ConfigUtils.parse_port()
110+
relay_port_end = System.get_env("RELAY_PORT_END", "65535") |> ConfigUtils.parse_port()
111+
112+
if relay_port_start > relay_port_end,
113+
do: raise("RELAY_PORT_END must be greater or equal to RELAY_PORT_END")
114+
109115
listener_count =
110116
case System.fetch_env("LISTENER_COUNT") do
111117
{:ok, count} ->
@@ -119,23 +125,23 @@ listener_count =
119125

120126
# AuthProvider/credentials configuration
121127
config :rel,
122-
auth_provider_ip:
123-
System.get_env("AUTH_PROVIDER_IP", "127.0.0.1") |> ConfigUtils.parse_ip_address(),
124-
auth_provider_port: System.get_env("AUTH_PROVIDER_PORT", "4000") |> ConfigUtils.parse_port(),
125-
auth_provider_allow_cors?:
126-
System.get_env("AUTH_PROVIDER_ALLOW_CORS", "false") |> ConfigUtils.is_truthy?(),
127-
auth_provider_use_tls?: use_tls?,
128-
keyfile: keyfile,
129-
certfile: certfile
128+
auth_ip: System.get_env("AUTH_IP", "127.0.0.1") |> ConfigUtils.parse_ip_address(),
129+
auth_port: System.get_env("AUTH_PORT", "4000") |> ConfigUtils.parse_port(),
130+
auth_allow_cors?: System.get_env("AUTH_ALLOW_CORS", "false") |> ConfigUtils.is_truthy?(),
131+
auth_use_tls?: auth_use_tls?,
132+
auth_keyfile: auth_keyfile,
133+
auth_certfile: auth_certfile
130134

131135
# TURN server configuration
132136
config :rel,
133137
listen_ip: listen_ip,
134138
external_listen_ip: external_listen_ip,
135139
relay_ip: relay_ip,
136140
external_relay_ip: external_relay_ip,
137-
listen_port: System.get_env("UDP_LISTEN_PORT", "3478") |> ConfigUtils.parse_port(),
138-
domain: System.get_env("DOMAIN", "example.com")
141+
listen_port: System.get_env("LISTEN_PORT", "3478") |> ConfigUtils.parse_port(),
142+
realm: System.get_env("REALM", "example.com"),
143+
relay_port_start: relay_port_start,
144+
relay_port_end: relay_port_end
139145

140146
# Metrics endpoint configuration
141147
config :rel,

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
restart: on-failure
77
network_mode: host
88
environment:
9-
DOMAIN: "${DOMAIN}"
9+
REALM: "${REALM}"
1010

1111
node-exporter:
1212
image: prom/node-exporter:v1.6.1

lib/rel/auth_provider.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Rel.AuthProvider do
88
def init(_opts), do: []
99

1010
def call(conn, _opts) do
11-
allow? = Application.fetch_env!(:rel, :auth_provider_allow_cors?)
11+
allow? = Application.fetch_env!(:rel, :auth_allow_cors?)
1212

1313
if allow? do
1414
CORSPlug.call(conn, CORSPlug.init([]))

lib/rel/listener.ex

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ defmodule Rel.Listener do
2121
alias ExSTUN.Message.Type
2222
alias ExSTUN.Message.Attribute.{Username, XORMappedAddress}
2323

24-
@buf_size 2 * 1024
25-
@default_alloc_ports MapSet.new(49_152..65_535)
24+
@buf_size 2 * 1024 * 1024
2625

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

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

325-
available_alloc_ports = MapSet.difference(@default_alloc_ports, used_alloc_ports)
324+
relay_port_start = Application.fetch_env!(:rel, :relay_port_start)
325+
relay_port_end = Application.fetch_env!(:rel, :relay_port_end)
326+
default_alloc_ports = MapSet.new(relay_port_start..relay_port_end)
327+
available_alloc_ports = MapSet.difference(default_alloc_ports, used_alloc_ports)
326328

327329
if MapSet.size(available_alloc_ports) == 0 do
328330
{:error, :out_of_ports}

lib/rel/utils.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ defmodule Rel.Utils do
3232
@spec build_error(atom(), integer(), Method.t()) ::
3333
{response :: binary(), log_msg :: String.t()}
3434
def build_error(reason, t_id, method) do
35-
domain = Application.fetch_env!(:rel, :domain)
35+
realm = Application.fetch_env!(:rel, :realm)
3636
{log_msg, code, with_attrs?} = translate_error(reason)
3737
error_type = %Type{class: :error_response, method: method}
3838

3939
attrs = [%ErrorCode{code: code}]
4040

4141
attrs =
4242
if with_attrs? do
43-
attrs ++ [%Nonce{value: build_nonce()}, %Realm{value: domain}]
43+
attrs ++ [%Nonce{value: build_nonce()}, %Realm{value: realm}]
4444
else
4545
attrs
4646
end

lib/rel_app.ex

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ defmodule Rel.App do
1010
def start(_, _) do
1111
Logger.info("Starting Rel v#{@version}")
1212

13-
auth_ip = Application.fetch_env!(:rel, :auth_provider_ip)
14-
auth_port = Application.fetch_env!(:rel, :auth_provider_port)
15-
use_tls? = Application.fetch_env!(:rel, :auth_provider_use_tls?)
16-
keyfile = Application.fetch_env!(:rel, :keyfile)
17-
certfile = Application.fetch_env!(:rel, :certfile)
13+
auth_ip = Application.fetch_env!(:rel, :auth_ip)
14+
auth_port = Application.fetch_env!(:rel, :auth_port)
15+
auth_use_tls? = Application.fetch_env!(:rel, :auth_use_tls?)
16+
auth_keyfile = Application.fetch_env!(:rel, :auth_keyfile)
17+
auth_certfile = Application.fetch_env!(:rel, :auth_certfile)
1818

1919
auth_opts =
20-
if use_tls? do
20+
if auth_use_tls? do
2121
[
2222
scheme: :https,
23-
certfile: certfile,
24-
keyfile: keyfile
23+
certfile: auth_certfile,
24+
keyfile: auth_keyfile
2525
]
2626
else
2727
[scheme: :http]
@@ -33,7 +33,7 @@ defmodule Rel.App do
3333

3434
metrics_ip = Application.fetch_env!(:rel, :metrics_ip)
3535
metrics_port = Application.fetch_env!(:rel, :metrics_port)
36-
metrics_opts = [metrics: metrics(), plug_cowboy_opts: [ip: metrics_ip, port: metrics_port]]
36+
metrics_opts = [metrics: metrics(), port: metrics_port, plug_cowboy_opts: [ip: metrics_ip]]
3737

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

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

sample.env

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Rel config env variables
2+
3+
# Values presented in this example file are used by default
4+
# except where explicitly specified otherwise
5+
6+
## TURN
7+
8+
# Server address and port on which Rel listens for TURN/STUN requests
9+
LISTEN_IP=0.0.0.0
10+
LISTEN_PORT=3478
11+
12+
# Server address as seen from the client
13+
# By default it is equal to LISTEN_PORT or (if LISTEN_PORT == 0.0.0.0) Rel
14+
# will try to guess the address based on host's network interfaces
15+
# It must be explicitly set when e.g. running in Docker without `--network=host`
16+
# EXTERNAL_LISTEN_IP=167.235.241.140
17+
18+
# Address and port range where relay address will be allocated
19+
RELAY_IP=0.0.0.0
20+
RELAY_PORT_START=49152
21+
RELAY_PORT_END=65535
22+
23+
# Relay address as seen from peers
24+
# Behave the same way as EXTERNAL_LISTEN_IP
25+
# EXTERNAL_RELAY_IP=167.235.241.140
26+
27+
# Values used in REALM STUN attribute, see https://datatracker.ietf.org/doc/html/rfc5389#section-15.7
28+
REALM=example.com
29+
30+
# Number of running listener processes. By default equal to number of running Erlang VM schedulers
31+
# LISTENER_COUNT=8
32+
33+
## AUTH PROVIDER
34+
35+
# Auth provider is available under http(s)://$AUTH_IP:$AUTH_PORT/
36+
AUTH_IP=127.0.0.1
37+
AUTH_PORT=4000
38+
39+
# whether to use HTTP or HTTPS
40+
# If true, AUTH_KEYFILE and AUTH_CERFILE must be explicitly set
41+
AUTH_USE_TLS=false
42+
# AUTH_KEYFILE=./rel.key
43+
# AUTH_CERTFILE=./rel.cert
44+
45+
# Whether to allos Cross-Origin Resource Sharing
46+
# May be useful when requesting credentials via JavaScript in the browser
47+
AUTH_ALLOW_CORS=false
48+
49+
## METRICS
50+
51+
# Prometheus metrics are served on http://$METRICS_IP:$METRICS_PORT/metrics
52+
METRICS_IP=127.0.0.1
53+
METRICS_PORT=9568

0 commit comments

Comments
 (0)