Skip to content

Commit 0845eb9

Browse files
committed
Ignore data frome stale sockets, timers, etc. Clean up state when connection is failed or completed
1 parent bed3d9c commit 0845eb9

File tree

2 files changed

+222
-27
lines changed

2 files changed

+222
-27
lines changed

lib/ex_ice/priv/ice_agent.ex

Lines changed: 128 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ defmodule ExICE.Priv.ICEAgent do
220220
end
221221

222222
@spec set_remote_credentials(t(), binary(), binary()) :: t()
223+
def set_remote_credentials(%__MODULE__{state: :failed} = ice_agent, _, _) do
224+
Logger.debug("Tried to set remote credentials in failed state. ICE restart needed. Ignoring.")
225+
ice_agent
226+
end
227+
223228
def set_remote_credentials(
224229
%__MODULE__{remote_ufrag: nil, remote_pwd: nil} = ice_agent,
225230
ufrag,
@@ -249,6 +254,11 @@ defmodule ExICE.Priv.ICEAgent do
249254
end
250255

251256
@spec gather_candidates(t()) :: t()
257+
def gather_candidates(%__MODULE__{state: :failed} = ice_agent) do
258+
Logger.warning("Can't gather candidates in state failed. ICE restart needed. Ignoring.")
259+
ice_agent
260+
end
261+
252262
def gather_candidates(%__MODULE__{gathering_state: :gathering} = ice_agent) do
253263
Logger.warning("Can't gather candidates. Gathering already in progress. Ignoring.")
254264
ice_agent
@@ -311,6 +321,12 @@ defmodule ExICE.Priv.ICEAgent do
311321
end
312322

313323
@spec add_remote_candidate(t(), Candidate.t()) :: t()
324+
def add_remote_candidate(%__MODULE__{state: :failed} = ice_agent, _) do
325+
# Completed state will be cought by the next clause
326+
Logger.debug("Can't add remote candidate in state failed. ICE restart needed. Ignoring.")
327+
ice_agent
328+
end
329+
314330
def add_remote_candidate(%__MODULE__{eoc: true} = ice_agent, remote_cand) do
315331
Logger.warning("""
316332
Received remote candidate after end-of-candidates. Ignoring.
@@ -361,6 +377,11 @@ defmodule ExICE.Priv.ICEAgent do
361377
end
362378

363379
@spec end_of_candidates(t()) :: t()
380+
def end_of_candidates(%__MODULE__{state: :failed} = ice_agent) do
381+
Logger.debug("Can't set end-of-candidates flag in state failed. Ignoring.")
382+
ice_agent
383+
end
384+
364385
def end_of_candidates(%__MODULE__{role: :controlled} = ice_agent) do
365386
Logger.debug("Setting end-of-candidates flag.")
366387
ice_agent = %{ice_agent | eoc: true}
@@ -418,7 +439,7 @@ defmodule ExICE.Priv.ICEAgent do
418439

419440
@spec handle_ta_timeout(t()) :: t()
420441
def handle_ta_timeout(%__MODULE__{state: state} = ice_agent)
421-
when state.state in [:completed, :failed] do
442+
when state in [:completed, :failed] do
422443
Logger.warning("""
423444
Ta timer fired in unexpected state: #{state}.
424445
Trying to update gathering and connection states.
@@ -477,6 +498,11 @@ defmodule ExICE.Priv.ICEAgent do
477498
end
478499

479500
@spec handle_eoc_timeout(t()) :: t()
501+
def handle_eoc_timeout(%__MODULE__{state: :failed} = ice_agent) do
502+
Logger.debug("EOC timer fired but we are in the failed state. Ignoring.")
503+
%{ice_agent | eoc_timer: nil}
504+
end
505+
480506
def handle_eoc_timeout(%{eoc: true} = ice_agent) do
481507
Logger.debug("EOC timer fired but EOC flag is already set. Ignoring.")
482508
%{ice_agent | eoc_timer: nil}
@@ -489,6 +515,12 @@ defmodule ExICE.Priv.ICEAgent do
489515
end
490516

491517
@spec handle_pair_timeout(t()) :: t()
518+
def handle_pair_timeout(%__MODULE__{state: state} = ice_agent)
519+
when state in [:failed, :completed] do
520+
Logger.debug("Pair timeout timer fired in state: #{state}. Ignoring.")
521+
ice_agent
522+
end
523+
492524
def handle_pair_timeout(ice_agent) do
493525
start_pair_timer()
494526

@@ -564,8 +596,16 @@ defmodule ExICE.Priv.ICEAgent do
564596
{:ok, _pair} ->
565597
ice_agent
566598

599+
:error when ice_agent.state in [:failed, :completed] ->
600+
Logger.warning("""
601+
Received keepalive request for non-existant candidate pair but we are in state: #{ice_agent.state}. \
602+
Ignoring.\
603+
""")
604+
605+
ice_agent
606+
567607
:error ->
568-
Logger.warning("Received keepalive request for non-existant candidate pair")
608+
Logger.warning("Received keepalive request for non-existant candidate pair. Ignoring.")
569609
ice_agent
570610
end
571611
end
@@ -577,6 +617,10 @@ defmodule ExICE.Priv.ICEAgent do
577617
:inet.port_number(),
578618
binary()
579619
) :: t()
620+
def handle_udp(%{state: :failed} = ice_agent, _socket, _src_ip, _src_port, _packet) do
621+
ice_agent
622+
end
623+
580624
def handle_udp(ice_agent, socket, src_ip, src_port, packet) do
581625
turn_tr_id = {socket, {src_ip, src_port}}
582626
turn_tr = Map.get(ice_agent.gathering_transactions, turn_tr_id)
@@ -588,19 +632,58 @@ defmodule ExICE.Priv.ICEAgent do
588632
handle_turn_gathering_transaction_response(ice_agent, turn_tr_id, turn_tr, packet)
589633

590634
from_turn?(ice_agent, src_ip, src_port) ->
591-
handle_turn_message(ice_agent, socket, src_ip, src_port, packet)
635+
local_cands = Map.values(ice_agent.local_cands)
636+
637+
case find_relay_cand_by_socket(local_cands, socket) do
638+
nil ->
639+
Logger.debug("""
640+
Couldn't find relay candidate for:
641+
socket: #{inspect(socket)}
642+
src address: #{inspect({src_ip, src_port})}.
643+
Ignoring incoming TURN message.
644+
""")
645+
646+
ice_agent
647+
648+
relay_cand ->
649+
handle_turn_message(ice_agent, relay_cand, src_ip, src_port, packet)
650+
end
592651

593652
ExSTUN.stun?(packet) ->
594-
handle_stun_message(ice_agent, socket, src_ip, src_port, packet)
653+
local_cands = Map.values(ice_agent.local_cands)
654+
655+
case find_host_cand(local_cands, socket) do
656+
nil ->
657+
Logger.debug("""
658+
Couldn't find host candidate for #{inspect(src_ip)}:#{src_port}. \
659+
Ignoring incoming STUN message.\
660+
""")
661+
662+
ice_agent
663+
664+
host_cand ->
665+
handle_stun_message(ice_agent, host_cand, src_ip, src_port, packet)
666+
end
595667

596668
true ->
597-
local_cand = find_host_cand(Map.values(ice_agent.local_cands), socket)
598-
remote_cand = find_remote_cand(Map.values(ice_agent.remote_cands), src_ip, src_port)
669+
with remote_cands <- Map.values(ice_agent.remote_cands),
670+
%_{} = local_cand <- find_host_cand(Map.values(ice_agent.local_cands), socket),
671+
%_{} = remote_cand <- find_remote_cand(remote_cands, src_ip, src_port) do
672+
%CandidatePair{} =
673+
pair = Checklist.find_pair(ice_agent.checklist, local_cand.base.id, remote_cand.id)
599674

600-
%CandidatePair{} =
601-
pair = Checklist.find_pair(ice_agent.checklist, local_cand.base.id, remote_cand.id)
675+
handle_data_message(ice_agent, pair, packet)
676+
else
677+
_ ->
678+
Logger.debug("""
679+
Couldn't find host or remote candidate for:
680+
socket: #{inspect(socket)}
681+
src address: #{inspect({src_ip, src_port})}.
682+
Ignoring incoming data message.
683+
""")
602684

603-
handle_data_message(ice_agent, pair, packet)
685+
ice_agent
686+
end
604687
end
605688
end
606689

@@ -900,9 +983,7 @@ defmodule ExICE.Priv.ICEAgent do
900983
end
901984
end
902985

903-
defp handle_turn_message(ice_agent, socket, src_ip, src_port, packet) do
904-
%cand_mod{} = cand = find_relay_cand_by_socket(Map.values(ice_agent.local_cands), socket)
905-
986+
defp handle_turn_message(ice_agent, %cand_mod{} = cand, src_ip, src_port, packet) do
906987
case cand_mod.receive_data(cand, src_ip, src_port, packet) do
907988
{:ok, cand} ->
908989
put_in(ice_agent.local_cands[cand.base.id], cand)
@@ -935,23 +1016,10 @@ defmodule ExICE.Priv.ICEAgent do
9351016
end
9361017
end
9371018

938-
defp handle_stun_message(ice_agent, socket, src_ip, src_port, packet) do
1019+
defp handle_stun_message(ice_agent, host_cand, src_ip, src_port, packet) do
9391020
case ExSTUN.Message.decode(packet) do
9401021
{:ok, msg} ->
941-
local_cands = Map.values(ice_agent.local_cands)
942-
943-
case find_host_cand(local_cands, socket) do
944-
nil ->
945-
Logger.debug("""
946-
Couldn't find host candidate for #{inspect(src_ip)}:#{src_port}. \
947-
Ignoring incoming STUN message.\
948-
""")
949-
950-
ice_agent
951-
952-
local_cand ->
953-
do_handle_stun_message(ice_agent, local_cand, src_ip, src_port, msg)
954-
end
1022+
do_handle_stun_message(ice_agent, host_cand, src_ip, src_port, msg)
9551023

9561024
{:error, reason} ->
9571025
Logger.warning("Couldn't decode stun message: #{inspect(reason)}")
@@ -1957,7 +2025,38 @@ defmodule ExICE.Priv.ICEAgent do
19572025

19582026
@doc false
19592027
@spec change_connection_state(t(), atom()) :: t()
2028+
def change_connection_state(ice_agent, :failed) do
2029+
Enum.each(ice_agent.sockets, fn socket ->
2030+
:ok = ice_agent.transport_module.close(socket)
2031+
end)
2032+
2033+
ice_agent =
2034+
%{
2035+
ice_agent
2036+
| sockets: [],
2037+
gathering_transactions: %{},
2038+
selected_pair_id: nil,
2039+
conn_checks: %{},
2040+
checklist: %{},
2041+
local_cands: %{},
2042+
remote_cands: %{},
2043+
local_ufrag: nil,
2044+
local_pwd: nil,
2045+
remote_ufrag: nil,
2046+
remote_pwd: nil,
2047+
eoc: false,
2048+
nominating?: {false, nil}
2049+
}
2050+
|> disable_timer()
2051+
2052+
do_change_connection_state(ice_agent, :failed)
2053+
end
2054+
19602055
def change_connection_state(ice_agent, new_conn_state) do
2056+
do_change_connection_state(ice_agent, new_conn_state)
2057+
end
2058+
2059+
defp do_change_connection_state(ice_agent, new_conn_state) do
19612060
Logger.debug("Connection state change: #{ice_agent.state} -> #{new_conn_state}")
19622061
notify(ice_agent.on_connection_state_change, {:connection_state_change, new_conn_state})
19632062
%__MODULE__{ice_agent | state: new_conn_state}
@@ -2135,6 +2234,8 @@ defmodule ExICE.Priv.ICEAgent do
21352234
%{ice_agent | ta_timer: timer}
21362235
end
21372236

2237+
defp disable_timer(%{ta_timer: nil} = ice_agent), do: ice_agent
2238+
21382239
defp disable_timer(ice_agent) do
21392240
Process.cancel_timer(ice_agent.ta_timer)
21402241

test/priv/ice_agent_test.exs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,78 @@ defmodule ExICE.Priv.ICEAgentTest do
952952
assert ice_agent.state == :failed
953953
end
954954

955+
test "cleans up agent state when the connection fails" do
956+
remote_cand = ExICE.Candidate.new(:host, address: {192, 168, 0, 3}, port: 8445)
957+
958+
ice_agent =
959+
ICEAgent.new(
960+
controlling_process: self(),
961+
role: :controlling,
962+
transport_module: Transport.Mock,
963+
if_discovery_module: IfDiscovery.Mock
964+
)
965+
|> ICEAgent.set_remote_credentials("remoteufrag", "remotepwd")
966+
|> ICEAgent.gather_candidates()
967+
|> ICEAgent.add_remote_candidate(remote_cand)
968+
969+
# save creds as they will be cleared after moving to the failed state
970+
local_ufrag = ice_agent.local_ufrag
971+
local_pwd = ice_agent.local_pwd
972+
973+
[socket] = ice_agent.sockets
974+
975+
# mark pair as failed
976+
[pair] = Map.values(ice_agent.checklist)
977+
ice_agent = put_in(ice_agent.checklist[pair.id], %{pair | state: :failed})
978+
979+
# set eoc flag
980+
ice_agent = ICEAgent.end_of_candidates(ice_agent)
981+
982+
# agent should have moved to the failed state
983+
assert ice_agent.state == :failed
984+
assert ice_agent.sockets == []
985+
assert ice_agent.local_cands == %{}
986+
assert ice_agent.remote_cands == %{}
987+
assert ice_agent.gathering_transactions == %{}
988+
assert ice_agent.selected_pair_id == nil
989+
assert ice_agent.conn_checks == %{}
990+
assert ice_agent.checklist == %{}
991+
assert ice_agent.local_ufrag == nil
992+
assert ice_agent.local_pwd == nil
993+
assert ice_agent.remote_ufrag == nil
994+
assert ice_agent.remote_pwd == nil
995+
assert ice_agent.eoc == false
996+
assert ice_agent.nominating? == {false, nil}
997+
998+
# assert that handle_udp ignores incoming data i.e. the state of ice agent didn't change
999+
new_ice_agent =
1000+
ICEAgent.handle_udp(ice_agent, socket, remote_cand.address, remote_cand.port, "some data")
1001+
1002+
assert ice_agent == new_ice_agent
1003+
1004+
# the same with incoming binding request
1005+
req =
1006+
binding_request(
1007+
ice_agent.role,
1008+
ice_agent.tiebreaker,
1009+
"remoteufrag",
1010+
local_ufrag,
1011+
local_pwd
1012+
)
1013+
1014+
new_ice_agent =
1015+
ICEAgent.handle_udp(ice_agent, socket, remote_cand.address, remote_cand.port, req)
1016+
1017+
assert ice_agent == new_ice_agent
1018+
1019+
# and handle_ta_timeout
1020+
new_ice_agent = ICEAgent.handle_ta_timeout(ice_agent)
1021+
assert ice_agent == new_ice_agent
1022+
end
1023+
1024+
test "cleans up agent state when the connection completes" do
1025+
end
1026+
9551027
@stun_ip {192, 168, 0, 3}
9561028
@stun_ip_str :inet.ntoa(@stun_ip)
9571029
@stun_port 19_302
@@ -1439,6 +1511,28 @@ defmodule ExICE.Priv.ICEAgentTest do
14391511
Message.new(%Type{class: :indication, method: :binding}) |> Message.encode()
14401512
end
14411513

1514+
defp binding_request(role, tiebreaker, local_ufrag, remote_ufrag, remote_pwd) do
1515+
ice_attrs =
1516+
if role == :controlled do
1517+
[%ICEControlling{tiebreaker: tiebreaker + 1}, %UseCandidate{}]
1518+
else
1519+
[%ICEControlled{tiebreaker: tiebreaker - 1}]
1520+
end
1521+
1522+
attrs =
1523+
[
1524+
%Username{value: "#{remote_ufrag}:#{local_ufrag}"},
1525+
%Priority{priority: 1234}
1526+
] ++ ice_attrs
1527+
1528+
request =
1529+
Message.new(%Type{class: :request, method: :binding}, attrs)
1530+
|> Message.with_integrity(remote_pwd)
1531+
|> Message.with_fingerprint()
1532+
1533+
Message.encode(request)
1534+
end
1535+
14421536
defp binding_response(t_id, transport_module, socket, remote_pwd) do
14431537
{:ok, {sock_ip, sock_port}} = transport_module.sockname(socket)
14441538

0 commit comments

Comments
 (0)