Skip to content

Commit 49b4be8

Browse files
committed
Fix crash on send error and keepalive response on pruned pair, release 0.16.0
1 parent b0de12e commit 49b4be8

6 files changed

Lines changed: 254 additions & 61 deletions

File tree

.credo.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
#
123123
{Credo.Check.Refactor.Apply, []},
124124
{Credo.Check.Refactor.CondStatements, []},
125-
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 12]},
125+
{Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 11]},
126126
{Credo.Check.Refactor.FunctionArity, []},
127127
{Credo.Check.Refactor.LongQuoteBlocks, []},
128128
{Credo.Check.Refactor.MatchInCondition, []},

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ as WebRTC multiplexes traffic on a single socket but PRs are welcomed
3030
```elixir
3131
def deps do
3232
[
33-
{:ex_ice, "~> 0.15.0"}
33+
{:ex_ice, "~> 0.16.0"}
3434
]
3535
end
3636
```

lib/ex_ice/priv/ice_agent.ex

Lines changed: 103 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -943,63 +943,92 @@ defmodule ExICE.Priv.ICEAgent do
943943

944944
def handle_ex_turn_msg(ice_agent, client_ref, msg) do
945945
tr_id_tr = find_gathering_transaction(ice_agent.gathering_transactions, client_ref)
946-
947946
cand = find_relay_cand_by_client(Map.values(ice_agent.local_cands), client_ref)
948947

949948
case {tr_id_tr, cand} do
950-
{nil, nil} ->
951-
ice_agent
949+
{nil, nil} -> ice_agent
950+
{{tr_id, tr}, nil} -> handle_ex_turn_msg_for_gathering_tx(ice_agent, tr_id, tr, msg)
951+
{nil, cand} -> handle_ex_turn_msg_for_relay_cand(ice_agent, cand, msg)
952+
end
953+
end
952954

953-
{{tr_id, tr}, nil} ->
954-
case ExTURN.Client.handle_message(tr.client, msg) do
955-
{:ok, client} ->
956-
tr = %{tr | client: client}
957-
put_in(ice_agent.gathering_transactions[tr_id], tr)
955+
defp handle_ex_turn_msg_for_gathering_tx(ice_agent, tr_id, tr, msg) do
956+
case ExTURN.Client.handle_message(tr.client, msg) do
957+
{:ok, client} ->
958+
put_in(ice_agent.gathering_transactions[tr_id], %{tr | client: client})
958959

959-
{:send, dst, data, client} ->
960-
tr = %{tr | client: client}
961-
:ok = ice_agent.transport_module.send(tr.socket, dst, data)
962-
put_in(ice_agent.gathering_transactions[tr_id], tr)
960+
{:send, dst, data, client} ->
961+
send_on_gathering_tx(ice_agent, tr_id, %{tr | client: client}, dst, data)
963962

964-
{:error, _reason, _client} ->
965-
{_, ice_agent} = pop_in(ice_agent.gathering_transactions[tr_id])
966-
update_gathering_state(ice_agent)
967-
end
963+
{:error, _reason, _client} ->
964+
drop_gathering_transaction(ice_agent, tr_id)
965+
end
966+
end
968967

969-
{nil, cand} ->
970-
case ExTURN.Client.handle_message(cand.client, msg) do
971-
{:ok, client} ->
972-
cand = %{cand | client: client}
973-
put_in(ice_agent.local_cands[cand.base.id], cand)
974-
975-
{:send, dst, data, client} ->
976-
cand = %{cand | client: client}
977-
ice_agent = put_in(ice_agent.local_cands[cand.base.id], cand)
978-
# we can't use do_send here as it will try to create permission for the turn address
979-
:ok = ice_agent.transport_module.send(cand.base.socket, dst, data)
980-
ice_agent
968+
defp send_on_gathering_tx(ice_agent, tr_id, tr, dst, data) do
969+
case ice_agent.transport_module.send(tr.socket, dst, data) do
970+
:ok ->
971+
put_in(ice_agent.gathering_transactions[tr_id], tr)
972+
973+
{:error, _reason} ->
974+
drop_gathering_transaction(ice_agent, tr_id)
975+
end
976+
end
977+
978+
defp drop_gathering_transaction(ice_agent, tr_id) do
979+
{_, ice_agent} = pop_in(ice_agent.gathering_transactions[tr_id])
980+
update_gathering_state(ice_agent)
981+
end
981982

982-
{:permission_expired, _ip, client} ->
983-
cand = %{cand | client: client}
984-
put_in(ice_agent.local_cands[cand.base.id], cand)
983+
defp handle_ex_turn_msg_for_relay_cand(ice_agent, cand, msg) do
984+
case ExTURN.Client.handle_message(cand.client, msg) do
985+
{:ok, client} ->
986+
update_relay_cand_client(ice_agent, cand, client)
985987

986-
{:channel_expired, _addr, client} ->
987-
cand = %{cand | client: client}
988-
put_in(ice_agent.local_cands[cand.base.id], cand)
988+
{:send, dst, data, client} ->
989+
send_on_relay_cand(ice_agent, cand, client, dst, data)
989990

990-
{:error, _reason, client} ->
991-
Logger.debug("""
992-
Couldn't handle TURN message on candidate: #{inspect(cand)}. \
993-
Closing candidate.\
994-
""")
991+
{:permission_expired, _ip, client} ->
992+
update_relay_cand_client(ice_agent, cand, client)
995993

996-
cand = %{cand | client: client}
997-
ice_agent = put_in(ice_agent.local_cands[cand.base.id], cand)
998-
close_candidate(ice_agent, cand)
999-
end
994+
{:channel_expired, _addr, client} ->
995+
update_relay_cand_client(ice_agent, cand, client)
996+
997+
{:error, _reason, client} ->
998+
Logger.debug("""
999+
Couldn't handle TURN message on candidate: #{inspect(cand)}. \
1000+
Closing candidate.\
1001+
""")
1002+
1003+
ice_agent
1004+
|> update_relay_cand_client(cand, client)
1005+
|> close_candidate(cand)
10001006
end
10011007
end
10021008

1009+
# we can't use do_send here as it will try to create permission for the turn address
1010+
defp send_on_relay_cand(ice_agent, cand, client, dst, data) do
1011+
cand = %{cand | client: client}
1012+
ice_agent = put_in(ice_agent.local_cands[cand.base.id], cand)
1013+
1014+
case ice_agent.transport_module.send(cand.base.socket, dst, data) do
1015+
:ok ->
1016+
ice_agent
1017+
1018+
{:error, _reason} ->
1019+
Logger.debug("""
1020+
Couldn't send TURN message on candidate: #{inspect(cand)}. \
1021+
Closing candidate.\
1022+
""")
1023+
1024+
close_candidate(ice_agent, cand)
1025+
end
1026+
end
1027+
1028+
defp update_relay_cand_client(ice_agent, cand, client) do
1029+
put_in(ice_agent.local_cands[cand.base.id], %{cand | client: client})
1030+
end
1031+
10031032
@spec close(t()) :: t()
10041033
def close(%__MODULE__{state: :closed} = ice_agent) do
10051034
ice_agent
@@ -1283,8 +1312,16 @@ defmodule ExICE.Priv.ICEAgent do
12831312

12841313
{:send, turn_addr, data, client} ->
12851314
tr = %{tr | client: client}
1286-
:ok = ice_agent.transport_module.send(tr.socket, turn_addr, data)
1287-
put_in(ice_agent.gathering_transactions[tr_id], tr)
1315+
1316+
case ice_agent.transport_module.send(tr.socket, turn_addr, data) do
1317+
:ok ->
1318+
put_in(ice_agent.gathering_transactions[tr_id], tr)
1319+
1320+
{:error, _reason} ->
1321+
Logger.debug("Failed to create TURN allocation.")
1322+
{_, ice_agent} = pop_in(ice_agent.gathering_transactions[tr_id])
1323+
ice_agent
1324+
end
12881325

12891326
{:error, _reason, _client} ->
12901327
Logger.debug("Failed to create TURN allocation.")
@@ -2019,16 +2056,27 @@ defmodule ExICE.Priv.ICEAgent do
20192056
ice_agent
20202057
end
20212058

2022-
defp handle_keepalive_response(
2059+
defp handle_keepalive_response(ice_agent, local_cand, src_ip, src_port, msg) do
2060+
{pair_id, ice_agent} = pop_in(ice_agent.keepalives[msg.transaction_id])
2061+
2062+
case Map.fetch(ice_agent.checklist, pair_id) do
2063+
{:ok, %CandidatePair{} = pair} ->
2064+
handle_keepalive_response_on_pair(ice_agent, local_cand, src_ip, src_port, msg, pair)
2065+
2066+
:error ->
2067+
Logger.debug("Ignoring keepalive response for pruned pair #{inspect(pair_id)}")
2068+
ice_agent
2069+
end
2070+
end
2071+
2072+
defp handle_keepalive_response_on_pair(
20232073
ice_agent,
20242074
local_cand,
20252075
src_ip,
20262076
src_port,
2027-
%Message{type: %Type{class: :success_response}} = msg
2077+
%Message{type: %Type{class: :success_response}} = msg,
2078+
pair
20282079
) do
2029-
{pair_id, ice_agent} = pop_in(ice_agent.keepalives[msg.transaction_id])
2030-
%CandidatePair{} = pair = Map.fetch!(ice_agent.checklist, pair_id)
2031-
20322080
with true <- symmetric?(ice_agent, local_cand.base.socket, {src_ip, src_port}, pair),
20332081
:ok <- authenticate_msg(msg, ice_agent.remote_pwd) do
20342082
Logger.debug("Received keepalive success response on: #{pair_info(ice_agent, pair)}")
@@ -2045,7 +2093,7 @@ defmodule ExICE.Priv.ICEAgent do
20452093
ka_local_cand = Map.fetch!(ice_agent.local_cands, pair.local_cand_id)
20462094
ka_remote_cand = Map.fetch!(ice_agent.remote_cands, pair.remote_cand_id)
20472095

2048-
pair = %CandidatePair{
2096+
pair = %{
20492097
pair
20502098
| non_symmetric_responses_received: pair.non_symmetric_responses_received + 1
20512099
}
@@ -2068,20 +2116,19 @@ defmodule ExICE.Priv.ICEAgent do
20682116
Not refreshing last_seen time.\
20692117
""")
20702118

2071-
pair = %CandidatePair{pair | responses_received: pair.responses_received + 1}
2119+
pair = %{pair | responses_received: pair.responses_received + 1}
20722120
put_in(ice_agent.checklist[pair.id], pair)
20732121
end
20742122
end
20752123

2076-
defp handle_keepalive_response(
2124+
defp handle_keepalive_response_on_pair(
20772125
ice_agent,
20782126
local_cand,
20792127
src_ip,
20802128
src_port,
2081-
%Message{type: %Type{class: :error_response}} = msg
2129+
%Message{type: %Type{class: :error_response}},
2130+
pair
20822131
) do
2083-
{pair_id, ice_agent} = pop_in(ice_agent.keepalives[msg.transaction_id])
2084-
%CandidatePair{} = pair = Map.fetch!(ice_agent.checklist, pair_id)
20852132
pair = %{pair | responses_received: pair.responses_received + 1}
20862133
ice_agent = put_in(ice_agent.checklist[pair.id], pair)
20872134

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule ExICE.MixProject do
22
use Mix.Project
33

4-
@version "0.15.0"
4+
@version "0.16.0"
55
@source_url "https://github.com/elixir-webrtc/ex_ice"
66

77
def project do

test/priv/ice_agent_test.exs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,62 @@ defmodule ExICE.Priv.ICEAgentTest do
750750
assert new_pair.last_seen == pair.last_seen
751751
assert new_pair.responses_received == pair.responses_received + 1
752752
end
753+
754+
test "success response for pruned pair", %{ice_agent: ice_agent} do
755+
ice_agent = connect(ice_agent)
756+
757+
[socket] = ice_agent.sockets
758+
[remote_cand] = Map.values(ice_agent.remote_cands)
759+
[pair] = Map.values(ice_agent.checklist)
760+
761+
ice_agent = ICEAgent.handle_keepalive_timeout(ice_agent, pair.id)
762+
assert packet = Transport.Mock.recv(socket)
763+
assert {:ok, req} = ExSTUN.Message.decode(packet)
764+
765+
resp =
766+
binding_response(
767+
req.transaction_id,
768+
ice_agent.transport_module,
769+
socket,
770+
ice_agent.remote_pwd
771+
)
772+
773+
{_, ice_agent} = pop_in(ice_agent.checklist[pair.id])
774+
assert ice_agent.checklist == %{}
775+
776+
ice_agent =
777+
ICEAgent.handle_udp(ice_agent, socket, remote_cand.address, remote_cand.port, resp)
778+
779+
assert ice_agent.checklist == %{}
780+
assert ice_agent.keepalives == %{}
781+
end
782+
783+
test "error response for pruned pair", %{ice_agent: ice_agent} do
784+
ice_agent = connect(ice_agent)
785+
786+
[socket] = ice_agent.sockets
787+
[remote_cand] = Map.values(ice_agent.remote_cands)
788+
[pair] = Map.values(ice_agent.checklist)
789+
790+
ice_agent = ICEAgent.handle_keepalive_timeout(ice_agent, pair.id)
791+
assert packet = Transport.Mock.recv(socket)
792+
assert {:ok, req} = ExSTUN.Message.decode(packet)
793+
794+
resp =
795+
Message.new(req.transaction_id, %Type{class: :error_response, method: :binding}, [
796+
%ErrorCode{code: 400}
797+
])
798+
|> Message.encode()
799+
800+
{_, ice_agent} = pop_in(ice_agent.checklist[pair.id])
801+
assert ice_agent.checklist == %{}
802+
803+
ice_agent =
804+
ICEAgent.handle_udp(ice_agent, socket, remote_cand.address, remote_cand.port, resp)
805+
806+
assert ice_agent.checklist == %{}
807+
assert ice_agent.keepalives == %{}
808+
end
753809
end
754810

755811
describe "incoming binding request" do
@@ -2663,6 +2719,77 @@ defmodule ExICE.Priv.ICEAgentTest do
26632719
refute Map.has_key?(cand.client.channel_addr, channel_number)
26642720
end
26652721

2722+
test "send failure on gathering transaction via handle_ex_turn_msg", %{
2723+
ice_agent: ice_agent
2724+
} do
2725+
[socket] = ice_agent.sockets
2726+
2727+
ice_agent = ICEAgent.handle_ta_timeout(ice_agent)
2728+
req = read_allocate_request(socket)
2729+
resp = allocate_error_response(req.transaction_id)
2730+
2731+
turn_tr_id = {socket, {@turn_ip, @turn_port}}
2732+
tr = Map.fetch!(ice_agent.gathering_transactions, turn_tr_id)
2733+
2734+
Transport.Mock.fail_send(socket)
2735+
2736+
ice_agent =
2737+
ICEAgent.handle_ex_turn_msg(
2738+
ice_agent,
2739+
tr.client.ref,
2740+
{:socket_data, @turn_ip, @turn_port, resp}
2741+
)
2742+
2743+
assert ice_agent.gathering_transactions == %{}
2744+
end
2745+
2746+
test "send failure on gathering transaction via handle_udp", %{ice_agent: ice_agent} do
2747+
[socket] = ice_agent.sockets
2748+
2749+
ice_agent = ICEAgent.handle_ta_timeout(ice_agent)
2750+
req = read_allocate_request(socket)
2751+
resp = allocate_error_response(req.transaction_id)
2752+
2753+
Transport.Mock.fail_send(socket)
2754+
2755+
ice_agent = ICEAgent.handle_udp(ice_agent, socket, @turn_ip, @turn_port, resp)
2756+
2757+
assert ice_agent.gathering_transactions == %{}
2758+
2759+
assert nil ==
2760+
ice_agent.local_cands
2761+
|> Map.values()
2762+
|> Enum.find(&(&1.base.type == :relay))
2763+
end
2764+
2765+
test "send failure on relay candidate", %{ice_agent: ice_agent} do
2766+
[socket] = ice_agent.sockets
2767+
2768+
ice_agent = ICEAgent.handle_ta_timeout(ice_agent)
2769+
req = read_allocate_request(socket)
2770+
resp = allocate_error_response(req.transaction_id)
2771+
ice_agent = ICEAgent.handle_udp(ice_agent, socket, @turn_ip, @turn_port, resp)
2772+
req = read_allocate_request(socket)
2773+
resp = allocate_success_response(req.transaction_id, ice_agent.transport_module, socket)
2774+
ice_agent = ICEAgent.handle_udp(ice_agent, socket, @turn_ip, @turn_port, resp)
2775+
2776+
cand = ice_agent.local_cands |> Map.values() |> Enum.find(&(&1.base.type == :relay))
2777+
assert %ExICE.Priv.Candidate.Relay{} = cand
2778+
refute cand.base.closed?
2779+
2780+
Transport.Mock.fail_send(socket)
2781+
2782+
ice_agent =
2783+
ICEAgent.handle_ex_turn_msg(
2784+
ice_agent,
2785+
cand.client.ref,
2786+
:refresh_alloc
2787+
)
2788+
2789+
updated_cand = ice_agent.local_cands[cand.base.id]
2790+
assert updated_cand.base.closed?
2791+
end
2792+
26662793
test "invalid TURN URL" do
26672794
ice_agent =
26682795
ICEAgent.new(

0 commit comments

Comments
 (0)