Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change name to Rel, add proper README #9

Merged
merged 4 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ RUN apk add --no-cache --update libncursesw openssl libstdc++

WORKDIR /app

COPY --from=build /app/_build/prod/rel/ex_turn ./
COPY --from=build /app/_build/prod/rel/rel ./

CMD ["bin/ex_turn", "start"]
CMD ["bin/rel", "start"]
121 changes: 117 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,119 @@
# ExTURN
# Rel

TURN server.
[![CI](https://img.shields.io/github/actions/workflow/status/elixir-webrtc/rel/ci.yml?logo=github&label=CI)](https://github.com/elixir-webrtc/rel/actions/workflows/ci.yml)
[![CI](https://img.shields.io/github/actions/workflow/status/elixir-webrtc/rel/build_deploy.yml?logo=github&label=CI)](https://github.com/elixir-webrtc/rel/actions/workflows/build_deploy.yml)
[![Package](https://ghcr-badge.egpl.dev/elixir-webrtc/rel/latest_tag?trim=major&label=latest)](https://github.com/elixir-webrtc/rel/pkgs/container/rel)

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)

This project is in early stage of development and some of the features described in the RFCs might be missing.
Expect breaking changes.

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).

## Public deployment

If you're in need of TURN server for testing purposes, feel free to use this Rel public deployment at `turn.bigcow.ovh`.

In case of any irregularities or bugs, please open an issue with description of the problem.
DO NOT use this deployment in production, as it's intended to be an aid in developement only.

To obtain a set of credentials, use the built-in credentials mechanism. It does not require any authentication, but the credentials must be refreshed after 3 hours if not used.

```console
$ curl -X POST "https://turn.bigcow.ovh/?service=turn&username=johnsmith"
{"password":"l6hs9SzUgudFeb5XjrfCfOWKeOQ=","ttl":1728,"uris":["turn:167.235.241.140:3478?transport=udp"],"username":"1691574817:johnsmith"}⏎
```

Use the obtained credentials in e.g. WebRTC's `RTCPeerConnection`:

```js
pc = new RTCPeerConnection({
iceServers: [
{
credential: "l6hs9SzUgudFeb5XjrfCfOWKeOQ=",
urls: "turn:167.235.241.140:3478?transport=udp",
username: "1691574817:johnsmith"
}
]
});
```

## Installation

1. From source

```console
git clone https://github.com/elixir-webrtc/rel.git
cd rel
mix deps.get
mix run --no-halt
```

2. In Docker

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

## Features and configuration

Currently, Rel is configured via environment variables.

### 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_NAME` variable specific to your deployment. It's used in e.g. `SOFTWARE` STUN attributes.

```console
DOMAIN_NAME=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
```

Implementation of [RFC 5766](https://datatracker.ietf.org/doc/html/rfc5766) and [RFC 6156](https://datatracker.ietf.org/doc/html/rfc6156#autoid-7).
Supports authentication described in [A REST API For Access to TURN Services](https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-turn-rest-00).
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Config

config :ex_turn,
config :rel,
# 1 day in seconds, see https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-turn-rest-00#section-2.2
credentials_lifetime: 24 * 60 * 60,
# 10 minutes in seconds
Expand Down
2 changes: 1 addition & 1 deletion config/prod.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Config

# FIXME: temporary, as `:credentials_lifetime` is a compile time variable atm
config :ex_turn, :credentials_lifetime, 3 * 24 * 24
config :rel, :credentials_lifetime, 3 * 24 * 24
8 changes: 4 additions & 4 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ external_relay_ip =
end

# AuthProvider/credentials configuration
config :ex_turn,
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(),
Expand All @@ -118,7 +118,7 @@ config :ex_turn,
certfile: certfile

# TURN server configuration
config :ex_turn,
config :rel,
listen_ip: listen_ip,
external_listen_ip: external_listen_ip,
relay_ip: relay_ip,
Expand All @@ -127,11 +127,11 @@ config :ex_turn,
domain_name: System.get_env("DOMAIN_NAME", "example.com")

# Metrics endpoint configuration
config :ex_turn,
config :rel,
metrics_ip: System.get_env("METRICS_IP", "127.0.0.1") |> ConfigUtils.parse_ip_address(),
metrics_port: System.get_env("METRICS_PORT", "9568") |> ConfigUtils.parse_port()

# Automatically generated secrets
config :ex_turn,
config :rel,
auth_secret: :crypto.strong_rand_bytes(64),
nonce_secret: :crypto.strong_rand_bytes(64)
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '3.2'
services:
turn:
image: ghcr.io/elixir-webrtc/ex_turn:${TAG}
image: ghcr.io/elixir-webrtc/rel:${TAG}
container_name: turn
restart: on-failure
network_mode: host
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.AllocationHandler do
defmodule Rel.AllocationHandler do
@moduledoc false
use GenServer, restart: :transient

Expand All @@ -7,15 +7,15 @@ defmodule ExTURN.AllocationHandler do
alias ExSTUN.Message
alias ExSTUN.Message.Type

alias ExTURN.Auth
alias ExTURN.Attribute.{ChannelNumber, Data, Lifetime, XORPeerAddress}
alias ExTURN.Utils
alias Rel.Auth
alias Rel.Attribute.{ChannelNumber, Data, Lifetime, XORPeerAddress}
alias Rel.Utils

@type five_tuple() ::
{:inet.ip_address(), :inet.port_number(), :inet.ip_address(), :inet.port_number(), :udp}

@permission_lifetime Application.compile_env!(:ex_turn, :permission_lifetime)
@channel_lifetime Application.compile_env!(:ex_turn, :channel_lifetime)
@permission_lifetime Application.compile_env!(:rel, :permission_lifetime)
@channel_lifetime Application.compile_env!(:rel, :channel_lifetime)

@spec start_link(term()) :: GenServer.on_start()
def start_link([five_tuple, alloc_socket | _rest] = args) do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.AdditionalAddressFamily do
defmodule Rel.Attribute.AdditionalAddressFamily do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.ChannelNumber do
defmodule Rel.Attribute.ChannelNumber do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.Data do
defmodule Rel.Attribute.Data do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.EvenPort do
defmodule Rel.Attribute.EvenPort do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.Lifetime do
defmodule Rel.Attribute.Lifetime do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.RequestedAddressFamily do
defmodule Rel.Attribute.RequestedAddressFamily do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.RequestedTransport do
defmodule Rel.Attribute.RequestedTransport do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.ReservationToken do
defmodule Rel.Attribute.ReservationToken do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.XORPeerAddress do
defmodule Rel.Attribute.XORPeerAddress do
@moduledoc """
STUN Message Attribute XOR Peer Address

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.Attribute.XORRelayedAddress do
defmodule Rel.Attribute.XORRelayedAddress do
@moduledoc false
@behaviour ExSTUN.Message.Attribute

Expand Down
12 changes: 6 additions & 6 deletions lib/ex_turn/auth.ex → lib/rel/auth.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
defmodule ExTURN.Auth do
defmodule Rel.Auth do
@moduledoc false
require Logger

alias ExSTUN.Message
alias ExSTUN.Message.Attribute.{MessageIntegrity, Nonce, Realm, Username}

@nonce_lifetime Application.compile_env!(:ex_turn, :nonce_lifetime)
@credentials_lifetime Application.compile_env!(:ex_turn, :credentials_lifetime)
@nonce_lifetime Application.compile_env!(:rel, :nonce_lifetime)
@credentials_lifetime Application.compile_env!(:rel, :credentials_lifetime)

@spec authenticate(Message.t(), username: String.t()) :: {:ok, binary()} | {:error, atom()}
def authenticate(%Message{} = msg, opts \\ []) do
auth_secret = Application.fetch_env!(:ex_turn, :auth_secret)
auth_secret = Application.fetch_env!(:rel, :auth_secret)

with :ok <- verify_message_integrity(msg),
{:ok, username, nonce} <- verify_attrs_presence(msg),
Expand Down Expand Up @@ -64,7 +64,7 @@ defmodule ExTURN.Auth do
|> :base64.decode()
|> String.split(" ", parts: 2)

nonce_secret = Application.fetch_env!(:ex_turn, :nonce_secret)
nonce_secret = Application.fetch_env!(:rel, :nonce_secret)

is_hash_valid? = hash == :crypto.hash(:sha256, "#{timestamp}:#{nonce_secret}")

Expand All @@ -77,7 +77,7 @@ defmodule ExTURN.Auth do
@spec generate_credentials(String.t() | nil) ::
{username :: String.t(), password :: String.t(), ttl :: non_neg_integer()}
def generate_credentials(username \\ nil) do
auth_secret = Application.fetch_env!(:ex_turn, :auth_secret)
auth_secret = Application.fetch_env!(:rel, :auth_secret)
timestamp = System.os_time(:second) + @credentials_lifetime

username = if is_nil(username), do: "#{timestamp}", else: "#{timestamp}:#{username}"
Expand Down
10 changes: 5 additions & 5 deletions lib/ex_turn/auth_provider.ex → lib/rel/auth_provider.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ExTURN.AuthProvider do
defmodule Rel.AuthProvider do
@moduledoc false
# REST service described in https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-turn-rest-00
defmodule ConditionalCORSPlug do
Expand All @@ -8,7 +8,7 @@ defmodule ExTURN.AuthProvider do
def init(_opts), do: []

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

if allow? do
CORSPlug.call(conn, CORSPlug.init([]))
Expand All @@ -22,7 +22,7 @@ defmodule ExTURN.AuthProvider do

require Logger

alias ExTURN.Auth
alias Rel.Auth

plug(ConditionalCORSPlug)
plug(:match)
Expand All @@ -37,8 +37,8 @@ defmodule ExTURN.AuthProvider do
username = Map.get(query_params, "username")
{username, password, ttl} = Auth.generate_credentials(username)

ip_addr = Application.fetch_env!(:ex_turn, :external_listen_ip)
port = Application.fetch_env!(:ex_turn, :listen_port)
ip_addr = Application.fetch_env!(:rel, :external_listen_ip)
port = Application.fetch_env!(:rel, :listen_port)

response =
Jason.encode!(%{
Expand Down
Loading