Skip to content

Commit 233556d

Browse files
committed
[WIP] TCP ICE
1 parent b9ad154 commit 233556d

File tree

17 files changed

+642
-104
lines changed

17 files changed

+642
-104
lines changed

.tool-versions

Lines changed: 0 additions & 2 deletions
This file was deleted.

example/peer.exs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Mix.install([{:gun, "~> 2.0.1"}, {:ex_ice, path: "../", force: true}, {:jason, "~> 1.4.0"}])
22

33
require Logger
4-
Logger.configure(level: :info)
4+
Logger.configure(level: :debug)
55

66
defmodule Peer do
77
use GenServer
@@ -95,12 +95,16 @@ defmodule Peer do
9595
role = String.to_atom(role)
9696

9797
{:ok, pid} =
98-
ICEAgent.start_link(role,
98+
ICEAgent.start_link(
99+
role: role,
99100
ip_filter: fn
100101
{_, _, _, _} -> true
101102
{_, _, _, _, _, _, _, _} -> false
102103
end,
103-
ice_servers: [%{urls: "stun:stun.l.google.com:19302"}]
104+
ice_servers: [%{urls: "stun:stun.nextcloud.com"}],
105+
transport: :tcp,
106+
# TODO napisac ostrzezenie odnosnie port range
107+
# ports: 50505..50555 |> Enum.shuffle()
104108
)
105109

106110
{:ok, ufrag, passwd} = ICEAgent.get_local_credentials(pid)
@@ -141,7 +145,7 @@ defmodule Peer do
141145
state
142146
end
143147

144-
def handle_ice_msg({:connection_state_change, :completed}, state) do
148+
def handle_ice_msg({:connection_state_change, :connected}, state) do
145149
Logger.info("ICE: :completed")
146150
Logger.info("Starting sending...")
147151
ref = Process.send_after(self(), :send_ping, 1000)

lib/ex_ice/candidate.ex

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ defmodule ExICE.Candidate do
33
ICE candidate representation.
44
"""
55

6-
@type type() :: :host | :srflx | :prflx | :relay
6+
@type type :: :host | :srflx | :prflx | :relay
7+
@type tcp_type :: :active | :passive | :so
78

8-
@type t() :: %__MODULE__{
9+
@type t :: %__MODULE__{
910
id: integer(),
1011
type: type(),
1112
address: :inet.ip_address() | String.t(),
@@ -14,7 +15,8 @@ defmodule ExICE.Candidate do
1415
foundation: integer(),
1516
port: :inet.port_number(),
1617
priority: integer(),
17-
transport: :udp | :tcp
18+
transport: :udp | :tcp,
19+
tcp_type: tcp_type() | nil
1820
}
1921

2022
@enforce_keys [
@@ -24,7 +26,8 @@ defmodule ExICE.Candidate do
2426
:port,
2527
:foundation,
2628
:priority,
27-
:transport
29+
:transport,
30+
:tcp_type
2831
]
2932
defstruct @enforce_keys ++ [:base_address, :base_port]
3033

@@ -38,7 +41,8 @@ defmodule ExICE.Candidate do
3841
priority: priority,
3942
address: address,
4043
port: port,
41-
type: type
44+
type: type,
45+
tcp_type: tcp_type
4246
} = cand
4347

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

5559
transport = transport_to_string(transport)
5660
address = address_to_string(address)
57-
58-
"#{foundation} #{component_id} #{transport} #{priority} #{address} #{port} typ #{type} #{related_addr}"
59-
|> String.trim()
61+
tcp_type = tcp_type_to_string(tcp_type)
62+
63+
[
64+
foundation,
65+
component_id,
66+
transport,
67+
priority,
68+
address,
69+
port,
70+
"typ",
71+
type,
72+
related_addr,
73+
tcp_type
74+
]
75+
|> Enum.reject(&(&1 == ""))
76+
|> Enum.join(" ")
6077
end
6178

6279
@spec unmarshal(String.t()) :: {:ok, t()} | {:error, term()}
6380
def unmarshal(string) do
64-
with [f_str, c_str, tr_str, pr_str, a_str, po_str, "typ", ty_str] <-
65-
String.split(string, " ", parts: 8),
81+
with [f_str, c_str, tr_str, pr_str, a_str, po_str, "typ", ty_str | rest] <-
82+
String.split(string, " "),
6683
{foundation, ""} <- Integer.parse(f_str),
6784
{_component_id, ""} <- Integer.parse(c_str),
6885
{:ok, transport} <- parse_transport(String.downcase(tr_str)),
6986
{priority, ""} <- Integer.parse(pr_str),
7087
{:ok, address} <- parse_address(a_str),
7188
{port, ""} <- Integer.parse(po_str),
72-
{:ok, type} <- parse_type(ty_str) do
73-
{:ok,
74-
new(
75-
type,
76-
address: address,
77-
port: port,
78-
priority: priority,
79-
foundation: foundation,
80-
transport: transport
81-
)}
89+
{:ok, type} <- parse_type(ty_str),
90+
{:ok, extra_config} <- parse_optional_attributes(rest) do
91+
config = [
92+
address: address,
93+
port: port,
94+
priority: priority,
95+
foundation: foundation,
96+
transport: transport
97+
]
98+
99+
{:ok, new(type, config ++ extra_config)}
82100
else
83101
err when is_list(err) -> {:error, :invalid_candidate}
84102
err -> err
@@ -89,12 +107,16 @@ defmodule ExICE.Candidate do
89107
def family(%__MODULE__{address: {_, _, _, _}}), do: :ipv4
90108
def family(%__MODULE__{address: {_, _, _, _, _, _, _, _}}), do: :ipv6
91109

110+
def tcp_type(%__MODULE__{tcp_type: tt}), do: tt
111+
92112
@doc false
93113
@spec new(type(), Keyword.t()) :: t()
94114
def new(type, config) when type in [:host, :srflx, :prflx, :relay] do
95115
transport = Keyword.get(config, :transport, :udp)
96116
address = Keyword.fetch!(config, :address)
97117

118+
tcp_type = if transport == :tcp, do: Keyword.fetch!(config, :tcp_type)
119+
98120
%__MODULE__{
99121
id: ExICE.Priv.Utils.id(),
100122
address: address,
@@ -104,16 +126,22 @@ defmodule ExICE.Candidate do
104126
port: Keyword.fetch!(config, :port),
105127
priority: Keyword.fetch!(config, :priority),
106128
transport: transport,
107-
type: type
129+
type: type,
130+
tcp_type: tcp_type
108131
}
109132
end
110133

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

114137
defp transport_to_string(:udp), do: "UDP"
138+
defp transport_to_string(:tcp), do: "TCP"
139+
140+
defp tcp_type_to_string(nil), do: ""
141+
defp tcp_type_to_string(type), do: "tcptype #{type}"
115142

116143
defp parse_transport("udp"), do: {:ok, :udp}
144+
defp parse_transport("tcp"), do: {:ok, :tcp}
117145
defp parse_transport(_other), do: {:error, :invalid_transport}
118146

119147
defp parse_address(address) do
@@ -124,9 +152,29 @@ defmodule ExICE.Candidate do
124152
end
125153
end
126154

127-
defp parse_type("host" <> _rest), do: {:ok, :host}
128-
defp parse_type("srflx" <> _rest), do: {:ok, :srflx}
129-
defp parse_type("prflx" <> _rest), do: {:ok, :prflx}
130-
defp parse_type("relay" <> _rest), do: {:ok, :relay}
155+
defp parse_type("host"), do: {:ok, :host}
156+
defp parse_type("srflx"), do: {:ok, :srflx}
157+
defp parse_type("prflx"), do: {:ok, :prflx}
158+
defp parse_type("relay"), do: {:ok, :relay}
131159
defp parse_type(_other), do: {:error, :invalid_type}
160+
161+
defp parse_optional_attributes(list, config \\ [])
162+
defp parse_optional_attributes([], config), do: {:ok, config}
163+
164+
defp parse_optional_attributes(["raddr", _2, _3, _4 | rest], config),
165+
do: parse_optional_attributes(rest, config)
166+
167+
defp parse_optional_attributes(["tcptype", tcp_type | rest], config) do
168+
case parse_tcp_type(tcp_type) do
169+
{:ok, tcp_type} -> parse_optional_attributes(rest, config ++ [tcp_type: tcp_type])
170+
err -> err
171+
end
172+
end
173+
174+
defp parse_optional_attributes(_other, config), do: {:ok, config}
175+
176+
defp parse_tcp_type("active"), do: {:ok, :active}
177+
defp parse_tcp_type("passive"), do: {:ok, :passive}
178+
defp parse_tcp_type("so"), do: {:ok, :so}
179+
defp parse_tcp_type(_other), do: {:error, :invalid_tcp_type}
132180
end

lib/ex_ice/ice_agent.ex

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ defmodule ExICE.ICEAgent do
107107
on_connection_state_change: pid() | nil,
108108
on_data: pid() | nil,
109109
on_new_candidate: pid() | nil,
110-
host_to_srflx_ip_mapper: host_to_srflx_ip_mapper() | nil
110+
host_to_srflx_ip_mapper: host_to_srflx_ip_mapper() | nil,
111+
transport: :udp | :tcp
111112
]
112113

113114
@doc """
@@ -321,6 +322,15 @@ defmodule ExICE.ICEAgent do
321322

322323
@impl true
323324
def init(opts) do
325+
# TODO: this is ugly, and will not allow us to run more than two TCP ICE agents at the same time
326+
opts =
327+
if opts[:transport] == :tcp do
328+
{:ok, _pid} = ExICE.Priv.Transport.TCP.Client.start_link()
329+
opts ++ [transport_module: ExICE.Priv.Transport.TCP.Client]
330+
else
331+
opts
332+
end
333+
324334
ice_agent = ExICE.Priv.ICEAgent.new(opts)
325335
{:ok, %{ice_agent: ice_agent, pending_eoc: false, pending_remote_cands: MapSet.new()}}
326336
end
@@ -478,6 +488,14 @@ defmodule ExICE.ICEAgent do
478488
{:noreply, %{state | ice_agent: ice_agent}}
479489
end
480490

491+
@impl true
492+
def handle_info({:tcp, _socket, _packet}, state) do
493+
# TODO: consider receiving TCP data in the ICE Agent process
494+
# ice_agent = ExICE.Priv.ICEAgent.handle_tcp(state.ice_agent, socket, packet)
495+
# {:noreply, %{state | ice_agent: ice_agent}}
496+
{:noreply, state}
497+
end
498+
481499
@impl true
482500
def handle_info({:ex_turn, ref, msg}, state) do
483501
ice_agent = ExICE.Priv.ICEAgent.handle_ex_turn_msg(state.ice_agent, ref, msg)

0 commit comments

Comments
 (0)