Skip to content

Commit 26cdfd0

Browse files
committed
Handle DTLS close_notify alert
1 parent 598dc4e commit 26cdfd0

File tree

8 files changed

+220
-26
lines changed

8 files changed

+220
-26
lines changed

examples/whip_whep/lib/whip_whep/forwarder.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ defmodule WhipWhep.Forwarder do
9494
@impl true
9595
def handle_info({:ex_webrtc, pc, {:connection_state_change, :failed}}, state) do
9696
Logger.info("Output peer connection (#{inspect(pc)}) state change: failed. Removing.")
97-
:ok = PeerConnection.close(pc)
97+
:ok = PeerConnection.stop(pc)
9898
pending_outputs = List.delete(state.pending_outputs, pc)
9999
outputs = Map.delete(state.outputs, pc)
100100
state = %{state | pending_outputs: pending_outputs, outputs: outputs}

lib/ex_webrtc/dtls_transport.ex

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ defmodule ExWebRTC.DTLSTransport do
146146
state = %{state | ice_connected: true}
147147

148148
if state.mode == :active do
149-
{packets, timeout} = ExDTLS.do_handshake(state.dtls)
149+
{:ok, packets, timeout} = ExDTLS.do_handshake(state.dtls)
150150
Process.send_after(self(), :dtls_timeout, timeout)
151151
:ok = do_send(state, packets)
152152
state = update_dtls_state(state, :connecting)
@@ -158,7 +158,7 @@ defmodule ExWebRTC.DTLSTransport do
158158
end
159159

160160
@impl true
161-
def handle_call(:set_ice_connected, _from, state) do
161+
def handle_call(:set_ice_connected, _from, %{dtls_state: :connecting} = state) do
162162
state = %{state | ice_connected: true}
163163

164164
if state.buffered_local_packets do
@@ -171,6 +171,17 @@ defmodule ExWebRTC.DTLSTransport do
171171
end
172172
end
173173

174+
@impl true
175+
def handle_call(:set_ice_connected, _from, state) do
176+
Logger.debug("""
177+
Setting ice connected in unexpected DTLS state: #{state.dtls_state}. \
178+
DTLS handshake won't be performed.\
179+
""")
180+
181+
state = %{state | ice_connected: true}
182+
{:reply, :ok, state}
183+
end
184+
174185
@impl true
175186
def handle_call(:get_certs_info, _from, state) do
176187
local_cert_info = %{
@@ -327,6 +338,14 @@ defmodule ExWebRTC.DTLSTransport do
327338
{:ok, state} ->
328339
{:noreply, state}
329340

341+
{:error, :peer_closed_for_writing} ->
342+
# See W3C WebRTC sec. 5.5.1
343+
# peer_closed_for_writing is returned when the remote side
344+
# sends close_notify alert
345+
ExDTLS.disconnect(state.dtls)
346+
state = update_dtls_state(state, :closed)
347+
{:noreply, state}
348+
330349
{:error, _reason} ->
331350
# See W3C WebRTC sec. 5.5.
332351
state = update_dtls_state(state, :failed)
@@ -345,6 +364,12 @@ defmodule ExWebRTC.DTLSTransport do
345364
Logger.debug("Stopping DTLSTransport with reason: #{inspect(reason)}")
346365
end
347366

367+
defp handle_ice_data({:data, _data}, %{dtls_state: dtls_state} = state)
368+
when dtls_state in [:failed, :closed] do
369+
Logger.debug("Received DTLS packets in state #{dtls_state}. Ignoring.")
370+
{:ok, state}
371+
end
372+
348373
defp handle_ice_data({:data, data}, %{dtls: nil} = state) do
349374
# received DTLS data from remote peer before receiving an
350375
# SDP answer and initializing the DTLS Transport, will buffer the data

lib/ex_webrtc/peer_connection.ex

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ defmodule ExWebRTC.PeerConnection do
6363
6464
For the exact meaning, refer to the [RTCDtlsTransport: state property](https://developer.mozilla.org/en-US/docs/Web/API/RTCDtlsTransport/state)
6565
"""
66-
@type dtls_transport_state() :: :new | :connecting | :connected | :failed
66+
@type dtls_transport_state() :: :new | :connecting | :connected | :closed | :failed
6767

6868
@typedoc """
6969
Possible signaling states.
@@ -105,8 +105,8 @@ defmodule ExWebRTC.PeerConnection do
105105
| {:track_muted, MediaStreamTrack.id()}
106106
| {:track_ended, MediaStreamTrack.id()}
107107
| {:data, DataChannel.ref(), binary()}
108-
| {:rtp, MediaStreamTrack.id(), String.t() | nil, ExRTP.Packet.t()}}
109-
| {:rtcp, [{MediaStreamTrack.id() | nil, ExRTCP.Packet.packet()}]}
108+
| {:rtp, MediaStreamTrack.id(), String.t() | nil, ExRTP.Packet.t()}
109+
| {:rtcp, [{MediaStreamTrack.id() | nil, ExRTCP.Packet.packet()}]}}
110110

111111
#### NON-MDN-API ####
112112

@@ -574,11 +574,20 @@ defmodule ExWebRTC.PeerConnection do
574574
@doc """
575575
Closes the PeerConnection.
576576
577-
This function kills the `peer_connection` process.
577+
This function doest not kill the `peer_connection` process.
578+
If you want to stop the `peer_connection` process, see `stop/1`.
578579
For more information, refer to the [RTCPeerConnection: close() method](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/close).
579580
"""
580581
@spec close(peer_connection()) :: :ok
581582
def close(peer_connection) do
583+
GenServer.call(peer_connection, :close)
584+
end
585+
586+
@doc """
587+
Closes and stops PeerConnection process.
588+
"""
589+
@spec stop(peer_connection()) :: :ok
590+
def stop(peer_connection) do
582591
GenServer.stop(peer_connection)
583592
end
584593

@@ -1271,6 +1280,11 @@ defmodule ExWebRTC.PeerConnection do
12711280
{:reply, stats, state}
12721281
end
12731282

1283+
@impl true
1284+
def handle_call(:close, _from, state) do
1285+
{:reply, :ok, state}
1286+
end
1287+
12741288
@impl true
12751289
def handle_cast({:send_rtp, track_id, packet, opts}, %{conn_state: conn_state} = state)
12761290
when conn_state != :failed do

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ defmodule ExWebRTC.MixProject do
5858
[
5959
{:ex_sdp, "~> 1.0"},
6060
{:ex_ice, "~> 0.12.0"},
61-
{:ex_dtls, "~> 0.17.0"},
61+
# {:ex_dtls, "~> 0.17.0"},
62+
{:ex_dtls, path: "/home/michal/Repos/elixir-webrtc/ex_dtls"},
6263
{:ex_libsrtp, "~> 0.7.1"},
6364
{:ex_rtp, "~> 0.4.0"},
6465
{:ex_rtcp, "~> 0.4.0"},

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [: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", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
3030
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
3131
"membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"},
32-
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
32+
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
3333
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
3434
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
3535
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},

test/ex_webrtc/dtls_transport_test.exs

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ defmodule ExWebRTC.DTLSTransportTest do
1010
|> Utils.hex_dump()
1111

1212
@rtp_header <<1::1, 0::1, 0::1, 0::1, 0::4, 0::1, 96::7, 1::16, 1::32, 1::32>>
13+
@next_rtp_header <<1::1, 0::1, 0::1, 0::1, 0::4, 0::1, 96::7, 2::16, 1::32, 1::32>>
1314
@rtp_payload <<0>>
1415
@rtp_packet <<@rtp_header::binary, @rtp_payload::binary>>
16+
@next_rtp_packet <<@next_rtp_header::binary, @rtp_payload::binary>>
1517

1618
# empty rr packet
1719
@rtcp_rr_header <<2::2, 0::1, 0::5, 201::8, 1::16, 1::32>>
@@ -128,7 +130,7 @@ defmodule ExWebRTC.DTLSTransportTest do
128130
:ok = DTLSTransport.set_ice_connected(dtls)
129131

130132
remote_dtls = ExDTLS.init(mode: :client, dtls_srtp: true)
131-
{packets, _timeout} = ExDTLS.do_handshake(remote_dtls)
133+
{:ok, packets, _timeout} = ExDTLS.do_handshake(remote_dtls)
132134

133135
Enum.each(packets, &ice_transport.send_dtls(ice_pid, {:data, &1}))
134136
refute_receive {:mock_ice, _packets}
@@ -180,7 +182,7 @@ defmodule ExWebRTC.DTLSTransportTest do
180182
:ok = DTLSTransport.start_dtls(dtls, :passive, @fingerprint)
181183

182184
remote_dtls = ExDTLS.init(mode: :client, dtls_srtp: true)
183-
{packets, _timeout} = ExDTLS.do_handshake(remote_dtls)
185+
{:ok, packets, _timeout} = ExDTLS.do_handshake(remote_dtls)
184186

185187
Enum.each(packets, &ice_transport.send_dtls(ice_pid, {:data, &1}))
186188
refute_receive {:mock_ice, _packets}
@@ -200,7 +202,7 @@ defmodule ExWebRTC.DTLSTransportTest do
200202

201203
:ok = DTLSTransport.set_ice_connected(dtls)
202204

203-
assert :ok = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
205+
assert {:ok, _, _, _} = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
204206
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
205207
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
206208

@@ -228,12 +230,12 @@ defmodule ExWebRTC.DTLSTransportTest do
228230

229231
:ok = DTLSTransport.start_dtls(dtls, :passive, remote_fingerprint)
230232

231-
{packets, _timeout} = ExDTLS.do_handshake(remote_dtls)
233+
{:ok, packets, _timeout} = ExDTLS.do_handshake(remote_dtls)
232234
:ok = DTLSTransport.set_ice_connected(dtls)
233235

234236
Enum.each(packets, &ice_transport.send_dtls(ice_pid, {:data, &1}))
235237

236-
assert :ok == check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
238+
assert {:ok, _, _, _} = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
237239
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
238240
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
239241

@@ -256,7 +258,7 @@ defmodule ExWebRTC.DTLSTransportTest do
256258

257259
:ok = DTLSTransport.set_ice_connected(dtls)
258260

259-
assert :ok = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
261+
assert {:ok, _, _, _} = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
260262
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
261263
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
262264

@@ -278,6 +280,60 @@ defmodule ExWebRTC.DTLSTransportTest do
278280
refute_receive {:mock_ice, _datachannel_packet}
279281
end
280282

283+
test "closes on receiving close_notify DTLS alert", %{
284+
dtls: dtls,
285+
ice_transport: ice_transport,
286+
ice_pid: ice_pid
287+
} do
288+
:ok = DTLSTransport.start_dtls(dtls, :active, @fingerprint)
289+
remote_dtls = ExDTLS.init(mode: :server, dtls_srtp: true)
290+
291+
:ok = DTLSTransport.set_ice_connected(dtls)
292+
293+
# perform DTLS-SRTP handshake
294+
assert {:ok, remote_lkm, remote_rkm, remote_profile} =
295+
check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
296+
297+
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
298+
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
299+
300+
# create SRTP for remote side
301+
{_remote_in_srtp, remote_out_srtp} = setup_srtp(remote_lkm, remote_rkm, remote_profile)
302+
303+
# assert packets can flow from remote to local
304+
{:ok, protected} = ExLibSRTP.protect(remote_out_srtp, @rtp_packet)
305+
ice_transport.send_dtls(ice_pid, {:data, protected})
306+
assert_receive {:dtls_transport, ^dtls, {:rtp, @rtp_packet}}
307+
308+
# disconnect and send close_notify from remote to local
309+
assert {:ok, packets} = ExDTLS.disconnect(remote_dtls)
310+
Enum.each(packets, &ice_transport.send_dtls(ice_pid, {:data, &1}))
311+
312+
# assert local received close_notify and moved to the closed state
313+
assert_receive {:dtls_transport, ^dtls, {:state_change, :closed}}
314+
315+
# assert that data cannot be sent by local
316+
:ok = DTLSTransport.send_rtp(dtls, @rtp_packet)
317+
refute_receive {:mock_ice, _rtp_packet}
318+
:ok = DTLSTransport.send_rtp(dtls, @rtcp_rr_packet)
319+
refute_receive {:mock_ice, _rtcp_packet}
320+
:ok = DTLSTransport.send_data(dtls, <<1, 2, 3>>)
321+
refute_receive {:mock_ice, _datachannel_packet}
322+
323+
# assert that incoming data is ignored by local
324+
{:ok, protected} = ExLibSRTP.protect(remote_out_srtp, @next_rtp_packet)
325+
ice_transport.send_dtls(ice_pid, {:data, protected})
326+
refute_receive {:dtls_transport, ^dtls, {:rtp, _data}}
327+
328+
# assert getting certs still works
329+
assert %{local_cert_info: local_cert, remote_cert_info: remote_cert} =
330+
DTLSTransport.get_certs_info(dtls)
331+
332+
assert local_cert != nil
333+
assert remote_cert != nil
334+
assert DTLSTransport.get_fingerprint(dtls) != nil
335+
end
336+
281337
defp check_handshake(dtls, ice_transport, ice_pid, remote_dtls) do
282338
assert_receive {:mock_ice, packets}
283339

@@ -289,17 +345,45 @@ defmodule ExWebRTC.DTLSTransportTest do
289345
Enum.each(packets, &ice_transport.send_dtls(ice_pid, {:data, &1}))
290346
check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
291347

292-
{:handshake_finished, _, _, _, packets} ->
348+
{:handshake_finished, lkm, rkm, profile, packets} ->
293349
Enum.each(packets, &ice_transport.send_dtls(ice_pid, {:data, &1}))
294-
:ok
350+
{:ok, lkm, rkm, profile}
295351

296-
{:handshake_finished, _, _, _} ->
297-
:ok
352+
{:handshake_finished, lkm, rkm, profile} ->
353+
{:ok, lkm, rkm, profile}
298354
end
299355
end
300356

301357
test "stop/1", %{dtls: dtls} do
302358
assert :ok == DTLSTransport.stop(dtls)
303359
assert false == Process.alive?(dtls)
304360
end
361+
362+
defp setup_srtp(lkm, rkm, profile) do
363+
in_srtp = ExLibSRTP.new()
364+
out_srtp = ExLibSRTP.new()
365+
366+
{:ok, crypto_profile} =
367+
ExLibSRTP.Policy.crypto_profile_from_dtls_srtp_protection_profile(profile)
368+
369+
inbound_policy = %ExLibSRTP.Policy{
370+
ssrc: :any_inbound,
371+
key: rkm,
372+
rtp: crypto_profile,
373+
rtcp: crypto_profile
374+
}
375+
376+
:ok = ExLibSRTP.add_stream(in_srtp, inbound_policy)
377+
378+
outbound_policy = %ExLibSRTP.Policy{
379+
ssrc: :any_outbound,
380+
key: lkm,
381+
rtp: crypto_profile,
382+
rtcp: crypto_profile
383+
}
384+
385+
:ok = ExLibSRTP.add_stream(out_srtp, outbound_policy)
386+
387+
{in_srtp, out_srtp}
388+
end
305389
end

0 commit comments

Comments
 (0)