| INTRODUCTION |
| ============ |
| This document is an attempt to describe the basics of the DTLS element. |
| It hasn't been written by the author(s) and so, besides being incomplete, |
| *IT MIGHT ALSO BE INCORRECT*. So take it with a pinch of salt. |
| |
| As always, if in doubt ask the #gstreamer IRC channel. |
| |
| THE INTERNALS |
| ============= |
| This plugin provides two main elements (dtlssrtpdec and dtlssrtpenc) and a few |
| minor elements necessary to implement them. The two elements dtlssrtpdec and |
| dtlssrtpenc are the only ones you are supposed to include in, respectively, the |
| RX and TX pipelines of your DTLS-enabled application. This means you're not |
| supposed to directly include those minor elements in your pipelines. |
| |
| dtlssrtpenc |
| ----------- |
| This element is to be included in the TX pipeline and will initiate the DTLS |
| handshake if configured to be the client. Its main configuration parameters are: |
| |
| - connection-id: a string that must match the connection-id in dtlssrtpdec; |
| - is-client: a boolean that indicates whether this is going to be the client |
| or the server during the DTLS handshake. |
| |
| Internally this element comprises the standard srtpenc element, the dtlsenc |
| element and a funnel to connect both these elements to one single output. |
| The srtpenc can be used to encrypt SRTP/SRTCP packets while the dtlsenc can be |
| used to encrypt generic data, e.g. for non-SRTP applications. |
| |
| NB With the current implementation the TX pipeline containing the dtlssrtpenc |
| must be created *AFTER* the RX pipeline. |
| |
| dtlssrtpdec |
| ----------- |
| It is to be included in the RX pipeline. Its main configuration parameters are: |
| |
| - connection-id: a string that must match the connection-id in dtlssrtpenc; |
| - pem: a string that can be used to provide your own certificate *AND* private |
| key in PEM format. The private key is required to carry out the |
| handshake so do not forget it or the DTLS negotiation will fail; |
| - peer_pem: a read only parameter that can be used to retrieve the |
| certificate sent from the other party in PEM format once the |
| handshake is completed. |
| |
| Internally this element comprises a dtlssrtpdemux, a standard srtpdec element |
| and the dtlsdec element. The dtlssrtpdemux element switches SRT(C)P packets to |
| the srtpdec element and DTLS packets to the dtlsdec element and discards any |
| other unknown packet. So, similarly for the dtlssrtpenc case, DTLS-SRTP |
| applications would exercise the srtpdec element and any other non-SRTP |
| application would exercise the dtlsdec element. |
| |
| NB With the current implementation the RX pipeline containing the dtlssrtpdec |
| must be created *BEFORE* the TX pipeline. |
| |
| EXAMPLE PIPELINE |
| ================ |
| The following is an example usage of the DTLS plugin. It is a python script that |
| creates two endpoints that exchange encrypted audio using DTLS to exchange the |
| encryption keys. |
| |
| NB In theory we would show an example gst-launch command. However that would not |
| be enough because you need two pairs of TX/RX pipelines for a proper |
| handshake and you can't use gst-launch two start 4 different pipelines. |
| This why there is a python script in here. |
| |
| ``` |
| #!/usr/bin/env python3 |
| |
| # create two endpoints, each with tx and rx pipelines using the DTLS |
| # elements and let audio flowing for a while to give time for a packet capture |
| |
| import time |
| from gi.repository import Gst, GObject, GLib |
| GObject.threads_init() |
| Gst.init(None) |
| |
| |
| def _start_pipeline(pipeline): |
| pipeline.set_state(Gst.State.PLAYING) |
| pipeline.get_state(Gst.CLOCK_TIME_NONE) |
| |
| |
| def _sleep_while_iterating_gloop(secs): |
| """ block for secs seconds but iterate the gloop while you do """ |
| for _ in range(10 * secs): |
| gloop = GLib.MainLoop() |
| gloop_context = gloop.get_context() |
| gloop_context.iteration(may_block=False) |
| time.sleep(0.1) |
| |
| def dtls_tx_pipeline_description(name, is_client, port): |
| return ' ! '.join([ |
| 'audiotestsrc is-live=true', |
| 'audio/x-raw, rate=8000, format=S16LE, channels=1', |
| 'opusenc frame-size=10', |
| 'rtpopuspay pt=103', |
| '.rtp_sink_0 dtlssrtpenc connection-id={name} is-client={client} .src', |
| 'udpsink port={port}' |
| ]).format(name=name, client=is_client, port=port) |
| |
| |
| def dtls_rx_pipeline_description(name, port): |
| return ' ! '.join([ |
| 'udpsrc port={port}', |
| '.sink dtlssrtpdec connection-id={name} .rtp_src', |
| 'queue', |
| 'fakesink async=false' |
| ]).format(name=name, port=port) |
| |
| |
| class Endpoint: |
| def __init__(self, name, is_client, tx_port, rx_port): |
| self.name = name |
| tx_pipeline_description = dtls_tx_pipeline_description( |
| name, is_client, tx_port |
| ) |
| rx_pipeline_description = dtls_rx_pipeline_description(name, rx_port) |
| print(rx_pipeline_description) |
| print(tx_pipeline_description) |
| |
| self.rx_pipeline = Gst.parse_launch(rx_pipeline_description) |
| self.tx_pipeline = Gst.parse_launch(tx_pipeline_description) |
| |
| def start(self): |
| # Start RX first, otherwise it fails due to the current implementation |
| self.start_rx_pipeline() |
| self.start_tx_pipeline() |
| |
| def start_rx_pipeline(self): |
| _start_pipeline(self.rx_pipeline) |
| |
| def start_tx_pipeline(self): |
| _start_pipeline(self.tx_pipeline) |
| |
| def stop(self): |
| def stop_pipeline(p): |
| p.set_state(Gst.State.NULL) |
| p.get_state(Gst.CLOCK_TIME_NONE) |
| stop_pipeline(self.tx_pipeline) |
| stop_pipeline(self.rx_pipeline) |
| |
| blue = Endpoint("blue", is_client=True, tx_port=23000, rx_port=23002) |
| red = Endpoint("red", is_client=False, tx_port=23002, rx_port=23000) |
| |
| red.start() |
| blue.start() |
| |
| _sleep_while_iterating_gloop(3) |
| |
| red.stop() |
| blue.stop() |
| ``` |