Skip to content

Commit 9a4eeac

Browse files
authored
Fix sending and handling keepalives (#62)
1 parent 4c51906 commit 9a4eeac

File tree

6 files changed

+253
-68
lines changed

6 files changed

+253
-68
lines changed

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.8.2"}
33+
{:ex_ice, "~> 0.8.3"}
3434
]
3535
end
3636
```

lib/ex_ice/ice_agent.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,8 @@ defmodule ExICE.ICEAgent do
386386
end
387387

388388
@impl true
389-
def handle_info({:keepalive, id}, state) do
390-
ice_agent = ExICE.Priv.ICEAgent.handle_keepalive(state.ice_agent, id)
389+
def handle_info({:keepalive_timeout, id}, state) do
390+
ice_agent = ExICE.Priv.ICEAgent.handle_keepalive_timeout(state.ice_agent, id)
391391
{:noreply, %{state | ice_agent: ice_agent}}
392392
end
393393

lib/ex_ice/priv/candidate_pair.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ defmodule ExICE.Priv.CandidatePair do
6666
end
6767

6868
def schedule_keepalive(pair, dest) do
69-
ref = Process.send_after(dest, {:keepalive, pair.id}, @tr_timeout)
69+
ref = Process.send_after(dest, {:keepalive_timeout, pair.id}, @tr_timeout)
7070
%{pair | keepalive_timer: ref}
7171
end
7272

lib/ex_ice/priv/ice_agent.ex

Lines changed: 111 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -673,24 +673,24 @@ defmodule ExICE.Priv.ICEAgent do
673673
end
674674
end
675675

676-
@spec handle_keepalive(t(), integer()) :: t()
677-
def handle_keepalive(%__MODULE__{selected_pair_id: id} = ice_agent, id) do
676+
@spec handle_keepalive_timeout(t(), integer()) :: t()
677+
def handle_keepalive_timeout(%__MODULE__{selected_pair_id: id} = ice_agent, id) do
678678
# if pair was selected, send keepalives only on that pair
679679
s_pair = Map.fetch!(ice_agent.checklist, id)
680680
pair = CandidatePair.schedule_keepalive(s_pair)
681681
ice_agent = %__MODULE__{ice_agent | checklist: Map.put(ice_agent.checklist, id, pair)}
682682
send_keepalive(ice_agent, ice_agent.checklist[id])
683683
end
684684

685-
def handle_keepalive(%__MODULE__{selected_pair_id: s_pair_id} = ice_agent, _id)
685+
def handle_keepalive_timeout(%__MODULE__{selected_pair_id: s_pair_id} = ice_agent, _id)
686686
when not is_nil(s_pair_id) do
687687
# note: current implementation assumes that, if selected pair exists, none of the already existing
688688
# valid pairs will ever become selected (only new appearing valid pairs)
689689
# that's why there's no call to `CandidatePair.schedule_keepalive/1`
690690
ice_agent
691691
end
692692

693-
def handle_keepalive(ice_agent, id) do
693+
def handle_keepalive_timeout(ice_agent, id) do
694694
# TODO: keepalives should be sent only if no data has been sent for @tr_timeout
695695
# atm, we send keepalives anyways, also it might be better to pace them with ta_timer
696696
# TODO: candidates not in a valid pair also should be kept alive (RFC 8445, sect 5.1.1.4)
@@ -1246,16 +1246,7 @@ defmodule ExICE.Priv.ICEAgent do
12461246
%Type{class: class, method: :binding}
12471247
when is_response(class) and is_map_key(ice_agent.keepalives, msg.transaction_id) ->
12481248
# TODO: this a good basis to implement consent freshness
1249-
Logger.debug("""
1250-
Received keepalive response from from #{inspect({src_ip, src_port})}, \
1251-
on: #{inspect({local_cand.base.base_address, local_cand.base.base_port})} \
1252-
""")
1253-
1254-
{pair_id, ice_agent} = pop_in(ice_agent.keepalives[msg.transaction_id])
1255-
1256-
pair = Map.fetch!(ice_agent.checklist, pair_id)
1257-
pair = %CandidatePair{pair | last_seen: now()}
1258-
put_in(ice_agent.checklist[pair.id], pair)
1249+
handle_keepalive_response(ice_agent, local_cand, src_ip, src_port, msg)
12591250

12601251
%Type{class: class, method: :binding} when is_response(class) ->
12611252
Logger.warning("""
@@ -1475,7 +1466,6 @@ defmodule ExICE.Priv.ICEAgent do
14751466
end
14761467

14771468
## BINDING RESPONSE HANDLING ##
1478-
14791469
defp handle_conn_check_response(ice_agent, local_cand, src_ip, src_port, msg) do
14801470
{%{pair_id: pair_id}, conn_checks} = Map.pop!(ice_agent.conn_checks, msg.transaction_id)
14811471
ice_agent = %__MODULE__{ice_agent | conn_checks: conn_checks}
@@ -1492,7 +1482,7 @@ defmodule ExICE.Priv.ICEAgent do
14921482
cc_local_cand = Map.fetch!(ice_agent.local_cands, conn_check_pair.local_cand_id)
14931483
cc_remote_cand = Map.fetch!(ice_agent.remote_cands, conn_check_pair.remote_cand_id)
14941484

1495-
Logger.warning("""
1485+
Logger.debug("""
14961486
Ignoring conn check response, non-symmetric src and dst addresses.
14971487
Sent from: #{inspect({cc_local_cand.base.base_address, cc_local_cand.base.base_port})}, \
14981488
to: #{inspect({cc_remote_cand.address, cc_remote_cand.port})}
@@ -1647,6 +1637,66 @@ defmodule ExICE.Priv.ICEAgent do
16471637
ice_agent
16481638
end
16491639

1640+
defp handle_keepalive_response(
1641+
ice_agent,
1642+
local_cand,
1643+
src_ip,
1644+
src_port,
1645+
%Message{type: %Type{class: :success_response}} = msg
1646+
) do
1647+
{pair_id, ice_agent} = pop_in(ice_agent.keepalives[msg.transaction_id])
1648+
pair = Map.fetch!(ice_agent.checklist, pair_id)
1649+
1650+
with true <- symmetric?(ice_agent, local_cand.base.socket, {src_ip, src_port}, pair),
1651+
:ok <- authenticate_msg(msg, ice_agent.remote_pwd) do
1652+
Logger.debug("Received keepalive success response on: #{pair_info(ice_agent, pair)}")
1653+
pair = %CandidatePair{pair | last_seen: now()}
1654+
put_in(ice_agent.checklist[pair.id], pair)
1655+
else
1656+
false ->
1657+
ka_local_cand = Map.fetch!(ice_agent.local_cands, pair.local_cand_id)
1658+
ka_remote_cand = Map.fetch!(ice_agent.remote_cands, pair.remote_cand_id)
1659+
1660+
Logger.debug("""
1661+
Ignoring keepalive success response, non-symmetric src and dst addresses.
1662+
Sent from: #{inspect({ka_local_cand.base.base_address, ka_local_cand.base.base_port})}, \
1663+
to: #{inspect({ka_remote_cand.address, ka_remote_cand.port})}
1664+
Recv from: #{inspect({src_ip, src_port})}, on: #{inspect({local_cand.base.base_address, local_cand.base.base_port})} \
1665+
Not refreshing last_seen time. \
1666+
""")
1667+
1668+
ice_agent
1669+
1670+
{:error, reason} ->
1671+
Logger.debug("""
1672+
Couldn't authenticate keepalive success response, reason: #{reason}. \
1673+
Not refreshing last_seen time.\
1674+
""")
1675+
1676+
ice_agent
1677+
end
1678+
end
1679+
1680+
defp handle_keepalive_response(
1681+
ice_agent,
1682+
local_cand,
1683+
src_ip,
1684+
src_port,
1685+
%Message{type: %Type{class: :error_response}} = msg
1686+
) do
1687+
{pair_id, ice_agent} = pop_in(ice_agent.keepalives[msg.transaction_id])
1688+
pair = Map.fetch!(ice_agent.checklist, pair_id)
1689+
1690+
Logger.debug("""
1691+
Received keepalive error response from #{inspect({src_ip, src_port})}, \
1692+
on: #{inspect({local_cand.base.base_address, local_cand.base.base_port})}. \
1693+
pair: #{pair_info(ice_agent, pair)} \
1694+
Not refreshing last_seen time. \
1695+
""")
1696+
1697+
ice_agent
1698+
end
1699+
16501700
# Adds valid pair according to sec 7.2.5.3.2
16511701
# TODO sec. 7.2.5.3.3
16521702
# The agent MUST set the states for all other Frozen candidate pairs in
@@ -2090,8 +2140,19 @@ defmodule ExICE.Priv.ICEAgent do
20902140
{ufrag, pwd}
20912141
end
20922142

2093-
defp authenticate_msg(msg, local_pwd) do
2094-
with :ok <- Message.authenticate(msg, local_pwd),
2143+
defp pair_info(ice_agent, pair) do
2144+
local_cand = Map.fetch!(ice_agent.local_cands, pair.local_cand_id)
2145+
remote_cand = Map.fetch!(ice_agent.remote_cands, pair.remote_cand_id)
2146+
2147+
"""
2148+
#{pair.id} \
2149+
l: #{:inet.ntoa(local_cand.base.address)}:#{local_cand.base.port} \
2150+
r: #{:inet.ntoa(remote_cand.address)}:#{remote_cand.port} \
2151+
"""
2152+
end
2153+
2154+
defp authenticate_msg(msg, pwd) do
2155+
with :ok <- Message.authenticate(msg, pwd),
20952156
:ok <- Message.check_fingerprint(msg) do
20962157
:ok
20972158
else
@@ -2402,17 +2463,11 @@ defmodule ExICE.Priv.ICEAgent do
24022463
end
24032464

24042465
defp send_keepalive(ice_agent, pair) do
2405-
Logger.debug("Sending keepalive")
2466+
Logger.debug("Sending keepalive on #{pair_info(ice_agent, pair)}")
24062467
local_cand = Map.fetch!(ice_agent.local_cands, pair.local_cand_id)
24072468
remote_cand = Map.fetch!(ice_agent.remote_cands, pair.remote_cand_id)
24082469

2409-
type = %Type{class: :request, method: :binding}
2410-
2411-
req =
2412-
type
2413-
|> Message.new()
2414-
|> Message.with_integrity(ice_agent.remote_pwd)
2415-
|> Message.with_fingerprint()
2470+
req = binding_request(ice_agent, false)
24162471

24172472
dst = {remote_cand.address, remote_cand.port}
24182473

@@ -2430,39 +2485,10 @@ defmodule ExICE.Priv.ICEAgent do
24302485
local_cand = Map.fetch!(ice_agent.local_cands, pair.local_cand_id)
24312486
remote_cand = Map.fetch!(ice_agent.remote_cands, pair.remote_cand_id)
24322487

2433-
type = %Type{class: :request, method: :binding}
2434-
2435-
role_attr =
2436-
if ice_agent.role == :controlling do
2437-
%ICEControlling{tiebreaker: ice_agent.tiebreaker}
2438-
else
2439-
%ICEControlled{tiebreaker: ice_agent.tiebreaker}
2440-
end
2441-
2442-
# priority sent to the other side has to be
2443-
# computed with the candidate type preference of
2444-
# peer-reflexive; refer to sec 7.1.1
2445-
priority = Candidate.priority(:prflx)
2446-
2447-
attrs = [
2448-
%Username{value: "#{ice_agent.remote_ufrag}:#{ice_agent.local_ufrag}"},
2449-
%Priority{priority: priority},
2450-
role_attr
2451-
]
2452-
24532488
# we can nominate only when being the controlling agent
24542489
# the controlled agent uses nominate? flag according to 7.3.1.5
2455-
attrs =
2456-
if pair.nominate? and ice_agent.role == :controlling do
2457-
attrs ++ [%UseCandidate{}]
2458-
else
2459-
attrs
2460-
end
2461-
2462-
req =
2463-
Message.new(type, attrs)
2464-
|> Message.with_integrity(ice_agent.remote_pwd)
2465-
|> Message.with_fingerprint()
2490+
nominate = pair.nominate? and ice_agent.role == :controlling
2491+
req = binding_request(ice_agent, nominate)
24662492

24672493
raw_req = Message.encode(req)
24682494

@@ -2489,6 +2515,34 @@ defmodule ExICE.Priv.ICEAgent do
24892515
end
24902516
end
24912517

2518+
defp binding_request(ice_agent, nominate) do
2519+
type = %Type{class: :request, method: :binding}
2520+
2521+
role_attr =
2522+
if ice_agent.role == :controlling do
2523+
%ICEControlling{tiebreaker: ice_agent.tiebreaker}
2524+
else
2525+
%ICEControlled{tiebreaker: ice_agent.tiebreaker}
2526+
end
2527+
2528+
# priority sent to the other side has to be
2529+
# computed with the candidate type preference of
2530+
# peer-reflexive; refer to sec 7.1.1
2531+
priority = Candidate.priority(:prflx)
2532+
2533+
attrs = [
2534+
%Username{value: "#{ice_agent.remote_ufrag}:#{ice_agent.local_ufrag}"},
2535+
%Priority{priority: priority},
2536+
role_attr
2537+
]
2538+
2539+
attrs = if nominate, do: attrs ++ [%UseCandidate{}], else: attrs
2540+
2541+
Message.new(type, attrs)
2542+
|> Message.with_integrity(ice_agent.remote_pwd)
2543+
|> Message.with_fingerprint()
2544+
end
2545+
24922546
defp do_send(ice_agent, %cand_mod{} = local_cand, dst, data, retry \\ true) do
24932547
{dst_ip, dst_port} = dst
24942548

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.8.2"
4+
@version "0.8.3"
55
@source_url "https://github.com/elixir-webrtc/ex_ice"
66

77
def project do

0 commit comments

Comments
 (0)