Skip to content

Commit e5006b6

Browse files
committed
Correctly calculate priority
1 parent 9918674 commit e5006b6

11 files changed

+153
-54
lines changed

lib/ex_ice/priv/candidate.ex

+34-9
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,25 @@ defmodule ExICE.Priv.Candidate do
2727
@callback send_data(t(), :inet.ip_address(), :inet.port_number(), binary()) ::
2828
{:ok, t()} | {:error, term(), t()}
2929

30-
@spec priority(:inet.ip_address(), type()) :: integer()
31-
def priority(address, type) do
30+
@spec priority!(%{:inet.ip_address() => non_neg_integer()}, :inet.ip_address(), type()) ::
31+
non_neg_integer()
32+
def priority!(local_preferences, base_address, type) do
33+
local_preference = Map.fetch!(local_preferences, base_address)
34+
do_priority(local_preference, type)
35+
end
36+
37+
@spec priority(%{:inet.ip_address() => non_neg_integer()}, :inet.ip_address(), type()) ::
38+
{%{:inet.ip_address() => non_neg_integer()}, non_neg_integer()}
39+
def priority(local_preferences, base_address, type) do
40+
local_preference =
41+
Map.get(local_preferences, base_address) || generate_local_preference(local_preferences)
42+
43+
local_preferences = Map.put(local_preferences, base_address, local_preference)
44+
45+
{local_preferences, do_priority(local_preference, type)}
46+
end
47+
48+
defp do_priority(local_preference, type) do
3249
type_preference =
3350
case type do
3451
:host -> 126
@@ -37,15 +54,23 @@ defmodule ExICE.Priv.Candidate do
3754
:relay -> 0
3855
end
3956

40-
# That's not fully correct as according to RFC 8445 sec. 5.1.2.1 we should:
41-
# * use value of 65535 when there is only one IP address
42-
# * use different values when there are multiple IP addresses
43-
# local_preference = 65_535
57+
2 ** 24 * type_preference + 2 ** 8 * local_preference + 2 ** 0 * (256 - 1)
58+
end
4459

45-
# just make sure that for different ip addresses, we have different local preference
46-
local_preference = :erlang.phash2(address, 65535)
60+
defp generate_local_preference(local_preferences, attempts \\ 200)
4761

48-
2 ** 24 * type_preference + 2 ** 8 * local_preference + 2 ** 0 * (256 - 1)
62+
defp generate_local_preference(_local_preferences, 0),
63+
do: raise("Couldn't generate local preference")
64+
65+
defp generate_local_preference(local_preferences, attempts) do
66+
# this should give us a number from 0 to 2**16-1
67+
<<pref::16>> = :crypto.strong_rand_bytes(2)
68+
69+
if Map.has_key?(local_preferences, pref) do
70+
generate_local_preference(local_preferences, attempts - 1)
71+
else
72+
pref
73+
end
4974
end
5075

5176
@spec foundation(type(), :inet.ip_address() | String.t(), :inet.ip_address() | nil, atom()) ::

lib/ex_ice/priv/candidate_base.ex

+1-9
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,14 @@ defmodule ExICE.Priv.CandidateBase do
3535
transport = :udp
3636
address = Keyword.fetch!(config, :address)
3737

38-
priority =
39-
if config[:priority] do
40-
config[:priority]
41-
else
42-
base_address = Keyword.fetch!(config, :base_address)
43-
ExICE.Priv.Candidate.priority(base_address, type)
44-
end
45-
4638
%__MODULE__{
4739
id: Utils.id(),
4840
address: address,
4941
base_address: config[:base_address],
5042
base_port: config[:base_port],
5143
foundation: Candidate.foundation(type, address, nil, transport),
5244
port: Keyword.fetch!(config, :port),
53-
priority: priority,
45+
priority: Keyword.fetch!(config, :priority),
5446
transport: transport,
5547
transport_module: Keyword.get(config, :transport_module, ExICE.Priv.Transport.UDP),
5648
socket: Keyword.fetch!(config, :socket),

lib/ex_ice/priv/gatherer.ex

+16-5
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,17 @@ defmodule ExICE.Priv.Gatherer do
8484
end)
8585
end
8686

87-
@spec gather_host_candidates(t(), [Transport.socket()]) :: [Candidate.t()]
88-
def gather_host_candidates(gatherer, sockets) do
89-
Enum.map(sockets, &create_new_host_candidate(gatherer, &1))
87+
@spec gather_host_candidates(t(), %{:inet.ip_address() => non_neg_integer()}, [
88+
Transport.socket()
89+
]) :: [Candidate.t()]
90+
def gather_host_candidates(gatherer, local_preferences, sockets) do
91+
{local_preferences, cands} =
92+
Enum.reduce(sockets, {local_preferences, []}, fn socket, {local_preferences, cands} ->
93+
{local_preferences, cand} = create_new_host_candidate(gatherer, local_preferences, socket)
94+
{local_preferences, [cand | cands]}
95+
end)
96+
97+
{local_preferences, Enum.reverse(cands)}
9098
end
9199

92100
@spec gather_srflx_candidate(t(), integer(), Transport.socket(), ExSTUN.URI.t()) ::
@@ -155,21 +163,24 @@ defmodule ExICE.Priv.Gatherer do
155163
Keyword.get_values(int, :addr)
156164
end
157165

158-
defp create_new_host_candidate(gatherer, socket) do
166+
defp create_new_host_candidate(gatherer, local_preferences, socket) do
159167
{:ok, {sock_ip, sock_port}} = gatherer.transport_module.sockname(socket)
160168

169+
{local_preferences, priority} = Candidate.priority(local_preferences, sock_ip, :host)
170+
161171
cand =
162172
Candidate.Host.new(
163173
address: sock_ip,
164174
port: sock_port,
165175
base_address: sock_ip,
166176
base_port: sock_port,
177+
priority: priority,
167178
transport_module: gatherer.transport_module,
168179
socket: socket
169180
)
170181

171182
Logger.debug("New candidate: #{inspect(cand)}")
172183

173-
cand
184+
{local_preferences, cand}
174185
end
175186
end

lib/ex_ice/priv/ice_agent.ex

+42-3
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ defmodule ExICE.Priv.ICEAgent do
8686
sockets: [],
8787
local_cands: %{},
8888
remote_cands: %{},
89+
local_preferences: %{},
8990
stun_servers: [],
9091
turn_servers: [],
9192
resolved_turn_servers: [],
@@ -305,7 +306,11 @@ defmodule ExICE.Priv.ICEAgent do
305306
ice_agent = change_gathering_state(ice_agent, :gathering)
306307

307308
{:ok, sockets} = Gatherer.open_sockets(ice_agent.gatherer)
308-
host_cands = Gatherer.gather_host_candidates(ice_agent.gatherer, sockets)
309+
310+
{local_preferences, host_cands} =
311+
Gatherer.gather_host_candidates(ice_agent.gatherer, ice_agent.local_preferences, sockets)
312+
313+
ice_agent = %__MODULE__{ice_agent | local_preferences: local_preferences}
309314

310315
ice_agent =
311316
Enum.reduce(host_cands, ice_agent, fn host_cand, ice_agent ->
@@ -1074,14 +1079,26 @@ defmodule ExICE.Priv.ICEAgent do
10741079
{client.turn_ip, client.turn_port} | ice_agent.resolved_turn_servers
10751080
]
10761081

1077-
ice_agent = %{ice_agent | resolved_turn_servers: resolved_turn_servers}
1082+
# Use sock_addr for calculating priority.
1083+
# In other case, we might duplicate priority.
1084+
{:ok, {sock_addr, _sock_port}} = ice_agent.transport_module.sockname(tr.socket)
1085+
1086+
{local_preferences, priority} =
1087+
Candidate.priority(ice_agent.local_preferences, sock_addr, :relay)
1088+
1089+
ice_agent = %{
1090+
ice_agent
1091+
| resolved_turn_servers: resolved_turn_servers,
1092+
local_preferences: local_preferences
1093+
}
10781094

10791095
relay_cand =
10801096
Candidate.Relay.new(
10811097
address: alloc_ip,
10821098
port: alloc_port,
10831099
base_address: alloc_ip,
10841100
base_port: alloc_port,
1101+
priority: priority,
10851102
transport_module: ice_agent.transport_module,
10861103
socket: tr.socket,
10871104
client: client
@@ -1741,12 +1758,18 @@ defmodule ExICE.Priv.ICEAgent do
17411758
nil ->
17421759
{:ok, {base_addr, base_port}} = ice_agent.transport_module.sockname(tr.socket)
17431760

1761+
{local_preferences, priority} =
1762+
Candidate.priority(ice_agent.local_preferences, base_addr, :srflx)
1763+
1764+
ice_agent = %__MODULE__{ice_agent | local_preferences: local_preferences}
1765+
17441766
cand =
17451767
Candidate.Srflx.new(
17461768
address: xor_addr,
17471769
port: xor_port,
17481770
base_address: base_addr,
17491771
base_port: base_port,
1772+
priority: priority,
17501773
transport_module: ice_agent.transport_module,
17511774
socket: tr.socket
17521775
)
@@ -2058,12 +2081,16 @@ defmodule ExICE.Priv.ICEAgent do
20582081
# TODO calculate correct prio and foundation
20592082
local_cand = Map.fetch!(ice_agent.local_cands, conn_check_pair.local_cand_id)
20602083

2084+
priority =
2085+
Candidate.priority!(ice_agent.local_preferences, local_cand.base.base_address, :prflx)
2086+
20612087
cand =
20622088
Candidate.Prflx.new(
20632089
address: xor_addr.address,
20642090
port: xor_addr.port,
20652091
base_address: local_cand.base.base_address,
20662092
base_port: local_cand.base.base_port,
2093+
priority: priority,
20672094
transport_module: ice_agent.transport_module,
20682095
socket: local_cand.base.socket
20692096
)
@@ -2808,7 +2835,19 @@ defmodule ExICE.Priv.ICEAgent do
28082835
# priority sent to the other side has to be
28092836
# computed with the candidate type preference of
28102837
# peer-reflexive; refer to sec 7.1.1
2811-
priority = Candidate.priority(local_candidate.base.base_address, :prflx)
2838+
priority =
2839+
if local_candidate.base.type == :relay do
2840+
{:ok, {sock_addr, _sock_port}} =
2841+
ice_agent.transport_module.sockname(local_candidate.base.socket)
2842+
2843+
Candidate.priority!(ice_agent.local_preferences, sock_addr, :prflx)
2844+
else
2845+
Candidate.priority!(
2846+
ice_agent.local_preferences,
2847+
local_candidate.base.base_address,
2848+
:prflx
2849+
)
2850+
end
28122851

28132852
attrs = [
28142853
%Username{value: "#{ice_agent.remote_ufrag}:#{ice_agent.local_ufrag}"},

test/candidate_test.exs

+28-4
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,45 @@ defmodule ExICE.CandidateTest do
88
port = 12_345
99

1010
%Candidate{foundation: f1} =
11-
Candidate.new(:host, address: ip, port: port, base_address: ip, base_port: port)
11+
Candidate.new(:host,
12+
address: ip,
13+
port: port,
14+
base_address: ip,
15+
base_port: port,
16+
priority: 123
17+
)
1218

1319
%Candidate{foundation: f2} =
14-
Candidate.new(:host, address: ip, port: port, base_address: ip, base_port: port)
20+
Candidate.new(:host,
21+
address: ip,
22+
port: port,
23+
base_address: ip,
24+
base_port: port,
25+
priority: 123
26+
)
1527

1628
assert f1 == f2
1729

1830
ip2 = {192, 168, 1, 2}
1931
port2 = 23_456
2032

2133
%Candidate{foundation: f1} =
22-
Candidate.new(:host, address: ip, port: port, base_address: ip, base_port: port)
34+
Candidate.new(:host,
35+
address: ip,
36+
port: port,
37+
base_address: ip,
38+
base_port: port,
39+
priority: 123
40+
)
2341

2442
%Candidate{foundation: f2} =
25-
Candidate.new(:host, address: ip2, port: port2, base_address: ip2, base_port: port2)
43+
Candidate.new(:host,
44+
address: ip2,
45+
port: port2,
46+
base_address: ip2,
47+
base_port: port2,
48+
priority: 122
49+
)
2650

2751
assert f1 != f2
2852
end

test/priv/candidate_pair_test.exs

+3-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ defmodule ExICE.Priv.CandidatePairTest do
1313
port: port1,
1414
base_address: addr1,
1515
base_port: port1,
16+
priority: 100,
1617
socket: nil
1718
)
1819

19-
c1 = %Candidate.Host{c1 | base: %{c1.base | priority: 100}}
20-
2120
addr2 = {192, 168, 1, 2}
2221
port2 = 23_456
2322

@@ -26,11 +25,10 @@ defmodule ExICE.Priv.CandidatePairTest do
2625
address: addr2,
2726
port: port2,
2827
base_address: addr2,
29-
base_port: port2
28+
base_port: port2,
29+
priority: 200
3030
)
3131

32-
c2 = %ExICE.Candidate{c2 | priority: 200}
33-
3432
c1c2 = CandidatePair.new(c1, c2, :controlling, :frozen)
3533
assert c1c2.priority == 429_496_730_000
3634

test/priv/checklist_test.exs

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ defmodule ExICE.Priv.ChecklistTest do
1717
port: local_port,
1818
base_address: local_addr,
1919
base_port: local_port,
20+
priority: 123,
2021
socket: nil
2122
)
2223

@@ -25,15 +26,17 @@ defmodule ExICE.Priv.ChecklistTest do
2526
address: local_addr,
2627
port: local_port,
2728
base_address: local_addr,
28-
base_port: local_port
29+
base_port: local_port,
30+
priority: 123
2931
)
3032

3133
remote_srflx_cand =
3234
ExICE.Candidate.new(:srflx,
3335
address: remote_srflx_addr,
3436
port: remote_srflx_port,
3537
base_address: remote_addr,
36-
base_port: remote_port
38+
base_port: remote_port,
39+
priority: 122
3740
)
3841

3942
host_pair =

test/priv/conn_check_handler/controlled_test.exs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ defmodule ExICE.Priv.ConnCheckHandler.ControlledTest do
2020
end
2121
end
2222

23-
@remote_cand ExICE.Candidate.new(:host, address: {192, 168, 0, 2}, port: 8445)
24-
@remote_cand2 ExICE.Candidate.new(:host, address: {192, 168, 0, 3}, port: 8445)
23+
@remote_cand ExICE.Candidate.new(:host, address: {192, 168, 0, 2}, port: 8445, priority: 123)
24+
@remote_cand2 ExICE.Candidate.new(:host, address: {192, 168, 0, 3}, port: 8445, priority: 122)
2525

2626
describe "incoming binding request" do
2727
setup do

test/priv/conn_check_handler/controlling_test.exs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ defmodule ExICE.Priv.ConnCheckHandler.ControllingTest do
2020
end
2121
end
2222

23-
@remote_cand ExICE.Candidate.new(:host, address: {192, 168, 0, 2}, port: 8445)
24-
@remote_cand2 ExICE.Candidate.new(:host, address: {192, 168, 0, 3}, port: 8445)
23+
@remote_cand ExICE.Candidate.new(:host, address: {192, 168, 0, 2}, port: 8445, priority: 123)
24+
@remote_cand2 ExICE.Candidate.new(:host, address: {192, 168, 0, 3}, port: 8445, priority: 122)
2525

2626
describe "incoming binding request" do
2727
setup do

test/priv/gatherer_test.exs

+7-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ defmodule ExICE.Priv.GathererTest do
4242
assert {:ok, sockets} = Gatherer.open_sockets(gatherer)
4343

4444
# there should only be one candidate
45-
assert [%Candidate.Host{} = c] = Gatherer.gather_host_candidates(gatherer, sockets)
45+
assert {local_preferences, [%Candidate.Host{} = c]} =
46+
Gatherer.gather_host_candidates(gatherer, %{}, sockets)
47+
48+
assert Map.has_key?(local_preferences, c.base.address)
49+
assert map_size(local_preferences) == 1
4650
assert c.base.address == {192, 168, 0, 1}
4751
assert c.base.base_address == {192, 168, 0, 1}
4852
assert c.base.port == c.base.base_port
@@ -65,7 +69,8 @@ defmodule ExICE.Priv.GathererTest do
6569

6670
{:ok, sockets} = Gatherer.open_sockets(gatherer)
6771

68-
[%Candidate.Host{} = c] = Gatherer.gather_host_candidates(gatherer, sockets)
72+
{_local_preferences, [%Candidate.Host{} = c]} =
73+
Gatherer.gather_host_candidates(gatherer, %{}, sockets)
6974

7075
assert :ok = Gatherer.gather_srflx_candidate(gatherer, 1234, c.base.socket, stun_server)
7176
assert packet = Transport.Mock.recv(c.base.socket)

0 commit comments

Comments
 (0)