Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use binding requests as keepalives #51

Merged
merged 4 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/ex_ice/priv/candidate_pair.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule ExICE.Priv.CandidatePair do
alias ExICE.Priv.{Candidate, Utils}

# Tr timeout (keepalives) in ms
@tr_timeout 15 * 1000
@tr_timeout 5 * 1000

@type state() :: :waiting | :in_progress | :succeeded | :failed | :frozen

Expand Down
32 changes: 28 additions & 4 deletions lib/ex_ice/priv/ice_agent.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule ExICE.Priv.ICEAgent do
# Pair timeout in ms.
# If we don't receive any data in this time,
# a pair is marked as faield.
@pair_timeout 5_000
@pair_timeout 8_000

# End-of-candidates timeout in ms.
# If we don't receive end-of-candidates indication in this time,
Expand Down Expand Up @@ -72,6 +72,7 @@ defmodule ExICE.Priv.ICEAgent do
gathering_transactions: %{},
checklist: %{},
conn_checks: %{},
keepalives: %{},
gathering_state: :new,
eoc: false,
# {did we nominate pair, pair id}
Expand Down Expand Up @@ -1049,6 +1050,20 @@ defmodule ExICE.Priv.ICEAgent do

handle_stun_gathering_transaction_response(ice_agent, msg)

%Type{class: class, method: :binding}
when is_response(class) and is_map_key(ice_agent.keepalives, msg.transaction_id) ->
# TODO: this a good basis to implement consent freshness
Logger.debug("""
Received keepalive response from from #{inspect({src_ip, src_port})}, \
on: #{inspect({local_cand.base.base_address, local_cand.base.base_port})} \
""")

{pair_id, ice_agent} = pop_in(ice_agent.keepalives, msg.transaction_id)

pair = Map.fetch!(ice_agent.checklist, pair_id)
pair = %CandidatePair{pair | last_seen: now()}
put_in(ice_agent.checklist[pair.id], pair)

%Type{class: class, method: :binding} when is_response(class) ->
Logger.warning("""
Ignoring binding response with unknown t_id: #{msg.transaction_id}.
Expand Down Expand Up @@ -2156,18 +2171,27 @@ defmodule ExICE.Priv.ICEAgent do

defp send_keepalive(ice_agent, pair) do
Logger.debug("Sending keepalive")
type = %Type{class: :indication, method: :binding}
local_cand = Map.fetch!(ice_agent.local_cands, pair.local_cand_id)
remote_cand = Map.fetch!(ice_agent.remote_cands, pair.remote_cand_id)

type = %Type{class: :request, method: :binding}

req =
type
|> Message.new()
|> Message.with_integrity(ice_agent.remote_pwd)
|> Message.with_fingerprint()

dst = {remote_cand.address, remote_cand.port}
{_result, ice_agent} = do_send(ice_agent, local_cand, dst, Message.encode(req))
ice_agent

case do_send(ice_agent, local_cand, dst, Message.encode(req)) do
{:ok, ice_agent} ->
keepalives = Map.put(ice_agent.keepalives, req.transaction_id, pair.id)
%__MODULE__{ice_agent | keepalives: keepalives}

{:error, ice_agent} ->
ice_agent
end
end

defp send_conn_check(ice_agent, pair) do
Expand Down
14 changes: 7 additions & 7 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
%{
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.31.2", "8b06d0a5ac69e1a54df35519c951f1f44a7b7ca9a5bb7a260cd8a174d6322ece", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "317346c14febaba9ca40fd97b5b5919f7751fb85d399cc8e7e8872049f37e0af"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"ex_stun": {:hex, :ex_stun, "0.2.0", "feb1fc7db0356406655b2a617805e6c712b93308c8ea2bf0ba1197b1f0866deb", [:mix], [], "hexpm", "1e01ba8290082ccbf37acaa5190d1f69b51edd6de2026a8d6d51368b29d115d0"},
"ex_turn": {:hex, :ex_turn, "0.1.0", "177405aadf3d754567d0d37cf881a83f9cacf8f45314d188633b04c4a9e7c1ec", [:mix], [{:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}], "hexpm", "d677737fb7d45274d5dac19fe3c26b9038b6effbc0a6b3e7417bccc76b6d1cd3"},
"excoveralls": {:hex, :excoveralls, "0.18.1", "a6f547570c6b24ec13f122a5634833a063aec49218f6fff27de9df693a15588c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d65f79db146bb20399f23046015974de0079668b9abb2f5aac074d078da60b8d"},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
}
43 changes: 42 additions & 1 deletion test/priv/ice_agent_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,47 @@ defmodule ExICE.Priv.ICEAgentTest do
end
end

describe "sends keepalives" do
setup do
remote_cand = ExICE.Candidate.new(:host, address: {192, 168, 0, 2}, port: 8445)

ice_agent =
ICEAgent.new(
controlling_process: self(),
role: :controlling,
if_discovery_module: IfDiscovery.Mock,
transport_module: Transport.Mock
)
|> ICEAgent.set_remote_credentials("someufrag", "somepwd")
|> ICEAgent.gather_candidates()
|> ICEAgent.add_remote_candidate(remote_cand)

%{ice_agent: ice_agent}
end

test "on connected pair", %{ice_agent: ice_agent} do
ice_agent = connect(ice_agent)

[socket] = ice_agent.sockets
[pair] = Map.values(ice_agent.checklist)
ice_agent = ICEAgent.handle_keepalive(ice_agent, pair.id)

assert packet = Transport.Mock.recv(socket)
assert {:ok, msg} = ExSTUN.Message.decode(packet)
assert msg.type == %ExSTUN.Message.Type{class: :request, method: :binding}
assert :ok == ExSTUN.Message.check_fingerprint(msg)
assert :ok == ExSTUN.Message.authenticate(msg, ice_agent.remote_pwd)
end

test "on unconnected pair", %{ice_agent: ice_agent} do
[socket] = ice_agent.sockets
[pair] = Map.values(ice_agent.checklist)
ICEAgent.handle_keepalive(ice_agent, pair.id)

assert nil == Transport.Mock.recv(socket)
end
end

describe "incoming binding request" do
setup do
ice_agent =
Expand Down Expand Up @@ -714,7 +755,7 @@ defmodule ExICE.Priv.ICEAgentTest do

# mock last_seen field
[pair] = Map.values(ice_agent.checklist)
last_seen = System.monotonic_time(:millisecond) - 5_000
last_seen = System.monotonic_time(:millisecond) - 10_000
pair = %{pair | last_seen: last_seen}
ice_agent = put_in(ice_agent.checklist[pair.id], pair)

Expand Down
Loading