Skip to content

Commit 6790fa2

Browse files
authored
Add basic valid pairs keepalives (#12)
1 parent 393849d commit 6790fa2

File tree

4 files changed

+74
-5
lines changed

4 files changed

+74
-5
lines changed

lib/candidate_pair.ex

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ defmodule ExICE.CandidatePair do
77
alias ExICE.ICEAgent
88
alias ExICE.Candidate
99

10+
# Tr timeout (keepalives) in ms
11+
@tr_timeout 15 * 1000
12+
1013
@type state() :: :waiting | :in_progress | :succeeded | :failed | :frozen
1114

1215
@type t() :: %__MODULE__{
@@ -19,7 +22,8 @@ defmodule ExICE.CandidatePair do
1922
state: state(),
2023
valid?: boolean,
2124
succeeded_pair_id: integer() | nil,
22-
discovered_pair_id: integer() | nil
25+
discovered_pair_id: integer() | nil,
26+
keepalive_timer: reference() | nil
2327
}
2428

2529
@enforce_keys [:id, :local_cand, :remote_cand, :priority]
@@ -30,7 +34,8 @@ defmodule ExICE.CandidatePair do
3034
state: :frozen,
3135
valid?: false,
3236
succeeded_pair_id: nil,
33-
discovered_pair_id: nil
37+
discovered_pair_id: nil,
38+
keepalive_timer: nil
3439
]
3540

3641
@doc false
@@ -50,6 +55,19 @@ defmodule ExICE.CandidatePair do
5055
}
5156
end
5257

58+
@spec schedule_keepalive(t(), Process.dest()) :: :ok
59+
def schedule_keepalive(pair, dest \\ self())
60+
61+
def schedule_keepalive(%{keepalive_timer: timer} = pair, dest) when is_reference(timer) do
62+
Process.cancel_timer(timer)
63+
schedule_keepalive(%{pair | keepalive_timer: nil}, dest)
64+
end
65+
66+
def schedule_keepalive(pair, dest) do
67+
ref = Process.send_after(dest, {:keepalive, pair.id}, @tr_timeout)
68+
%{pair | keepalive_timer: ref}
69+
end
70+
5371
@spec recompute_priority(t(), ICEAgent.role()) :: t()
5472
def recompute_priority(pair, role) do
5573
%__MODULE__{pair | priority: priority(role, pair.local_cand, pair.remote_cand)}

lib/ice_agent.ex

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,42 @@ defmodule ExICE.ICEAgent do
411411
end
412412
end
413413

414+
@impl true
415+
def handle_info({:keepalive, id}, %{selected_pair: s_pair} = state)
416+
when not is_nil(s_pair) and s_pair.id == id do
417+
# if pair was selected, send keepalives only on that pair
418+
pair = CandidatePair.schedule_keepalive(s_pair)
419+
send_keepalive(state.checklist[id])
420+
state = put_in(state.checklist[id], pair)
421+
{:noreply, state}
422+
end
423+
424+
@impl true
425+
def handle_info({:keepalive, _id}, %{selected_pair: s_pair} = state) when not is_nil(s_pair) do
426+
# note: current implementation assumes that, if selected pair exists, none of the already existing
427+
# valid pairs will ever become selected (only new appearing valid pairs)
428+
# that's why there's no call to `CandidatePair.schedule_keepalive/1`
429+
{:noreply, state}
430+
end
431+
432+
@impl true
433+
def handle_info({:keepalive, id}, state) do
434+
# TODO: keepalives should be send only if no data has been send for @tr_timeout
435+
# atm, we send keepalives anyways, also it might be better to pace them with ta_timer
436+
# TODO: candidates not in a valid pair also should be kept alive (RFC 8445, sect 5.1.1.4)
437+
case Map.fetch(state.checklist, id) do
438+
{:ok, pair} ->
439+
pair = CandidatePair.schedule_keepalive(pair)
440+
state = put_in(state.checklist[id], pair)
441+
send_keepalive(pair)
442+
{:noreply, state}
443+
444+
:error ->
445+
Logger.warning("Received keepalive request for non-existant candidate pair")
446+
{:noreply, state}
447+
end
448+
end
449+
414450
@impl true
415451
def handle_info({:udp, socket, src_ip, src_port, packet}, state) do
416452
if ExSTUN.is_stun(packet) do
@@ -774,6 +810,9 @@ defmodule ExICE.ICEAgent do
774810

775811
{pair_id, state} = add_valid_pair(valid_pair, conn_check_pair, checklist_pair, state)
776812

813+
pair = CandidatePair.schedule_keepalive(state.checklist[pair_id])
814+
state = put_in(state.checklist[pair_id], pair)
815+
777816
# get new conn check pair as it will have updated
778817
# discovered and succeeded pair fields
779818
conn_check_pair = Map.fetch!(state.checklist, conn_check_pair.id)
@@ -1445,6 +1484,18 @@ defmodule ExICE.ICEAgent do
14451484
%{state | ta_timer: nil}
14461485
end
14471486

1487+
defp send_keepalive(pair) do
1488+
type = %Type{class: :indication, method: :binding}
1489+
1490+
req =
1491+
type
1492+
|> Message.new()
1493+
|> Message.with_fingerprint()
1494+
1495+
dst = {pair.remote_cand.address, pair.remote_cand.port}
1496+
do_send(pair.local_cand.socket, dst, Message.encode(req))
1497+
end
1498+
14481499
@doc false
14491500
@spec send_conn_check(CandidatePair.t(), map()) :: {CandidatePair.t(), map()}
14501501
def send_conn_check(pair, state) do

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ defmodule ExICE.MixProject do
3030
defp deps do
3131
[
3232
{:ex_stun, "~> 0.1.0"},
33-
{:excoveralls, "~> 0.14.6", only: :test, runtime: false},
33+
{:excoveralls, "~> 0.15", only: :test, runtime: false},
3434
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
3535
{:credo, "~> 1.6", only: [:dev, :test], runtime: false}
3636
]

mix.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
"earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"},
66
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
77
"ex_stun": {:hex, :ex_stun, "0.1.0", "252474bf4c8519fbf4bc0fbfc6a1b846a634b1478c65dbbfb4b6ab4e33c2a95a", [:mix], [], "hexpm", "629fc8be45b624a92522f81d85ba001877b1f0745889a2419bdb678790d7480c"},
8-
"excoveralls": {:hex, :excoveralls, "0.14.6", "610e921e25b180a8538229ef547957f7e04bd3d3e9a55c7c5b7d24354abbba70", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0eceddaa9785cfcefbf3cd37812705f9d8ad34a758e513bb975b081dce4eb11e"},
8+
"excoveralls": {:hex, :excoveralls, "0.17.0", "279f124dba347903bb654bc40745c493ae265d45040001b4899ea1edf88078c7", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "08b638d114387a888f9cb8d65f2a0021ec04c3e447b793efa7c1e734aba93004"},
99
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
1010
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
1111
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
12-
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
12+
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
1313
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
1414
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
1515
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},

0 commit comments

Comments
 (0)