Skip to content
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
2 changes: 0 additions & 2 deletions .tool-versions

This file was deleted.

3 changes: 2 additions & 1 deletion example/peer.exs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ defmodule Peer do
role = String.to_atom(role)

{:ok, pid} =
ICEAgent.start_link(role,
ICEAgent.start_link(
role: role,
ip_filter: fn
{_, _, _, _} -> true
{_, _, _, _, _, _, _, _} -> false
Expand Down
99 changes: 74 additions & 25 deletions lib/ex_ice/candidate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ defmodule ExICE.Candidate do
ICE candidate representation.
"""

@type type() :: :host | :srflx | :prflx | :relay
@type type :: :host | :srflx | :prflx | :relay
@type tcp_type :: :active | :passive | :so

@type t() :: %__MODULE__{
@type t :: %__MODULE__{
id: integer(),
type: type(),
address: :inet.ip_address() | String.t(),
Expand All @@ -14,7 +15,8 @@ defmodule ExICE.Candidate do
foundation: integer(),
port: :inet.port_number(),
priority: integer(),
transport: :udp | :tcp
transport: :udp | :tcp,
tcp_type: tcp_type() | nil
}

@enforce_keys [
Expand All @@ -24,7 +26,8 @@ defmodule ExICE.Candidate do
:port,
:foundation,
:priority,
:transport
:transport,
:tcp_type
]
defstruct @enforce_keys ++ [:base_address, :base_port]

Expand All @@ -38,7 +41,8 @@ defmodule ExICE.Candidate do
priority: priority,
address: address,
port: port,
type: type
type: type,
tcp_type: tcp_type
} = cand

# This is based on RFC 8839 sec. 5.1.
Expand All @@ -54,31 +58,45 @@ defmodule ExICE.Candidate do

transport = transport_to_string(transport)
address = address_to_string(address)

"#{foundation} #{component_id} #{transport} #{priority} #{address} #{port} typ #{type} #{related_addr}"
|> String.trim()
tcp_type = tcp_type_to_string(tcp_type)

[
foundation,
component_id,
transport,
priority,
address,
port,
"typ",
type,
related_addr,
tcp_type
]
|> Enum.reject(&(&1 == ""))
|> Enum.join(" ")
end

@spec unmarshal(String.t()) :: {:ok, t()} | {:error, term()}
def unmarshal(string) do
with [f_str, c_str, tr_str, pr_str, a_str, po_str, "typ", ty_str] <-
String.split(string, " ", parts: 8),
with [f_str, c_str, tr_str, pr_str, a_str, po_str, "typ", ty_str | rest] <-
String.split(string, " "),
{foundation, ""} <- Integer.parse(f_str),
{_component_id, ""} <- Integer.parse(c_str),
{:ok, transport} <- parse_transport(String.downcase(tr_str)),
{priority, ""} <- Integer.parse(pr_str),
{:ok, address} <- parse_address(a_str),
{port, ""} <- Integer.parse(po_str),
{:ok, type} <- parse_type(ty_str) do
{:ok,
new(
type,
address: address,
port: port,
priority: priority,
foundation: foundation,
transport: transport
)}
{:ok, type} <- parse_type(ty_str),
{:ok, extra_config} <- parse_optional_attributes(rest) do
config = [
address: address,
port: port,
priority: priority,
foundation: foundation,
transport: transport
]

{:ok, new(type, config ++ extra_config)}
else
err when is_list(err) -> {:error, :invalid_candidate}
err -> err
Expand All @@ -89,12 +107,17 @@ defmodule ExICE.Candidate do
def family(%__MODULE__{address: {_, _, _, _}}), do: :ipv4
def family(%__MODULE__{address: {_, _, _, _, _, _, _, _}}), do: :ipv6

@spec tcp_type(t()) :: tcp_type() | nil
def tcp_type(%__MODULE__{tcp_type: tt}), do: tt

@doc false
@spec new(type(), Keyword.t()) :: t()
def new(type, config) when type in [:host, :srflx, :prflx, :relay] do
transport = Keyword.get(config, :transport, :udp)
address = Keyword.fetch!(config, :address)

tcp_type = if transport == :tcp, do: Keyword.fetch!(config, :tcp_type)

%__MODULE__{
id: ExICE.Priv.Utils.id(),
address: address,
Expand All @@ -104,16 +127,22 @@ defmodule ExICE.Candidate do
port: Keyword.fetch!(config, :port),
priority: Keyword.fetch!(config, :priority),
transport: transport,
type: type
type: type,
tcp_type: tcp_type
}
end

defp address_to_string(address) when is_binary(address), do: address
defp address_to_string(address), do: :inet.ntoa(address)

defp transport_to_string(:udp), do: "UDP"
defp transport_to_string(:tcp), do: "TCP"

defp tcp_type_to_string(nil), do: ""
defp tcp_type_to_string(type), do: "tcptype #{type}"

defp parse_transport("udp"), do: {:ok, :udp}
defp parse_transport("tcp"), do: {:ok, :tcp}
defp parse_transport(_other), do: {:error, :invalid_transport}

defp parse_address(address) do
Expand All @@ -124,9 +153,29 @@ defmodule ExICE.Candidate do
end
end

defp parse_type("host" <> _rest), do: {:ok, :host}
defp parse_type("srflx" <> _rest), do: {:ok, :srflx}
defp parse_type("prflx" <> _rest), do: {:ok, :prflx}
defp parse_type("relay" <> _rest), do: {:ok, :relay}
defp parse_type("host"), do: {:ok, :host}
defp parse_type("srflx"), do: {:ok, :srflx}
defp parse_type("prflx"), do: {:ok, :prflx}
defp parse_type("relay"), do: {:ok, :relay}
defp parse_type(_other), do: {:error, :invalid_type}

defp parse_optional_attributes(list, config \\ [])
defp parse_optional_attributes([], config), do: {:ok, config}

defp parse_optional_attributes(["raddr", _2, _3, _4 | rest], config),
do: parse_optional_attributes(rest, config)

defp parse_optional_attributes(["tcptype", tcp_type | rest], config) do
case parse_tcp_type(tcp_type) do
{:ok, tcp_type} -> parse_optional_attributes(rest, config ++ [tcp_type: tcp_type])
err -> err
end
end

defp parse_optional_attributes(_other, config), do: {:ok, config}

defp parse_tcp_type("active"), do: {:ok, :active}
defp parse_tcp_type("passive"), do: {:ok, :passive}
defp parse_tcp_type("so"), do: {:ok, :so}
defp parse_tcp_type(_other), do: {:error, :invalid_tcp_type}
end
21 changes: 20 additions & 1 deletion lib/ex_ice/ice_agent.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ defmodule ExICE.ICEAgent do
until role is set, adding remote candidates or gathering local candidates won't possible, and calls to these
functions will be ignored. Defaults to `nil`.
* `ip_filter` - filter applied when gathering host candidates
* `ports` - ports that will be used when gathering host candidates, otherwise the ports are chosen by the OS
* `transport` - transport protocol to be used.
* `udp` - use UDP only (default).
* `tcp`- use TCP only. Note that relay candidates for TCP transport are not yet supported.
* `ports` - ports that will be used when gathering host candidates, otherwise the ports are chosen by the OS.
Warning: when using `transport: :tcp` and setting this option, make sure that this port range is used only by ICE Agents
from this Erlang VM instance. Refer to the source code (`lib/ex_ice/priv/transport/tcp.ex`) for more info.
* `ice_servers` - list of STUN/TURN servers
* `ice_transport_policy` - candidate types to be used.
* `all` - all ICE candidates will be considered (default).
Expand All @@ -94,6 +99,7 @@ defmodule ExICE.ICEAgent do
@type opts() :: [
role: role() | nil,
ip_filter: ip_filter(),
transport: :udp | :tcp,
ports: Enumerable.t(non_neg_integer()),
ice_servers: [
%{
Expand Down Expand Up @@ -325,6 +331,13 @@ defmodule ExICE.ICEAgent do
def init(opts) do
if Keyword.has_key?(opts, :logger_metadata), do: Logger.metadata(opts[:logger_metadata])

opts =
if opts[:transport] == :tcp do
opts ++ [transport_module: ExICE.Priv.Transport.TCP]
else
opts ++ [transport_module: ExICE.Priv.Transport.UDP]
end

ice_agent = ExICE.Priv.ICEAgent.new(opts)
{:ok, %{ice_agent: ice_agent, pending_eoc: false, pending_remote_cands: MapSet.new()}}
end
Expand Down Expand Up @@ -482,6 +495,12 @@ defmodule ExICE.ICEAgent do
{:noreply, %{state | ice_agent: ice_agent}}
end

@impl true
def handle_info({:tcp, socket, src_ip, src_port, packet}, state) do
ice_agent = ExICE.Priv.ICEAgent.handle_tcp(state.ice_agent, socket, src_ip, src_port, packet)
{:noreply, %{state | ice_agent: ice_agent}}
end

@impl true
def handle_info({:ex_turn, ref, msg}, state) do
ice_agent = ExICE.Priv.ICEAgent.handle_ex_turn_msg(state.ice_agent, ref, msg)
Expand Down
21 changes: 11 additions & 10 deletions lib/ex_ice/priv/app.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ defmodule ExICE.Priv.App do
kernel_ver = kernel_version()

children =
if kernel_ver >= {9, 1} do
[{ExICE.Priv.MDNS.Resolver, :gen_udp}]
else
Logger.warning("""
Not starting MDNS resolver as it requires kernel version >= 9.1.
Detected kernel version: #{inspect(kernel_ver)}
""")

[]
end
[{Registry, keys: :unique, name: ExICE.Priv.Registry}] ++
if kernel_ver >= {9, 1} do
[{ExICE.Priv.MDNS.Resolver, ExICE.Priv.Transport.UDP}]
else
Logger.warning("""
Not starting mDNS resolver as it requires kernel version >= 9.1.
Detected kernel version: #{inspect(kernel_ver)}
""")

[]
end

Supervisor.start_link(children, strategy: :one_for_one)
end
Expand Down
Loading
Loading