diff --git a/configure.ac b/configure.ac index 4822af13be..f466a3eec5 100644 --- a/configure.ac +++ b/configure.ac @@ -2137,6 +2137,18 @@ AG_GST_CHECK_FEATURE(SRTP, [srtp library], srtp, [ AC_SUBST(SRTP_CFLAGS) ]) +dnl *** dtls *** +translit(dnm, m, l) AM_CONDITIONAL(USE_DTLS, true) +AG_GST_CHECK_FEATURE(DTLS, [DTLS plugin], dtls, [ + PKG_CHECK_MODULES(DTLS, [ openssl >= 0.9.5 libcrypto ], [ + HAVE_DTLS="yes" + AC_SUBST(DTLS_CFLAGS) + AC_SUBST(DTLS_LIBS) + ], [ + HAVE_DTLS="no" + ]) +]) + dnl *** linsys *** translit(dnm, m, l) AM_CONDITIONAL(USE_LINSYS, true) AG_GST_CHECK_FEATURE(LINSYS, [Linear Systems SDI plugin], linsys, [ @@ -3014,6 +3026,7 @@ AM_CONDITIONAL(USE_UVCH264, false) AM_CONDITIONAL(USE_WEBP, false) AM_CONDITIONAL(USE_OPENH264, false) AM_CONDITIONAL(USE_X265, false) +AM_CONDITIONAL(USE_DTLS, false) fi dnl of EXT plugins @@ -3310,6 +3323,7 @@ ext/webp/Makefile ext/x265/Makefile ext/xvid/Makefile ext/zbar/Makefile +ext/dtls/Makefile po/Makefile.in docs/Makefile docs/plugins/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 43cb5a89d6..394c71df26 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -412,6 +412,12 @@ else X265_DIR= endif +if USE_DTLS +DTLS_DIR=dtls +else +DTLS_DIR= +endif + SUBDIRS=\ $(VOAACENC_DIR) \ $(ASSRENDER_DIR) \ @@ -480,7 +486,8 @@ SUBDIRS=\ $(RTMP_DIR) \ $(HLS_DIR) \ $(WEBP_DIR) \ - $(X265_DIR) + $(X265_DIR) \ + $(DTLS_DIR) DIST_SUBDIRS = \ assrender \ @@ -546,6 +553,7 @@ DIST_SUBDIRS = \ zbar \ rtmp \ webp \ - x265 + x265 \ + dtls include $(top_srcdir)/common/parallel-subdirs.mak diff --git a/ext/dtls/Makefile.am b/ext/dtls/Makefile.am new file mode 100644 index 0000000000..5846506ade --- /dev/null +++ b/ext/dtls/Makefile.am @@ -0,0 +1,36 @@ +plugin_LTLIBRARIES = libgstdtls.la + +libgstdtls_la_SOURCES = \ + plugin.c \ + gstdtlsdec.c \ + gstdtlsenc.c \ + gstdtlssrtpdemux.c \ + gstdtlssrtpbin.c \ + gstdtlssrtpdec.c \ + gstdtlssrtpenc.c \ + gstdtlsagent.c \ + gstdtlsconnection.c \ + gstdtlscertificate.c + +libgstdtls_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(DTLS_CFLAGS) + +libgstdtls_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(DTLS_LIBS) +libgstdtls_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstdtls_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = \ + gstdtlsdec.h \ + gstdtlsenc.h \ + gstdtlssrtpdemux.h \ + gstdtlssrtpbin.h \ + gstdtlssrtpdec.h \ + gstdtlssrtpenc.h \ + gstdtlsagent.h \ + gstdtlsconnection.h \ + gstdtlscertificate.h \ + gstdtlscommon.h + diff --git a/ext/dtls/gstdtlsagent.c b/ext/dtls/gstdtlsagent.c new file mode 100644 index 0000000000..67b552eb06 --- /dev/null +++ b/ext/dtls/gstdtlsagent.c @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlsagent.h" + +#include "gstdtlscommon.h" + +#ifdef __APPLE__ +# define __AVAILABILITYMACROS__ +# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER +#endif + +#include +#include + +#if ER_DTLS_USE_GST_LOG + GST_DEBUG_CATEGORY_STATIC(er_dtls_agent_debug); +# define GST_CAT_DEFAULT er_dtls_agent_debug + G_DEFINE_TYPE_WITH_CODE(ErDtlsAgent, er_dtls_agent, G_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT(er_dtls_agent_debug, "gstdtlsagent", 0, "Ericsson DTLS Agent")); +#else + G_DEFINE_TYPE(ErDtlsAgent, er_dtls_agent, G_TYPE_OBJECT); +#endif + +#define ER_DTLS_AGENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ER_TYPE_DTLS_AGENT, ErDtlsAgentPrivate)) + +enum { + PROP_0, + PROP_CERTIFICATE, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +struct _ErDtlsAgentPrivate { + SSL_CTX *ssl_context; + + ErDtlsCertificate *certificate; +}; + +static void er_dtls_agent_finalize(GObject *gobject); +static void er_dtls_agent_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +const gchar *er_dtls_agent_peek_id(ErDtlsAgent *); + +static GRWLock *ssl_locks; + +static void ssl_locking_function(gint mode, gint lock_num, const gchar *file, gint line) +{ + gboolean locking; + gboolean reading; + GRWLock *lock; + + locking = mode & CRYPTO_LOCK; + reading = mode & CRYPTO_READ; + lock = &ssl_locks[lock_num]; + + LOG_LOG(NULL, "%s SSL lock for %s, thread=%p location=%s:%d", + locking ? "locking" : "unlocking", reading ? "reading" : "writing", + g_thread_self(), file, line); + + if (locking) { + if (reading) { + g_rw_lock_reader_lock(lock); + } else { + g_rw_lock_writer_lock(lock); + } + } else { + if (reading) { + g_rw_lock_reader_unlock(lock); + } else { + g_rw_lock_writer_unlock(lock); + } + } +} + +static gulong ssl_thread_id_function(void) +{ + return (gulong) g_thread_self(); +} + +void _er_dtls_init_openssl() +{ + static gsize is_init = 0; + gint i; + gint num_locks; + + if (g_once_init_enter(&is_init)) { + if (OPENSSL_VERSION_NUMBER < 0x1000100fL) { + LOG_WARNING(NULL, "Incorrect OpenSSL version, should be >= 1.0.1, is %s", OPENSSL_VERSION_TEXT); + g_assert_not_reached(); + } + + LOG_INFO(NULL, "initializing openssl %lx", OPENSSL_VERSION_NUMBER); + SSL_library_init(); + SSL_load_error_strings(); + ERR_load_BIO_strings(); + + num_locks = CRYPTO_num_locks(); + ssl_locks = g_new(GRWLock, num_locks); + for (i = 0; i < num_locks; ++i) { + g_rw_lock_init(&ssl_locks[i]); + } + CRYPTO_set_locking_callback(ssl_locking_function); + CRYPTO_set_id_callback(ssl_thread_id_function); + + g_once_init_leave(&is_init, 1); + } +} + +static void er_dtls_agent_class_init(ErDtlsAgentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(ErDtlsAgentPrivate)); + + gobject_class->set_property = er_dtls_agent_set_property; + gobject_class->finalize = er_dtls_agent_finalize; + + properties[PROP_CERTIFICATE] = + g_param_spec_object("certificate", + "ErDtlsCertificate", + "Sets the certificate of the agent", + ER_TYPE_DTLS_CERTIFICATE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + _er_dtls_init_openssl(); +} + +static void er_dtls_agent_init(ErDtlsAgent *self) +{ + ErDtlsAgentPrivate *priv = ER_DTLS_AGENT_GET_PRIVATE(self); + self->priv = priv; + + ERR_clear_error(); + + priv->ssl_context = SSL_CTX_new(DTLSv1_method()); + if (ERR_peek_error() || !priv->ssl_context) { + char buf[512]; + + priv->ssl_context = NULL; + + LOG_WARNING(self, "Error creating SSL Context: %s", ERR_error_string(ERR_get_error(), buf)); + + g_return_if_reached(); + } + + SSL_CTX_set_verify_depth(priv->ssl_context, 2); + SSL_CTX_set_tlsext_use_srtp(priv->ssl_context, "SRTP_AES128_CM_SHA1_80"); + SSL_CTX_set_cipher_list(priv->ssl_context, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + SSL_CTX_set_read_ahead(priv->ssl_context, 1); +#if OPENSSL_VERSION_NUMBER >= 0x1000200fL + SSL_CTX_set_ecdh_auto(priv->ssl_context, 1); +#endif +} + +static void er_dtls_agent_finalize(GObject *gobject) +{ + ErDtlsAgentPrivate *priv = ER_DTLS_AGENT(gobject)->priv; + + SSL_CTX_free(priv->ssl_context); + priv->ssl_context = NULL; + + LOG_DEBUG(gobject, "finalized"); + + G_OBJECT_CLASS(er_dtls_agent_parent_class)->finalize(gobject); +} + +static void er_dtls_agent_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + ErDtlsAgent *self = ER_DTLS_AGENT(object); + ErDtlsCertificate *certificate; + + switch (prop_id) { + case PROP_CERTIFICATE: + certificate = ER_DTLS_CERTIFICATE(g_value_get_object(value)); + g_return_if_fail(ER_IS_DTLS_CERTIFICATE(certificate)); + g_return_if_fail(self->priv->ssl_context); + + self->priv->certificate = certificate; + g_object_ref(certificate); + + if (!SSL_CTX_use_certificate(self->priv->ssl_context, _er_dtls_certificate_get_internal_certificate(certificate))) { + LOG_WARNING(self, "could not use certificate"); + g_return_if_reached(); + } + + if (!SSL_CTX_use_PrivateKey(self->priv->ssl_context, _er_dtls_certificate_get_internal_key(certificate))) { + LOG_WARNING(self, "could not use private key"); + g_return_if_reached(); + } + + if (!SSL_CTX_check_private_key(self->priv->ssl_context)) { + LOG_WARNING(self, "invalid private key"); + g_return_if_reached(); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +ErDtlsCertificate *er_dtls_agent_get_certificate(ErDtlsAgent *self) +{ + g_return_val_if_fail(ER_IS_DTLS_AGENT(self), NULL); + if (self->priv->certificate) { + g_object_ref(self->priv->certificate); + } + return self->priv->certificate; +} + +gchar *er_dtls_agent_get_certificate_pem(ErDtlsAgent *self) +{ + gchar *pem; + g_return_val_if_fail(ER_IS_DTLS_AGENT(self), NULL); + g_return_val_if_fail(ER_IS_DTLS_CERTIFICATE(self->priv->certificate), NULL); + + g_object_get(self->priv->certificate, "pem", &pem, NULL); + + return pem; +} + +const ErDtlsAgentContext _er_dtls_agent_peek_context(ErDtlsAgent *self) +{ + g_return_val_if_fail(ER_IS_DTLS_AGENT(self), NULL); + return self->priv->ssl_context; +} diff --git a/ext/dtls/gstdtlsagent.h b/ext/dtls/gstdtlsagent.h new file mode 100644 index 0000000000..71f45ac549 --- /dev/null +++ b/ext/dtls/gstdtlsagent.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlsagent_h +#define gstdtlsagent_h + +#include "gstdtlscertificate.h" + +#include + +G_BEGIN_DECLS + +#define ER_TYPE_DTLS_AGENT (er_dtls_agent_get_type()) +#define ER_DTLS_AGENT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ER_TYPE_DTLS_AGENT, ErDtlsAgent)) +#define ER_DTLS_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ER_TYPE_DTLS_AGENT, ErDtlsAgentClass)) +#define ER_IS_DTLS_AGENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ER_TYPE_DTLS_AGENT)) +#define ER_IS_DTLS_AGENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ER_TYPE_DTLS_AGENT)) +#define ER_DTLS_AGENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ER_TYPE_DTLS_AGENT, ErDtlsAgentClass)) + +typedef gpointer ErDtlsAgentContext; + +typedef struct _ErDtlsAgent ErDtlsAgent; +typedef struct _ErDtlsAgentClass ErDtlsAgentClass; +typedef struct _ErDtlsAgentPrivate ErDtlsAgentPrivate; + +/* + * ErDtlsAgent: + * + * A context for creating ErDtlsConnections with a ErDtlsCertificate. + * ErDtlsAgent needs to be constructed with the "certificate" property set. + */ +struct _ErDtlsAgent { + GObject parent_instance; + + ErDtlsAgentPrivate *priv; +}; + +struct _ErDtlsAgentClass { + GObjectClass parent_class; +}; + +GType er_dtls_agent_get_type(void) G_GNUC_CONST; + +/* + * Returns the certificate used by the agent. + */ +ErDtlsCertificate *er_dtls_agent_get_certificate(ErDtlsAgent *); + +/* + * Returns the certificate used by the agent, in PEM format. + */ +gchar *er_dtls_agent_get_certificate_pem(ErDtlsAgent *self); + +/* internal */ +void _er_dtls_init_openssl(void); +const ErDtlsAgentContext _er_dtls_agent_peek_context(ErDtlsAgent *); + +G_END_DECLS + +#endif /* gstdtlsagent_h */ diff --git a/ext/dtls/gstdtlscertificate.c b/ext/dtls/gstdtlscertificate.c new file mode 100644 index 0000000000..1fc3760283 --- /dev/null +++ b/ext/dtls/gstdtlscertificate.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlscertificate.h" + +#include "gstdtlsagent.h" +#include "gstdtlscommon.h" + +#ifdef __APPLE__ +# define __AVAILABILITYMACROS__ +# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER +#endif + +#include + +#if ER_DTLS_USE_GST_LOG + GST_DEBUG_CATEGORY_STATIC(er_dtls_certificate_debug); +# define GST_CAT_DEFAULT er_dtls_certificate_debug + G_DEFINE_TYPE_WITH_CODE(ErDtlsCertificate, er_dtls_certificate, G_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT(er_dtls_certificate_debug, "gstdtlscertificate", 0, "Ericsson DTLS Certificate")); +#else + G_DEFINE_TYPE(ErDtlsCertificate, er_dtls_certificate, G_TYPE_OBJECT); +#endif + +#define ER_DTLS_CERTIFICATE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificatePrivate)) + +enum { + PROP_0, + PROP_PEM, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +#define DEFAULT_PEM NULL + +struct _ErDtlsCertificatePrivate { + X509 *x509; + EVP_PKEY *private_key; + + gchar *pem; +}; + +static void er_dtls_certificate_finalize(GObject *gobject); +static void er_dtls_certificate_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +static void er_dtls_certificate_get_property(GObject *, guint prop_id, GValue *, GParamSpec *); + +static void init_generated(ErDtlsCertificate *); +static void init_from_pem_string(ErDtlsCertificate *, const gchar *pem); + +static void er_dtls_certificate_class_init(ErDtlsCertificateClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(ErDtlsCertificatePrivate)); + + gobject_class->set_property = er_dtls_certificate_set_property; + gobject_class->get_property = er_dtls_certificate_get_property; + + properties[PROP_PEM] = + g_param_spec_string("pem", + "Pem string", + "A string containing a X509 certificate and RSA private key in PEM format", + DEFAULT_PEM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + _er_dtls_init_openssl(); + + gobject_class->finalize = er_dtls_certificate_finalize; +} + +static void er_dtls_certificate_init(ErDtlsCertificate *self) +{ + ErDtlsCertificatePrivate *priv = ER_DTLS_CERTIFICATE_GET_PRIVATE(self); + self->priv = priv; + + priv->x509 = NULL; + priv->private_key = NULL; + priv->pem = NULL; +} + +static void er_dtls_certificate_finalize(GObject *gobject) +{ + ErDtlsCertificatePrivate *priv = ER_DTLS_CERTIFICATE(gobject)->priv; + + X509_free(priv->x509); + priv->x509 = NULL; + + EVP_PKEY_free(priv->private_key); + priv->private_key = NULL; + + + g_free(priv->pem); + priv->pem = NULL; + + G_OBJECT_CLASS(er_dtls_certificate_parent_class)->finalize(gobject); +} + +static void er_dtls_certificate_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + ErDtlsCertificate *self = ER_DTLS_CERTIFICATE(object); + const gchar *pem; + + switch (prop_id) { + case PROP_PEM: + pem = g_value_get_string(value); + if (pem) { + init_from_pem_string(self, pem); + } else { + init_generated(self); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void er_dtls_certificate_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + ErDtlsCertificate *self = ER_DTLS_CERTIFICATE(object); + + switch (prop_id) { + case PROP_PEM: + g_return_if_fail(self->priv->pem); + g_value_set_string(value, self->priv->pem); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void init_generated(ErDtlsCertificate *self) +{ + ErDtlsCertificatePrivate *priv = self->priv; + RSA *rsa; + X509_NAME *name = NULL; + + g_return_if_fail(!priv->x509); + g_return_if_fail(!priv->private_key); + + priv->private_key = EVP_PKEY_new(); + + if (!priv->private_key) { + LOG_WARNING(self, "failed to create private key"); + return; + } + + priv->x509 = X509_new(); + + if (!priv->x509) { + LOG_WARNING(self, "failed to create certificate"); + EVP_PKEY_free(priv->private_key); + priv->private_key = NULL; + return; + } + rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); + + if (!rsa) { + LOG_WARNING(self, "failed to generate RSA"); + EVP_PKEY_free(priv->private_key); + priv->private_key = NULL; + X509_free(priv->x509); + priv->x509 = NULL; + return; + } + + if (!EVP_PKEY_assign_RSA(priv->private_key, rsa)) { + LOG_WARNING(self, "failed to assign RSA"); + RSA_free(rsa); + rsa = NULL; + EVP_PKEY_free(priv->private_key); + priv->private_key = NULL; + X509_free(priv->x509); + priv->x509 = NULL; + return; + } + rsa = NULL; + + X509_set_version(priv->x509, 2); + ASN1_INTEGER_set(X509_get_serialNumber(priv->x509), 0); + X509_gmtime_adj(X509_get_notBefore(priv->x509), 0); + X509_gmtime_adj(X509_get_notAfter(priv->x509), 31536000L); /* A year */ + X509_set_pubkey(priv->x509, priv->private_key); + + name = X509_get_subject_name(priv->x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) "SE", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) "OpenWebRTC", -1, -1, 0); + X509_set_issuer_name(priv->x509, name); + name = NULL; + + if (!X509_sign(priv->x509, priv->private_key, EVP_sha256())) { + LOG_WARNING(self, "failed to sign certificate"); + EVP_PKEY_free(priv->private_key); + priv->private_key = NULL; + X509_free(priv->x509); + priv->x509 = NULL; + return; + } + + self->priv->pem = _er_dtls_x509_to_pem(priv->x509); +} + +static void init_from_pem_string(ErDtlsCertificate *self, const gchar *pem) +{ + ErDtlsCertificatePrivate *priv = self->priv; + BIO *bio; + + g_return_if_fail(pem); + g_return_if_fail(!priv->x509); + g_return_if_fail(!priv->private_key); + + bio = BIO_new_mem_buf((gpointer) pem, -1); + g_return_if_fail(bio); + + priv->x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + + if (!priv->x509) { + LOG_WARNING(self, "failed to read certificate from pem string"); + return; + } + + (void) BIO_reset(bio); + + priv->private_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + + BIO_free(bio); + bio = NULL; + + if (!priv->private_key) { + LOG_WARNING(self, "failed to read private key from pem string"); + X509_free(priv->x509); + priv->x509 = NULL; + return; + } + + self->priv->pem = g_strdup(pem); +} + +gchar *_er_dtls_x509_to_pem(gpointer x509) +{ +#define ER_DTLS_BIO_BUFFER_SIZE 4096 + BIO *bio; + gchar buffer[ER_DTLS_BIO_BUFFER_SIZE] = {0}; + gint len; + gchar *pem = NULL; + + bio = BIO_new(BIO_s_mem()); + g_return_val_if_fail(bio, NULL); + + if (!PEM_write_bio_X509(bio, (X509 *) x509)) { + g_warn_if_reached(); + goto beach; + } + + len = BIO_read(bio, buffer, ER_DTLS_BIO_BUFFER_SIZE); + if (!len) { + g_warn_if_reached(); + goto beach; + } + + pem = g_strndup(buffer, len); + +beach: + BIO_free(bio); + + return pem; +} + +ErDtlsCertificateInternalCertificate _er_dtls_certificate_get_internal_certificate(ErDtlsCertificate *self) +{ + g_return_val_if_fail(ER_IS_DTLS_CERTIFICATE(self), NULL); + return self->priv->x509; +} + +ErDtlsCertificateInternalKey _er_dtls_certificate_get_internal_key(ErDtlsCertificate *self) +{ + g_return_val_if_fail(ER_IS_DTLS_CERTIFICATE(self), NULL); + return self->priv->private_key; +} diff --git a/ext/dtls/gstdtlscertificate.h b/ext/dtls/gstdtlscertificate.h new file mode 100644 index 0000000000..02fcc55ecc --- /dev/null +++ b/ext/dtls/gstdtlscertificate.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlscertificate_h +#define gstdtlscertificate_h + +#include + +G_BEGIN_DECLS + +#define ER_TYPE_DTLS_CERTIFICATE (er_dtls_certificate_get_type()) +#define ER_DTLS_CERTIFICATE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificate)) +#define ER_DTLS_CERTIFICATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificateClass)) +#define ER_IS_DTLS_CERTIFICATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ER_TYPE_DTLS_CERTIFICATE)) +#define ER_IS_DTLS_CERTIFICATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ER_TYPE_DTLS_CERTIFICATE)) +#define ER_DTLS_CERTIFICATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ER_TYPE_DTLS_CERTIFICATE, ErDtlsCertificateClass)) + +typedef gpointer ErDtlsCertificateInternalCertificate; +typedef gpointer ErDtlsCertificateInternalKey; + +typedef struct _ErDtlsCertificate ErDtlsCertificate; +typedef struct _ErDtlsCertificateClass ErDtlsCertificateClass; +typedef struct _ErDtlsCertificatePrivate ErDtlsCertificatePrivate; + +/* + * ErDtlsCertificate: + * + * Handles a X509 certificate and a private key. + * If a certificate is created without the "pem" property, a self-signed certificate is generated. + */ +struct _ErDtlsCertificate { + GObject parent_instance; + + ErDtlsCertificatePrivate *priv; +}; + +struct _ErDtlsCertificateClass { + GObjectClass parent_class; +}; + +GType er_dtls_certificate_get_type(void) G_GNUC_CONST; + +/* internal */ +ErDtlsCertificateInternalCertificate _er_dtls_certificate_get_internal_certificate(ErDtlsCertificate *); +ErDtlsCertificateInternalKey _er_dtls_certificate_get_internal_key(ErDtlsCertificate *); +gchar *_er_dtls_x509_to_pem(gpointer x509); + +G_END_DECLS + +#endif /* gstdtlscertificate_h */ diff --git a/ext/dtls/gstdtlscommon.h b/ext/dtls/gstdtlscommon.h new file mode 100644 index 0000000000..1ed901f7fe --- /dev/null +++ b/ext/dtls/gstdtlscommon.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlscommon_h +#define gstdtlscommon_h + +#ifndef ER_DTLS_USE_GST_LOG +# define ER_DTLS_USE_GST_LOG 0 +#endif + +#if ER_DTLS_USE_GST_LOG +# include +#endif + +G_BEGIN_DECLS + +#define UNUSED(param) while (0) { (void)(param); } + +#if ER_DTLS_USE_GST_LOG +# define LOG_ERROR(obj, ...) GST_ERROR_OBJECT(obj, __VA_ARGS__ ) +# define LOG_WARNING(obj, ...) GST_WARNING_OBJECT(obj, __VA_ARGS__ ) +# define LOG_FIXME(obj, ...) GST_FIXME_OBJECT(obj, __VA_ARGS__ ) +# define LOG_INFO(obj, ...) GST_INFO_OBJECT(obj, __VA_ARGS__ ) +# define LOG_DEBUG(obj, ...) GST_DEBUG_OBJECT(obj, __VA_ARGS__ ) +# define LOG_LOG(obj, ...) GST_LOG_OBJECT(obj, __VA_ARGS__ ) +# define LOG_TRACE(obj, ...) GST_TRACE_OBJECT(obj, __VA_ARGS__ ) +#else +# define LOG_ERROR(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "%p: "fmt, obj, ##__VA_ARGS__) +# define LOG_WARNING(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "%p: "fmt, obj, ##__VA_ARGS__) +# define LOG_FIXME(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "%p: "fmt, obj, ##__VA_ARGS__) +# define LOG_INFO(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "%p: "fmt, obj, ##__VA_ARGS__) +# define LOG_DEBUG(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "%p: "fmt, obj, ##__VA_ARGS__) +# define LOG_LOG(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%p: "fmt, obj, ##__VA_ARGS__) +# define LOG_TRACE(obj, fmt, ...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%p: "fmt, obj, ##__VA_ARGS__) +#endif + +G_END_DECLS + +#endif /* gstdtlscommon_h */ diff --git a/ext/dtls/gstdtlsconnection.c b/ext/dtls/gstdtlsconnection.c new file mode 100644 index 0000000000..a2969df530 --- /dev/null +++ b/ext/dtls/gstdtlsconnection.c @@ -0,0 +1,852 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlsconnection.h" + +#include "gstdtlsagent.h" +#include "gstdtlscertificate.h" +#include "gstdtlscommon.h" + +#ifdef __APPLE__ +# define __AVAILABILITYMACROS__ +# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER +#endif + +#include +#include + +#if ER_DTLS_USE_GST_LOG + GST_DEBUG_CATEGORY_STATIC(er_dtls_connection_debug); +# define GST_CAT_DEFAULT er_dtls_connection_debug + G_DEFINE_TYPE_WITH_CODE(ErDtlsConnection, er_dtls_connection, G_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT(er_dtls_connection_debug, "gstdtlsconnection", 0, "Ericsson DTLS Connection")); +#else + G_DEFINE_TYPE(ErDtlsConnection, er_dtls_connection, G_TYPE_OBJECT); +#endif + +#define ER_DTLS_CONNECTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), ER_TYPE_DTLS_CONNECTION, ErDtlsConnectionPrivate)) + +#define SRTP_KEY_LEN 16 +#define SRTP_SALT_LEN 14 + +enum { + SIGNAL_ON_ENCODER_KEY, + SIGNAL_ON_DECODER_KEY, + SIGNAL_ON_PEER_CERTIFICATE, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS]; + +enum { + PROP_0, + PROP_AGENT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +static int connection_ex_index; + +struct _ErDtlsConnectionPrivate { + SSL *ssl; + BIO *bio; + GThread *thread; + + gboolean is_client; + gboolean is_alive; + gboolean keys_exported; + gboolean timeout_set; + + GMutex mutex; + GCond condition; + gpointer bio_buffer; + gint bio_buffer_len; + gint bio_buffer_offset; + + GClosure *send_closure; +}; + +static void er_dtls_connection_finalize(GObject *gobject); +static void er_dtls_connection_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); + +static void log_state(ErDtlsConnection *, const gchar *str); +static gpointer connection_timeout_thread_func(ErDtlsConnection *); +static void export_srtp_keys(ErDtlsConnection *); +static void openssl_poll(ErDtlsConnection *); +static int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx); + +static BIO_METHOD* BIO_s_er_dtls_connection(); +static int bio_method_write(BIO *, const char *data, int size); +static int bio_method_read(BIO *, char *out_buffer, int size); +static long bio_method_ctrl(BIO *, int cmd, long arg1, void *arg2); +static int bio_method_new(BIO *); +static int bio_method_free(BIO *); + +static void er_dtls_connection_class_init(ErDtlsConnectionClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(ErDtlsConnectionPrivate)); + + gobject_class->set_property = er_dtls_connection_set_property; + + connection_ex_index = SSL_get_ex_new_index(0, "gstdtlsagent connection index", NULL, NULL, NULL); + + signals[SIGNAL_ON_DECODER_KEY] = + g_signal_new("on-decoder-key", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 3, + G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT); + + signals[SIGNAL_ON_ENCODER_KEY] = + g_signal_new("on-encoder-key", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 3, + G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT); + + signals[SIGNAL_ON_PEER_CERTIFICATE] = + g_signal_new("on-peer-certificate", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, + G_TYPE_STRING); + + properties[PROP_AGENT] = + g_param_spec_object("agent", + "ERDtlsAgent", + "Agent to use in creation of the connection", + ER_TYPE_DTLS_AGENT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + _er_dtls_init_openssl(); + + gobject_class->finalize = er_dtls_connection_finalize; +} + +static void er_dtls_connection_init(ErDtlsConnection *self) +{ + ErDtlsConnectionPrivate *priv = ER_DTLS_CONNECTION_GET_PRIVATE(self); + self->priv = priv; + + priv->ssl = NULL; + priv->bio = NULL; + priv->thread = NULL; + + priv->send_closure = NULL; + + priv->is_client = FALSE; + priv->is_alive = TRUE; + priv->keys_exported = FALSE; + priv->timeout_set = FALSE; + + priv->bio_buffer = NULL; + priv->bio_buffer_len = 0; + priv->bio_buffer_offset = 0; + + g_mutex_init(&priv->mutex); + g_cond_init(&priv->condition); +} + +static void er_dtls_connection_finalize(GObject *gobject) +{ + ErDtlsConnection *self = ER_DTLS_CONNECTION(gobject); + ErDtlsConnectionPrivate *priv = self->priv; + + + SSL_free(priv->ssl); + priv->ssl = NULL; + + if (priv->send_closure) { + g_closure_unref(priv->send_closure); + priv->send_closure = NULL; + } + + g_mutex_clear(&priv->mutex); + g_cond_clear(&priv->condition); + + LOG_DEBUG(self, "finalized"); + + G_OBJECT_CLASS(er_dtls_connection_parent_class)->finalize(gobject); +} + +static void er_dtls_connection_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + ErDtlsConnection *self = ER_DTLS_CONNECTION(object); + ErDtlsAgent *agent; + ErDtlsConnectionPrivate *priv = self->priv; + SSL_CTX *ssl_context; + + switch (prop_id) { + case PROP_AGENT: + g_return_if_fail(!priv->ssl); + agent = ER_DTLS_AGENT(g_value_get_object(value)); + g_return_if_fail(ER_IS_DTLS_AGENT(agent)); + + ssl_context = _er_dtls_agent_peek_context(agent); + + priv->ssl = SSL_new(ssl_context); + g_return_if_fail(priv->ssl); + + priv->bio = BIO_new(BIO_s_er_dtls_connection()); + g_return_if_fail(priv->bio); + + priv->bio->ptr = self; + SSL_set_bio(priv->ssl, priv->bio, priv->bio); + + SSL_set_verify(priv->ssl, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, openssl_verify_callback); + SSL_set_ex_data(priv->ssl, connection_ex_index, self); + + log_state(self, "connection created"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +void er_dtls_connection_start(ErDtlsConnection *self, gboolean is_client) +{ + g_return_if_fail(ER_IS_DTLS_CONNECTION(self)); + ErDtlsConnectionPrivate *priv = self->priv; + g_return_if_fail(priv->send_closure); + g_return_if_fail(priv->ssl); + g_return_if_fail(priv->bio); + + LOG_TRACE(self, "locking @ start"); + g_mutex_lock(&priv->mutex); + LOG_TRACE(self, "locked @ start"); + + priv->is_alive = TRUE; + priv->timeout_set = FALSE; + priv->bio_buffer = NULL; + priv->bio_buffer_len = 0; + priv->bio_buffer_offset = 0; + priv->keys_exported = FALSE; + + priv->is_client = is_client; + if (priv->is_client) { + SSL_set_connect_state(priv->ssl); + } else { + SSL_set_accept_state(priv->ssl); + } + log_state(self, "initial state set"); + + openssl_poll(self); + + log_state(self, "first poll done"); + priv->thread = NULL; + + LOG_TRACE(self, "unlocking @ start"); + g_mutex_unlock(&priv->mutex); +} + +void er_dtls_connection_start_timeout(ErDtlsConnection *self) +{ + g_return_if_fail(ER_IS_DTLS_CONNECTION(self)); + + ErDtlsConnectionPrivate *priv = self->priv; + GError *error = NULL; + gchar *thread_name = g_strdup_printf("connection_thread_%p", self); + + LOG_TRACE(self, "locking @ start_timeout"); + g_mutex_lock(&priv->mutex); + LOG_TRACE(self, "locked @ start_timeout"); + + LOG_INFO(self, "starting connection timeout"); + priv->thread = g_thread_try_new(thread_name, + (GThreadFunc) connection_timeout_thread_func, self, &error); + if (error) { + LOG_WARNING(self, "error creating connection thread: %s (%d)", + error->message, error->code); + g_clear_error(&error); + } + + g_free(thread_name); + + LOG_TRACE(self, "unlocking @ start_timeout"); + g_mutex_unlock(&priv->mutex); +} + +void er_dtls_connection_stop(ErDtlsConnection *self) +{ + g_return_if_fail(ER_IS_DTLS_CONNECTION(self)); + g_return_if_fail(self->priv->ssl); + g_return_if_fail(self->priv->bio); + + LOG_DEBUG(self, "stopping connection"); + + LOG_TRACE(self, "locking @ stop"); + g_mutex_lock(&self->priv->mutex); + LOG_TRACE(self, "locked @ stop"); + + self->priv->is_alive = FALSE; + LOG_TRACE(self, "signaling @ stop"); + g_cond_signal(&self->priv->condition); + LOG_TRACE(self, "signaled @ stop"); + + LOG_TRACE(self, "unlocking @ stop"); + g_mutex_unlock(&self->priv->mutex); + + LOG_DEBUG(self, "stopped connection"); +} + +void er_dtls_connection_close(ErDtlsConnection *self) +{ + g_return_if_fail(ER_IS_DTLS_CONNECTION(self)); + g_return_if_fail(self->priv->ssl); + g_return_if_fail(self->priv->bio); + + LOG_DEBUG(self, "closing connection"); + + LOG_TRACE(self, "locking @ close"); + g_mutex_lock(&self->priv->mutex); + LOG_TRACE(self, "locked @ close"); + + if (self->priv->is_alive) { + self->priv->is_alive = FALSE; + g_cond_signal(&self->priv->condition); + } + + LOG_TRACE(self, "unlocking @ close"); + g_mutex_unlock(&self->priv->mutex); + + if (self->priv->thread) { + g_thread_join(self->priv->thread); + self->priv->thread = NULL; + } + + LOG_DEBUG(self, "closed connection"); +} + +void er_dtls_connection_set_send_callback(ErDtlsConnection *self, GClosure *closure) +{ + g_return_if_fail(ER_IS_DTLS_CONNECTION(self)); + + LOG_TRACE(self, "locking @ set_send_callback"); + g_mutex_lock(&self->priv->mutex); + LOG_TRACE(self, "locked @ set_send_callback"); + + self->priv->send_closure = closure; + + if (closure && G_CLOSURE_NEEDS_MARSHAL(closure)) { + g_closure_set_marshal(closure, g_cclosure_marshal_generic); + } + + LOG_TRACE(self, "unlocking @ set_send_callback"); + g_mutex_unlock(&self->priv->mutex); +} + +gint er_dtls_connection_process(ErDtlsConnection *self, gpointer data, gint len) +{ + g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), 0); + ErDtlsConnectionPrivate *priv = self->priv; + gint result; + + g_return_val_if_fail(self->priv->ssl, 0); + g_return_val_if_fail(self->priv->bio, 0); + + LOG_TRACE(self, "locking @ process"); + g_mutex_lock(&priv->mutex); + LOG_TRACE(self, "locked @ process"); + + g_warn_if_fail(!priv->bio_buffer); + + priv->bio_buffer = data; + priv->bio_buffer_len = len; + priv->bio_buffer_offset = 0; + + log_state(self, "process start"); + + if (SSL_want_write(priv->ssl)) { + openssl_poll(self); + log_state(self, "process want write, after poll"); + } + + result = SSL_read(priv->ssl, data, len); + + log_state(self, "process after read"); + + openssl_poll(self); + + log_state(self, "process after poll"); + + LOG_DEBUG(self, "read result: %d", result); + + LOG_TRACE(self, "unlocking @ process"); + g_mutex_unlock(&priv->mutex); + + return result; +} + +gint er_dtls_connection_send(ErDtlsConnection *self, gpointer data, gint len) +{ + g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), 0); + int ret = 0; + + g_return_val_if_fail(self->priv->ssl, 0); + g_return_val_if_fail(self->priv->bio, 0); + + LOG_TRACE(self, "locking @ send"); + g_mutex_lock(&self->priv->mutex); + LOG_TRACE(self, "locked @ send"); + + if (SSL_is_init_finished(self->priv->ssl)) { + ret = SSL_write(self->priv->ssl, data, len); + LOG_DEBUG(self, "data sent: input was %d B, output is %d B", len, ret); + } else { + LOG_WARNING(self, "tried to send data before handshake was complete"); + ret = 0; + } + + LOG_TRACE(self, "unlocking @ send"); + g_mutex_unlock(&self->priv->mutex); + + return ret; +} + +/* + ###### ####### ## ## + ## ## ## ## ### ## + ## ## ## #### ## + ## ## ## ## ## ## + ## ## ## ## #### + ## ## ## ## ## ### + ###### ####### ## ## +*/ + +static void log_state(ErDtlsConnection *self, const gchar *str) +{ + ErDtlsConnectionPrivate *priv = self->priv; + guint states = 0; + + states |= (!!SSL_is_init_finished(priv->ssl) << 0); + states |= (!!SSL_in_init(priv->ssl) << 4); + states |= (!!SSL_in_before(priv->ssl) << 8); + states |= (!!SSL_in_connect_init(priv->ssl) << 12); + states |= (!!SSL_in_accept_init(priv->ssl) << 16); + states |= (!!SSL_want_write(priv->ssl) << 20); + states |= (!!SSL_want_read(priv->ssl) << 24); + + LOG_LOG(self, "%s: role=%s buf=(%d,%p:%d/%d) %x|%x %s", + str, + priv->is_client ? "client" : "server", + pqueue_size(priv->ssl->d1->sent_messages), + priv->bio_buffer, + priv->bio_buffer_offset, + priv->bio_buffer_len, + states, SSL_get_state(priv->ssl), + SSL_state_string_long(priv->ssl)); +} + +static gpointer connection_timeout_thread_func(ErDtlsConnection *self) +{ + ErDtlsConnectionPrivate *priv = self->priv; + struct timeval timeout; + gint64 end_time, wait_time; + gint ret; + + while (priv->is_alive) { + LOG_TRACE(self, "locking @ timeout"); + g_mutex_lock(&priv->mutex); + LOG_TRACE(self, "locked @ timeout"); + + if (DTLSv1_get_timeout(priv->ssl, &timeout)) { + wait_time = timeout.tv_sec * G_USEC_PER_SEC + timeout.tv_usec; + + if (wait_time) { + LOG_DEBUG(self, "waiting for %" G_GINT64_FORMAT " usec", wait_time); + + end_time = g_get_monotonic_time() + wait_time; + + LOG_TRACE(self, "wait @ timeout"); + g_cond_wait_until(&priv->condition, &priv->mutex, end_time); + LOG_TRACE(self, "continued @ timeout"); + } + + ret = DTLSv1_handle_timeout(priv->ssl); + + LOG_DEBUG(self, "handle timeout returned %d, is_alive: %d", ret, priv->is_alive); + + if (ret < 0) { + LOG_TRACE(self, "unlocking @ timeout failed"); + g_mutex_unlock(&priv->mutex); + break; /* self failed after DTLS1_TMO_ALERT_COUNT (12) attempts */ + } + + if (ret > 0) { + log_state(self, "handling timeout before poll"); + openssl_poll(self); + log_state(self, "handling timeout after poll"); + } + } else { + LOG_DEBUG(self, "waiting indefinitely"); + + priv->timeout_set = FALSE; + + while (!priv->timeout_set && priv->is_alive) { + LOG_TRACE(self, "wait @ timeout"); + g_cond_wait(&priv->condition, &priv->mutex); + } + LOG_TRACE(self, "continued @ timeout"); + } + + LOG_TRACE(self, "unlocking @ timeout"); + g_mutex_unlock(&priv->mutex); + } + + log_state(self, "timeout thread exiting"); + + return NULL; +} + +static void export_srtp_keys(ErDtlsConnection *self) +{ + typedef struct { + guint8 v[SRTP_KEY_LEN]; + } Key; + + typedef struct { + guint8 v[SRTP_SALT_LEN]; + } Salt; + + struct { + Key client_key; + Key server_key; + Salt client_salt; + Salt server_salt; + } exported_keys; + + struct { + Key key; + Salt salt; + } client_key, server_key; + + SRTP_PROTECTION_PROFILE *profile; + ErDtlsSrtpCipher cipher; + ErDtlsSrtpAuth auth; + gint success; + + static gchar export_string[] = "EXTRACTOR-dtls_srtp"; + + success = SSL_export_keying_material(self->priv->ssl, + (gpointer) &exported_keys, 60, export_string, strlen(export_string), NULL, 0, 0); + + if (!success) { + LOG_WARNING(self, "failed to export srtp keys"); + return; + } + + profile = SSL_get_selected_srtp_profile(self->priv->ssl); + + LOG_INFO(self, "keys received, profile is %s", profile->name); + + switch (profile->id) { + case SRTP_AES128_CM_SHA1_80: + cipher = ER_DTLS_SRTP_CIPHER_AES_128_ICM; + auth = ER_DTLS_SRTP_AUTH_HMAC_SHA1_80; + break; + case SRTP_AES128_CM_SHA1_32: + cipher = ER_DTLS_SRTP_CIPHER_AES_128_ICM; + auth = ER_DTLS_SRTP_AUTH_HMAC_SHA1_32; + break; + default: + LOG_WARNING(self, "invalid crypto suite set by handshake"); + goto beach; + } + + client_key.key = exported_keys.client_key; + server_key.key = exported_keys.server_key; + client_key.salt = exported_keys.client_salt; + server_key.salt = exported_keys.server_salt; + + if (self->priv->is_client) { + g_signal_emit(self, signals[SIGNAL_ON_ENCODER_KEY], 0, &client_key, cipher, auth); + g_signal_emit(self, signals[SIGNAL_ON_DECODER_KEY], 0, &server_key, cipher, auth); + } else { + g_signal_emit(self, signals[SIGNAL_ON_ENCODER_KEY], 0, &server_key, cipher, auth); + g_signal_emit(self, signals[SIGNAL_ON_DECODER_KEY], 0, &client_key, cipher, auth); + } + +beach: + self->priv->keys_exported = TRUE; +} + +static void openssl_poll(ErDtlsConnection *self) +{ + int ret; + char buf[512]; + int error; + + log_state(self, "poll: before handshake"); + + ret = SSL_do_handshake(self->priv->ssl); + + log_state(self, "poll: after handshake"); + + if (ret == 1) { + if (!self->priv->keys_exported) { + LOG_INFO(self, "handshake just completed successfully, exporting keys"); + export_srtp_keys(self); + } else { + LOG_INFO(self, "handshake is completed"); + } + return; + } else { + if (ret == 0) { + LOG_DEBUG(self, "do_handshake encountered EOF"); + } else if (ret == -1) { + LOG_WARNING(self, "do_handshake encountered BIO error"); + } else { + LOG_DEBUG(self, "do_handshake returned %d", ret); + } + } + + error = SSL_get_error(self->priv->ssl, ret); + + switch (error) { + case SSL_ERROR_NONE: + LOG_WARNING(self, "no error, handshake should be done"); + break; + case SSL_ERROR_SSL: + LOG_LOG(self, "SSL error %d: %s", error, ERR_error_string(ERR_get_error(), buf)); + break; + case SSL_ERROR_WANT_READ: + LOG_LOG(self, "SSL wants read"); + break; + case SSL_ERROR_WANT_WRITE: + LOG_LOG(self, "SSL wants write"); + break; + case SSL_ERROR_SYSCALL: { + LOG_LOG(self, "SSL syscall (error) : %lu", ERR_get_error()); + break; + } + default: + LOG_WARNING(self, "Unknown SSL error: %d, ret: %d", error, ret); + } +} + +static int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + ErDtlsConnection *self; + SSL *ssl; + BIO *bio; + gchar *pem = NULL; + gboolean accepted = FALSE; + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + self = SSL_get_ex_data(ssl, connection_ex_index); + g_return_val_if_fail(ER_IS_DTLS_CONNECTION(self), FALSE); + + pem = _er_dtls_x509_to_pem(x509_ctx->cert); + + if (!pem) { + LOG_WARNING(self, "failed to convert received certificate to pem format"); + } else { + bio = BIO_new(BIO_s_mem()); + if (bio) { + gchar buffer[2048]; + gint len; + + len = X509_NAME_print_ex(bio, X509_get_subject_name(x509_ctx->cert), 1, XN_FLAG_MULTILINE); + BIO_read(bio, buffer, len); + buffer[len] = '\0'; + LOG_DEBUG(self, "Peer certificate received:\n%s", buffer); + BIO_free(bio); + } else { + LOG_DEBUG(self, "failed to create certificate print membio"); + } + + g_signal_emit(self, signals[SIGNAL_ON_PEER_CERTIFICATE], 0, pem, &accepted); + g_free(pem); + } + + return accepted; +} + +/* + ######## #### ####### + ## ## ## ## ## + ## ## ## ## ## + ######## ## ## ## + ## ## ## ## ## + ## ## ## ## ## + ######## #### ####### +*/ + +static BIO_METHOD custom_bio_methods = { + BIO_TYPE_BIO, + "stream", + bio_method_write, + bio_method_read, + NULL, + NULL, + bio_method_ctrl, + bio_method_new, + bio_method_free, + NULL, +}; + +static BIO_METHOD* BIO_s_er_dtls_connection() +{ + return &custom_bio_methods; +} + +static int bio_method_write(BIO *bio, const char *data, int size) +{ + ErDtlsConnection *self = ER_DTLS_CONNECTION(bio->ptr); + + LOG_LOG(self, "BIO: writing %d", size); + + if (self->priv->send_closure) { + GValue values[3] = { G_VALUE_INIT }; + + g_value_init(&values[0], ER_TYPE_DTLS_CONNECTION); + g_value_set_object(&values[0], self); + + g_value_init(&values[1], G_TYPE_POINTER); + g_value_set_pointer(&values[1], (gpointer) data); + + g_value_init(&values[2], G_TYPE_INT); + g_value_set_int(&values[2], size); + + g_closure_invoke(self->priv->send_closure, NULL, 3, values, NULL); + } + + return size; +} + +static int bio_method_read(BIO *bio, char *out_buffer, int size) +{ + ErDtlsConnection *self = ER_DTLS_CONNECTION(bio->ptr); + ErDtlsConnectionPrivate *priv = self->priv; + guint internal_size; + gint copy_size; + + internal_size = priv->bio_buffer_len - priv->bio_buffer_offset; + + if (!priv->bio_buffer) { + LOG_LOG(self, "BIO: EOF"); + return 0; + } + + if (!out_buffer || size <= 0) { + LOG_WARNING(self, "BIO: read got invalid arguments"); + if (internal_size) { + BIO_set_retry_read(bio); + } + return internal_size; + } + + if (size > internal_size) { + copy_size = internal_size; + } else { + copy_size = size; + } + + LOG_DEBUG(self, "reading %d/%d bytes %d at offset %d, output buff size is %d", + copy_size, priv->bio_buffer_len, internal_size, priv->bio_buffer_offset, size); + + memcpy(out_buffer, priv->bio_buffer + priv->bio_buffer_offset, copy_size); + priv->bio_buffer_offset += copy_size; + + if (priv->bio_buffer_len == priv->bio_buffer_offset) { + priv->bio_buffer = NULL; + } + + return copy_size; +} + +static long bio_method_ctrl(BIO *bio, int cmd, long arg1, void *arg2) +{ + ErDtlsConnection *self = ER_DTLS_CONNECTION(bio->ptr); + ErDtlsConnectionPrivate *priv = self->priv; + + switch (cmd) { + case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: + case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT: + LOG_LOG(self, "BIO: Timeout set"); + priv->timeout_set = TRUE; + g_cond_signal(&priv->condition); + return 1; + case BIO_CTRL_RESET: + priv->bio_buffer = NULL; + priv->bio_buffer_len = 0; + priv->bio_buffer_offset = 0; + LOG_LOG(self, "BIO: EOF reset"); + return 1; + case BIO_CTRL_EOF: { + gint eof = !(priv->bio_buffer_len - priv->bio_buffer_offset); + LOG_LOG(self, "BIO: EOF query returned %d", eof); + return eof; + } + case BIO_CTRL_WPENDING: + LOG_LOG(self, "BIO: pending write"); + return 1; + case BIO_CTRL_PENDING: { + gint pending = priv->bio_buffer_len - priv->bio_buffer_offset; + LOG_LOG(self, "BIO: %d bytes pending", pending); + return pending; + } + case BIO_CTRL_FLUSH: + LOG_LOG(self, "BIO: flushing"); + return 1; + case BIO_CTRL_DGRAM_QUERY_MTU: + LOG_DEBUG(self, "BIO: MTU query, returning 0..."); + return 0; + case BIO_CTRL_DGRAM_MTU_EXCEEDED: + LOG_WARNING(self, "BIO: MTU exceeded"); + return 0; + default: + LOG_LOG(self, "BIO: unhandled ctrl, %d", cmd); + return 0; + } +} + +static int bio_method_new(BIO *bio) +{ + LOG_LOG(NULL, "BIO: new"); + + bio->shutdown = 0; + bio->init = 1; + + return 1; +} + +static int bio_method_free(BIO *bio) +{ + if (!bio) { + LOG_LOG(NULL, "BIO free called with null bio"); + return 0; + } + + LOG_LOG(ER_DTLS_CONNECTION(bio->ptr), "BIO free"); + return 0; +} diff --git a/ext/dtls/gstdtlsconnection.h b/ext/dtls/gstdtlsconnection.h new file mode 100644 index 0000000000..916d8ad9f3 --- /dev/null +++ b/ext/dtls/gstdtlsconnection.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlsconnection_h +#define gstdtlsconnection_h + +#include + +G_BEGIN_DECLS + +#define ER_TYPE_DTLS_CONNECTION (er_dtls_connection_get_type()) +#define ER_DTLS_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), ER_TYPE_DTLS_CONNECTION, ErDtlsConnection)) +#define ER_DTLS_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), ER_TYPE_DTLS_CONNECTION, ErDtlsConnectionClass)) +#define ER_IS_DTLS_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), ER_TYPE_DTLS_CONNECTION)) +#define ER_IS_DTLS_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), ER_TYPE_DTLS_CONNECTION)) +#define ER_DTLS_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), ER_TYPE_DTLS_CONNECTION, ErDtlsConnectionClass)) + +typedef struct _ErDtlsConnection ErDtlsConnection; +typedef struct _ErDtlsConnectionClass ErDtlsConnectionClass; +typedef struct _ErDtlsConnectionPrivate ErDtlsConnectionPrivate; + +/** + * ErDtlsSrtpCipher: + * @ER_DTLS_SRTP_CIPHER_AES_128_ICM: aes-128-icm + * + * SRTP Cipher selected by the DTLS handshake, should match the enums in gstsrtp + */ +typedef enum { + ER_DTLS_SRTP_CIPHER_AES_128_ICM = 1 +} ErDtlsSrtpCipher; + +/** + * ErDtlsSrtpAuth: + * @ER_DTLS_SRTP_AUTH_HMAC_SHA1_32: hmac-sha1-32 + * @ER_DTLS_SRTP_AUTH_HMAC_SHA1_80: hmac-sha1-80 + * + * SRTP Auth selected by the DTLS handshake, should match the enums in gstsrtp + */ +typedef enum { + ER_DTLS_SRTP_AUTH_HMAC_SHA1_32 = 1, + ER_DTLS_SRTP_AUTH_HMAC_SHA1_80 = 2 +} ErDtlsSrtpAuth; + +#define ER_DTLS_SRTP_MASTER_KEY_LENGTH 30 + +/* + * ErDtlsConnection: + * + * A class that handles a single DTLS connection. + * Any connection needs to be created with the agent property set. + * Once the DTLS handshake is completed, on-encoder-key and on-decoder-key will be signalled. + */ +struct _ErDtlsConnection { + GObject parent_instance; + + ErDtlsConnectionPrivate *priv; +}; + +struct _ErDtlsConnectionClass { + GObjectClass parent_class; +}; + +GType er_dtls_connection_get_type(void) G_GNUC_CONST; + +void er_dtls_connection_start(ErDtlsConnection *, gboolean is_client); +void er_dtls_connection_start_timeout(ErDtlsConnection *); + +/* + * Stops the connections, it is not required to call this function. + */ +void er_dtls_connection_stop(ErDtlsConnection *); + +/* + * Closes the connection, the function will block until the connection has been stopped. + * If stop is called some time before, close will return instantly. + */ +void er_dtls_connection_close(ErDtlsConnection *); + +/* + * Sets the closure that will be called whenever data needs to be sent. + * + * The closure will get called with the following arguments: + * void cb(ErDtlsConnection *, gpointer data, gint length, gpointer user_data) + */ +void er_dtls_connection_set_send_callback(ErDtlsConnection *, GClosure *); + +/* + * Processes data that has been recevied, the transformation is done in-place. + * Returns the length of the plaintext data that was decoded, if no data is available, 0<= will be returned. + */ +gint er_dtls_connection_process(ErDtlsConnection *, gpointer ptr, gint len); + +/* + * If the DTLS handshake is completed this function will encode the given data. + * Returns the length of the data sent, or 0 if the DTLS handshake is not completed. + */ +gint er_dtls_connection_send(ErDtlsConnection *, gpointer ptr, gint len); + +G_END_DECLS + +#endif /* gstdtlsconnection_h */ diff --git a/ext/dtls/gstdtlsdec.c b/ext/dtls/gstdtlsdec.c new file mode 100644 index 0000000000..70865be908 --- /dev/null +++ b/ext/dtls/gstdtlsdec.c @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlsdec.h" + +#include "gstdtlscertificate.h" + +static GstStaticPadTemplate sink_template = + GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("application/x-dtls") + ); + +static GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY + ); + +GST_DEBUG_CATEGORY_STATIC(er_dtls_dec_debug); +#define GST_CAT_DEFAULT er_dtls_dec_debug + +#define gst_er_dtls_dec_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE(GstErDtlsDec, gst_er_dtls_dec, GST_TYPE_ELEMENT, + GST_DEBUG_CATEGORY_INIT(er_dtls_dec_debug, "erdtlsdec", 0, "Ericsson DTLS Decoder")); + +#define UNUSED(param) while (0) { (void)(param); } + +enum { + SIGNAL_ON_KEY_RECEIVED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS]; + +enum { + PROP_0, + PROP_CONNECTION_ID, + PROP_PEM, + PROP_PEER_PEM, + + PROP_DECODER_KEY, + PROP_SRTP_CIPHER, + PROP_SRTP_AUTH, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +#define DEFAULT_CONNECTION_ID NULL +#define DEFAULT_PEM NULL +#define DEFAULT_PEER_PEM NULL + +#define DEFAULT_DECODER_KEY NULL +#define DEFAULT_SRTP_CIPHER 0 +#define DEFAULT_SRTP_AUTH 0 + + +static void gst_er_dtls_dec_finalize(GObject *); +static void gst_er_dtls_dec_dispose(GObject *); +static void gst_er_dtls_dec_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +static void gst_er_dtls_dec_get_property(GObject *, guint prop_id, GValue *, GParamSpec *); + +static GstStateChangeReturn gst_er_dtls_dec_change_state(GstElement *, GstStateChange); +static GstPad *gst_er_dtls_dec_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *); +static void gst_er_dtls_dec_release_pad(GstElement *, GstPad *); + +static void on_key_received(ErDtlsConnection *, gpointer key, guint cipher, guint auth, GstErDtlsDec *); +static gboolean on_peer_certificate_received(ErDtlsConnection *, gchar *pem, GstErDtlsDec *); +static GstFlowReturn sink_chain(GstPad *, GstObject *parent, GstBuffer *); + +static ErDtlsAgent *get_agent_by_pem(const gchar *pem); +static void agent_weak_ref_notify(gchar *pem, ErDtlsAgent *); +static void create_connection(GstErDtlsDec *, gchar *id); +static void connection_weak_ref_notify(gchar *id, ErDtlsConnection *); + +static void gst_er_dtls_dec_class_init(GstErDtlsDecClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + gobject_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_finalize); + gobject_class->dispose = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_dispose); + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_get_property); + + element_class->change_state = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_change_state); + element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_request_new_pad); + element_class->release_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_dec_release_pad); + + signals[SIGNAL_ON_KEY_RECEIVED] = + g_signal_new("on-key-received", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0); + + properties[PROP_CONNECTION_ID] = + g_param_spec_string("connection-id", + "Connection id", + "Every encoder/decoder pair should have the same, unique, connection-id", + DEFAULT_CONNECTION_ID, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_PEM] = + g_param_spec_string("pem", + "PEM string", + "A string containing a X509 certificate and RSA private key in PEM format", + DEFAULT_PEM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_PEER_PEM] = + g_param_spec_string("peer-pem", + "Peer PEM string", + "The X509 certificate received in the DTLS handshake, in PEM format", + DEFAULT_PEER_PEM, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_DECODER_KEY] = + g_param_spec_boxed("decoder-key", + "Decoder key", + "SRTP key that should be used by the decider", + GST_TYPE_CAPS, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTP_CIPHER] = + g_param_spec_uint("srtp-cipher", + "SRTP cipher", + "The SRTP cipher selected in the DTLS handshake. " + "The value will be set to an ErDtlsSrtpCipher.", + 0, ER_DTLS_SRTP_CIPHER_AES_128_ICM, DEFAULT_SRTP_CIPHER, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTP_AUTH] = + g_param_spec_uint("srtp-auth", + "SRTP authentication", + "The SRTP authentication selected in the DTLS handshake. " + "The value will be set to an ErDtlsSrtpAuth.", + 0, ER_DTLS_SRTP_AUTH_HMAC_SHA1_80, DEFAULT_SRTP_AUTH, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&src_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sink_template)); + + gst_element_class_set_static_metadata(element_class, + "DTLS Decoder", + "Decoder/Network/DTLS", + "Decodes DTLS packets", + "Patrik Oldsberg patrik.oldsberg@ericsson.com"); +} + +static void gst_er_dtls_dec_init(GstErDtlsDec *self) +{ + GstPad *sink; + self->agent = get_agent_by_pem(NULL); + self->connection_id = NULL; + self->connection = NULL; + self->peer_pem = NULL; + + self->decoder_key = NULL; + self->srtp_cipher = DEFAULT_SRTP_CIPHER; + self->srtp_auth = DEFAULT_SRTP_AUTH; + + g_mutex_init(&self->src_mutex); + + self->src = NULL; + sink = gst_pad_new_from_static_template(&sink_template, "sink"); + g_return_if_fail(sink); + + gst_pad_set_chain_function(sink, GST_DEBUG_FUNCPTR(sink_chain)); + + gst_element_add_pad(GST_ELEMENT(self), sink); +} + +static void gst_er_dtls_dec_finalize(GObject *object) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(object); + + if (self->decoder_key) { + gst_buffer_unref(self->decoder_key); + self->decoder_key = NULL; + } + + g_free(self->connection_id); + self->connection_id = NULL; + + g_free(self->peer_pem); + self->peer_pem = NULL; + + g_mutex_clear(&self->src_mutex); + + GST_LOG_OBJECT(self, "finalized"); + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_er_dtls_dec_dispose(GObject *object) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(object); + + if (self->agent) { + g_object_unref(self->agent); + self->agent = NULL; + } + + if (self->connection) { + g_object_unref(self->connection); + self->connection = NULL; + } +} + +static void gst_er_dtls_dec_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(object); + + switch (prop_id) { + case PROP_CONNECTION_ID: + g_free(self->connection_id); + self->connection_id = g_value_dup_string(value); + g_return_if_fail(self->agent); + create_connection(self, self->connection_id); + break; + case PROP_PEM: + if (self->agent) { + g_object_unref(self->agent); + } + self->agent = get_agent_by_pem(g_value_get_string(value)); + if (self->connection_id) { + create_connection(self, self->connection_id); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void gst_er_dtls_dec_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(object); + + switch (prop_id) { + case PROP_CONNECTION_ID: + g_value_set_string(value, self->connection_id); + break; + case PROP_PEM: + g_value_take_string(value, er_dtls_agent_get_certificate_pem(self->agent)); + break; + case PROP_PEER_PEM: + g_value_set_string(value, self->peer_pem); + break; + case PROP_DECODER_KEY: + g_value_set_boxed(value, self->decoder_key); + break; + case PROP_SRTP_CIPHER: + g_value_set_uint(value, self->srtp_cipher); + break; + case PROP_SRTP_AUTH: + g_value_set_uint(value, self->srtp_auth); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static GstStateChangeReturn gst_er_dtls_dec_change_state(GstElement *element, GstStateChange transition) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(element); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (self->connection) { + g_signal_connect_object(self->connection, + "on-decoder-key", G_CALLBACK(on_key_received), self, 0); + g_signal_connect_object(self->connection, + "on-peer-certificate", G_CALLBACK(on_peer_certificate_received), self, 0); + } else { + GST_WARNING_OBJECT(self, "trying to change state to ready without connection id and pem"); + return GST_STATE_CHANGE_FAILURE; + } + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); + + return ret; +} + +static GstPad *gst_er_dtls_dec_request_new_pad(GstElement *element, + GstPadTemplate *tmpl, const gchar *name, const GstCaps *caps) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(element); + + GST_DEBUG_OBJECT(element, "requesting pad"); + + g_return_val_if_fail(!self->src, NULL); + g_return_val_if_fail(tmpl->direction == GST_PAD_SRC, NULL); + + g_mutex_lock(&self->src_mutex); + + self->src = gst_pad_new_from_template(tmpl, name); + g_return_val_if_fail(self->src, NULL); + + if (caps) { + g_object_set(self->src, "caps", caps, NULL); + } + + gst_pad_set_active(self->src, TRUE); + gst_element_add_pad(element, self->src); + + g_mutex_unlock(&self->src_mutex); + + return self->src; +} + +static void gst_er_dtls_dec_release_pad(GstElement *element, GstPad *pad) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(element); + + g_mutex_lock(&self->src_mutex); + + g_return_if_fail(self->src == pad); + gst_element_remove_pad(element, self->src); + self->src = NULL; + + GST_DEBUG_OBJECT(self, "releasing src pad"); + + g_mutex_unlock(&self->src_mutex); + + GST_ELEMENT_GET_CLASS(element)->release_pad(element, pad); +} + +static void on_key_received(ErDtlsConnection *connection, gpointer key, guint cipher, guint auth, GstErDtlsDec *self) +{ + gpointer key_dup; + gchar *key_str; + + UNUSED(connection); + g_return_if_fail(GST_IS_ER_DTLS_DEC(self)); + + self->srtp_cipher = cipher; + self->srtp_auth = auth; + + key_dup = g_memdup(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH); + self->decoder_key = gst_buffer_new_wrapped(key_dup, ER_DTLS_SRTP_MASTER_KEY_LENGTH); + + key_str = g_base64_encode(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH); + GST_INFO_OBJECT(self, "received key: %s", key_str); + g_free(key_str); + + g_signal_emit(self, signals[SIGNAL_ON_KEY_RECEIVED], 0); +} + +static gboolean signal_peer_certificate_received(GWeakRef *ref) +{ + GstErDtlsDec *self; + + self = g_weak_ref_get(ref); + g_weak_ref_clear(ref); + g_free(ref); + ref = NULL; + + if (self) { + g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_PEER_PEM]); + g_object_unref(self); + self = NULL; + } + + return FALSE; +} + +static gboolean on_peer_certificate_received(ErDtlsConnection *connection, gchar *pem, GstErDtlsDec *self) +{ + GWeakRef *ref; + + UNUSED(connection); + g_return_val_if_fail(GST_IS_ER_DTLS_DEC(self), TRUE); + + GST_DEBUG_OBJECT(self, "Received peer certificate PEM: \n%s", pem); + + self->peer_pem = g_strdup(pem); + + ref = g_new(GWeakRef, 1); + g_weak_ref_init(ref, self); + + g_idle_add((GSourceFunc) signal_peer_certificate_received, ref); + + return TRUE; +} + +static GstFlowReturn sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + GstErDtlsDec *self = GST_ER_DTLS_DEC(parent); + GstFlowReturn ret = GST_FLOW_OK; + GstMapInfo map_info = GST_MAP_INFO_INIT; + gint size; + + if (!self->agent) { + gst_buffer_unref(buffer); + return GST_FLOW_OK; + } + + GST_DEBUG_OBJECT(self, "received buffer from %s with length %zd", + self->connection_id, gst_buffer_get_size(buffer)); + + gst_buffer_map(buffer, &map_info, GST_MAP_READWRITE); + + if (!map_info.size) { + gst_buffer_unmap(buffer, &map_info); + return GST_FLOW_OK; + } + + size = er_dtls_connection_process(self->connection, map_info.data, map_info.size); + gst_buffer_unmap(buffer, &map_info); + + if (size <= 0) { + gst_buffer_unref(buffer); + + return GST_FLOW_OK; + } + + g_mutex_lock(&self->src_mutex); + + if (self->src) { + gst_buffer_set_size(buffer, size); + GST_LOG_OBJECT(self, "decoded buffer with length %d, pushing", size); + ret = gst_pad_push(self->src, buffer); + } else { + GST_LOG_OBJECT(self, "dropped buffer with length %d, not linked", size); + gst_buffer_unref(buffer); + } + + g_mutex_unlock(&self->src_mutex); + + return ret; +} + +static GHashTable *agent_table = NULL; +G_LOCK_DEFINE_STATIC(agent_table); + +static ErDtlsAgent *generated_cert_agent = NULL; + +static ErDtlsAgent *get_agent_by_pem(const gchar *pem) +{ + ErDtlsAgent *agent; + + if (!pem) { + if (g_once_init_enter (&generated_cert_agent)) { + ErDtlsAgent *new_agent; + + new_agent = g_object_new(ER_TYPE_DTLS_AGENT, "certificate", + g_object_new(ER_TYPE_DTLS_CERTIFICATE, NULL), NULL); + + GST_DEBUG_OBJECT(generated_cert_agent, "no agent with generated cert found, creating new"); + g_once_init_leave (&generated_cert_agent, new_agent); + } else { + GST_DEBUG_OBJECT(generated_cert_agent, "using agent with generated cert"); + } + + agent = generated_cert_agent; + g_object_ref(agent); + } else { + G_LOCK(agent_table); + + if (!agent_table) { + agent_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + } + + agent = ER_DTLS_AGENT(g_hash_table_lookup(agent_table, pem)); + + if (!agent) { + agent = g_object_new(ER_TYPE_DTLS_AGENT, + "certificate", g_object_new(ER_TYPE_DTLS_CERTIFICATE, "pem", pem, NULL), NULL); + + g_object_weak_ref(G_OBJECT(agent), (GWeakNotify) agent_weak_ref_notify, (gpointer) g_strdup(pem)); + + g_hash_table_insert(agent_table, g_strdup(pem), agent); + + GST_DEBUG_OBJECT(agent, "no agent found, created new"); + } else { + g_object_ref(agent); + GST_DEBUG_OBJECT(agent, "agent found"); + } + + G_UNLOCK(agent_table); + } + + + return agent; +} + +static void agent_weak_ref_notify(gchar *pem, ErDtlsAgent *agent) +{ + UNUSED(agent); + + G_LOCK(agent_table); + g_hash_table_remove(agent_table, pem); + G_UNLOCK(agent_table); + + g_free(pem); + pem = NULL; +} + +static GHashTable *connection_table = NULL; +G_LOCK_DEFINE_STATIC(connection_table); + +ErDtlsConnection *gst_er_dtls_dec_fetch_connection(gchar *id) +{ + ErDtlsConnection *connection; + g_return_val_if_fail(id, NULL); + + GST_DEBUG("fetching '%s' from connection table, size is %d", + id, g_hash_table_size(connection_table)); + + G_LOCK(connection_table); + + connection = g_hash_table_lookup(connection_table, id); + + if (connection) { + g_object_ref(connection); + g_hash_table_remove(connection_table, id); + } else { + GST_WARNING("no connection with id '%s' found", id); + } + + G_UNLOCK(connection_table); + + return connection; +} + +static void create_connection(GstErDtlsDec *self, gchar *id) +{ + g_return_if_fail(GST_IS_ER_DTLS_DEC(self)); + g_return_if_fail(ER_IS_DTLS_AGENT(self->agent)); + + if (self->connection) { + g_object_unref(self->connection); + self->connection = NULL; + } + + G_LOCK(connection_table); + + if (!connection_table) { + connection_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + } + + if (g_hash_table_contains(connection_table, id)) { + G_UNLOCK(connection_table); + + g_return_if_reached(); + } + + self->connection = g_object_new(ER_TYPE_DTLS_CONNECTION, "agent", self->agent, NULL); + + g_object_weak_ref(G_OBJECT(self->connection), (GWeakNotify) connection_weak_ref_notify, g_strdup(id)); + + g_hash_table_insert(connection_table, g_strdup(id), self->connection); + + G_UNLOCK(connection_table); +} + +static void connection_weak_ref_notify(gchar *id, ErDtlsConnection *connection) +{ + UNUSED(connection); + + G_LOCK(connection_table); + g_hash_table_remove(connection_table, id); + G_UNLOCK(connection_table); + + g_free(id); + id = NULL; +} diff --git a/ext/dtls/gstdtlsdec.h b/ext/dtls/gstdtlsdec.h new file mode 100644 index 0000000000..af8e6f5276 --- /dev/null +++ b/ext/dtls/gstdtlsdec.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlsdec_h +#define gstdtlsdec_h + +#include "gstdtlsagent.h" +#include "gstdtlsconnection.h" + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_ER_DTLS_DEC \ + (gst_er_dtls_dec_get_type()) +#define GST_ER_DTLS_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_DEC, GstErDtlsDec)) +#define GST_ER_DTLS_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_DEC, GstErDtlsDecClass)) +#define GST_IS_ER_DTLS_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_DEC)) +#define GST_IS_ER_DTLS_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_DEC)) + +typedef struct _GstErDtlsDec GstErDtlsDec; +typedef struct _GstErDtlsDecClass GstErDtlsDecClass; + +struct _GstErDtlsDec { + GstElement element; + + GstPad *src; + GMutex src_mutex; + + ErDtlsAgent *agent; + ErDtlsConnection *connection; + GMutex connection_mutex; + gchar *connection_id; + gchar *peer_pem; + + GstBuffer *decoder_key; + guint srtp_cipher; + guint srtp_auth; +}; + +struct _GstErDtlsDecClass { + GstElementClass parent_class; +}; + +GType gst_er_dtls_dec_get_type(void); + +gboolean gst_er_dtls_dec_plugin_init(GstPlugin *); + +ErDtlsConnection *gst_er_dtls_dec_fetch_connection(gchar *id); + +G_END_DECLS + +#endif /* gstdtlsdec_h */ diff --git a/ext/dtls/gstdtlsenc.c b/ext/dtls/gstdtlsenc.c new file mode 100644 index 0000000000..9dadab1e26 --- /dev/null +++ b/ext/dtls/gstdtlsenc.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlsenc.h" + +#include "gstdtlsdec.h" + +static GstStaticPadTemplate sink_template = + GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY + ); + +static GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("application/x-dtls") + ); + +GST_DEBUG_CATEGORY_STATIC(er_dtls_enc_debug); +#define GST_CAT_DEFAULT er_dtls_enc_debug + +#define gst_er_dtls_enc_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE(GstErDtlsEnc, gst_er_dtls_enc, GST_TYPE_ELEMENT, + GST_DEBUG_CATEGORY_INIT(er_dtls_enc_debug, "erdtlsenc", 0, "Ericsson DTLS Encoder")); + +enum { + SIGNAL_ON_KEY_RECEIVED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS]; + +enum { + PROP_0, + PROP_CONNECTION_ID, + PROP_IS_CLIENT, + + PROP_ENCODER_KEY, + PROP_SRTP_CIPHER, + PROP_SRTP_AUTH, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +#define DEFAULT_CONNECTION_ID NULL +#define DEFAULT_IS_CLIENT FALSE + +#define DEFAULT_ENCODER_KEY NULL +#define DEFAULT_SRTP_CIPHER 0 +#define DEFAULT_SRTP_AUTH 0 + +#define INITIAL_QUEUE_SIZE 64 + +static void gst_er_dtls_enc_finalize(GObject *); +static void gst_er_dtls_enc_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +static void gst_er_dtls_enc_get_property(GObject *, guint prop_id, GValue *, GParamSpec *); + +static GstStateChangeReturn gst_er_dtls_enc_change_state(GstElement *, GstStateChange); +static GstPad *gst_er_dtls_enc_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *); + +static gboolean src_activate_mode(GstPad *, GstObject *, GstPadMode, gboolean active); +static void src_task_loop(GstPad *); + +static GstFlowReturn sink_chain(GstPad *, GstObject *, GstBuffer *); + +static void on_key_received(ErDtlsConnection *, gpointer key, guint cipher, guint auth, GstErDtlsEnc *); +static void on_send_data(ErDtlsConnection *, gconstpointer data, gint length, GstErDtlsEnc *); + +static void gst_er_dtls_enc_class_init(GstErDtlsEncClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + gobject_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_get_property); + + element_class->change_state = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_change_state); + element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_enc_request_new_pad); + + signals[SIGNAL_ON_KEY_RECEIVED] = + g_signal_new("on-key-received", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0); + + properties[PROP_CONNECTION_ID] = + g_param_spec_string("connection-id", + "Connection id", + "Every encoder/decoder pair should have the same, unique, connection-id", + DEFAULT_CONNECTION_ID, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_IS_CLIENT] = + g_param_spec_boolean("is-client", + "Is client", + "Set to true if the decoder should act as" + "client and initiate the handshake", + DEFAULT_IS_CLIENT, + GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ENCODER_KEY] = + g_param_spec_boxed("encoder-key", + "Encoder key", + "Master key that should be used by the SRTP encoder", + GST_TYPE_BUFFER, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTP_CIPHER] = + g_param_spec_uint("srtp-cipher", + "SRTP cipher", + "The SRTP cipher selected in the DTLS handshake. " + "The value will be set to an ErDtlsSrtpCipher.", + 0, ER_DTLS_SRTP_CIPHER_AES_128_ICM, DEFAULT_SRTP_CIPHER, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTP_AUTH] = + g_param_spec_uint("srtp-auth", + "SRTP authentication", + "The SRTP authentication selected in the DTLS handshake. " + "The value will be set to an ErDtlsSrtpAuth.", + 0, ER_DTLS_SRTP_AUTH_HMAC_SHA1_80, DEFAULT_SRTP_AUTH, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&src_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sink_template)); + + gst_element_class_set_static_metadata(element_class, + "DTLS Encoder", + "Encoder/Network/DTLS", + "Encodes packets with DTLS", + "Patrik Oldsberg patrik.oldsberg@ericsson.com"); +} + +static void gst_er_dtls_enc_init(GstErDtlsEnc *self) +{ + self->connection_id = NULL; + self->connection = NULL; + + self->is_client = DEFAULT_IS_CLIENT; + + self->encoder_key = NULL; + self->srtp_cipher = DEFAULT_SRTP_CIPHER; + self->srtp_auth = DEFAULT_SRTP_AUTH; + + self->queue = g_ptr_array_sized_new(INITIAL_QUEUE_SIZE); + + g_mutex_init(&self->queue_lock); + g_cond_init(&self->queue_cond_add); + + self->src = gst_pad_new_from_static_template(&src_template, "src"); + g_return_if_fail(self->src); + + gst_pad_set_activatemode_function(self->src, GST_DEBUG_FUNCPTR(src_activate_mode)); + + gst_element_add_pad(GST_ELEMENT(self), self->src); +} + +static void gst_er_dtls_enc_finalize(GObject *object) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC(object); + + if (self->encoder_key) { + gst_buffer_unref(self->encoder_key); + self->encoder_key = NULL; + } + + g_mutex_lock(&self->queue_lock); + + g_ptr_array_set_free_func(self->queue, (GDestroyNotify) gst_buffer_unref); + g_ptr_array_unref(self->queue); + self->queue = NULL; + + g_mutex_unlock(&self->queue_lock); + + g_mutex_clear(&self->queue_lock); + g_cond_clear(&self->queue_cond_add); + + GST_LOG_OBJECT(self, "finalized"); + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_er_dtls_enc_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC(object); + + switch (prop_id) { + case PROP_CONNECTION_ID: + self->connection_id = g_value_dup_string(value); + break; + case PROP_IS_CLIENT: + self->is_client = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void gst_er_dtls_enc_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC(object); + + switch (prop_id) { + case PROP_CONNECTION_ID: + g_value_set_string(value, self->connection_id); + break; + case PROP_IS_CLIENT: + g_value_set_boolean(value, self->is_client); + break; + case PROP_ENCODER_KEY: + g_value_set_boxed(value, self->encoder_key); + break; + case PROP_SRTP_CIPHER: + g_value_set_uint(value, self->srtp_cipher); + break; + case PROP_SRTP_AUTH: + g_value_set_uint(value, self->srtp_auth); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static GstStateChangeReturn gst_er_dtls_enc_change_state(GstElement *element, GstStateChange transition) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC(element); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (self->connection_id) { + self->connection = gst_er_dtls_dec_fetch_connection(self->connection_id); + + if (!self->connection) { + GST_WARNING_OBJECT(self, + "invalid connection id: '%s', connection not found or already in use", + self->connection_id); + return GST_STATE_CHANGE_FAILURE; + } + + g_signal_connect_object(self->connection, + "on-encoder-key", G_CALLBACK(on_key_received), self, 0); + + er_dtls_connection_set_send_callback(self->connection, + g_cclosure_new(G_CALLBACK(on_send_data), self, NULL)); + } else { + GST_WARNING_OBJECT(self, "trying to change state to ready without connection id"); + return GST_STATE_CHANGE_FAILURE; + } + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT(self, "starting connection %s", self->connection_id); + er_dtls_connection_start(self->connection, self->is_client); + + gst_pad_set_active(self->src, TRUE); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT(self, "stopping connection %s", self->connection_id); + gst_pad_set_active(self->src, FALSE); + + er_dtls_connection_stop(self->connection); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + GST_DEBUG_OBJECT(self, "closing connection %s", self->connection_id); + + if (self->connection) { + er_dtls_connection_close(self->connection); + er_dtls_connection_set_send_callback(self->connection, NULL); + g_object_unref(self->connection); + self->connection = NULL; + } + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); + + return ret; +} + +static GstPad *gst_er_dtls_enc_request_new_pad(GstElement *element, + GstPadTemplate *templ, const gchar *name, const GstCaps *caps) +{ + GstPad *sink; + gboolean ret; + + GST_DEBUG_OBJECT(element, "sink pad requested"); + + g_return_val_if_fail(templ->direction == GST_PAD_SINK, NULL); + + sink = gst_pad_new_from_template(templ, name); + g_return_val_if_fail(sink, NULL); + + if (caps) { + g_object_set(sink, "caps", caps, NULL); + } + + gst_pad_set_chain_function(sink, GST_DEBUG_FUNCPTR(sink_chain)); + + ret = gst_pad_set_active(sink, TRUE); + g_warn_if_fail(ret); + + gst_element_add_pad(element, sink); + + return sink; +} + +static gboolean src_activate_mode(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean active) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC (parent); + gboolean success = TRUE; + g_return_val_if_fail(mode == GST_PAD_MODE_PUSH, FALSE); + + if (active) { + GST_DEBUG_OBJECT(self, "src pad activating in push mode"); + + self->send_initial_events = TRUE; + success = gst_pad_start_task(pad, (GstTaskFunction) src_task_loop, self->src, NULL); + if (!success) { + GST_WARNING_OBJECT(self, "failed to activate pad task"); + } + } else { + GST_DEBUG_OBJECT(self, "deactivating src pad"); + + g_mutex_lock(&self->queue_lock); + GST_PAD_MODE(pad) = GST_PAD_MODE_NONE; + g_cond_signal(&self->queue_cond_add); + g_mutex_unlock(&self->queue_lock); + success = gst_pad_stop_task(pad); + if (!success) { + GST_WARNING_OBJECT(self, "failed to deactivate pad task"); + } + } + + return success; +} + +static void src_task_loop(GstPad *pad) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC(GST_PAD_PARENT(pad)); + GstFlowReturn ret; + GstPad *peer; + gboolean peer_is_active; + + GST_TRACE_OBJECT(self, "src loop: acquiring lock"); + g_mutex_lock(&self->queue_lock); + GST_TRACE_OBJECT(self, "src loop: acquired lock"); + + if (!gst_pad_is_active(pad)) { + GST_LOG_OBJECT(self, "src task loop entered on inactive pad"); + GST_TRACE_OBJECT(self, "src loop: releasing lock"); + g_mutex_unlock(&self->queue_lock); + return; + } + + while (!self->queue->len) { + GST_TRACE_OBJECT(self, "src loop: queue empty, waiting for add"); + g_cond_wait(&self->queue_cond_add, &self->queue_lock); + GST_TRACE_OBJECT(self, "src loop: add signaled"); + + if (!gst_pad_is_active(pad)) { + GST_LOG_OBJECT(self, "pad inactive, task returning"); + GST_TRACE_OBJECT(self, "src loop: releasing lock"); + g_mutex_unlock(&self->queue_lock); + return; + } + } + GST_TRACE_OBJECT(self, "src loop: queue has element"); + + peer = gst_pad_get_peer(pad); + peer_is_active = gst_pad_is_active(peer); + gst_object_unref(peer); + + if (peer_is_active) { + GstBuffer *buffer; + gboolean start_connection_timeout = FALSE; + + if (self->send_initial_events) { + GstSegment segment; + gchar s_id[32]; + GstCaps *caps; + + g_snprintf (s_id, sizeof (s_id), "erdtlsenc-%08x", g_random_int ()); + gst_pad_push_event (self->src, gst_event_new_stream_start (s_id)); + caps = gst_caps_new_empty_simple ("application/x-dtls"); + gst_pad_push_event (self->src, gst_event_new_caps (caps)); + gst_caps_unref (caps); + gst_segment_init (&segment, GST_FORMAT_BYTES); + gst_pad_push_event (self->src, gst_event_new_segment (&segment)); + self->send_initial_events = FALSE; + start_connection_timeout = TRUE; + } + + buffer = g_ptr_array_remove_index(self->queue, 0); + + GST_TRACE_OBJECT(self, "src loop: releasing lock"); + g_mutex_unlock(&self->queue_lock); + + ret = gst_pad_push(self->src, buffer); + if (start_connection_timeout) + er_dtls_connection_start_timeout (self->connection); + + if (G_UNLIKELY(ret != GST_FLOW_OK)) { + GST_WARNING_OBJECT(self, "failed to push buffer on src pad: %s", gst_flow_get_name(ret)); + } + } else { + g_warn_if_reached(); + GST_TRACE_OBJECT(self, "src loop: releasing lock"); + g_mutex_unlock(&self->queue_lock); + } +} + +static GstFlowReturn sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + GstErDtlsEnc *self = GST_ER_DTLS_ENC(parent); + GstMapInfo map_info; + gint ret; + + gst_buffer_map(buffer, &map_info, GST_MAP_READ); + + if (map_info.size) { + ret = er_dtls_connection_send(self->connection, map_info.data, map_info.size); + if (ret != map_info.size) { + GST_WARNING_OBJECT(self, "error sending data: %d B were written, expected value was %zd B", + ret, map_info.size); + } + } + + gst_buffer_unmap(buffer, &map_info); + + gst_buffer_unref(buffer); + + return GST_FLOW_OK; +} + +static void on_key_received(ErDtlsConnection *connection, gpointer key, guint cipher, guint auth, GstErDtlsEnc *self) +{ + gpointer key_dup; + gchar *key_str; + + g_return_if_fail(GST_IS_ER_DTLS_ENC(self)); + g_return_if_fail(ER_IS_DTLS_CONNECTION(connection)); + + self->srtp_cipher = cipher; + self->srtp_auth = auth; + + key_dup = g_memdup(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH); + self->encoder_key = gst_buffer_new_wrapped(key_dup, ER_DTLS_SRTP_MASTER_KEY_LENGTH); + + key_str = g_base64_encode(key, ER_DTLS_SRTP_MASTER_KEY_LENGTH); + GST_INFO_OBJECT(self, "received key: %s", key_str); + g_free(key_str); + + g_signal_emit(self, signals[SIGNAL_ON_KEY_RECEIVED], 0); +} + +static void on_send_data(ErDtlsConnection *connection, gconstpointer data, gint length, GstErDtlsEnc *self) +{ + GstBuffer *buffer; + + GST_DEBUG_OBJECT(self, "sending data from %s with length %d", self->connection_id, length); + + buffer = gst_buffer_new_wrapped(g_memdup(data, length), length); + + GST_TRACE_OBJECT(self, "send data: acquiring lock"); + g_mutex_lock(&self->queue_lock); + GST_TRACE_OBJECT(self, "send data: acquired lock"); + + g_ptr_array_add(self->queue, buffer); + + GST_TRACE_OBJECT(self, "send data: signaling add"); + g_cond_signal(&self->queue_cond_add); + + GST_TRACE_OBJECT(self, "send data: releasing lock"); + g_mutex_unlock(&self->queue_lock); +} diff --git a/ext/dtls/gstdtlsenc.h b/ext/dtls/gstdtlsenc.h new file mode 100644 index 0000000000..f6218c121f --- /dev/null +++ b/ext/dtls/gstdtlsenc.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlsenc_h +#define gstdtlsenc_h + +#include "gstdtlsagent.h" +#include "gstdtlsconnection.h" + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_ER_DTLS_ENC (gst_er_dtls_enc_get_type()) +#define GST_ER_DTLS_ENC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_ENC, GstErDtlsEnc)) +#define GST_ER_DTLS_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_ENC, GstErDtlsEncClass)) +#define GST_IS_ER_DTLS_ENC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_ENC)) +#define GST_IS_ER_DTLS_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_ENC)) + +typedef struct _GstErDtlsEnc GstErDtlsEnc; +typedef struct _GstErDtlsEncClass GstErDtlsEncClass; + +struct _GstErDtlsEnc { + GstElement element; + + GstPad *src; + + GPtrArray *queue; + GMutex queue_lock; + GCond queue_cond_add; + + ErDtlsConnection *connection; + gchar *connection_id; + + gboolean is_client; + + GstBuffer *encoder_key; + guint srtp_cipher; + guint srtp_auth; + + gboolean send_initial_events; +}; + +struct _GstErDtlsEncClass { + GstElementClass parent_class; +}; + +GType gst_er_dtls_enc_get_type(void); + +gboolean gst_er_dtls_enc_plugin_init(GstPlugin *); + +G_END_DECLS + +#endif /* gstdtlsenc_h */ diff --git a/ext/dtls/gstdtlssrtpbin.c b/ext/dtls/gstdtlssrtpbin.c new file mode 100644 index 0000000000..c477a6216c --- /dev/null +++ b/ext/dtls/gstdtlssrtpbin.c @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlssrtpbin.h" + +#define gst_er_dtls_srtp_bin_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE(GstErDtlsSrtpBin, gst_er_dtls_srtp_bin, GST_TYPE_BIN); + +enum { + PROP_0, + PROP_CONNECTION_ID, + PROP_KEY, + PROP_SRTP_AUTH, + PROP_SRTP_CIPHER, + PROP_SRTCP_AUTH, + PROP_SRTCP_CIPHER, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +#define DEFAULT_CONNECTION_ID NULL +#define DEFAULT_KEY NULL +#define DEFAULT_SRTP_AUTH NULL +#define DEFAULT_SRTP_CIPHER NULL +#define DEFAULT_SRTCP_AUTH NULL +#define DEFAULT_SRTCP_CIPHER NULL + +static void gst_er_dtls_srtp_bin_finalize(GObject *); +static void gst_er_dtls_srtp_bin_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +static void gst_er_dtls_srtp_bin_get_property(GObject *, guint prop_id, GValue *, GParamSpec *); + +static void gst_er_dtls_srtp_bin_class_init(GstErDtlsSrtpBinClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_bin_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_bin_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_bin_get_property); + + klass->remove_dtls_element = NULL; + + properties[PROP_CONNECTION_ID] = + g_param_spec_string("connection-id", + "Connection id", + "Every encoder/decoder pair should have the same, unique, connection-id", + DEFAULT_CONNECTION_ID, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_KEY] = + g_param_spec_boxed("key", + "Key", + "SRTP master key, if this property is set, DTLS will be disabled", + GST_TYPE_BUFFER, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTP_CIPHER] = + g_param_spec_string("srtp-cipher", + "SRTP Cipher", + "SRTP cipher name, should be 'null' or 'aes-128-icm', " + "if this property is set, DTLS will be disabled", + DEFAULT_SRTP_CIPHER, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTCP_CIPHER] = + g_param_spec_string("srtcp-cipher", + "SRTCP Cipher", + "SRTCP cipher name, should be 'null' or 'aes-128-icm', " + "if this property is set, DTLS will be disabled", + DEFAULT_SRTCP_CIPHER, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTP_AUTH] = + g_param_spec_string("srtp-auth", + "SRTP Auth", + "SRTP auth name, should be 'null', 'hmac-sha1-32' or 'hmac-sha1-80', " + "if this property is set, DTLS will be disabled", + DEFAULT_SRTP_AUTH, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + + properties[PROP_SRTCP_AUTH] = + g_param_spec_string("srtcp-auth", + "SRTCP Auth", + "SRTCP auth name, should be 'null', 'hmac-sha1-32' or 'hmac-sha1-80', " + "if this property is set, DTLS will be disabled", + DEFAULT_SRTCP_AUTH, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); +} + +static void gst_er_dtls_srtp_bin_init(GstErDtlsSrtpBin *self) +{ + self->key = NULL; + self->key_is_set = FALSE; + self->srtp_auth = NULL; + self->srtp_cipher = NULL; + self->srtcp_auth = NULL; + self->srtcp_cipher = NULL; +} + +static void gst_er_dtls_srtp_bin_finalize(GObject *object) +{ + GstErDtlsSrtpBin *self = GST_ER_DTLS_SRTP_BIN(object); + + if (self->key) { + gst_buffer_unref(self->key); + self->key = NULL; + } + g_free(self->srtp_auth); + self->srtp_auth = NULL; + g_free(self->srtp_cipher); + self->srtp_cipher = NULL; + g_free(self->srtcp_auth); + self->srtcp_auth = NULL; + g_free(self->srtcp_cipher); + self->srtcp_cipher = NULL; + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_er_dtls_srtp_bin_set_property(GObject *object, + guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstErDtlsSrtpBin *self = GST_ER_DTLS_SRTP_BIN(object); + GstErDtlsSrtpBinClass *klass = GST_ER_DTLS_SRTP_BIN_GET_CLASS(self); + + switch (prop_id) { + case PROP_CONNECTION_ID: + if (self->dtls_element) { + g_object_set_property(G_OBJECT(self->dtls_element), "connection-id", value); + } else { + g_warning("tried to set connection-id after disabling DTLS"); + } + break; + case PROP_KEY: + if (self->key) { + gst_buffer_unref(self->key); + } + self->key = g_value_dup_boxed(value); + self->key_is_set = TRUE; + klass->remove_dtls_element(self); + break; + case PROP_SRTP_AUTH: + g_free(self->srtp_auth); + self->srtp_auth = g_value_dup_string(value); + klass->remove_dtls_element(self); + break; + case PROP_SRTP_CIPHER: + g_free(self->srtp_cipher); + self->srtp_cipher = g_value_dup_string(value); + klass->remove_dtls_element(self); + break; + case PROP_SRTCP_AUTH: + g_free(self->srtcp_auth); + self->srtcp_auth = g_value_dup_string(value); + klass->remove_dtls_element(self); + break; + case PROP_SRTCP_CIPHER: + g_free(self->srtcp_cipher); + self->srtcp_cipher = g_value_dup_string(value); + klass->remove_dtls_element(self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void gst_er_dtls_srtp_bin_get_property(GObject *object, + guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstErDtlsSrtpBin *self = GST_ER_DTLS_SRTP_BIN(object); + + switch (prop_id) { + case PROP_CONNECTION_ID: + if (self->dtls_element) { + g_object_get_property(G_OBJECT(self->dtls_element), "connection-id", value); + } else { + g_warning("tried to get connection-id after disabling DTLS"); + } + break; + case PROP_KEY: + if (self->key) { + g_value_set_boxed(value, self->key); + } + break; + case PROP_SRTP_AUTH: + g_value_set_string(value, self->srtp_auth); + break; + case PROP_SRTP_CIPHER: + g_value_set_string(value, self->srtp_cipher); + break; + case PROP_SRTCP_AUTH: + g_value_set_string(value, self->srtcp_auth); + break; + case PROP_SRTCP_CIPHER: + g_value_set_string(value, self->srtcp_cipher); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} diff --git a/ext/dtls/gstdtlssrtpbin.h b/ext/dtls/gstdtlssrtpbin.h new file mode 100644 index 0000000000..54aedc557b --- /dev/null +++ b/ext/dtls/gstdtlssrtpbin.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlssrtpbin_h +#define gstdtlssrtpbin_h + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_ER_DTLS_SRTP_BIN (gst_er_dtls_srtp_bin_get_type()) +#define GST_ER_DTLS_SRTP_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_BIN, GstErDtlsSrtpBin)) +#define GST_ER_DTLS_SRTP_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_BIN, GstErDtlsSrtpBinClass)) +#define GST_ER_DTLS_SRTP_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_ER_DTLS_SRTP_BIN, GstErDtlsSrtpBinClass)) +#define GST_IS_ER_DTLS_SRTP_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_BIN)) +#define GST_IS_ER_DTLS_SRTP_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_BIN)) + +typedef struct _GstErDtlsSrtpBin GstErDtlsSrtpBin; +typedef struct _GstErDtlsSrtpBinClass GstErDtlsSrtpBinClass; + +struct _GstErDtlsSrtpBin { + GstBin bin; + + GstElement *dtls_element; + + gboolean key_is_set; + GstBuffer *key; + gchar *srtp_cipher; + gchar *srtp_auth; + gchar *srtcp_cipher; + gchar *srtcp_auth; +}; + +struct _GstErDtlsSrtpBinClass { + GstBinClass parent_class; + + void (*remove_dtls_element)(GstErDtlsSrtpBin *); +}; + +GType gst_er_dtls_srtp_bin_get_type(void); + +G_END_DECLS + +#endif /* gstdtlssrtpbin_h */ diff --git a/ext/dtls/gstdtlssrtpdec.c b/ext/dtls/gstdtlssrtpdec.c new file mode 100644 index 0000000000..7d8b3416a3 --- /dev/null +++ b/ext/dtls/gstdtlssrtpdec.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlssrtpdec.h" + +#include "gstdtlsconnection.h" + +static GstStaticPadTemplate sink_template = + GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY + ); + +static GstStaticPadTemplate rtp_src_template = + GST_STATIC_PAD_TEMPLATE("rtp_src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("application/x-rtp;application/x-rtcp") + ); + +static GstStaticPadTemplate data_src_template = + GST_STATIC_PAD_TEMPLATE("data_src", + GST_PAD_SRC, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY + ); + +GST_DEBUG_CATEGORY_STATIC(er_dtls_srtp_dec_debug); +#define GST_CAT_DEFAULT er_dtls_srtp_dec_debug + +#define gst_er_dtls_srtp_dec_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE(GstErDtlsSrtpDec, gst_er_dtls_srtp_dec, GST_TYPE_ER_DTLS_SRTP_BIN, + GST_DEBUG_CATEGORY_INIT(er_dtls_srtp_dec_debug, "erdtlssrtpdec", 0, "Ericsson DTLS Decoder")); + +#define UNUSED(param) while (0) { (void)(param); } + +enum { + PROP_0, + PROP_PEM, + PROP_PEER_PEM, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +#define DEFAULT_PEM NULL +#define DEFAULT_PEER_PEM NULL + +static void gst_er_dtls_srtp_dec_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +static void gst_er_dtls_srtp_dec_get_property(GObject *, guint prop_id, GValue *, GParamSpec *); + +static GstPad *gst_er_dtls_srtp_dec_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *); +static GstCaps *on_decoder_request_key(GstElement *srtp_decoder, guint ssrc, GstErDtlsSrtpBin *); +static void on_peer_pem(GstElement *srtp_decoder, GParamSpec *pspec, GstErDtlsSrtpDec *self); + +static void gst_er_dtls_srtp_dec_remove_dtls_element(GstErDtlsSrtpBin *); +static GstPadProbeReturn remove_dtls_decoder_probe_callback(GstPad *, GstPadProbeInfo *, GstElement *); + +static GstPadProbeReturn drop_funnel_rtcp_caps(GstPad *, GstPadProbeInfo *, gpointer); + +static void gst_er_dtls_srtp_dec_class_init(GstErDtlsSrtpDecClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstErDtlsSrtpBinClass *dtls_srtp_bin_class; + + gobject_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + dtls_srtp_bin_class = (GstErDtlsSrtpBinClass *) klass; + + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_get_property); + + element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_request_new_pad); + + dtls_srtp_bin_class->remove_dtls_element = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_dec_remove_dtls_element); + + properties[PROP_PEM] = + g_param_spec_string("pem", + "PEM string", + "A string containing a X509 certificate and RSA private key in PEM format", + DEFAULT_PEM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_PEER_PEM] = + g_param_spec_string("peer-pem", + "Peer PEM string", + "The X509 certificate received in the DTLS handshake, in PEM format", + DEFAULT_PEER_PEM, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sink_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&rtp_src_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&data_src_template)); + + gst_element_class_set_static_metadata(element_class, + "DTLS-SRTP Decoder", + "Decoder/Network/DTLS/SRTP", + "Decodes SRTP packets with a key received from DTLS", + "Patrik Oldsberg patrik.oldsberg@ericsson.com"); +} + +static void gst_er_dtls_srtp_dec_init(GstErDtlsSrtpDec *self) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS(GST_ELEMENT(self)); + GstPadTemplate *templ; + GstPad *target_pad, *ghost_pad, *pad; + gboolean ret; + +/* + +--------------------+ + +--------------+ .-o| erdtlsdec |o-R----------data + | dtls|o-' +--------------------+ + sink---o| dtlsdemux | + | srt(c)p|o-. +-----------+ +-----------+ + +--------------+ '-o|srtp rtp|o---o|rtp | + | srtpdec | | funnel |o---rt(c)p + o|srtcp rtcp|o---o|rtcp | + +-----------+ +-----------+ +*/ + + self->srtp_dec = gst_element_factory_make("srtpdec", "srtp-decoder"); + if (!self->srtp_dec) { + GST_ERROR_OBJECT(self, "failed to create srtp_dec, is the srtp plugin registered?"); + return; + } + self->dtls_srtp_demux = gst_element_factory_make("erdtlssrtpdemux", "dtls-srtp-demux"); + if (!self->dtls_srtp_demux) { + GST_ERROR_OBJECT(self, "failed to create dtls_srtp_demux"); + return; + } + self->bin.dtls_element = gst_element_factory_make("erdtlsdec", "dtls-decoder"); + if (!self->bin.dtls_element) { + GST_ERROR_OBJECT(self, "failed to create dtls_dec"); + return; + } + self->funnel = gst_element_factory_make("funnel", "funnel"); + if (!self->funnel) { + GST_ERROR_OBJECT(self, "failed to create funnel"); + return; + } + + gst_bin_add_many(GST_BIN(self), + self->dtls_srtp_demux, + self->bin.dtls_element, + self->srtp_dec, + self->funnel, + NULL); + + ret = gst_element_link_pads(self->dtls_srtp_demux, "dtls_src", self->bin.dtls_element, NULL); + g_return_if_fail(ret); + ret = gst_element_link_pads(self->dtls_srtp_demux, "rtp_src", self->srtp_dec, "rtp_sink"); + g_return_if_fail(ret); + ret = gst_element_link_pads(self->srtp_dec, "rtp_src", self->funnel, "sink_0"); + g_return_if_fail(ret); + ret = gst_element_link_pads(self->srtp_dec, "rtcp_src", self->funnel, "sink_1"); + g_return_if_fail(ret); + + pad = gst_element_get_static_pad(self->funnel, "sink_1"); + gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, drop_funnel_rtcp_caps, NULL, NULL); + gst_object_unref(pad); + + templ = gst_element_class_get_pad_template(klass, "rtp_src"); + target_pad = gst_element_get_static_pad(self->funnel, "src"); + ghost_pad = gst_ghost_pad_new_from_template("rtp_src", target_pad, templ); + gst_object_unref(target_pad); + g_return_if_fail(ghost_pad); + + ret = gst_element_add_pad(GST_ELEMENT(self), ghost_pad); + g_return_if_fail(ret); + + templ = gst_element_class_get_pad_template(klass, "sink"); + target_pad = gst_element_get_static_pad(self->dtls_srtp_demux, "sink"); + ghost_pad = gst_ghost_pad_new_from_template("sink", target_pad, templ); + gst_object_unref(target_pad); + g_return_if_fail(ghost_pad); + + ret = gst_element_add_pad(GST_ELEMENT(self), ghost_pad); + g_return_if_fail(ret); + + g_signal_connect(self->srtp_dec, "request-key", G_CALLBACK(on_decoder_request_key), self); + g_signal_connect(self->bin.dtls_element, "notify::peer-pem", G_CALLBACK(on_peer_pem), self); +} + +static void gst_er_dtls_srtp_dec_set_property(GObject *object, + guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(object); + + switch (prop_id) { + case PROP_PEM: + if (self->bin.dtls_element) { + g_object_set_property(G_OBJECT(self->bin.dtls_element), "pem", value); + } else { + GST_WARNING_OBJECT(self, "tried to set pem after disabling DTLS"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void gst_er_dtls_srtp_dec_get_property(GObject *object, + guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(object); + + switch (prop_id) { + case PROP_PEM: + if (self->bin.dtls_element) { + g_object_get_property(G_OBJECT(self->bin.dtls_element), "pem", value); + } else { + GST_WARNING_OBJECT(self, "tried to get pem after disabling DTLS"); + } + break; + case PROP_PEER_PEM: + if (self->bin.dtls_element) { + g_object_get_property(G_OBJECT(self->bin.dtls_element), "peer-pem", value); + } else { + GST_WARNING_OBJECT(self, "tried to get peer-pem after disabling DTLS"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static GstPad *gst_er_dtls_srtp_dec_request_new_pad(GstElement *element, + GstPadTemplate *templ, const gchar *name, const GstCaps *caps) +{ + GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(element); + GstElementClass *klass = GST_ELEMENT_GET_CLASS(element); + GstPad *ghost_pad = NULL; + gboolean ret; + + GST_DEBUG_OBJECT(element, "pad requested"); + + g_return_val_if_fail(self->bin.dtls_element, NULL); + g_return_val_if_fail(!self->bin.key_is_set, NULL); + + if (templ == gst_element_class_get_pad_template(klass, "data_src")) { + GstPad *target_pad; + + target_pad = gst_element_get_request_pad(self->bin.dtls_element, "src"); + + ghost_pad = gst_ghost_pad_new_from_template(name, target_pad, templ); + gst_object_unref(target_pad); + g_return_val_if_fail(ghost_pad, NULL); + + ret = gst_pad_set_active(ghost_pad, TRUE); + g_return_val_if_fail(ret, NULL); + ret = gst_element_add_pad(element, ghost_pad); + g_return_val_if_fail(ret, NULL); + + GST_LOG_OBJECT(self, "added data src pad"); + + if (caps) { + g_object_set(ghost_pad, "caps", caps, NULL); + } + + return ghost_pad; + } + + g_return_val_if_reached(NULL); +} + +static GstCaps *on_decoder_request_key(GstElement *srtp_decoder, + guint ssrc, GstErDtlsSrtpBin *bin) +{ + GstCaps *key_caps; + GstBuffer *key_buffer = NULL; + guint cipher; + guint auth; + + if (bin->key_is_set) { + if (bin->key) { + if (bin->srtp_cipher && bin->srtp_auth && bin->srtcp_cipher && bin->srtcp_auth) { + GST_DEBUG_OBJECT(bin, "setting srtp key"); + return gst_caps_new_simple("application/x-srtp", + "srtp-key", GST_TYPE_BUFFER, gst_buffer_copy(bin->key), + "srtp-auth", G_TYPE_STRING, bin->srtp_auth, + "srtcp-auth", G_TYPE_STRING, bin->srtcp_auth, + "srtp-cipher", G_TYPE_STRING, bin->srtp_cipher, + "srtcp-cipher", G_TYPE_STRING, bin->srtcp_cipher, + NULL); + } else { + GST_WARNING_OBJECT(bin, "srtp key is set but not all ciphers and auths"); + return NULL; + } + } + + GST_DEBUG_OBJECT(bin, "setting srtp key to null"); + return gst_caps_new_simple("application/x-srtp", + "srtp-key", GST_TYPE_BUFFER, NULL, + "srtp-auth", G_TYPE_STRING, "null", + "srtcp-auth", G_TYPE_STRING, "null", + "srtp-cipher", G_TYPE_STRING, "null", + "srtcp-cipher", G_TYPE_STRING, "null", + NULL); + } + + if (bin->dtls_element) { + g_object_get(bin->dtls_element, + "decoder-key", &key_buffer, + NULL); + } + + if (key_buffer) { + g_object_get(bin->dtls_element, + "srtp-cipher", &cipher, + "srtp-auth", &auth, + NULL); + + g_return_val_if_fail(cipher == ER_DTLS_SRTP_CIPHER_AES_128_ICM, NULL); + + key_caps = gst_caps_new_simple("application/x-srtp", + "srtp-key", GST_TYPE_BUFFER, key_buffer, + "srtp-cipher", G_TYPE_STRING, "aes-128-icm", + "srtcp-cipher", G_TYPE_STRING, "aes-128-icm", + NULL); + + switch (auth) { + case ER_DTLS_SRTP_AUTH_HMAC_SHA1_32: + gst_caps_set_simple(key_caps, + "srtp-auth", G_TYPE_STRING, "hmac-sha1-32", + "srtcp-auth", G_TYPE_STRING, "hmac-sha1-32", + NULL); + break; + case ER_DTLS_SRTP_AUTH_HMAC_SHA1_80: + gst_caps_set_simple(key_caps, + "srtp-auth", G_TYPE_STRING, "hmac-sha1-80", + "srtcp-auth", G_TYPE_STRING, "hmac-sha1-80", + NULL); + break; + default: + g_return_val_if_reached(NULL); + break; + } + + return key_caps; + } + + return NULL; +} + +static void on_peer_pem(GstElement *srtp_decoder, GParamSpec *pspec, GstErDtlsSrtpDec *self) +{ + UNUSED(srtp_decoder); + UNUSED(pspec); + g_return_if_fail(self); + g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_PEER_PEM]); +} + +static void gst_er_dtls_srtp_dec_remove_dtls_element(GstErDtlsSrtpBin *bin) +{ + GstErDtlsSrtpDec *self = GST_ER_DTLS_SRTP_DEC(bin); + GstPad *demux_pad; + gulong id; + + if (!bin->dtls_element) { + return; + } + + demux_pad = gst_element_get_static_pad(self->dtls_srtp_demux, "dtls_src"); + + id = gst_pad_add_probe(demux_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) remove_dtls_decoder_probe_callback, bin->dtls_element, NULL); + g_return_if_fail(id); + bin->dtls_element = NULL; + + gst_pad_push_event(demux_pad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new_empty("dummy"))); + + gst_object_unref(demux_pad); +} + +static GstPadProbeReturn remove_dtls_decoder_probe_callback(GstPad *pad, + GstPadProbeInfo *info, GstElement *element) +{ + gst_pad_remove_probe(pad, GST_PAD_PROBE_INFO_ID(info)); + + gst_element_set_state(GST_ELEMENT(element), GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element); + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn drop_funnel_rtcp_caps(GstPad *pad, GstPadProbeInfo *info, gpointer data) +{ + /* FIXME: This is needed for setting the proper caps until + * GStreamer supports MIXED caps or another mechanism to + * prevent renegotiation all the time when two different caps + * are going over the same pad + */ + if (GST_EVENT_TYPE (info->data) == GST_EVENT_CAPS) { + GstCaps *caps, *peercaps; + GstStructure *s; + + gst_event_parse_caps (GST_EVENT (info->data), &caps); + s = gst_caps_get_structure (caps, 0); + if (gst_structure_has_name (s, "application/x-rtcp")) { + peercaps = gst_pad_query_caps (pad, NULL); + + /* If the peer does not accept RTCP, we are linked to + * the RTP sinkpad of rtpbin. In that case we have to + * drop the RTCP caps and assume that we sent RTP caps + * before here, which is very likely but not guaranteed + * if for some reason we receive RTCP before any RTP. + * In that unlikely case we will get event misordering + * warnings later, instead of getting them always as + * happens now. + */ + if (peercaps && !gst_caps_is_subset (caps, peercaps)) { + gst_caps_unref (peercaps); + return GST_PAD_PROBE_DROP; + } + gst_caps_replace (&peercaps, NULL); + } + } + + return GST_PAD_PROBE_OK; +} diff --git a/ext/dtls/gstdtlssrtpdec.h b/ext/dtls/gstdtlssrtpdec.h new file mode 100644 index 0000000000..bb7c25c133 --- /dev/null +++ b/ext/dtls/gstdtlssrtpdec.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlssrtpdec_h +#define gstdtlssrtpdec_h + +#include "gstdtlssrtpbin.h" + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_ER_DTLS_SRTP_DEC (gst_er_dtls_srtp_dec_get_type()) +#define GST_ER_DTLS_SRTP_DEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_DEC, GstErDtlsSrtpDec)) +#define GST_ER_DTLS_SRTP_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_DEC, GstErDtlsSrtpDecClass)) +#define GST_IS_ER_DTLS_SRTP_DEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_DEC)) +#define GST_IS_ER_DTLS_SRTP_DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_DEC)) + +typedef struct _GstErDtlsSrtpDec GstErDtlsSrtpDec; +typedef struct _GstErDtlsSrtpDecClass GstErDtlsSrtpDecClass; + +struct _GstErDtlsSrtpDec { + GstErDtlsSrtpBin bin; + + GstElement *dtls_srtp_demux; + GstElement *srtp_dec; + GstElement *funnel; +}; + +struct _GstErDtlsSrtpDecClass { + GstErDtlsSrtpBinClass parent_class; +}; + +GType gst_er_dtls_srtp_dec_get_type(void); + +gboolean gst_er_dtls_srtp_dec_plugin_init(GstPlugin *); + +G_END_DECLS + +#endif /* gstdtlssrtpdec_h */ diff --git a/ext/dtls/gstdtlssrtpdemux.c b/ext/dtls/gstdtlssrtpdemux.c new file mode 100644 index 0000000000..b4854dee5b --- /dev/null +++ b/ext/dtls/gstdtlssrtpdemux.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlssrtpdemux.h" + +#define PACKET_IS_DTLS(b) (b > 0x13 && b < 0x40) +#define PACKET_IS_RTP(b) (b > 0x7f && b < 0xc0) + +static GstStaticPadTemplate sink_template = + GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY + ); + +static GstStaticPadTemplate rtp_src_template = + GST_STATIC_PAD_TEMPLATE("rtp_src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-rtp;" + "application/x-rtcp;" + "application/x-srtp;" + "application/x-srtcp") + ); + +static GstStaticPadTemplate dtls_src_template = + GST_STATIC_PAD_TEMPLATE("dtls_src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("application/x-dtls") + ); + +GST_DEBUG_CATEGORY_STATIC(er_er_dtls_srtp_demux_debug); +#define GST_CAT_DEFAULT er_er_dtls_srtp_demux_debug + +#define gst_er_dtls_srtp_demux_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE(GstErDtlsSrtpDemux, gst_er_dtls_srtp_demux, GST_TYPE_ELEMENT, + GST_DEBUG_CATEGORY_INIT(er_er_dtls_srtp_demux_debug, "erdtlssrtpdemux", 0, "Ericsson DTLS SRTP Demultiplexer")); + +static GstFlowReturn sink_chain(GstPad *, GstObject *self, GstBuffer *); + +static void gst_er_dtls_srtp_demux_class_init(GstErDtlsSrtpDemuxClass *klass) +{ + GstElementClass *element_class; + + element_class = (GstElementClass *) klass; + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&sink_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&rtp_src_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&dtls_src_template)); + + gst_element_class_set_static_metadata(element_class, + "DTLS SRTP Demultiplexer", + "DTLS/SRTP/Demux", + "Demultiplexes DTLS and SRTP packets", + "Patrik Oldsberg patrik.oldsberg@ericsson.com"); +} + +static void gst_er_dtls_srtp_demux_init(GstErDtlsSrtpDemux *self) +{ + GstPad *sink; + + sink = gst_pad_new_from_static_template(&sink_template, "sink"); + self->rtp_src = gst_pad_new_from_static_template(&rtp_src_template, "rtp_src"); + self->dtls_src = gst_pad_new_from_static_template(&dtls_src_template, "dtls_src"); + g_return_if_fail(sink); + g_return_if_fail(self->rtp_src); + g_return_if_fail(self->dtls_src); + + gst_pad_set_chain_function(sink, GST_DEBUG_FUNCPTR(sink_chain)); + + gst_element_add_pad(GST_ELEMENT(self), sink); + gst_element_add_pad(GST_ELEMENT(self), self->rtp_src); + gst_element_add_pad(GST_ELEMENT(self), self->dtls_src); +} + +static GstFlowReturn sink_chain(GstPad *pad, GstObject *parent, GstBuffer *buffer) +{ + GstErDtlsSrtpDemux *self = GST_ER_DTLS_SRTP_DEMUX(parent); + guint8 first_byte; + + if (gst_buffer_get_size(buffer) == 0) { + GST_LOG_OBJECT(self, "received buffer with size 0"); + gst_buffer_unref(buffer); + return GST_FLOW_OK; + } + + if (gst_buffer_extract(buffer, 0, &first_byte, 1) != 1) { + GST_WARNING_OBJECT(self, "could not extract first byte from buffer"); + gst_buffer_unref(buffer); + return GST_FLOW_OK; + } + + if (PACKET_IS_DTLS(first_byte)) { + GST_LOG_OBJECT(self, "pushing dtls packet"); + + return gst_pad_push(self->dtls_src, buffer); + } + + if (PACKET_IS_RTP(first_byte)) { + GST_LOG_OBJECT(self, "pushing rtp packet"); + + return gst_pad_push(self->rtp_src, buffer); + } + + GST_WARNING_OBJECT(self, "received invalid buffer: %x", first_byte); + gst_buffer_unref(buffer); + return GST_FLOW_OK; +} diff --git a/ext/dtls/gstdtlssrtpdemux.h b/ext/dtls/gstdtlssrtpdemux.h new file mode 100644 index 0000000000..166c8e93a3 --- /dev/null +++ b/ext/dtls/gstdtlssrtpdemux.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlssrtpdemux_h +#define gstdtlssrtpdemux_h + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_ER_DTLS_SRTP_DEMUX \ + (gst_er_dtls_srtp_demux_get_type()) +#define GST_ER_DTLS_SRTP_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_DEMUX, GstErDtlsSrtpDemux)) +#define GST_ER_DTLS_SRTP_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_DEMUX, GstErDtlsSrtpDemuxClass)) +#define GST_IS_ER_DTLS_SRTP_DEMUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_DEMUX)) +#define GST_IS_ER_DTLS_SRTP_DEMUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_DEMUX)) + +typedef struct _GstErDtlsSrtpDemux GstErDtlsSrtpDemux; +typedef struct _GstErDtlsSrtpDemuxClass GstErDtlsSrtpDemuxClass; + +struct _GstErDtlsSrtpDemux { + GstElement element; + + GstPad *rtp_src; + GstPad *dtls_src; +}; + +struct _GstErDtlsSrtpDemuxClass { + GstElementClass parent_class; +}; + +GType gst_er_dtls_srtp_demux_get_type(void); + +gboolean gst_er_dtls_srtp_demux_plugin_init(GstPlugin *); + +G_END_DECLS + +#endif /* gstdtlssrtpdemux_h */ diff --git a/ext/dtls/gstdtlssrtpenc.c b/ext/dtls/gstdtlssrtpenc.c new file mode 100644 index 0000000000..34b45a1310 --- /dev/null +++ b/ext/dtls/gstdtlssrtpenc.c @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlssrtpenc.h" + +#include + +static GstStaticPadTemplate rtp_sink_template = + GST_STATIC_PAD_TEMPLATE("rtp_sink_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS("application/x-rtp;application/x-rtcp") + ); + +static GstStaticPadTemplate rtcp_sink_template = + GST_STATIC_PAD_TEMPLATE("rtcp_sink_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS("application/x-rtp;application/x-rtcp") + ); + +static GstStaticPadTemplate data_sink_template = + GST_STATIC_PAD_TEMPLATE("data_sink", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS_ANY + ); + +static GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY + ); + +GST_DEBUG_CATEGORY_STATIC(er_dtls_srtp_enc_debug); +#define GST_CAT_DEFAULT er_dtls_srtp_enc_debug + +#define gst_er_dtls_srtp_enc_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE(GstErDtlsSrtpEnc, gst_er_dtls_srtp_enc, GST_TYPE_ER_DTLS_SRTP_BIN, + GST_DEBUG_CATEGORY_INIT(er_dtls_srtp_enc_debug, "erdtlssrtpenc", 0, "Ericsson DTLS Decoder")); + +enum { + SIGNAL_ON_KEY_SET, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS]; + +enum { + PROP_0, + PROP_IS_CLIENT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +#define DEFAULT_IS_CLIENT FALSE + +static gboolean transform_enum(GBinding *, const GValue *source_value, GValue *target_value, GEnumClass *); + +static void gst_er_dtls_srtp_enc_set_property(GObject *, guint prop_id, const GValue *, GParamSpec *); +static void gst_er_dtls_srtp_enc_get_property(GObject *, guint prop_id, GValue *, GParamSpec *); + +static GstPad *add_ghost_pad(GstElement *, const gchar *name, GstPad *, GstPadTemplate *); +static GstPad *gst_er_dtls_srtp_enc_request_new_pad(GstElement *, GstPadTemplate *, const gchar *name, const GstCaps *); + +static void on_key_received(GObject *encoder, GstErDtlsSrtpEnc *); + +static void gst_er_dtls_srtp_enc_remove_dtls_element(GstErDtlsSrtpBin *); +static GstPadProbeReturn remove_dtls_encoder_probe_callback(GstPad *, GstPadProbeInfo *, GstElement *); + +static void gst_er_dtls_srtp_enc_class_init(GstErDtlsSrtpEncClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + GstErDtlsSrtpBinClass *dtls_srtp_bin_class; + + gobject_class = (GObjectClass *) klass; + element_class = (GstElementClass *) klass; + dtls_srtp_bin_class = (GstErDtlsSrtpBinClass *) klass; + + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_get_property); + + element_class->request_new_pad = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_request_new_pad); + + dtls_srtp_bin_class->remove_dtls_element = GST_DEBUG_FUNCPTR(gst_er_dtls_srtp_enc_remove_dtls_element); + + signals[SIGNAL_ON_KEY_SET] = + g_signal_new("on-key-set", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_generic, G_TYPE_NONE, 0); + + properties[PROP_IS_CLIENT] = + g_param_spec_boolean("is-client", + "Is client", + "Set to true if the decoder should act as " + "client and initiate the handshake", + DEFAULT_IS_CLIENT, + GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties); + + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&rtp_sink_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&rtcp_sink_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&data_sink_template)); + gst_element_class_add_pad_template(element_class, + gst_static_pad_template_get(&src_template)); + + gst_element_class_set_static_metadata(element_class, + "DTLS-SRTP Encoder", + "Encoder/Network/DTLS/SRTP", + "Encodes SRTP packets with a key received from DTLS", + "Patrik Oldsberg patrik.oldsberg@ericsson.com"); +} + +static void gst_er_dtls_srtp_enc_init(GstErDtlsSrtpEnc *self) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS(GST_ELEMENT(self)); + static GEnumClass *cipher_enum_class, *auth_enum_class; + gboolean ret; + +/* + +--------------------+ +-----------------+ + rtp_sink-R-o|rtp_sink rtp_src|o-R-o| | + | srtpenc | | | + rtcp_sink-R-o|srtcp_sink rtcp_src|o-R-o| | + +--------------------+ | funnel |o---src + | | + +--------------------+ | | + data_sink-R-o| erdtlsenc |o---o| | + +--------------------+ +-----------------+ +*/ + + self->srtp_enc = gst_element_factory_make("srtpenc", "srtp-encoder"); + if (!self->srtp_enc) { + GST_ERROR_OBJECT(self, "failed to create srtp encoder, is the srtp plugin registered?"); + return; + } + g_return_if_fail(self->srtp_enc); + self->bin.dtls_element = gst_element_factory_make("erdtlsenc", "dtls-encoder"); + if (!self->bin.dtls_element) { + GST_ERROR_OBJECT(self, "failed to create dtls encoder"); + return; + } + self->funnel = gst_element_factory_make("funnel", "funnel"); + if (!self->funnel) { + GST_ERROR_OBJECT(self, "failed to create funnel"); + return; + } + + gst_bin_add_many(GST_BIN(self), self->bin.dtls_element, self->srtp_enc, self->funnel, NULL); + + ret = gst_element_link(self->bin.dtls_element, self->funnel); + g_return_if_fail(ret); + + add_ghost_pad(GST_ELEMENT(self), "src", + gst_element_get_static_pad(self->funnel, "src"), + gst_element_class_get_pad_template(klass, "src")); + + g_signal_connect(self->bin.dtls_element, "on-key-received", G_CALLBACK(on_key_received), self); + + if (g_once_init_enter(&cipher_enum_class)) { + GType type = g_type_from_name("GstSrtpCipherType"); + g_assert(type); + g_once_init_leave(&cipher_enum_class, g_type_class_peek(type)); + } + if (g_once_init_enter(&auth_enum_class)) { + GType type = g_type_from_name("GstSrtpAuthType"); + g_assert(type); + g_once_init_leave(&auth_enum_class, g_type_class_peek(type)); + } + + g_object_set(self->srtp_enc, "random-key", TRUE, NULL); + + g_object_bind_property(G_OBJECT(self), "key", self->srtp_enc, "key", G_BINDING_DEFAULT); + g_object_bind_property_full(G_OBJECT(self), "srtp-cipher", self->srtp_enc, "rtp-cipher", G_BINDING_DEFAULT, + (GBindingTransformFunc) transform_enum, NULL, cipher_enum_class, NULL); + g_object_bind_property_full(G_OBJECT(self), "srtcp-cipher", self->srtp_enc, "rtcp-cipher", G_BINDING_DEFAULT, + (GBindingTransformFunc) transform_enum, NULL, cipher_enum_class, NULL); + g_object_bind_property_full(G_OBJECT(self), "srtp-auth", self->srtp_enc, "rtp-auth", G_BINDING_DEFAULT, + (GBindingTransformFunc) transform_enum, NULL, auth_enum_class, NULL); + g_object_bind_property_full(G_OBJECT(self), "srtcp-auth", self->srtp_enc, "rtcp-auth", G_BINDING_DEFAULT, + (GBindingTransformFunc) transform_enum, NULL, auth_enum_class, NULL); +} + +static gboolean transform_enum(GBinding *binding, const GValue *source_value, GValue *target_value, GEnumClass *enum_class) +{ + GEnumValue *enum_value; + const gchar *nick; + + nick = g_value_get_string(source_value); + g_return_val_if_fail(nick, FALSE); + + enum_value = g_enum_get_value_by_nick(enum_class, nick); + g_return_val_if_fail(enum_value, FALSE); + + GST_DEBUG_OBJECT(g_binding_get_source(binding), + "transforming enum from %s to %d", nick, enum_value->value); + + g_value_set_enum(target_value, enum_value->value); + + return TRUE; +} + +static void gst_er_dtls_srtp_enc_set_property(GObject *object, + guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(object); + + switch (prop_id) { + case PROP_IS_CLIENT: + if (self->bin.dtls_element) { + g_object_set_property(G_OBJECT(self->bin.dtls_element), "is-client", value); + } else { + GST_WARNING_OBJECT(self, "tried to set is-client after disabling DTLS"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static void gst_er_dtls_srtp_enc_get_property(GObject *object, + guint prop_id, GValue *value, GParamSpec *pspec) +{ + GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(object); + + switch (prop_id) { + case PROP_IS_CLIENT: + if (self->bin.dtls_element) { + g_object_get_property(G_OBJECT(self->bin.dtls_element), "is-client", value); + } else { + GST_WARNING_OBJECT(self, "tried to get is-client after disabling DTLS"); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec); + } +} + +static GstPad *add_ghost_pad(GstElement *element, + const gchar *name, GstPad *target, GstPadTemplate *templ) +{ + GstPad *pad; + gboolean ret; + + pad = gst_ghost_pad_new_from_template(name, target, templ); + gst_object_unref(target); + target = NULL; + + ret = gst_pad_set_active(pad, TRUE); + g_warn_if_fail(ret); + + ret = gst_element_add_pad(element, pad); + g_warn_if_fail(ret); + + return pad; +} + +static GstPad *gst_er_dtls_srtp_enc_request_new_pad(GstElement *element, + GstPadTemplate *templ, const gchar *name, const GstCaps *caps) +{ + GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(element); + GstElementClass *klass = GST_ELEMENT_GET_CLASS(element); + GstPad *target_pad; + GstPad *ghost_pad = NULL; + guint pad_n; + gchar *srtp_src_name; + + GST_DEBUG_OBJECT(element, "pad requested"); + + g_return_val_if_fail(templ->direction == GST_PAD_SINK, NULL); + g_return_val_if_fail(self->srtp_enc, NULL); + + if (templ == gst_element_class_get_pad_template(klass, "rtp_sink_%d")) { + target_pad = gst_element_get_request_pad(self->srtp_enc, name); + g_return_val_if_fail(target_pad, NULL); + + sscanf(GST_PAD_NAME(target_pad), "rtp_sink_%d", &pad_n); + srtp_src_name = g_strdup_printf("rtp_src_%d", pad_n); + + gst_element_link_pads(self->srtp_enc, srtp_src_name, self->funnel, NULL); + + g_free(srtp_src_name); + + ghost_pad = add_ghost_pad(element, name, target_pad, templ); + + GST_LOG_OBJECT(self, "added rtp sink pad"); + } else if (templ == gst_element_class_get_pad_template(klass, "rtcp_sink_%d")) { + target_pad = gst_element_get_request_pad(self->srtp_enc, name); + g_return_val_if_fail(target_pad, NULL); + + sscanf(GST_PAD_NAME(target_pad), "rtcp_sink_%d", &pad_n); + srtp_src_name = g_strdup_printf("rtcp_src_%d", pad_n); + + gst_element_link_pads(self->srtp_enc, srtp_src_name, self->funnel, NULL); + + g_free(srtp_src_name); + + ghost_pad = add_ghost_pad(element, name, target_pad, templ); + + GST_LOG_OBJECT(self, "added rtcp sink pad"); + } else if (templ == gst_element_class_get_pad_template(klass, "data_sink")) { + g_return_val_if_fail(self->bin.dtls_element, NULL); + target_pad = gst_element_get_request_pad(self->bin.dtls_element, "sink"); + + ghost_pad = add_ghost_pad(element, name, target_pad, templ); + + GST_LOG_OBJECT(self, "added data sink pad"); + } else { + g_warn_if_reached(); + } + + if (caps && ghost_pad) { + g_object_set(ghost_pad, "caps", caps, NULL); + } + + return ghost_pad; +} + +static void on_key_received(GObject *encoder, GstErDtlsSrtpEnc *self) +{ + GstErDtlsSrtpBin *bin = GST_ER_DTLS_SRTP_BIN(self); + GstBuffer *buffer = NULL; + guint cipher, auth; + + if (!(bin->key_is_set || bin->srtp_cipher || bin->srtp_auth + || bin->srtcp_cipher || bin->srtcp_auth)) { + g_object_get(encoder, + "encoder-key", &buffer, + "srtp-cipher", &cipher, + "srtp-auth", &auth, + NULL); + + g_object_set(self->srtp_enc, + "rtp-cipher", cipher, + "rtcp-cipher", cipher, + "rtp-auth", auth, + "rtcp-auth", auth, + "key", buffer, + "random-key", FALSE, + NULL); + + g_signal_emit(self, signals[SIGNAL_ON_KEY_SET], 0); + } else { + GST_DEBUG_OBJECT(self, "ignoring keys received from DTLS handshake, key struct is set"); + } +} + +static void gst_er_dtls_srtp_enc_remove_dtls_element(GstErDtlsSrtpBin *bin) +{ + GstErDtlsSrtpEnc *self = GST_ER_DTLS_SRTP_ENC(bin); + GstPad *dtls_sink_pad, *peer_pad; + gulong id; + guint rtp_cipher = 1, rtcp_cipher = 1, rtp_auth = 1, rtcp_auth = 1; + + if (!bin->dtls_element) { + return; + } + + g_object_get(self->srtp_enc, + "rtp-cipher", &rtp_cipher, + "rtcp-cipher", &rtcp_cipher, + "rtp-auth", &rtp_auth, + "rtcp-auth", &rtcp_auth, + NULL); + + if (!rtp_cipher && !rtcp_cipher && !rtp_auth && !rtcp_auth) { + g_object_set(self->srtp_enc, "random-key", FALSE, NULL); + } + + dtls_sink_pad = gst_element_get_static_pad(bin->dtls_element, "sink"); + + if (!dtls_sink_pad) { + gst_element_set_state(GST_ELEMENT(bin->dtls_element), GST_STATE_NULL); + gst_bin_remove(GST_BIN(self), bin->dtls_element); + bin->dtls_element = NULL; + return; + } + + peer_pad = gst_pad_get_peer(dtls_sink_pad); + g_return_if_fail(peer_pad); + gst_object_unref(dtls_sink_pad); + dtls_sink_pad = NULL; + + id = gst_pad_add_probe(peer_pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, + (GstPadProbeCallback) remove_dtls_encoder_probe_callback, bin->dtls_element, NULL); + g_return_if_fail(id); + bin->dtls_element = NULL; + + gst_pad_push_event(peer_pad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, gst_structure_new_empty("dummy"))); + + gst_object_unref(peer_pad); +} + +static GstPadProbeReturn remove_dtls_encoder_probe_callback(GstPad *pad, + GstPadProbeInfo *info, GstElement *element) +{ + gst_pad_remove_probe(pad, GST_PAD_PROBE_INFO_ID(info)); + + gst_element_set_state(GST_ELEMENT(element), GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element); + + return GST_PAD_PROBE_OK; +} diff --git a/ext/dtls/gstdtlssrtpenc.h b/ext/dtls/gstdtlssrtpenc.h new file mode 100644 index 0000000000..c61a6a418c --- /dev/null +++ b/ext/dtls/gstdtlssrtpenc.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifndef gstdtlssrtpenc_h +#define gstdtlssrtpenc_h + +#include "gstdtlssrtpbin.h" + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_ER_DTLS_SRTP_ENC (gst_er_dtls_srtp_enc_get_type()) +#define GST_ER_DTLS_SRTP_ENC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_ER_DTLS_SRTP_ENC, GstErDtlsSrtpEnc)) +#define GST_ER_DTLS_SRTP_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_ER_DTLS_SRTP_ENC, GstErDtlsSrtpEncClass)) +#define GST_IS_ER_DTLS_SRTP_ENC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_ER_DTLS_SRTP_ENC)) +#define GST_IS_ER_DTLS_SRTP_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_ER_DTLS_SRTP_ENC)) + +typedef struct _GstErDtlsSrtpEnc GstErDtlsSrtpEnc; +typedef struct _GstErDtlsSrtpEncClass GstErDtlsSrtpEncClass; + +struct _GstErDtlsSrtpEnc { + GstErDtlsSrtpBin bin; + + GstElement *srtp_enc; + GstElement *funnel; +}; + +struct _GstErDtlsSrtpEncClass { + GstErDtlsSrtpBinClass parent_class; +}; + +GType gst_er_dtls_srtp_enc_get_type(void); + +gboolean gst_er_dtls_srtp_enc_plugin_init(GstPlugin *); + +guint gst_er_dtls_srtp_enc_get_cipher_value_by_nick(const gchar *cipher_nick); +guint gst_er_dtls_srtp_enc_get_auth_value_by_nick(const gchar *auth_nick); + +G_END_DECLS + +#endif /* gstdtlssrtpenc_h */ diff --git a/ext/dtls/plugin.c b/ext/dtls/plugin.c new file mode 100644 index 0000000000..0aa811d18b --- /dev/null +++ b/ext/dtls/plugin.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014, Ericsson AB. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtlsdec.h" +#include "gstdtlsenc.h" +#include "gstdtlssrtpenc.h" +#include "gstdtlssrtpdec.h" +#include "gstdtlssrtpdemux.h" + +#include + +static gboolean plugin_init(GstPlugin *plugin) +{ + return gst_element_register(plugin, "erdtlsenc", GST_RANK_NONE, GST_TYPE_ER_DTLS_ENC) + && gst_element_register(plugin, "erdtlsdec", GST_RANK_NONE, GST_TYPE_ER_DTLS_DEC) + && gst_element_register(plugin, "erdtlssrtpdec", GST_RANK_NONE, GST_TYPE_ER_DTLS_SRTP_DEC) + && gst_element_register(plugin, "erdtlssrtpenc", GST_RANK_NONE, GST_TYPE_ER_DTLS_SRTP_ENC) + && gst_element_register(plugin, "erdtlssrtpdemux", GST_RANK_NONE, GST_TYPE_ER_DTLS_SRTP_DEMUX); +} + +/* PACKAGE: this is usually set by autotools depending on some _INIT macro + * in configure.ac and then written into and defined in config.h, but we can + * just set it ourselves here in case someone doesn't use autotools to + * compile this code. GST_PLUGIN_DEFINE needs PACKAGE to be defined. + */ +#ifndef PACKAGE +#define PACKAGE "erdtls" +#endif + +/* gstreamer looks for this structure to register plugins + */ +GST_PLUGIN_DEFINE( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + erdtls, + "Ericsson DTLS decoder and encoder plugins", + plugin_init, + PACKAGE_VERSION, + "BSD", + "OpenWebRTC GStreamer plugins", + "http://www.openwebrtc.io/" +)