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

Move DTLS utilities to DTLSTransport module #10

Merged
merged 4 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions examples/example.exs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ defmodule Peer do
offer = %SessionDescription{type: :offer, sdp: sdp}
:ok = PeerConnection.set_remote_description(state.peer_connection, offer)
{:ok, answer} = PeerConnection.create_answer(state.peer_connection)
:ok = PeerConnection.set_local_description(state.peer_connection, answer)
msg = %{"type" => "answer", "sdp" => answer.sdp}
:gun.ws_send(state.conn, state.stream, {:text, Jason.encode!(msg)})
end
Expand Down
128 changes: 128 additions & 0 deletions lib/ex_webrtc/dtls_transport.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
defmodule ExWebRTC.DTLSTransport do
@moduledoc false

require Logger

alias ExICE.ICEAgent

defstruct [
:ice_agent,
:ice_state,
:client,
:buffered_packets,
:cert,
:pkey,
:fingerprint,
:mode,
finished: false,
should_start: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would favor using mode field instead, which currerntly remains unset

]

def new(ice_agent) do
# temporary hack to generate certs
{:ok, cert_client} = ExDTLS.start_link(client_mode: true, dtls_srtp: true)
{:ok, cert} = ExDTLS.get_cert(cert_client)
{:ok, pkey} = ExDTLS.get_pkey(cert_client)
{:ok, fingerprint} = ExDTLS.get_cert_fingerprint(cert_client)
:ok = ExDTLS.stop(cert_client)

%__MODULE__{
ice_agent: ice_agent,
cert: cert,
pkey: pkey,
fingerprint: fingerprint
}
end

def start(dtls, :passive) do
{:ok, client} =
ExDTLS.start_link(
client_mode: false,
dtls_srtp: true,
pkey: dtls.pkey,
cert: dtls.cert
)

%__MODULE__{dtls | client: client}
end

def start(dtls, :active) do
{:ok, client} =
ExDTLS.start_link(
client_mode: true,
dtls_srtp: true,
pkey: dtls.pkey,
cert: dtls.cert
)

# we assume that ICE in not in connected state yet
%__MODULE__{dtls | client: client, should_start: true}
end

def update_ice_state(dtls, :connected) do
dtls =
if dtls.should_start do
{:ok, packets} = ExDTLS.do_handshake(dtls.client)
:ok = ICEAgent.send_data(dtls.ice_agent, packets)
%__MODULE__{dtls | should_start: false}

Check warning on line 67 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L63-L67

Added lines #L63 - L67 were not covered by tests
else
dtls

Check warning on line 69 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L69

Added line #L69 was not covered by tests
end

dtls =
if dtls.buffered_packets do
Logger.debug("Sending buffered DTLS packets")
ICEAgent.send_data(dtls.ice_agent, dtls.buffered_packets)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this was coppied but could we always match against :ok?

%__MODULE__{dtls | buffered_packets: nil}

Check warning on line 76 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L72-L76

Added lines #L72 - L76 were not covered by tests
else
dtls

Check warning on line 78 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L78

Added line #L78 was not covered by tests
end

%__MODULE__{dtls | ice_state: :connected}

Check warning on line 81 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L81

Added line #L81 was not covered by tests
end

def update_ice_state(dtls, new_state) do
%__MODULE__{dtls | ice_state: new_state}

Check warning on line 85 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L85

Added line #L85 was not covered by tests
end

def handle_info(dtls, {:retransmit, packets})
when dtls.ice_state in [:connected, :completed] do
ICEAgent.send_data(dtls.ice_agent, packets)
dtls

Check warning on line 91 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L90-L91

Added lines #L90 - L91 were not covered by tests
end

def handle_info(%{buffered_packets: packets} = dtls, {:retransmit, packets}) do
# we got DTLS packets from the other side but
# we haven't established ICE connection yet so
# packets to retransmit have to be the same as dtls_buffered_packets
dtls

Check warning on line 98 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L98

Added line #L98 was not covered by tests
end

def process_data(dtls, data) do
case ExDTLS.process(dtls.client, data) do

Check warning on line 102 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L102

Added line #L102 was not covered by tests
{:handshake_packets, packets} when dtls.ice_state in [:connected, :completed] ->
:ok = ICEAgent.send_data(dtls.ice_agent, packets)
dtls

Check warning on line 105 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L104-L105

Added lines #L104 - L105 were not covered by tests

{:handshake_packets, packets} ->
Logger.debug("""

Check warning on line 108 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L108

Added line #L108 was not covered by tests
Generated local DTLS packets but ICE is not in the connected or completed state yet.
We will send those packets once ICE is ready.
""")

%__MODULE__{dtls | buffered_packets: packets}

Check warning on line 113 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L113

Added line #L113 was not covered by tests

{:handshake_finished, _keying_material, packets} ->
Logger.debug("DTLS handshake finished")
ICEAgent.send_data(dtls.ice_agent, packets)
%__MODULE__{dtls | finished: true}

Check warning on line 118 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L116-L118

Added lines #L116 - L118 were not covered by tests

{:handshake_finished, _keying_material} ->
Logger.debug("DTLS handshake finished")
%__MODULE__{dtls | finished: true}

Check warning on line 122 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L121-L122

Added lines #L121 - L122 were not covered by tests

:handshake_want_read ->
dtls

Check warning on line 125 in lib/ex_webrtc/dtls_transport.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/dtls_transport.ex#L125

Added line #L125 was not covered by tests
end
end
end
103 changes: 45 additions & 58 deletions lib/ex_webrtc/peer_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
alias ExICE.ICEAgent

alias ExWebRTC.{
DTLSTransport,
IceCandidate,
MediaStreamTrack,
RTPTransceiver,
Expand Down Expand Up @@ -37,9 +38,7 @@
:pending_remote_desc,
:ice_agent,
:ice_state,
:dtls_client,
:dtls_buffered_packets,
dtls_finished: false,
:dtls_transport,
transceivers: [],
signaling_state: :stable,
last_offer: nil,
Expand Down Expand Up @@ -114,13 +113,13 @@
|> Enum.filter(&String.starts_with?(&1, "stun:"))

{:ok, ice_agent} = ICEAgent.start_link(:controlled, stun_servers: stun_servers)
{:ok, dtls_client} = ExDTLS.start_link(client_mode: false, dtls_srtp: true)
dtls_transport = DTLSTransport.new(ice_agent)

state = %__MODULE__{
owner: owner,
config: config,
ice_agent: ice_agent,
dtls_client: dtls_client
dtls_transport: dtls_transport
}

{:ok, state}
Expand All @@ -144,7 +143,6 @@
transceivers = assign_mids(state.transceivers, next_mid)

{:ok, ice_ufrag, ice_pwd} = ICEAgent.get_local_credentials(state.ice_agent)
{:ok, dtls_fingerprint} = ExDTLS.get_cert_fingerprint(state.dtls_client)

offer =
%ExSDP{ExSDP.new() | timing: %ExSDP.Timing{start_time: 0, stop_time: 0}}
Expand All @@ -156,7 +154,7 @@
ice_ufrag: ice_ufrag,
ice_pwd: ice_pwd,
ice_options: "trickle",
fingerprint: {:sha256, Utils.hex_dump(dtls_fingerprint)},
fingerprint: {:sha256, Utils.hex_dump(state.dtls_transport.fingerprint)},
setup: :actpass,
rtcp: true
]
Expand Down Expand Up @@ -200,7 +198,6 @@
{:offer, remote_offer} = state.pending_remote_desc

{:ok, ice_ufrag, ice_pwd} = ICEAgent.get_local_credentials(state.ice_agent)
{:ok, dtls_fingerprint} = ExDTLS.get_cert_fingerprint(state.dtls_client)

answer =
%ExSDP{ExSDP.new() | timing: %ExSDP.Timing{start_time: 0, stop_time: 0}}
Expand All @@ -212,7 +209,7 @@
ice_ufrag: ice_ufrag,
ice_pwd: ice_pwd,
ice_options: "trickle",
fingerprint: {:sha256, Utils.hex_dump(dtls_fingerprint)},
fingerprint: {:sha256, Utils.hex_dump(state.dtls_transport.fingerprint)},
setup: :active
]

Expand Down Expand Up @@ -286,6 +283,11 @@
end
end

@impl true
def handle_call({:add_ice_candidate, _}, _from, %{current_remote_desc: nil} = state) do
{:reply, {:error, :no_remote_description}, state}

Check warning on line 288 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L288

Added line #L288 was not covered by tests
end

@impl true
def handle_call({:add_ice_candidate, candidate}, _from, state) do
with "candidate:" <> attr <- candidate.candidate do
Expand Down Expand Up @@ -322,12 +324,8 @@

@impl true
def handle_info({:ex_ice, _from, :connected}, state) do
if state.dtls_buffered_packets do
Logger.debug("Sending buffered DTLS packets")
ICEAgent.send_data(state.ice_agent, state.dtls_buffered_packets)
end

{:noreply, %__MODULE__{state | ice_state: :connected, dtls_buffered_packets: nil}}
dtls = DTLSTransport.update_ice_state(state.dtls_transport, :connected)
{:noreply, %__MODULE__{state | dtls_transport: dtls, ice_state: :connected}}

Check warning on line 328 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L327-L328

Added lines #L327 - L328 were not covered by tests
end

@impl true
Expand All @@ -345,50 +343,16 @@
end

@impl true
def handle_info({:ex_ice, _from, {:data, data}}, %{dtls_finished: false} = state) do
case ExDTLS.process(state.dtls_client, data) do
{:handshake_packets, packets} when state.ice_state in [:connected, :completed] ->
:ok = ICEAgent.send_data(state.ice_agent, packets)
{:noreply, state}

{:handshake_packets, packets} ->
Logger.debug("""
Generated local DTLS packets but ICE is not in the connected or completed state yet.
We will send those packets once ICE is ready.
""")

{:noreply, %__MODULE__{state | dtls_buffered_packets: packets}}

{:handshake_finished, _keying_material, packets} ->
Logger.debug("DTLS handshake finished")
ICEAgent.send_data(state.ice_agent, packets)
{:noreply, %__MODULE__{state | dtls_finished: true}}

{:handshake_finished, _keying_material} ->
Logger.debug("DTLS handshake finished")
{:noreply, %__MODULE__{state | dtls_finished: true}}

:handshake_want_read ->
{:noreply, state}
end
def handle_info({:ex_ice, _from, {:data, data}}, state)
when not state.dtls_transport.finished do
dtls = DTLSTransport.process_data(state.dtls_transport, data)
{:noreply, %__MODULE__{state | dtls_transport: dtls}}

Check warning on line 349 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L348-L349

Added lines #L348 - L349 were not covered by tests
end

@impl true
def handle_info({:ex_dtls, _from, {:retransmit, packets}}, state)
when state.ice_state in [:connected, :completed] do
ICEAgent.send_data(state.ice_agent, packets)
{:noreply, state}
end

@impl true
def handle_info(
{:ex_dtls, _from, {:retransmit, packets}},
%{dtls_buffered_packets: packets} = state
) do
# we got DTLS packets from the other side but
# we haven't established ICE connection yet so
# packets to retransmit have to be the same as dtls_buffered_packets
{:noreply, state}
def handle_info({:ex_dtls, _from, msg}, state) do
dtls = DTLSTransport.handle_info(state.dtls_transport, msg)
{:noreply, %__MODULE__{state | dtls_transport: dtls}}

Check warning on line 355 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L354-L355

Added lines #L354 - L355 were not covered by tests
end

@impl true
Expand All @@ -401,7 +365,15 @@
new_transceivers = update_local_transceivers(type, state.transceivers, sdp)
state = set_description(:local, type, sdp, state)

{:ok, %{state | transceivers: new_transceivers}}
dtls =
if type == :answer do
{:setup, setup} = ExSDP.Media.get_attribute(hd(sdp.media), :setup)
DTLSTransport.start(state.dtls_transport, setup)
else
state.dtls_transport
end

{:ok, %{state | transceivers: new_transceivers, dtls_transport: dtls}}
end

defp update_local_transceivers(:offer, transceivers, _sdp) do
Expand Down Expand Up @@ -437,7 +409,22 @@

state = set_description(:remote, type, sdp, state)

{:ok, %{state | transceivers: new_transceivers}}
dtls =
if type == :answer do
{:setup, setup} = ExSDP.Media.get_attribute(hd(sdp.media), :setup)

setup =
case setup do
:active -> :passive
:passive -> :active

Check warning on line 419 in lib/ex_webrtc/peer_connection.ex

View check run for this annotation

Codecov / codecov/patch

lib/ex_webrtc/peer_connection.ex#L419

Added line #L419 was not covered by tests
end

DTLSTransport.start(state.dtls_transport, setup)
else
state.dtls_transport
end

{:ok, %{state | transceivers: new_transceivers, dtls_transport: dtls}}
else
error -> error
end
Expand Down
Loading