Skip to content

Commit 35f8e8a

Browse files
committed
Don't move to the closed state after receiving close_notify alert
1 parent 83fbefd commit 35f8e8a

File tree

13 files changed

+220
-103
lines changed

13 files changed

+220
-103
lines changed

examples/chat/lib/chat/peer_handler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ defmodule Chat.PeerHandler do
106106
defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do
107107
Logger.info("Connection state changed: #{conn_state}")
108108

109-
if conn_state in [:failed, :closed] do
110-
{:stop, {:shutdown, :pc_failed_or_closed}, state}
109+
if conn_state == :failed do
110+
{:stop, {:shutdown, :pc_failed}, state}
111111
else
112112
{:ok, state}
113113
end

examples/dtmf/lib/dtmf/peer_handler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ defmodule Dtmf.PeerHandler do
125125
defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do
126126
Logger.info("Connection state changed: #{conn_state}")
127127

128-
if conn_state in [:failed, :closed] do
129-
{:stop, {:shutdown, :pc_failed_or_closed}, state}
128+
if conn_state == :failed do
129+
{:stop, {:shutdown, :pc_failed}, state}
130130
else
131131
{:ok, state}
132132
end

examples/echo/lib/echo/peer_handler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ defmodule Echo.PeerHandler do
115115
defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do
116116
Logger.info("Connection state changed: #{conn_state}")
117117

118-
if conn_state in [:failed, :closed] do
119-
{:stop, {:shutdown, :pc_failed_or_closed}, state}
118+
if conn_state == :failed do
119+
{:stop, {:shutdown, :pc_failed}, state}
120120
else
121121
{:ok, state}
122122
end

examples/save_to_file/lib/save_to_file/peer_handler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ defmodule SaveToFile.PeerHandler do
136136
defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do
137137
Logger.info("Connection state changed: #{conn_state}")
138138

139-
if conn_state in [:failed, :closed] do
140-
{:stop, {:shutdown, :pc_failed_or_closed}, state}
139+
if conn_state == :failed do
140+
{:stop, {:shutdown, :pc_failed}, state}
141141
else
142142
{:ok, state}
143143
end

examples/send_from_file/lib/send_from_file/peer_handler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ defmodule SendFromFile.PeerHandler do
249249
defp handle_webrtc_msg({:connection_state_change, conn_state}, state) do
250250
Logger.info("Connection state changed: #{conn_state}")
251251

252-
if conn_state in [:failed, :closed] do
253-
{:stop, {:shutdown, :pc_failed_or_closed}, state}
252+
if conn_state == :failed do
253+
{:stop, {:shutdown, :pc_failed}, state}
254254
else
255255
{:ok, state}
256256
end

examples/whip_whep/lib/whip_whep/forwarder.ex

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,12 @@ defmodule WhipWhep.Forwarder do
6969
end
7070

7171
@impl true
72-
def handle_info(
73-
{:ex_webrtc, pc, {:connection_state_change, conn_state}},
74-
%{input_pc: pc} = state
75-
)
76-
when conn_state in [:failed, :closed] do
77-
Logger.info("Input peer connection (#{inspect(pc)}) state change: #{conn_state}. Removing.")
72+
def handle_info({:ex_webrtc, pc, {:connection_state_change, :failed}}, %{input_pc: pc} = state) do
73+
Logger.info("Input peer connection (#{inspect(pc)}) state change: failed. Removing.")
7874
:ok = PeerConnection.stop(state.input_pc)
7975

80-
Enum.each(state.pending_outputs, &PeerConnection.close(&1))
81-
Enum.each(state.outputs, fn {pc, _output} -> PeerConnection.close(pc) end)
76+
Enum.each(state.pending_outputs, &PeerConnection.stop(&1))
77+
Enum.each(state.outputs, fn {pc, _output} -> PeerConnection.stop(pc) end)
8278

8379
state = %{
8480
state
@@ -124,9 +120,8 @@ defmodule WhipWhep.Forwarder do
124120
end
125121

126122
@impl true
127-
def handle_info({:ex_webrtc, pc, {:connection_state_change, conn_state}}, state)
128-
when conn_state in [:failed, :closed] do
129-
Logger.info("Output peer connection (#{inspect(pc)}) state change: #{conn_state}. Removing.")
123+
def handle_info({:ex_webrtc, pc, {:connection_state_change, :failed}}, state) do
124+
Logger.info("Output peer connection (#{inspect(pc)}) state change: failed. Removing.")
130125
:ok = PeerConnection.stop(pc)
131126
pending_outputs = List.delete(state.pending_outputs, pc)
132127
outputs = Map.delete(state.outputs, pc)

lib/ex_webrtc/dtls_transport.ex

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ defmodule ExWebRTC.DTLSTransport do
6262
GenServer.call(dtls_transport, :set_ice_connected)
6363
end
6464

65+
@spec set_ice_disconnected(dtls_transport()) :: :ok
66+
def set_ice_disconnected(dtls_transport) do
67+
GenServer.call(dtls_transport, :set_ice_disconnected)
68+
end
69+
6570
@spec get_certs_info(dtls_transport()) :: %{
6671
local_cert_info: cert_info(),
6772
remote_cert_info: cert_info() | nil
@@ -131,8 +136,8 @@ defmodule ExWebRTC.DTLSTransport do
131136
remote_cert: nil,
132137
remote_base64_cert: nil,
133138
remote_fingerprint: nil,
134-
in_srtp: ExLibSRTP.new(),
135-
out_srtp: ExLibSRTP.new(),
139+
in_srtp: nil,
140+
out_srtp: nil,
136141
# sha256 hex dump
137142
peer_fingerprint: nil,
138143
dtls_state: :new,
@@ -187,6 +192,12 @@ defmodule ExWebRTC.DTLSTransport do
187192
{:reply, :ok, state}
188193
end
189194

195+
@impl true
196+
def handle_call(:set_ice_disconnected, _from, state) do
197+
state = %{state | ice_connected: false}
198+
{:reply, :ok, state}
199+
end
200+
190201
@impl true
191202
def handle_call(:get_certs_info, _from, state) do
192203
local_cert_info = %{
@@ -217,9 +228,18 @@ defmodule ExWebRTC.DTLSTransport do
217228
end
218229

219230
@impl true
220-
def handle_call({:start_dtls, mode, peer_fingerprint}, _from, %{dtls: nil} = state)
221-
when mode in [:active, :passive] do
222-
Logger.debug("Started DTLSTransport with role #{mode}")
231+
def handle_call(
232+
{:start_dtls, mode, peer_fingerprint},
233+
_from,
234+
%{dtls: dtls, dtls_state: dtls_state} = state
235+
)
236+
when mode in [:active, :passive] and (dtls == nil or dtls_state == :closed) do
237+
if dtls_state == :closed do
238+
Logger.debug("Re-initializing DTLSTransport with role #{mode}")
239+
else
240+
Logger.debug("Starting DTLSTransport with role #{mode}")
241+
end
242+
223243
ex_dtls_mode = if mode == :active, do: :client, else: :server
224244

225245
dtls =
@@ -234,16 +254,26 @@ defmodule ExWebRTC.DTLSTransport do
234254
# plant the buffered remote packets in the mailbox
235255
# as if it came from the ICE transport
236256
case state.buffered_remote_packets do
237-
nil -> :ok
238-
data -> send(self(), {:ex_ice, state.ice_pid, {:data, data}})
257+
nil ->
258+
:ok
259+
260+
data ->
261+
dbg(:sending)
262+
send(self(), {:ex_ice, state.ice_pid, {:data, data}})
239263
end
240264

241265
state = %{
242266
state
243267
| dtls: dtls,
268+
dtls_state: :new,
244269
mode: mode,
270+
in_srtp: ExLibSRTP.new(),
271+
out_srtp: ExLibSRTP.new(),
245272
peer_fingerprint: peer_fingerprint,
246-
buffered_remote_packets: nil
273+
# cleare remote info in case we are re-initializing dtls transport
274+
remote_cert: nil,
275+
remote_base64_cert: nil,
276+
remote_fingerprint: nil
247277
}
248278

249279
{:reply, :ok, state}
@@ -262,9 +292,7 @@ defmodule ExWebRTC.DTLSTransport do
262292

263293
@impl true
264294
def handle_call(:close, _from, state) do
265-
{:ok, packets} = ExDTLS.close(state.dtls)
266-
:ok = do_send(state, packets)
267-
state = update_dtls_state(state, :closed, notify: false)
295+
state = do_close(state, notify: false)
268296
{:reply, :ok, state}
269297
end
270298

@@ -360,8 +388,7 @@ defmodule ExWebRTC.DTLSTransport do
360388
# See W3C WebRTC sec. 5.5.1
361389
# peer_closed_for_writing is returned when the remote side
362390
# sends close_notify alert
363-
ExDTLS.close(state.dtls)
364-
state = update_dtls_state(state, :closed)
391+
state = do_close(state)
365392
{:noreply, state}
366393

367394
{:error, _reason} ->
@@ -382,9 +409,20 @@ defmodule ExWebRTC.DTLSTransport do
382409
Logger.debug("Stopping DTLSTransport with reason: #{inspect(reason)}")
383410
end
384411

385-
defp handle_ice_data({:data, _data}, %{dtls_state: dtls_state} = state)
386-
when dtls_state in [:failed, :closed] do
387-
Logger.debug("Received DTLS packets in state #{dtls_state}. Ignoring.")
412+
defp handle_ice_data({:data, _data}, %{dtls_state: :closed} = state) do
413+
# Ignore incoming packets when we are in the closed state.
414+
# IMPORTNAT!!!
415+
# This isn't optimal.
416+
# When DTLSTransport has been closed because of receiving close_notify alert,
417+
# it can still be re-used after doing an ice restart with a different peer.
418+
# In such case, we might start getting remote ICE packets before getting remote SDP answer
419+
# (see the case clause below).
420+
# These packets could be buffered and processed once we receive said SDP answer to speedup
421+
# connection establishment time (instead of waiting for retransmissions).
422+
# However, it is hard to differentiate this case from a case where DTLSTransport received
423+
# close_notify alert, but the remote side behaves incorrectly and still sends ICE packets.
424+
# Buffering incoming packets in closed state, we don't know whether they belong to current or previous
425+
# session, which can lead to hard to find bugs.
388426
{:ok, state}
389427
end
390428

@@ -542,6 +580,23 @@ defmodule ExWebRTC.DTLSTransport do
542580
%{state | buffered_remote_rtp_packets: []}
543581
end
544582

583+
defp do_close(state, opts \\ []) do
584+
{:ok, packets} = ExDTLS.close(state.dtls)
585+
:ok = do_send(state, packets)
586+
587+
%{
588+
state
589+
| buffered_local_packets: nil,
590+
buffered_remote_packets: nil,
591+
buffered_remote_rtp_packets: [],
592+
in_srtp: nil,
593+
out_srtp: nil,
594+
dtls: nil,
595+
mode: nil
596+
}
597+
|> update_dtls_state(:closed, opts)
598+
end
599+
545600
defp do_send(state, data) when is_list(data) do
546601
Enum.each(data, &(:ok = do_send(state, &1)))
547602
end

lib/ex_webrtc/peer_connection.ex

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ defmodule ExWebRTC.PeerConnection do
685685

686686
@impl true
687687
def handle_call(
688-
{:set_sender_code, _sender_id, _codec},
688+
{:set_sender_codec, _sender_id, _codec},
689689
_from,
690690
%{signaling_state: :closed} = state
691691
) do
@@ -1151,7 +1151,7 @@ defmodule ExWebRTC.PeerConnection do
11511151
def handle_call(
11521152
{:create_data_channel, _label, _opts},
11531153
_from,
1154-
%{connection_state: :closed} = state
1154+
%{signaling_state: :closed} = state
11551155
) do
11561156
{:reply, {:error, :invalid_state}, state}
11571157
end
@@ -1382,14 +1382,28 @@ defmodule ExWebRTC.PeerConnection do
13821382

13831383
@impl true
13841384
def handle_call(:close, _from, %{signaling_state: :closed} = state) do
1385-
{:reply, {:error, :invalid_state}, state}
1385+
{:reply, :ok, state}
13861386
end
13871387

13881388
@impl true
13891389
def handle_call(:close, _from, state) do
13901390
Logger.debug("Closing peer connection")
1391-
# don't emit state change events
1392-
state = do_close(state, notify: false)
1391+
transceivers = Enum.map(state.transceivers, &RTPTransceiver.stop(&1))
1392+
sctp_transport = SCTPTransport.close_abruptly(state.sctp_transport)
1393+
:ok = DTLSTransport.close(state.dtls_transport)
1394+
:ok = state.ice_transport.close(state.ice_pid)
1395+
1396+
state =
1397+
%{
1398+
state
1399+
| ice_state: :closed,
1400+
dtls_state: :closed,
1401+
transceivers: transceivers,
1402+
sctp_transport: sctp_transport
1403+
}
1404+
|> update_signaling_state(:closed, notify: false)
1405+
|> update_conn_state(:closed, notify: false)
1406+
13931407
{:reply, :ok, state}
13941408
end
13951409

@@ -1518,6 +1532,10 @@ defmodule ExWebRTC.PeerConnection do
15181532
:ok = DTLSTransport.set_ice_connected(state.dtls_transport)
15191533
end
15201534

1535+
if new_ice_state == :failed do
1536+
:ok = DTLSTransport.set_ice_disconnected(state.dtls_transport)
1537+
end
1538+
15211539
{:noreply, state}
15221540
end
15231541

@@ -1548,17 +1566,12 @@ defmodule ExWebRTC.PeerConnection do
15481566

15491567
next_conn_state = next_conn_state(state.ice_state, new_dtls_state)
15501568

1551-
if next_conn_state == :closed and state.conn_state != :closed do
1552-
state = do_close(state)
1553-
{:noreply, state}
1554-
else
1555-
state =
1556-
%{state | dtls_state: new_dtls_state}
1557-
|> update_conn_state(next_conn_state)
1558-
|> maybe_connect_sctp()
1569+
state =
1570+
%{state | dtls_state: new_dtls_state}
1571+
|> update_conn_state(next_conn_state)
1572+
|> maybe_connect_sctp()
15591573

1560-
{:noreply, state}
1561-
end
1574+
{:noreply, state}
15621575
end
15631576

15641577
@impl true
@@ -2325,13 +2338,8 @@ defmodule ExWebRTC.PeerConnection do
23252338
# the order of these clauses is important
23262339
defp next_conn_state(ice_state, dtls_state)
23272340

2328-
# Give closed precedence over failed.
2329-
# Failed connection can be restarted.
2330-
# Closed connection can't be reused.
23312341
defp next_conn_state(:closed, _dtls_state), do: :closed
23322342

2333-
defp next_conn_state(_ice_state, :closed), do: :closed
2334-
23352343
defp next_conn_state(:failed, _dtls_state), do: :failed
23362344

23372345
defp next_conn_state(_ice_state, :failed), do: :failed
@@ -2342,8 +2350,9 @@ defmodule ExWebRTC.PeerConnection do
23422350
when ice_state in [:new, :checking] or dtls_state in [:new, :connecting],
23432351
do: :connecting
23442352

2345-
defp next_conn_state(ice_state, :connected) when ice_state in [:connected, :completed],
2346-
do: :connected
2353+
defp next_conn_state(ice_state, dtls_state)
2354+
when ice_state in [:connected, :completed] and dtls_state in [:connected, :closed],
2355+
do: :connected
23472356

23482357
defp update_conn_state(state, conn_state, opts \\ [])
23492358
defp update_conn_state(%{conn_state: conn_state} = state, conn_state, _opts), do: state
@@ -2551,27 +2560,6 @@ defmodule ExWebRTC.PeerConnection do
25512560
%SessionDescription{type: type, sdp: to_string(sdp)}
25522561
end
25532562

2554-
defp do_close(state, opts \\ []) do
2555-
transceivers = Enum.map(state.transceivers, &RTPTransceiver.stop(&1))
2556-
sctp_transport = SCTPTransport.close_abruptly(state.sctp_transport)
2557-
:ok = DTLSTransport.close(state.dtls_transport)
2558-
:ok = state.ice_transport.close(state.ice_pid)
2559-
2560-
if opts[:notify] != false do
2561-
notify(state.owner, {:ice_connection_state_change, :closed})
2562-
end
2563-
2564-
%{
2565-
state
2566-
| ice_state: :closed,
2567-
dtls_state: :closed,
2568-
transceivers: transceivers,
2569-
sctp_transport: sctp_transport
2570-
}
2571-
|> update_signaling_state(:closed, opts)
2572-
|> update_conn_state(:closed, opts)
2573-
end
2574-
25752563
defp generate_ssrcs(state) do
25762564
generate_ssrcs(state.transceivers, Map.keys(state.demuxer.ssrc_to_mid))
25772565
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ defmodule ExWebRTC.MixProject do
6262
{:ex_ice, github: "elixir-webrtc/ex_ice", branch: "close"},
6363
# {:ex_dtls, "~> 0.17.0"},
6464
# {:ex_dtls, path: "/home/michal/Repos/elixir-webrtc/ex_dtls"},
65-
{:ex_dtls, github: "elixir-webrtc/ex_dtls", branch: "disconnect"},
65+
{:ex_dtls, github: "elixir-webrtc/ex_dtls"},
6666
{:ex_libsrtp, "~> 0.7.1"},
6767
{:ex_rtp, "~> 0.4.0"},
6868
{:ex_rtcp, "~> 0.4.0"},

0 commit comments

Comments
 (0)