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

Deployment #7

Merged
merged 37 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
11c8c11
Add docker-compose template
LVala Jul 26, 2023
b15c0ec
Add missing '/' in Dockerfile
LVala Jul 26, 2023
9bc8883
Add basic docker-compose.yml
LVala Jul 26, 2023
7466ead
Refactor configuration
LVala Jul 27, 2023
00237a0
Return proper TURN address when requesting credentials from AuthProvider
LVala Jul 27, 2023
1fa47e4
Add example env file
LVala Jul 28, 2023
b101249
Add grafana provisioning configuration
LVala Jul 28, 2023
1bb1097
Enable HTTPS for AuthProvider
LVala Jul 31, 2023
8b095b2
Add workflow to deploy image on tag
LVala Jul 31, 2023
925e095
Bump elixir version to 1.15
LVala Jul 31, 2023
9876ffd
Add option to set Grafana credentials in docker-compose.yml
LVala Jul 31, 2023
08e279e
Allow to pass env variables to turn container
LVala Jul 31, 2023
a0d267e
Use `network_mode: host` in every container, improve config
LVala Aug 1, 2023
cd098e2
Allow to configure metrics endpoint
LVala Aug 1, 2023
061c047
Run Grafana and Prometheus on localhost
LVala Aug 1, 2023
fa0af0a
docker-compose.yml improvements
LVala Aug 1, 2023
b5cbca1
Add deployment workflow
LVala Aug 1, 2023
d838425
Deployment action typo fix
LVala Aug 1, 2023
c606063
Pull image with specific tag instead of latest
LVala Aug 1, 2023
547f457
Fix typo in GH workflows
LVala Aug 1, 2023
f534d8f
Update deployment workflow (test)
LVala Aug 2, 2023
d36ad02
Update deployment workflow - fix type
LVala Aug 2, 2023
0bf5550
Update deployment workflow - further improvements
LVala Aug 2, 2023
3e0c9b9
Update deployment workflow - fix typo
LVala Aug 2, 2023
b4e6c7b
Update deployment workflow - fix typo
LVala Aug 2, 2023
570d348
Simplify supervisor logic, bugfixes
LVala Aug 2, 2023
3b3f683
Improve config, add option to set credentials lifetime
LVala Aug 2, 2023
1eba046
Set `:credentials_lifetime` via `:prod` config
LVala Aug 3, 2023
9757bda
Improve deployment workflow
LVala Aug 3, 2023
641710f
Fail on `gen_udp` functions
LVala Aug 3, 2023
79c599f
Improve supervision tree
LVala Aug 3, 2023
6dfe096
Change `backend` option of UDP sockets
LVala Aug 3, 2023
67fff60
Formatting change in GH workflows
LVala Aug 3, 2023
65f49ba
Apply requested changes
LVala Aug 8, 2023
359f554
Fix issues with external IP addresses
LVala Aug 8, 2023
6eb886d
Change default LISTEN_IP to 0.0.0.0
LVala Aug 8, 2023
90c4b04
Fix typo
LVala Aug 8, 2023
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
86 changes: 78 additions & 8 deletions config/runtime.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Config

defmodule ConfigParser do
require Logger

defmodule ConfigUtils do
def parse_ip_address(ip) do
case ip |> to_charlist() |> :inet.parse_address() do
{:ok, parsed_ip} ->
Expand All @@ -26,8 +28,49 @@ defmodule ConfigParser do
""")
end
end

def guess_external_ip(listen_ip)
when listen_ip not in [{0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}],
do: listen_ip

def guess_external_ip(_listen_ip) do
with {:ok, opts} <- :inet.getifaddrs(),
addrs <- Enum.map(opts, fn {_intf, opt} -> opt end),
addrs <- Enum.filter(addrs, &is_valid?(&1)),
addrs <- Enum.flat_map(addrs, &Keyword.get_values(&1, :addr)),
addrs <- Enum.filter(addrs, &(not link_local?(&1) and not any?(&1))),
false <- Enum.empty?(addrs) do
Comment on lines +37 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better split this into case and casual pipeline

case Enum.find(addrs, &(not private?(&1))) do
nil -> hd(addrs)
other -> other
end
else
_other ->
raise "Cannot find an external IP address, pass one explicitely via EXTERNAL_IP env variable"
end
end

defp is_valid?(inter) do
flags = Keyword.get(inter, :flags)
:up in flags and :loopback not in flags
end

defp link_local?({169, 254, _, _}), do: true
defp link_local?({0xFE80, _, _, _, _, _, _, _}), do: true
defp link_local?(_other), do: false

defp any?({0, 0, 0, 0}), do: true
defp any?({0, 0, 0, 0, 0, 0, 0, 0}), do: true
defp any?(_other), do: false

defp private?({10, _, _, _}), do: true
defp private?({192, 168, _, _}), do: true
defp private?({172, b, _, _}) when b in 16..31, do: true
defp private?({0xFC00, 0, 0, 0, 0, 0, 0, 0}), do: true
defp private?(_other), do: false
end

# HTTPS for AuthProvider
use_tls? = System.get_env("AUTH_PROVIDER_USE_TLS") not in ["0", "false", nil]
keyfile = System.get_env("KEY_FILE_PATH")
certfile = System.get_env("CERT_FILE_PATH")
Expand All @@ -36,24 +79,51 @@ if use_tls? and (is_nil(keyfile) or is_nil(certfile)) do
raise "Both KEY_FILE_PATH and CERT_FILE_PATH must be set is TLS is used"
end

# IP addresses for TURN
listen_ip = System.get_env("LISTEN_IP", "0.0.0.0") |> ConfigUtils.parse_ip_address()

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

relay_ip =
case System.fetch_env("RELAY_IP") do
{:ok, addr} -> ConfigUtils.parse_ip_address(addr)
:error -> listen_ip
end

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

# AuthProvider/credentials configuration
config :ex_turn,
auth_provider_ip:
System.get_env("AUTH_PROVIDER_IP", "127.0.0.1") |> ConfigParser.parse_ip_address(),
auth_provider_port: System.get_env("AUTH_PROVIDER_PORT", "4000") |> ConfigParser.parse_port(),
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_use_tls?: use_tls?,
keyfile: keyfile,
certfile: certfile

# TURN server configuration
config :ex_turn,
relay_ip: System.get_env("RELAY_IP", "127.0.0.1") |> ConfigParser.parse_ip_address(),
listen_ip: System.get_env("LISTEN_IP", "127.0.0.1") |> ConfigParser.parse_ip_address(),
listen_port: System.get_env("UDP_LISTEN_PORT", "3478") |> ConfigParser.parse_port(),
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_name: System.get_env("DOMAIN_NAME", "example.com")

# Metrics endpoint configuration
config :ex_turn,
metrics_ip: System.get_env("METRICS_IP", "127.0.0.1") |> ConfigParser.parse_ip_address(),
metrics_port: System.get_env("METRICS_PORT", "9568") |> ConfigParser.parse_port()
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,
auth_secret: :crypto.strong_rand_bytes(64),
nonce_secret: :crypto.strong_rand_bytes(64)
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ services:
restart: on-failure
network_mode: host
environment:
LISTEN_IP: 0.0.0.0
REPLAY_IP: 0.0.0.0
DOMAIN: "${DOMAIN}"

prometheus:
Expand Down
15 changes: 1 addition & 14 deletions lib/ex_turn/auth_provider.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ defmodule ExTURN.AuthProvider do
username = Map.get(query_params, "username")
{username, password, ttl} = Auth.generate_credentials(username)

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

ip_addr = if(ip_addr == {0, 0, 0, 0}, do: get_public_ip_addr(), else: ip_addr)

response =
Jason.encode!(%{
"username" => username,
Expand Down Expand Up @@ -53,15 +51,4 @@ defmodule ExTURN.AuthProvider do
match _ do
send_resp(conn, 400, "invalid request")
end

defp get_public_ip_addr() do
with {:ok, opts} <- :inet.getifaddrs(),
addrs <- Enum.flat_map(opts, fn {_if, opt} -> Keyword.get_values(opt, :addr) end),
addr <- Enum.find(addrs, &match?({a, _, _, _} when a != 127, &1)),
false <- is_nil(addr) do
addr
else
_other -> "AuthProvider listens on 0.0.0.0, but failed to find public IP interface"
end
end
end
9 changes: 5 additions & 4 deletions lib/ex_turn/listener.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule ExTURN.Listener do
def listen(ip, port) do
listener_addr = "#{:inet.ntoa(ip)}:#{port}/UDP"

Logger.info("Starting a new listener on #{listener_addr}")
Logger.info("Starting a new listener on: #{listener_addr}")
Logger.metadata(listener: listener_addr)

{:ok, socket} =
Expand Down Expand Up @@ -160,14 +160,15 @@ defmodule ExTURN.Listener do
:ok <- check_even_port(even_port),
{:ok, alloc_port} <- get_available_port(),
{:ok, lifetime} <- Utils.get_lifetime(msg) do
alloc_ip = Application.fetch_env!(:ex_turn, :relay_ip)
relay_ip = Application.fetch_env!(:ex_turn, :relay_ip)
external_relay_ip = Application.fetch_env!(:ex_turn, :external_relay_ip)

type = %Type{class: :success_response, method: msg.type.method}

response =
msg.transaction_id
|> Message.new(type, [
%XORRelayedAddress{port: alloc_port, address: alloc_ip},
%XORRelayedAddress{port: alloc_port, address: external_relay_ip},
%Lifetime{lifetime: lifetime},
%XORMappedAddress{port: c_port, address: c_ip}
])
Expand All @@ -178,7 +179,7 @@ defmodule ExTURN.Listener do
:gen_udp.open(
alloc_port,
[
{:ifaddr, alloc_ip},
{:ifaddr, relay_ip},
{:active, true},
{:recbuf, 1024 * 1024},
:binary
Expand Down
6 changes: 0 additions & 6 deletions lib/ex_turn/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@ defmodule ExTURN.Supervisor do
@moduledoc false
use Supervisor

require Logger

@version Mix.Project.config()[:version]

@spec start_link(any()) :: Supervisor.on_start()
def start_link(_arg) do
Supervisor.start_link(__MODULE__, nil, name: __MODULE__)
end

@impl true
def init(_arg) do
Logger.info("Starting ExTURN v#{@version}")

listen_ip = Application.fetch_env!(:ex_turn, :listen_ip)
listen_port = Application.fetch_env!(:ex_turn, :listen_port)

Expand Down
14 changes: 14 additions & 0 deletions lib/ex_turn_app.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ defmodule ExTURN.App do
@moduledoc false
use Application

require Logger

@version Mix.Project.config()[:version]

@impl true
def start(_, _) do
Logger.info("Starting ExTURN v#{@version}")

auth_provider_ip = Application.fetch_env!(:ex_turn, :auth_provider_ip)
auth_provider_port = Application.fetch_env!(:ex_turn, :auth_provider_port)
use_tls? = Application.fetch_env!(:ex_turn, :auth_provider_use_tls?)
Expand Down Expand Up @@ -32,6 +38,14 @@ defmodule ExTURN.App do
[plug: ExTURN.AuthProvider, ip: auth_provider_ip, port: auth_provider_port] ++ scheme_opts}
]

Logger.info(
"Starting Prometheus metrics endpoint at: http://#{:inet.ntoa(metrics_ip)}:#{metrics_port}/metrics"
)

Logger.info(
"Starting credentials endpoint at: #{if(use_tls?, do: ~c"https", else: ~c"http")}://#{:inet.ntoa(auth_provider_ip)}:#{auth_provider_port}/"
)

Supervisor.start_link(children, strategy: :one_for_one)
end

Expand Down
Loading