Skip to content

Commit 72e2a46

Browse files
committed
Add support for aggressive nomination in controlling agent
1 parent b58acee commit 72e2a46

File tree

5 files changed

+269
-15
lines changed

5 files changed

+269
-15
lines changed

lib/ex_ice/ice_agent.ex

+7
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ defmodule ExICE.ICEAgent do
6161
* `ice_transport_policy` - candidate types to be used.
6262
* `all` - all ICE candidates will be considered (default).
6363
* `relay` - only relay candidates will be considered.
64+
* `aggressive_nomination` - whether to use aggressive nomination from RFC 5245.
65+
ExICE aims to implement RFC 8445, which removes aggressive nomination.
66+
In particular, RFC 8445 assumes that data can be sent on any valid pair (no need for nomination).
67+
While this behavior is supported by most of the implementations, some of them still require
68+
a pair to be nominated by the controlling agent before they start sending data.
69+
Defaults to true.
6470
* `on_gathering_state_change` - where to send gathering state change notifications. Defaults to a process that spawns `ExICE`.
6571
* `on_connection_state_change` - where to send connection state change notifications. Defaults to a process that spawns `ExICE`.
6672
* `on_data` - where to send data. Defaults to a process that spawns `ExICE`.
@@ -78,6 +84,7 @@ defmodule ExICE.ICEAgent do
7884
}
7985
],
8086
ice_transport_policy: :all | :relay,
87+
aggressive_nomination: boolean(),
8188
on_gathering_state_change: pid() | nil,
8289
on_connection_state_change: pid() | nil,
8390
on_data: pid() | nil,

lib/ex_ice/priv/conn_check_handler/controlled.ex

+2-1
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,12 @@ defmodule ExICE.Priv.ConnCheckHandler.Controlled do
203203

204204
%ICEAgent{ice_agent | selected_pair_id: pair.id}
205205
else
206+
Logger.debug("Not selecting a new pair as it has lower priority.")
206207
ice_agent
207208
end
208209

209210
true ->
210-
Logger.debug("Not selecting a new pair as it has lower priority or has the same id")
211+
Logger.debug("Not selecting a new pair as it has the same id")
211212
ice_agent
212213
end
213214
end

lib/ex_ice/priv/conn_check_handler/controlling.ex

+8-4
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,12 @@ defmodule ExICE.Priv.ConnCheckHandler.Controlling do
9797
def update_nominated_flag(ice_agent, _pair_id, false), do: ice_agent
9898

9999
@impl true
100-
def update_nominated_flag(%ICEAgent{eoc: true} = ice_agent, pair_id, true) do
100+
def update_nominated_flag(
101+
%ICEAgent{eoc: eoc, aggressive_nomination: aggressive_nomination} = ice_agent,
102+
pair_id,
103+
true
104+
)
105+
when (aggressive_nomination == false and eoc == true) or aggressive_nomination == true do
101106
Logger.debug("Nomination succeeded. Selecting pair: #{inspect(pair_id)}")
102107

103108
pair = Map.fetch!(ice_agent.checklist, pair_id)
@@ -106,12 +111,11 @@ defmodule ExICE.Priv.ConnCheckHandler.Controlling do
106111

107112
# the controlling agent could nominate only when eoc was set
108113
# and checklist finished
109-
unless Checklist.finished?(ice_agent.checklist) do
114+
if not ice_agent.aggressive_nomination and not Checklist.finished?(ice_agent.checklist) do
110115
Logger.warning("Nomination succeeded but checklist hasn't finished.")
111116
end
112117

113-
ice_agent = %ICEAgent{ice_agent | nominating?: {false, nil}, selected_pair_id: pair.id}
114-
ICEAgent.change_connection_state(ice_agent, :completed)
118+
%ICEAgent{ice_agent | nominating?: {false, nil}, selected_pair_id: pair.id}
115119
end
116120

117121
defp resolve_pair(ice_agent, pair) do

lib/ex_ice/priv/ice_agent.ex

+39-5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ defmodule ExICE.Priv.ICEAgent do
5959
:on_gathering_state_change,
6060
:on_data,
6161
:on_new_candidate,
62+
:aggressive_nomination,
6263
:if_discovery_module,
6364
:transport_module,
6465
:gatherer,
@@ -149,6 +150,7 @@ defmodule ExICE.Priv.ICEAgent do
149150
on_gathering_state_change: opts[:on_gathering_state_change] || controlling_process,
150151
on_data: opts[:on_data] || controlling_process,
151152
on_new_candidate: opts[:on_new_candidate] || controlling_process,
153+
aggressive_nomination: Keyword.get(opts, :aggressive_nomination, true),
152154
if_discovery_module: if_discovery_module,
153155
transport_module: transport_module,
154156
gatherer: Gatherer.new(if_discovery_module, transport_module, ip_filter, ports),
@@ -430,6 +432,13 @@ defmodule ExICE.Priv.ICEAgent do
430432
update_connection_state(ice_agent)
431433
end
432434

435+
def end_of_candidates(%__MODULE__{role: :controlling, aggressive_nomination: true} = ice_agent) do
436+
Logger.debug("Setting end-of-candidates flag")
437+
ice_agent = %{ice_agent | eoc: true}
438+
# we might need to move to the completed state
439+
update_connection_state(ice_agent)
440+
end
441+
433442
def end_of_candidates(%__MODULE__{role: :controlling} = ice_agent) do
434443
Logger.debug("Setting end-of-candidates flag.")
435444
ice_agent = %{ice_agent | eoc: true}
@@ -2189,7 +2198,11 @@ defmodule ExICE.Priv.ICEAgent do
21892198
end
21902199
end
21912200

2192-
defp time_to_nominate?(%__MODULE__{state: :connected} = ice_agent) do
2201+
# In aggressive nomination, there is no additional connectivity check.
2202+
# Instead, every connectivity check includes UseCandidate flag.
2203+
defp time_to_nominate?(%__MODULE__{aggressive_nomination: true}), do: false
2204+
2205+
defp time_to_nominate?(%__MODULE__{aggressive_nomination: false, state: :connected} = ice_agent) do
21932206
{nominating?, _} = ice_agent.nominating?
21942207
# if we are not during nomination and we know there won't be further candidates,
21952208
# there are no checks waiting or in-progress,
@@ -2515,8 +2528,19 @@ defmodule ExICE.Priv.ICEAgent do
25152528
end
25162529
end
25172530

2531+
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
25182532
defp update_connection_state(%__MODULE__{state: :checking} = ice_agent) do
25192533
cond do
2534+
# in aggressive nomination, we might move directly from checking to completed
2535+
ice_agent.selected_pair_id != nil and ice_agent.eoc == true and
2536+
ice_agent.gathering_state == :complete and Checklist.finished?(ice_agent.checklist) ->
2537+
Logger.debug("""
2538+
Found a valid pair, there won't be any further local or remote candidates. \
2539+
Changing connection state to complete.\
2540+
""")
2541+
2542+
change_connection_state(ice_agent, :completed)
2543+
25202544
Checklist.get_valid_pair(ice_agent.checklist) != nil ->
25212545
Logger.debug("Found a valid pair. Changing connection state to connected")
25222546
change_connection_state(ice_agent, :connected)
@@ -2550,7 +2574,7 @@ defmodule ExICE.Priv.ICEAgent do
25502574
Checklist.finished?(ice_agent.checklist) ->
25512575
change_connection_state(ice_agent, :failed)
25522576

2553-
# Assuming the controlling side uses regulard nomination,
2577+
# Assuming the controlling side uses regular nomination,
25542578
# the controlled side could move to the completed
25552579
# state as soon as it receives nomination request (or after
25562580
# successful triggered check caused by nomination request).
@@ -2570,9 +2594,12 @@ defmodule ExICE.Priv.ICEAgent do
25702594

25712595
change_connection_state(ice_agent, :completed)
25722596

2573-
ice_agent.role == :controlling and ice_agent.selected_pair_id != nil ->
2597+
ice_agent.role == :controlling and ice_agent.selected_pair_id != nil and
2598+
ice_agent.eoc == true and ice_agent.gathering_state == :complete and
2599+
Checklist.finished?(ice_agent.checklist) ->
25742600
Logger.debug("""
2575-
We have selected pair and we are the controlling agent. Changing state to completed.\
2601+
Finished all conn checks, there won't be any further local or remote candidates
2602+
and we have selected pair. Changing connection state to completed.\
25762603
""")
25772604

25782605
change_connection_state(ice_agent, :completed)
@@ -2727,7 +2754,14 @@ defmodule ExICE.Priv.ICEAgent do
27272754

27282755
# we can nominate only when being the controlling agent
27292756
# the controlled agent uses nominate? flag according to 7.3.1.5
2730-
nominate = pair.nominate? and ice_agent.role == :controlling
2757+
nominate =
2758+
ice_agent.role == :controlling and (pair.nominate? or ice_agent.aggressive_nomination)
2759+
2760+
# set nominate? flag in case we are running aggressive nomination
2761+
# but don't override it if we are controlled agent and it was already set to true
2762+
pair = %CandidatePair{pair | nominate?: pair.nominate? || nominate}
2763+
ice_agent = put_in(ice_agent.checklist[pair.id], pair)
2764+
27312765
req = binding_request(ice_agent, nominate)
27322766

27332767
raw_req = Message.encode(req)

0 commit comments

Comments
 (0)