155 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			155 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 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 on the GStreamer Matrix room:
 | |
| https://matrix.to/#/#gstreamer:gstreamer.org.
 | |
| 
 | |
| 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()
 | |
| ```
 |