Implemented WebRTC

This commit is contained in:
roxlu 2018-06-01 09:19:32 +02:00 committed by Thulinma
parent dce4cddadd
commit 7e8eb634e6
20 changed files with 6712 additions and 1 deletions

View file

@ -143,9 +143,11 @@ set(libHeaders
lib/encode.h
lib/bitfields.h
lib/bitstream.h
lib/certificate.h
lib/checksum.h
lib/config.h
lib/defines.h
lib/dtls_srtp_handshake.h
lib/dtsc.h
lib/encryption.h
lib/flv_tag.h
@ -168,11 +170,15 @@ set(libHeaders
lib/procs.h
lib/rijndael.h
lib/rtmpchunks.h
lib/rtp_fec.h
lib/rtp.h
lib/sdp.h
lib/sdp_media.h
lib/shared_memory.h
lib/socket.h
lib/srtp.h
lib/stream.h
lib/stun.h
lib/theora.h
lib/timing.h
lib/tinythread.h
@ -201,7 +207,9 @@ add_library (mist
lib/encode.cpp
lib/bitfields.cpp
lib/bitstream.cpp
lib/certificate.cpp
lib/config.cpp
lib/dtls_srtp_handshake.cpp
lib/dtsc.cpp
lib/dtscmeta.cpp
lib/encryption.cpp
@ -224,11 +232,15 @@ add_library (mist
lib/procs.cpp
lib/rijndael.cpp
lib/rtmpchunks.cpp
lib/rtp_fec.cpp
lib/rtp.cpp
lib/sdp.cpp
lib/sdp_media.cpp
lib/shared_memory.cpp
lib/socket.cpp
lib/srtp.cpp
lib/stream.cpp
lib/stun.cpp
lib/theora.cpp
lib/timing.cpp
lib/tinythread.cpp
@ -253,7 +265,7 @@ target_link_libraries(mist
${LIBRT}
)
if (NOT DEFINED NOSSL )
target_link_libraries(mist mbedtls mbedx509 mbedcrypto)
target_link_libraries(mist mbedtls mbedx509 mbedcrypto srtp2)
endif()
install(
FILES ${libHeaders}
@ -460,6 +472,7 @@ makeOutput(EBML ebml)
makeOutput(Push push)#LTS
makeOutput(RTSP rtsp)#LTS
makeOutput(WAV wav)#LTS
makeOutput(WebRTC webrtc http)#LTS
if (NOT DEFINED NOSSL )
makeOutput(HTTPS https)#LTS
endif()

240
lib/certificate.cpp Normal file
View file

@ -0,0 +1,240 @@
#include "certificate.h"
#include "defines.h"
Certificate::Certificate()
:rsa_ctx(NULL)
{
memset((void*)&cert, 0x00, sizeof(cert));
memset((void*)&key, 0x00, sizeof(key));
}
int Certificate::init(const std::string &countryName,
const std::string &organization,
const std::string& commonName)
{
mbedtls_ctr_drbg_context rand_ctx = {};
mbedtls_entropy_context entropy_ctx = {};
mbedtls_x509write_cert write_cert = {};
const char* personalisation = "mbedtls-self-signed-key";
std::string subject_name = "C=" +countryName +",O=" +organization +",CN=" +commonName;
time_t time_from = { 0 };
time_t time_to = { 0 };
char time_from_str[20] = { 0 };
char time_to_str[20] = { 0 };
mbedtls_mpi serial_mpi = { 0 };
char serial_hex[17] = { 0 };
uint64_t serial_num = 0;
uint8_t* serial_ptr = (uint8_t*)&serial_num;
int r = 0;
int i = 0;
uint8_t buf[4096] = { 0 };
// validate
if (countryName.empty()) {
FAIL_MSG("Given `countryName`, C=<countryName>, is empty.");
r = -1;
goto error;
}
if (organization.empty()) {
FAIL_MSG("Given `organization`, O=<organization>, is empty.");
r = -2;
goto error;
}
if (commonName.empty()) {
FAIL_MSG("Given `commonName`, CN=<commonName>, is empty.");
r = -3;
goto error;
}
// initialize random number generator
mbedtls_ctr_drbg_init(&rand_ctx);
mbedtls_entropy_init(&entropy_ctx);
r = mbedtls_ctr_drbg_seed(&rand_ctx, mbedtls_entropy_func, &entropy_ctx, (const unsigned char*)personalisation, strlen(personalisation));
if (0 != r) {
FAIL_MSG("Failed to initialize and seed the entropy context.");
r = -10;
goto error;
}
// initialize the public key context
mbedtls_pk_init(&key);
r = mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
if (0 != r) {
FAIL_MSG("Faild to initialize the PK context.");
r = -20;
goto error;
}
rsa_ctx = mbedtls_pk_rsa(key);
if (NULL == rsa_ctx) {
FAIL_MSG("Failed to get the RSA context from from the public key context (key).");
r = -30;
goto error;
}
r = mbedtls_rsa_gen_key(rsa_ctx, mbedtls_ctr_drbg_random, &rand_ctx, 2048, 65537);
if (0 != r) {
FAIL_MSG("Failed to generate a private key.");
r = -40;
goto error;
}
// calc the valid from and until time.
time_from = time(NULL);
time_from = (time_from < 1000000000) ? 1000000000 : time_from;
time_to = time_from + (60 * 60 * 24 * 365); // valid for a year
if (time_to < time_from) {
time_to = INT_MAX;
}
r = strftime(time_from_str, sizeof(time_from_str), "%Y%m%d%H%M%S", gmtime(&time_from));
if (0 == r) {
FAIL_MSG("Failed to generate the valid-from time string.");
r = -50;
goto error;
}
r = strftime(time_to_str, sizeof(time_to_str), "%Y%m%d%H%M%S", gmtime(&time_to));
if (0 == r) {
FAIL_MSG("Failed to generate the valid-to time string.");
r = -60;
goto error;
}
r = mbedtls_ctr_drbg_random((void*)&rand_ctx, (uint8_t*)&serial_num, sizeof(serial_num));
if (0 != r) {
FAIL_MSG("Failed to generate a random u64.");
r = -70;
goto error;
}
for (i = 0; i < 8; ++i) {
sprintf(serial_hex + (i * 2), "%02x", serial_ptr[i]);
}
// start creating the certificate
mbedtls_x509write_crt_init(&write_cert);
mbedtls_x509write_crt_set_md_alg(&write_cert, MBEDTLS_MD_SHA256);
mbedtls_x509write_crt_set_issuer_key(&write_cert, &key);
mbedtls_x509write_crt_set_subject_key(&write_cert, &key);
r = mbedtls_x509write_crt_set_subject_name(&write_cert, subject_name.c_str());
if (0 != r) {
FAIL_MSG("Failed to set the subject name.");
r = -80;
goto error;
}
r = mbedtls_x509write_crt_set_issuer_name(&write_cert, subject_name.c_str());
if (0 != r) {
FAIL_MSG("Failed to set the issuer name.");
r = -90;
goto error;
}
r = mbedtls_x509write_crt_set_validity(&write_cert, time_from_str, time_to_str);
if (0 != r) {
FAIL_MSG("Failed to set the x509 validity string.");
r = -100;
goto error;
}
r = mbedtls_x509write_crt_set_basic_constraints(&write_cert, 0, -1);
if (0 != r) {
FAIL_MSG("Failed ot set the basic constraints for the certificate.");
r = -110;
goto error;
}
r = mbedtls_x509write_crt_set_subject_key_identifier(&write_cert);
if (0 != r) {
FAIL_MSG("Failed to set the subjectKeyIdentifier.");
r = -120;
goto error;
}
r = mbedtls_x509write_crt_set_authority_key_identifier(&write_cert);
if (0 != r) {
FAIL_MSG("Failed to set the authorityKeyIdentifier.");
r = -130;
goto error;
}
// set certificate serial; mpi is used to perform i/o
mbedtls_mpi_init(&serial_mpi);
mbedtls_mpi_read_string(&serial_mpi, 16, serial_hex);
r = mbedtls_x509write_crt_set_serial(&write_cert, &serial_mpi);
if (0 != r) {
FAIL_MSG("Failed to set the certificate serial.");
r = -140;
goto error;
}
// write the certificate into a PEM structure
r = mbedtls_x509write_crt_pem(&write_cert, buf, sizeof(buf), mbedtls_ctr_drbg_random, &rand_ctx);
if (0 != r) {
FAIL_MSG("Failed to create the PEM data from the x509 write structure.");
r = -150;
goto error;
}
// convert the PEM data into out `mbedtls_x509_cert` member.
// len should be PEM including the string null terminating
// char. @todo there must be a way to convert the write
// struct into a `mbedtls_x509_cert` w/o calling this parse
// function.
mbedtls_x509_crt_init(&cert);
r = mbedtls_x509_crt_parse(&cert, (const unsigned char*)buf, strlen((char*)buf) + 1);
if (0 != r) {
mbedtls_strerror(r, (char*)buf, sizeof(buf));
FAIL_MSG("Failed to convert the mbedtls_x509write_crt into a mbedtls_x509_crt: %s", buf);
r = -160;
goto error;
}
error:
// cleanup
mbedtls_ctr_drbg_free(&rand_ctx);
mbedtls_entropy_free(&entropy_ctx);
mbedtls_x509write_crt_free(&write_cert);
mbedtls_mpi_free(&serial_mpi);
if (r < 0) {
shutdown();
}
return r;
}
int Certificate::shutdown() {
rsa_ctx = NULL;
mbedtls_pk_free(&key);
mbedtls_x509_crt_free(&cert);
return 0;
}
std::string Certificate::getFingerprintSha256() {
uint8_t fingerprint_raw[32] = {};
uint8_t fingerprint_hex[128] = {};
mbedtls_md_type_t hash_type = MBEDTLS_MD_SHA256;
mbedtls_sha256(cert.raw.p, cert.raw.len, fingerprint_raw, 0);
for (int i = 0; i < 32; ++i) {
sprintf((char*)(fingerprint_hex + (i * 3)), ":%02X", (int)fingerprint_raw[i]);
}
fingerprint_hex[32 * 3] = '\0';
std::string result = std::string((char*)fingerprint_hex + 1, (32 * 3) - 1);
return result;
}

34
lib/certificate.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
/*
MBEDTLS BASED CERTIFICATE
=========================
This class can be used to generate a self-signed x509
certificate which enables you to perform secure
communication. This certificate uses a 2048 bits RSA key.
*/
#include <string>
#include <mbedtls/config.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/x509_csr.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/md.h>
#include <mbedtls/error.h>
#include <mbedtls/sha256.h>
class Certificate {
public:
Certificate();
int init(const std::string &countryName, const std::string &organization, const std::string& commonName);
int shutdown();
std::string getFingerprintSha256();
public:
mbedtls_x509_crt cert;
mbedtls_pk_context key; /* key context, stores private and public key. */
mbedtls_rsa_context* rsa_ctx; /* rsa context, stored in key_ctx */
};

420
lib/dtls_srtp_handshake.cpp Normal file
View file

@ -0,0 +1,420 @@
#include <algorithm>
#include "defines.h"
#include "dtls_srtp_handshake.h"
/* Write mbedtls into a log file. */
#define LOG_TO_FILE 0
#if LOG_TO_FILE
# include <fstream>
#endif
/* ----------------------------------------- */
static void print_mbedtls_error(int r);
static void print_mbedtls_debug_message(void *ctx, int level, const char *file, int line, const char *str);
static int on_mbedtls_wants_to_read(void* user, unsigned char* buf, size_t len); /* Called when mbedtls wants to read data from e.g. a socket. */
static int on_mbedtls_wants_to_write(void* user, const unsigned char* buf, size_t len); /* Called when mbedtls wants to write data to e.g. a socket. */
static std::string mbedtls_err_to_string(int r);
/* ----------------------------------------- */
DTLSSRTPHandshake::DTLSSRTPHandshake()
:write_callback(NULL)
,cert(NULL)
,key(NULL)
{
memset((void*)&entropy_ctx, 0x00, sizeof(entropy_ctx));
memset((void*)&rand_ctx, 0x00, sizeof(rand_ctx));
memset((void*)&ssl_ctx, 0x00, sizeof(ssl_ctx));
memset((void*)&ssl_conf, 0x00, sizeof(ssl_conf));
memset((void*)&cookie_ctx, 0x00, sizeof(cookie_ctx));
memset((void*)&timer_ctx, 0x00, sizeof(timer_ctx));
}
int DTLSSRTPHandshake::init(mbedtls_x509_crt* certificate,
mbedtls_pk_context* privateKey,
int(*writeCallback)(const uint8_t* data, int* nbytes)
)
{
int r = 0;
mbedtls_ssl_srtp_profile srtp_profiles[] = {
MBEDTLS_SRTP_AES128_CM_HMAC_SHA1_80,
MBEDTLS_SRTP_AES128_CM_HMAC_SHA1_32
};
if (!writeCallback) {
FAIL_MSG("No writeCallack function given.");
r = -3;
goto error;
}
if (!certificate) {
FAIL_MSG("Given certificate is null.");
r = -5;
goto error;
}
if (!privateKey) {
FAIL_MSG("Given key is null.");
r = -10;
goto error;
}
cert = certificate;
key = privateKey;
/* init the contexts */
mbedtls_entropy_init(&entropy_ctx);
mbedtls_ctr_drbg_init(&rand_ctx);
mbedtls_ssl_init(&ssl_ctx);
mbedtls_ssl_config_init(&ssl_conf);
mbedtls_ssl_cookie_init(&cookie_ctx);
/* seed and setup the random number generator */
r = mbedtls_ctr_drbg_seed(&rand_ctx, mbedtls_entropy_func, &entropy_ctx, (const unsigned char*)"mist-srtp", 9);
if (0 != r) {
print_mbedtls_error(r);
r = -20;
goto error;
}
/* load defaults into our ssl_conf */
r = mbedtls_ssl_config_defaults(&ssl_conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_PRESET_DEFAULT);
if (0 != r) {
print_mbedtls_error(r);
r = -30;
goto error;
}
mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE);
mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &rand_ctx);
mbedtls_ssl_conf_dbg(&ssl_conf, print_mbedtls_debug_message, stdout);
mbedtls_ssl_conf_ca_chain(&ssl_conf, cert, NULL);
mbedtls_ssl_conf_cert_profile(&ssl_conf, &mbedtls_x509_crt_profile_default);
mbedtls_debug_set_threshold(10);
/* enable SRTP */
r = mbedtls_ssl_conf_dtls_srtp_protection_profiles(&ssl_conf, srtp_profiles, sizeof(srtp_profiles) / sizeof(srtp_profiles[0]));
if (0 != r) {
print_mbedtls_error(r);
r = -40;
goto error;
}
/* cert certificate chain + key, so we can verify the client-hello signed data */
r = mbedtls_ssl_conf_own_cert(&ssl_conf, cert, key);
if (0 != r) {
print_mbedtls_error(r);
r = -50;
goto error;
}
/* cookie setup (e.g. to prevent ddos). */
r = mbedtls_ssl_cookie_setup(&cookie_ctx, mbedtls_ctr_drbg_random, &rand_ctx);
if (0 != r) {
print_mbedtls_error(r);
r = -60;
goto error;
}
/* register callbacks for dtls cookies (server only). */
mbedtls_ssl_conf_dtls_cookies(&ssl_conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, &cookie_ctx);
/* setup the ssl context for use. note that ssl_conf will be referenced internall by the context and therefore should be kept around. */
r = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf);
if (0 != r) {
print_mbedtls_error(r);
r = -70;
goto error;
}
/* set bio handlers */
mbedtls_ssl_set_bio(&ssl_ctx, (void*)this, on_mbedtls_wants_to_write, on_mbedtls_wants_to_read, NULL);
/* set temp id, just adds some exta randomness */
{
std::string remote_id = "mist";
r = mbedtls_ssl_set_client_transport_id(&ssl_ctx, (const unsigned char*)remote_id.c_str(), remote_id.size());
if (0 != r) {
print_mbedtls_error(r);
r = -80;
goto error;
}
}
/* set timer callbacks */
mbedtls_ssl_set_timer_cb(&ssl_ctx, &timer_ctx, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
write_callback = writeCallback;
error:
if (r < 0) {
shutdown();
}
return r;
}
int DTLSSRTPHandshake::shutdown() {
/* cleanup the refs from the settings. */
cert = NULL;
key = NULL;
buffer.clear();
cipher.clear();
remote_key.clear();
remote_salt.clear();
local_key.clear();
local_salt.clear();
/* free our contexts; we do not free the `settings.cert` and `settings.key` as they are owned by the user of this class. */
mbedtls_entropy_free(&entropy_ctx);
mbedtls_ctr_drbg_free(&rand_ctx);
mbedtls_ssl_free(&ssl_ctx);
mbedtls_ssl_config_free(&ssl_conf);
mbedtls_ssl_cookie_free(&cookie_ctx);
return 0;
}
/* ----------------------------------------- */
int DTLSSRTPHandshake::parse(const uint8_t* data, size_t nbytes) {
if (NULL == data) {
ERROR_MSG("Given `data` is NULL.");
return -1;
}
if (0 == nbytes) {
ERROR_MSG("Given nbytes is 0.");
return -2;
}
if (MBEDTLS_SSL_HANDSHAKE_OVER == ssl_ctx.state) {
ERROR_MSG("Already finished the handshake.");
return -3;
}
/* copy incoming data into a temporary buffer which is read via our `bio` read function. */
int r = 0;
std::copy(data, data + nbytes, std::back_inserter(buffer));
do {
r = mbedtls_ssl_handshake(&ssl_ctx);
switch (r) {
/* 0 = handshake done. */
case 0: {
if (0 != extractKeyingMaterial()) {
ERROR_MSG("Failed to extract keying material after handshake was done.");
return -2;
}
return 0;
}
/* see the dtls server example; this is used to prevent certain attacks (ddos) */
case MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED: {
if (0 != resetSession()) {
ERROR_MSG("Failed to reset the session which is necessary when we need to verify the HELLO.");
return -3;
}
break;
}
case MBEDTLS_ERR_SSL_WANT_READ: {
DONTEVEN_MSG("mbedtls wants a bit more data before it can continue parsing the DTLS handshake.");
break;
}
default: {
ERROR_MSG("A serious mbedtls error occured.");
print_mbedtls_error(r);
return -2;
}
}
}
while (MBEDTLS_ERR_SSL_WANT_WRITE == r);
return 0;
}
/* ----------------------------------------- */
int DTLSSRTPHandshake::resetSession() {
std::string remote_id = "mist"; /* @todo for now we hardcoded this... */
int r = 0;
r = mbedtls_ssl_session_reset(&ssl_ctx);
if (0 != r) {
print_mbedtls_error(r);
return -1;
}
r = mbedtls_ssl_set_client_transport_id(&ssl_ctx, (const unsigned char*)remote_id.c_str(), remote_id.size());
if (0 != r) {
print_mbedtls_error(r);
return -2;
}
buffer.clear();
return 0;
}
/*
master key is 128 bits => 16 bytes.
master salt is 112 bits => 14 bytes
*/
int DTLSSRTPHandshake::extractKeyingMaterial() {
int r = 0;
uint8_t keying_material[MBEDTLS_DTLS_SRTP_MAX_KEY_MATERIAL_LENGTH] = {};
size_t keying_material_len = sizeof(keying_material);
r = mbedtls_ssl_get_dtls_srtp_key_material(&ssl_ctx, keying_material, &keying_material_len);
if (0 != r) {
print_mbedtls_error(r);
return -1;
}
/* @todo following code is for server mode only */
mbedtls_ssl_srtp_profile srtp_profile = mbedtls_ssl_get_dtls_srtp_protection_profile(&ssl_ctx);
switch (srtp_profile) {
case MBEDTLS_SRTP_AES128_CM_HMAC_SHA1_80: {
cipher = "SRTP_AES128_CM_SHA1_80";
break;
}
case MBEDTLS_SRTP_AES128_CM_HMAC_SHA1_32: {
cipher = "SRTP_AES128_CM_SHA1_32";
break;
}
default: {
ERROR_MSG("Unhandled SRTP profile, cannot extract keying material.");
return -6;
}
}
remote_key.assign((char*)(&keying_material[0]) + 0, 16);
local_key.assign((char*)(&keying_material[0]) + 16, 16);
remote_salt.assign((char*)(&keying_material[0]) + 32, 14);
local_salt.assign((char*)(&keying_material[0]) + 46, 14);
DONTEVEN_MSG("Extracted the DTLS SRTP keying material with cipher %s.", cipher.c_str());
DONTEVEN_MSG("Remote DTLS SRTP key size is %zu.", remote_key.size());
DONTEVEN_MSG("Remote DTLS SRTP salt size is %zu.", remote_salt.size());
DONTEVEN_MSG("Local DTLS SRTP key size is %zu.", local_key.size());
DONTEVEN_MSG("Local DTLS SRTP salt size is %zu.", local_salt.size());
return 0;
}
/* ----------------------------------------- */
/*
This function is called by mbedtls whenever it wants to read
some data. The documentation states the following: "For DTLS,
you need to provide either a non-NULL f_recv_timeout
callback, or a f_recv that doesn't block." As this
implementation is completely decoupled from any I/O and uses
a "push" model instead of a "pull" model we have to copy new
input bytes into a temporary buffer (see parse), but we act
as if we were using a non-blocking socket, which means:
- we return MBETLS_ERR_SSL_WANT_READ when there is no data left to read
- when there is data in our temporary buffer, we read from that
*/
static int on_mbedtls_wants_to_read(void* user, unsigned char* buf, size_t len) {
DTLSSRTPHandshake* hs = static_cast<DTLSSRTPHandshake*>(user);
if (NULL == hs) {
ERROR_MSG("Failed to cast the user pointer into a DTLSSRTPHandshake.");
return -1;
}
/* figure out how much we can read. */
if (hs->buffer.size() == 0) {
return MBEDTLS_ERR_SSL_WANT_READ;
}
size_t nbytes = hs->buffer.size();
if (nbytes > len) {
nbytes = len;
}
/* "read" into the given buffer. */
memcpy(buf, &hs->buffer[0], nbytes);
hs->buffer.erase(hs->buffer.begin(), hs->buffer.begin() + nbytes);
return (int)nbytes;
}
static int on_mbedtls_wants_to_write(void* user, const unsigned char* buf, size_t len) {
DTLSSRTPHandshake* hs = static_cast<DTLSSRTPHandshake*>(user);
if (!hs) {
FAIL_MSG("Failed to cast the user pointer into a DTLSSRTPHandshake.");
return -1;
}
if (!hs->write_callback) {
FAIL_MSG("The `write_callback` member is NULL.");
return -2;
}
int nwritten = (int)len;
if (0 != hs->write_callback(buf, &nwritten)) {
FAIL_MSG("Failed to write some DTLS handshake data.");
return -3;
}
if (nwritten != (int)len) {
FAIL_MSG("The DTLS-SRTP handshake listener MUST write all the data.");
return -4;
}
return nwritten;
}
/* ----------------------------------------- */
static void print_mbedtls_error(int r) {
char buf[1024] = {};
mbedtls_strerror(r, buf, sizeof(buf));
ERROR_MSG("mbedtls error: %s", buf);
}
static void print_mbedtls_debug_message(void *ctx, int level, const char *file, int line, const char *str) {
DONTEVEN_MSG("%s:%04d: %.*s", file, line, strlen(str) - 1, str);
#if LOG_TO_FILE
static std::ofstream ofs;
if (!ofs.is_open()) {
ofs.open("mbedtls.log", std::ios::out);
}
if (!ofs.is_open()) {
return;
}
ofs << str;
ofs.flush();
#endif
}
static std::string mbedtls_err_to_string(int r) {
switch (r) {
case MBEDTLS_ERR_SSL_WANT_READ: { return "MBEDTLS_ERR_SSL_WANT_READ"; }
case MBEDTLS_ERR_SSL_WANT_WRITE: { return "MBEDTLS_ERR_SSL_WANT_WRITE"; }
default: {
print_mbedtls_error(r);
return "UNKNOWN";
}
}
}
/* ---------------------------------------- */

59
lib/dtls_srtp_handshake.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <stdint.h>
#include <deque>
#include <mbedtls/config.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/certs.h>
#include <mbedtls/x509.h>
#include <mbedtls/ssl.h>
#include <mbedtls/ssl_cookie.h>
#include <mbedtls/error.h>
#include <mbedtls/debug.h>
#include <mbedtls/timing.h>
/* ----------------------------------------- */
class DTLSSRTPHandshake {
public:
DTLSSRTPHandshake();
int init(mbedtls_x509_crt* certificate, mbedtls_pk_context* privateKey, int(*writeCallback)(const uint8_t* data, int* nbytes)); // writeCallback should return 0 on succes < 0 on error. nbytes holds the number of bytes to be sent and needs to be set to the number of bytes actually sent.
int shutdown();
int parse(const uint8_t* data, size_t nbytes);
bool hasKeyingMaterial();
private:
int extractKeyingMaterial();
int resetSession();
private:
mbedtls_x509_crt* cert; /* Certificate, we do not own the key. Make sure it's kept alive during the livetime of this class instance. */
mbedtls_pk_context* key; /* Private key, we do not own the key. Make sure it's kept alive during the livetime of this class instance. */
mbedtls_entropy_context entropy_ctx;
mbedtls_ctr_drbg_context rand_ctx;
mbedtls_ssl_context ssl_ctx;
mbedtls_ssl_config ssl_conf;
mbedtls_ssl_cookie_ctx cookie_ctx;
mbedtls_timing_delay_context timer_ctx;
public:
int (*write_callback)(const uint8_t* data, int* nbytes);
std::deque<uint8_t> buffer; /* Accessed from BIO callbback. We copy the bytes you pass into `parse()` into this temporary buffer which is read by a trigger to `mbedlts_ssl_handshake()`. */
std::string cipher; /* selected SRTP cipher. */
std::string remote_key;
std::string remote_salt;
std::string local_key;
std::string local_salt;
};
/* ----------------------------------------- */
inline bool DTLSSRTPHandshake::hasKeyingMaterial() {
return (0 != remote_key.size()
&& 0 != remote_salt.size()
&& 0 != local_key.size()
&& 0 != local_salt.size());
}
/* ----------------------------------------- */

569
lib/rtp_fec.cpp Normal file
View file

@ -0,0 +1,569 @@
#include "defines.h"
#include "rtp_fec.h"
#include "rtp.h"
namespace RTP{
/// Based on the `block PT` value, we can either find the
/// contents of the codec payload (e.g. H264, VP8) or a ULPFEC header
/// (RFC 5109). The structure of the ULPFEC data is as follows.
///
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | RTP Header (12 octets or more) |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | FEC Header (10 octets) |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | FEC Level 0 Header |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | FEC Level 0 Payload |
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | FEC Level 1 Header |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | FEC Level 1 Payload |
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Cont. |
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
/// FEC HEADER:
///
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |E|L|P|X| CC |M| PT recovery | SN base |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | TS recovery |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | length recovery |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
///
/// FEC LEVEL HEADER
///
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Protection Length | mask |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | mask cont. (present only when L = 1) |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
PacketFEC::PacketFEC() {
}
PacketFEC::~PacketFEC() {
receivedSeqNums.clear();
coveredSeqNums.clear();
}
bool PacketFEC::initWithREDPacket(const char* data, size_t nbytes) {
if (!data) {
FAIL_MSG("Given fecData pointer is NULL.");
return false;
}
if (nbytes < 23) {
FAIL_MSG("Given fecData is too small. Should be at least: 12 (RTP) + 1 (RED) + 10 (FEC) 23 bytes.");
return false;
}
if (coveredSeqNums.size() != 0) {
FAIL_MSG("It seems we're already initialized; coveredSeqNums already set.");
return false;
}
if (receivedSeqNums.size() != 0) {
FAIL_MSG("It seems we're already initialized; receivedSeqNums is not empty.");
return false;
}
// Decode RED header.
RTP::Packet rtpPkt(data, nbytes);
uint8_t* redHeader = (uint8_t*)(data + rtpPkt.getHsize());
uint8_t moreBlocks = redHeader[0] & 0x80;
if (moreBlocks == 1) {
FAIL_MSG("RED header indicates there are multiple blocks. Haven't seen this before (@todo implement, exiting now).");
// \todo do not EXIT!
return false;
}
// Copy the data, starting at the FEC header (skip RTP + RED header)
size_t numHeaderBytes = rtpPkt.getHsize() + 1;
if (numHeaderBytes > nbytes) {
FAIL_MSG("Invalid FEC packet; too small to contain FEC data.");
return false;
}
fecPacketData.assign(NULL, 0);
fecPacketData.append(data + numHeaderBytes, nbytes - numHeaderBytes);
// Extract the sequence numbers this packet protects.
if (!extractCoveringSequenceNumbers()) {
FAIL_MSG("Failed to extract the protected sequence numbers for this FEC.");
// @todo we probably want to reset our set.
return false;
}
return true;
}
uint8_t PacketFEC::getExtensionFlag() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get extension-flag from the FEC header; fecPacketData member is not set. Not initialized?");
return 0;
}
return ((fecPacketData[0] & 0x80) >> 7);
}
uint8_t PacketFEC::getLongMaskFlag() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get the long-mask-flag from the FEC header. fecPacketData member is not set. Not initialized?");
return 0;
}
return ((fecPacketData[0] & 0x40) >> 6);
}
// Returns 0 (error), 2 or 6, wich are the valid sizes of the mask.
uint8_t PacketFEC::getNumBytesUsedForMask() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get the number of bytes used by the mask. fecPacketData member is not set. Not initialized?");
return 0;
}
if (getLongMaskFlag() == 0) {
return 2;
}
return 6;
}
uint16_t PacketFEC::getSequenceBaseNumber() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get the sequence base number. fecPacketData member is not set. Not initialized?");
return 0;
}
return (uint16_t) (fecPacketData[2] << 8) | fecPacketData[3];
}
char* PacketFEC::getFECHeader() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get fec header. fecPacketData member is not set. Not initialized?");
}
return fecPacketData;
}
char* PacketFEC::getLevel0Header() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get the level 0 header. fecPacketData member is not set. Not initialized?");
return NULL;
}
return (char*)(fecPacketData + 10);
}
char* PacketFEC::getLevel0Payload() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get the level 0 payload. fecPacketData member is not set. Not initialized?");
return NULL;
}
// 10 bytes for FEC header
// 2 bytes for `Protection Length`
// 2 or 6 bytes for `mask`.
return (char*)(fecPacketData + 10 + 2 + getNumBytesUsedForMask());
}
uint16_t PacketFEC::getLevel0ProtectionLength() {
if (fecPacketData.size() == 0) {
FAIL_MSG("Cannot get the level 0 protection length. fecPacketData member is not set. Not initialized?");
return 0;
}
char* level0Header = getLevel0Header();
if (!level0Header) {
FAIL_MSG("Failed to get the level 0 header, cannot get protection length.");
return 0;
}
uint16_t protectionLength = (level0Header[0] << 8) | level0Header[1];
return protectionLength;
}
uint16_t PacketFEC::getLengthRecovery() {
char* fecHeader = getFECHeader();
if (!fecHeader) {
FAIL_MSG("Cannot get the FEC header which we need to get the `length recovery` field. Not initialized?");
return 0;
}
uint16_t lengthRecovery = (fecHeader[8] << 8) | fecHeader[9];
return lengthRecovery;
}
// Based on InsertFecPacket of forward_error_correction.cc from
// Chromium. (used as reference). The `mask` from the
// FEC-level-header can be 2 or 6 bytes long. Whenever a bit is
// set to 1 it means that we have to calculate the sequence
// number for that bit. To calculate the sequence number we
// start with the `SN base` value (base sequence number) and
// use the bit offset to increment the SN-base value. E.g.
// when it's bit 4 and SN-base is 230, it meas that this FEC
// packet protects the media packet with sequence number
// 230. We have to start counting the bit numbers from the
// most-significant-bit (e.g. 1 << 7).
bool PacketFEC::extractCoveringSequenceNumbers() {
if (coveredSeqNums.size() != 0) {
FAIL_MSG("Cannot extract protected sequence numbers; looks like we already did that.");
return false;
}
size_t maskNumBytes = getNumBytesUsedForMask();
if (maskNumBytes != 2 && maskNumBytes != 6) {
FAIL_MSG("Invalid mask size (%u) cannot extract sequence numbers.", maskNumBytes);
return false;
}
char* maskPtr = getLevel0Header();
if (!maskPtr) {
FAIL_MSG("Failed to get the level-0 header ptr. Cannot extract protected sequence numbers.");
return false;
}
uint16_t seqNumBase = getSequenceBaseNumber();
if (seqNumBase == 0) {
WARN_MSG("Base sequence number is 0; it's possible but unlikely.");
}
// Skip the `Protection Length`
maskPtr = maskPtr + 2;
for (uint16_t byteDX = 0; byteDX < maskNumBytes; ++byteDX) {
uint8_t maskByte = maskPtr[byteDX];
for (uint16_t bitDX = 0; bitDX < 8; ++bitDX) {
if (maskByte & (1 << 7 - bitDX)) {
uint16_t seqNum = seqNumBase + (byteDX << 3) + bitDX;
coveredSeqNums.insert(seqNum);
}
}
}
return true;
}
// \todo rename coversSequenceNumber
bool PacketFEC::coversSequenceNumber(uint16_t sn) {
return (coveredSeqNums.count(sn) == 0) ? false : true;
}
void PacketFEC::addReceivedSequenceNumber(uint16_t sn) {
if (false == coversSequenceNumber(sn)) {
FAIL_MSG("Trying to add a received sequence number this instance is not handling (%u).", sn);
return;
}
receivedSeqNums.insert(sn);
}
/// This function can be called to recover a missing packet. A
/// FEC packet is received with a list of media packets it
/// might be able to recover; this PacketFEC is received after
/// we should have received the media packets it's protecting.
///
/// Here we first fill al list with the received sequence
/// numbers that we're protecting; when we're missing one
/// packet this function will try to recover it.
///
/// The `receivedMediaPackets` is the history of media packets
/// that you received and keep in a memory. These are used
/// when XORing when we reconstruct a packet.
void PacketFEC::tryToRecoverMissingPacket(std::map<uint16_t, Packet>& receivedMediaPackets, Packet& reconstructedPacket) {
// Mark all the media packets that we protect and which have
// been received as "received" in our internal list.
std::set<uint16_t>::iterator protIt = coveredSeqNums.begin();
while (protIt != coveredSeqNums.end()) {
if (receivedMediaPackets.count(*protIt) == 1) {
addReceivedSequenceNumber(*protIt);
}
protIt++;
}
// We have received all media packets that we could recover;
// so there is no need for this FEC packet.
// @todo Jaron shall we reuse allocs/PacketFECs?
if (receivedSeqNums.size() == coveredSeqNums.size()) {
return;
}
if (coveredSeqNums.size() != (receivedSeqNums.size() + 1)) {
// missing more then 1 packet. we can only recover when
// one packet is lost.
return;
}
// Find missing sequence number.
uint16_t missingSeqNum = 0;
protIt = coveredSeqNums.begin();
while (protIt != coveredSeqNums.end()) {
if (receivedSeqNums.count(*protIt) == 0) {
missingSeqNum = *protIt;
break;
}
++protIt;
}
if (!coversSequenceNumber(missingSeqNum)) {
WARN_MSG("We cannot recover %u.", missingSeqNum);
return;
}
// Copy FEC into new RTP-header
char* fecHeader = getFECHeader();
if (!fecHeader) {
FAIL_MSG("Failed to get the fec header. Cannot recover.");
return;
}
recoverData.assign(NULL, 0);
recoverData.append(fecHeader, 12);
// Copy FEC into new RTP-payload
char* level0Payload = getLevel0Payload();
if (!level0Payload) {
FAIL_MSG("Failed to get the level-0 payload data (XOR'd media data from FEC packet).");
return;
}
uint16_t level0ProtLen = getLevel0ProtectionLength();
if (level0ProtLen == 0) {
FAIL_MSG("Failed to get the level-0 protection length.");
return;
}
recoverData.append(level0Payload, level0ProtLen);
uint8_t recoverLength[2] = { fecHeader[8], fecHeader[9] };
// XOR headers
protIt = coveredSeqNums.begin();
while (protIt != coveredSeqNums.end()) {
uint16_t seqNum = *protIt;
if (seqNum == missingSeqNum) {
++protIt;
continue;
}
Packet& mediaPacket = receivedMediaPackets[seqNum];
char* mediaData = mediaPacket.ptr();
uint16_t mediaSize = mediaPacket.getPayloadSize();
uint8_t* mediaSizePtr = (uint8_t*)&mediaSize;
WARN_MSG(" => XOR header with %u, size: %u.", seqNum, mediaSize);
// V, P, X, CC, M, PT
recoverData[0] ^= mediaData[0];
recoverData[1] ^= mediaData[1];
// Timestamp
recoverData[4] ^= mediaData[4];
recoverData[5] ^= mediaData[5];
recoverData[6] ^= mediaData[6];
recoverData[7] ^= mediaData[7];
// Length of recovered media packet
recoverLength[0] ^= mediaSizePtr[1];
recoverLength[1] ^= mediaSizePtr[0];
++protIt;
}
uint16_t recoverPayloadSize = ntohs(*(uint16_t*)recoverLength);
// XOR payloads
protIt = coveredSeqNums.begin();
while (protIt != coveredSeqNums.end()) {
uint16_t seqNum = *protIt;
if (seqNum == missingSeqNum) {
++protIt;
continue;
}
Packet& mediaPacket = receivedMediaPackets[seqNum];
char* mediaData = mediaPacket.ptr() + mediaPacket.getHsize();
for (size_t i = 0; i < recoverPayloadSize; ++i) {
recoverData[12 + i] ^= mediaData[i];
}
++protIt;
}
// And setup the reconstructed packet.
reconstructedPacket = Packet(recoverData, recoverPayloadSize);
reconstructedPacket.setSequence(missingSeqNum);
// @todo check what other header fields we need to fix.
}
void FECSorter::addPacket(const Packet &pack){
if (tmpVideoLossPrevention & SDP_LOSS_PREVENTION_ULPFEC) {
packetHistory[pack.getSequence()] = pack;
while (packetHistory.begin()->first < pack.getSequence() - 500){
packetHistory.erase(packetHistory.begin());
}
}
Sorter::addPacket(pack);
}
/// This function will handle RED packets that may be used to
/// encapsulate ULPFEC or simply the codec payload (e.g. H264,
/// VP8). This function is created to handle FEC with
/// WebRTC. When we want to use FEC with WebRTC we have to add
/// both the `a=rtpmap:<ulp-fmt> ulpfec/90000` and
/// `a=rtpmap<red-fmt> red/90000` lines to the SDP. FEC is
/// always used together with RED (RFC 1298). It turns out
/// that with WebRTC the RED only adds one byte after the RTP
/// header (only the `F` and `block PT`, see below)`. The
/// `block PT` is the payload type of the data that
/// follows. This would be `<ulp-fmt>` for FEC data. Though
/// these RED packets may contain FEC or just the media:
/// H264/VP8.
///
/// RED HEADER:
///
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |F| block PT | timestamp offset | block length |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
void FECSorter::addREDPacket(char* dat,
unsigned int len,
uint8_t codecPayloadType,
uint8_t REDPayloadType,
uint8_t ULPFECPayloadType)
{
RTP::Packet pkt(dat, len);
if (pkt.getPayloadType() != REDPayloadType) {
FAIL_MSG("Requested to add a RED packet, but it has an invalid payload type.");
return;
}
// Check if the `F` flag is set. Chromium will always set
// this to 0 (at time of writing, check: https://goo.gl/y1eJ6k
uint8_t* REDHeader = (uint8_t*)(dat + pkt.getHsize());
uint8_t moreBlocksAvailable = REDHeader[0] & 0x80;
if (moreBlocksAvailable == 1) {
FAIL_MSG("Not yet received a RED packet that had it's F bit set; @todo implement.");
exit(EXIT_FAILURE);
return;
}
// Extract the `block PT` field which can be the media-pt,
// fec-pt. When it's just media that follows, we move all
// data one byte up and reconstruct a normal media packet.
uint8_t blockPayloadType = REDHeader[0] & 0x7F;
if (blockPayloadType == codecPayloadType) {
memmove(dat + pkt.getHsize(), dat + pkt.getHsize() + 1, len - pkt.getHsize() - 1);
dat[1] &= 0x80;
dat[1] |= codecPayloadType;
RTP::Packet mediaPacket((const char*)dat, len -1);
addPacket(mediaPacket);
return;
}
// When the payloadType equals our ULP/FEC payload type, we
// received a REC packet (RFC 5109) that contains FEC data
// and a list of sequence number that it covers and can
// reconstruct.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// \todo Jaron, I'm now just generating a `PacketFEC` on the heap
// and we're not managing destruction anywhere atm; I guess
// re-use or destruction needs to be part of the algo that
// is going to deal with FEC.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if (blockPayloadType == ULPFECPayloadType) {
WARN_MSG(" => got fec packet: %u", pkt.getSequence());
PacketFEC* fec = new PacketFEC();
if (!fec->initWithREDPacket(dat, len)) {
delete fec;
fec = NULL;
FAIL_MSG("Failed to initialize a `PacketFEC`");
}
fecPackets.push_back(fec);
Packet recreatedPacket;
fec->tryToRecoverMissingPacket(packetHistory, recreatedPacket);
if (recreatedPacket.ptr() != NULL) {
char* pl = recreatedPacket.getPayload();
WARN_MSG(" => reconstructed %u, %02X %02X %02X %02X | %02X %02X %02X %02X", recreatedPacket.getSequence(), pl[0], pl[1], pl[2], pl[3], pl[4], pl[5], pl[6], pl[7]);
addPacket(recreatedPacket);
}
return;
}
FAIL_MSG("Unhandled RED block payload type %u. Check the answer SDP.", blockPayloadType);
}
/// Each FEC packet is capable of recovering a limited amount
/// of media packets. Some experimentation showed that most
/// often one FEC is used to protect somewhere between 2-10
/// media packets. Each FEC packet has a list of sequence
/// number that it can recover when all other media packets
/// have been received except the one that we want to
/// recover. This function returns the FEC packet might be able
/// to recover the given sequence number.
PacketFEC* FECSorter::getFECPacketWhichCoversSequenceNumber(uint16_t sn) {
size_t nfecs = fecPackets.size();
for (size_t i = 0; i < nfecs; ++i) {
PacketFEC* fec = fecPackets[i];
if (fec->coversSequenceNumber(sn)) {
return fec;
}
}
return NULL;
}
void FECPacket::sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void* userData, void callBack(void* userData, const char* payload, uint32_t nbytes)) {
char *rtcpData = (char *)malloc(32);
if (!rtcpData){
FAIL_MSG("Could not allocate 32 bytes. Something is seriously messed up.");
return;
}
if (!(sorter.lostCurrent + sorter.packCurrent)){sorter.packCurrent++;}
rtcpData[0] = 0x81; // version 2, no padding, one receiver report
rtcpData[1] = 201; // receiver report
Bit::htobs(rtcpData + 2, 7); // 7 4-byte words follow the header
Bit::htobl(rtcpData + 4, mySSRC); // set receiver identifier
Bit::htobl(rtcpData + 8, theirSSRC); // set source identifier
rtcpData[12] =
(sorter.lostCurrent * 255) /
(sorter.lostCurrent + sorter.packCurrent); // fraction lost since prev RR
Bit::htob24(rtcpData + 13, sorter.lostTotal); // cumulative packets lost since start
Bit::htobl(rtcpData + 16, sorter.rtpSeq | (sorter.packTotal &
0xFFFF0000ul)); // highest sequence received
Bit::htobl(rtcpData + 20, 0); /// \TODO jitter (diff in timestamp vs packet arrival)
Bit::htobl(rtcpData + 24, 0); /// \TODO last SR (middle 32 bits of last SR or zero)
Bit::htobl(rtcpData + 28, 0); /// \TODO delay since last SR in 2b seconds + 2b fraction
callBack(userData, rtcpData, 32);
sorter.lostCurrent = 0;
sorter.packCurrent = 0;
free(rtcpData);
}
}

100
lib/rtp_fec.h Normal file
View file

@ -0,0 +1,100 @@
#pragma once
#include "rtp.h"
#include "sdp_media.h"
#include "util.h"
#include <set>
namespace RTP{
/// Util class that can be used to retrieve information from a
/// FEC packet. A FEC packet is contains recovery data. This
/// data can be used to reconstruct a media packet. This class
/// was created and tested for the WebRTC implementation where
/// each FEC packet is encapsulated by a RED packet (RFC 1298).
/// A RED packet may contain ordinary payload data -or- FEC
/// data (RFC 5109). We assume that the data given into
/// `initWithREDPacket()` contains FEC data and you did a bit
/// of parsing to figure this out: by checking if the `block
/// PT` from the RED header is the ULPFEC payload type; if so
/// this PacketFEC class can be used.
class PacketFEC{
public:
PacketFEC();
~PacketFEC();
bool initWithREDPacket(
const char *data,
size_t nbytes); /// Initialize using the given data. `data` must point to the first byte of
/// the RTP packet which contains the RED and FEC headers and data.
uint8_t getExtensionFlag(); ///< From fec header: should be 0, see
///< https://tools.ietf.org/html/rfc5109#section-7.3.
uint8_t
getLongMaskFlag(); ///< From fec header: returns 0 when the short mask version is used (16
///< bits), otherwise 1 (48 bits). The mask is used to calculate what
///< sequence numbers are protected, starting at the base sequence number.
uint16_t getSequenceBaseNumber(); ///< From fec header: get the base sequence number. The base
///< sequence number is used together with the mask to
///< determine what the sequence numbers of the media packets
///< are that the fec data protects.
uint8_t getNumBytesUsedForMask(); ///< From fec level header: a fec packet can protected up to
///< 48 media packets. Which sequence numbers are stored using
///< a mask bit string. This returns either 2 or 6.
char *getLevel0Header(); ///< Get a pointer to the start of the fec-level-0 header (contains the
///< protection-length and mask)
char *getLevel0Payload(); /// < Get a pointer to the actual FEC data. This is the XOR'd header
/// and paylaod.
char *getFECHeader(); ///< Get a pointer to the first byte of the FEC header.
uint16_t getLevel0ProtectionLength(); ///< Get the length of the `getLevel0Payload()`.
uint16_t
getLengthRecovery(); ///< Get the `length recovery` value (Little Endian). This value is used
///< while XORing to recover the length of the missing media packet.
bool coversSequenceNumber(uint16_t sn); ///< Returns true when this `PacketFEC` instance is used
///< to protect the given sequence number.
void
addReceivedSequenceNumber(uint16_t sn); ///< Whenever you receive a media packet (complete) call
///< this as we need to know if enough media packets
///< exist that are needed to recover another one.
void tryToRecoverMissingPacket(
std::map<uint16_t, RTP::Packet> &receivedMediaPackets,
Packet &reconstructedPacket); ///< Pass in a `std::map` indexed by sequence number of -all-
///< the media packets that you keep as history. When this
///< `PacketFEC` is capable of recovering a media packet it
///< will fill the packet passed by reference.
private:
bool
extractCoveringSequenceNumbers(); ///< Used internally to fill the `coveredSeqNums` member which
///< tell us what media packets this FEC packet rotects.
public:
Util::ResizeablePointer fecPacketData;
Util::ResizeablePointer recoverData;
std::set<uint16_t>
coveredSeqNums; ///< The sequence numbers of the packets that this FEC protects.
std::set<uint16_t>
receivedSeqNums; ///< We keep track of sequence numbers that were received (at some higher
///< level). We can only recover 1 media packet and this is used to check
///< if this `PacketFEC` instance is capable of recovering anything.
};
class FECSorter : public Sorter{
public:
void addPacket(const Packet &pack);
void addREDPacket(char *dat, unsigned int len, uint8_t codecPayloadType, uint8_t REDPayloadType,
uint8_t ULPFECPayloadType);
PacketFEC *getFECPacketWhichCoversSequenceNumber(uint16_t sn);
uint8_t tmpVideoLossPrevention; ///< TMP used to drop packets for FEC; see output_webrtc.cpp
///< `handleSignalingCommandRemoteOfferForInput()`. This
///< variable should be rmeoved when cleaning up.
private:
std::map<uint16_t, Packet> packetHistory;
std::vector<PacketFEC *> fecPackets;
};
class FECPacket : public Packet{
public:
void sendRTCP_RR(RTP::FECSorter &sorter, uint32_t mySSRC, uint32_t theirSSRC, void *userData,
void callBack(void *userData, const char *payload, uint32_t nbytes));
};
}// namespace RTP

1184
lib/sdp_media.cpp Normal file

File diff suppressed because it is too large Load diff

220
lib/sdp_media.h Normal file
View file

@ -0,0 +1,220 @@
#pragma once
#include <string>
#include <map>
#include <set>
#include <vector>
#include "dtsc.h"
#define SDP_PAYLOAD_TYPE_NONE 9999 /// Define that is used to indicate a payload type is not set.
#define SDP_LOSS_PREVENTION_NONE 0
#define SDP_LOSS_PREVENTION_NACK (1 << 1) /// Use simple NACK based loss prevention. (e.g. send a NACK to pusher of video stream when a packet is lost)
#define SDP_LOSS_PREVENTION_ULPFEC (1 << 2) /// Use FEC (See rtp.cpp, PacketRED). When used we try to add the correct `a=rtpmap` for RED and ULPFEC to the SDP when supported by the offer.
namespace SDP{
/// A MediaFormat stores the infomation that is specific for an
/// encoding. With RTSP there is often just one media format
/// per media line. Though with WebRTC, where an SDP is used to
/// determine a common capability, one media line can contain
/// different formats. These formats are indicated by with the
/// <fmt> attribute of the media line. For each <fmt> there may
/// be one or more custom properties. For each property, like
/// the `encodingName` (e.g. VP8, VP9, H264, etc). we create a
/// new `SDP::MediaFormat` object and store it in the `formats`
/// member of `SDP::Media`.
///
/// When you want to retrieve some specific data and there is a
/// getter function defined for it, you SHOULD use this
/// function as these functions add some extra logic based on
/// the set members.
class MediaFormat{
public:
MediaFormat();
std::string getFormatParameterForName(
const std::string &name) const; ///< Get a parameter which was part of the `a=fmtp:` line.
uint32_t getAudioSampleRate()
const; ///< Returns the audio sample rate. When `audioSampleRate` has been set this will be
///< returned, otherwise we use the `payloadType` to determine the samplerate or
///< return 0 when we fail to determine to samplerate.
uint32_t getAudioNumChannels()
const; ///< Returns the number of audio channels. When `audioNumChannels` has been set this
///< will be returned, otherwise we use the `payloadType` when it's set to determine
///< the samplerate or we return 0 when we can't determine the number of channels.
uint32_t getAudioBitSize()
const; ///< Returns the audio bitsize. When `audioBitSize` has been set this will be
///< returned, othwerise we use the `encodingName` to determine the right
///< `audioBitSize` or 0 when we can't determine the `audioBitSize`
uint32_t
getVideoRate() const; ///< Returns the video time base. When `videoRate` has been set this will
///< be returned, otherwise we use the `encodingName` to determine the
///< right value or 0 when we can't determine the video rate.
uint32_t getVideoOrAudioRate() const; ///< Returns whichever rate has been set.
uint64_t getPayloadType() const; ///< Returns the `payloadType` member.
int32_t
getPacketizationModeForH264(); ///< When this represents a h264 format this will return the
///< packetization mode when it was provided in the SDP
std::string getProfileLevelIdForH264(); ///< When this represents a H264 format, this will return the profile-level-id from the format parameters.
operator bool() const;
public:
uint64_t payloadType; ///< The payload type as set in the media line (the <fmt> is -the-
///< payloadType).
uint64_t associatedPayloadType; ///< From `a=fmtp:<pt> apt=<apt>`; maps this format to another payload type.
int32_t audioSampleRate; ///< Samplerate of the audio type.
int32_t audioNumChannels; ///< Number of audio channels extracted from the `a=fmtp` or set in
///< `setDefaultsForPayloadType()`.
int32_t audioBitSize; ///< Number of bits used in case this is an audio type 8, 16, set in
///< `setDefaultsForCodec()` and `setDefaultsForPayloadType()`.
int32_t videoRate; ///< Video framerate, e.g. 9000
std::string encodingName; ///< Stores the UPPERCASED encoding name from the `a=rtpmap:<payload
///< type> <encoding name>
std::string iceUFrag; ///< From `a=ice-ufrag:<ufrag>, used with WebRTC / STUN.
std::string icePwd; ///< From `a=ice-pwd:<pwd>`, used with WebRTC / STUN
std::string rtpmap; ///< The `a=<rtpmap:...> value; value between brackets.
std::map<std::string, std::string>
formatParameters; ///< Stores the var-val pairs from `a=fmtp:<fmt>` entry e.g. =
///< `packetization-mode=1;profile-level-id=4d0029;sprop-parameter-sets=Z00AKeKQCADDYC3AQEBpB4kRUA==,aO48gA==`
///< */
std::set<std::string>
rtcpFormats; ///< Stores the `fb-val` from the line with `a=rtcp-fb:<fmt> <fb-val>`.
};
class Media{
public:
Media();
bool parseMediaLine(const std::string &sdpLine); ///< Parses `m=` line. Creates a `MediaFormat`
///< entry for each of the found <fmt> values.
bool parseRtpMapLine(
const std::string &sdpLine); ///< Parses `a=rtpmap:` line which contains the some codec
///< specific info. When this line contains the samplerate and
///< number of audio channels they will be extracted.
bool parseRtspControlLine(const std::string &sdpLine); ///< Parses `a=control:`
bool parseFrameRateLine(const std::string &sdpLine); ///< Parses `a=framerate:`
bool parseFormatParametersLine(const std::string &sdpLine); ///< Parses `a=fmtp:<payload-type>`.
bool parseRtcpFeedbackLine(
const std::string &sdpLine); ///< Parses `a=rtcp-fb:<payload-type>`. See RFC4584
bool parseFingerprintLine(
const std::string
&sdpLine); ///< Parses `a=fingerprint:<hash-func> <value>`. See
///< https://tools.ietf.org/html/rfc8122#section-5, used with WebRTC
bool parseSSRCLine(const std::string &sdpLine); ///< Parses `a=ssrc:<ssrc>`.
MediaFormat *getFormatForSdpLine(
const std::string
&sdpLine); ///< Returns the track to which this SDP line applies. This means that the
///< SDP line should be formatteed like: `a=something:[payloadtype]`.
MediaFormat *getFormatForPayloadType(
uint64_t &payloadType); ///< Finds the `MediaFormat` in `formats`. Returns NULL when no
///< format was found for the given payload type. .
MediaFormat *getFormatForEncodingName(
const std::string
&encName); ///< Finds the `MediaFormats in `formats`. Returns NULL when no format was
///< found for the given encoding name. E.g. `VP8`, `VP9`, `H264`
std::vector<SDP::MediaFormat *> getFormatsForEncodingName(const std::string &encName);
std::string getIcePwdForFormat(
const MediaFormat
&fmt); ///< The `a=ice-pwd` can be session global or media specific. This function will
///< check if the `SDP::MediaFormat` has a ice-pwd that we should use.
uint32_t
getSSRC() const; ///< Returns the first SSRC `a=ssrc:<value>` value found for the media.
operator bool() const;
MediaFormat* getRetransMissionFormatForPayloadType(uint64_t pt); ///< When available, it resurns the RTX format that is directly associated with the media (not encapsulated with a RED header). RTX can be combined with FEC in which case it's supposed to be stored in RED packets. The `encName` should be something like H264,VP8; e.g. the format for which you want to get the RTX format.
public:
std::string type; ///< The `media` field of the media line: `m=<media> <port> <proto> <fmt>`,
///< like "video" or "audio"
std::string proto; ///< The `proto` field of the media line: `m=<media> <port> <proto> <fmt>`,
///< like "video" or "audio"
std::string control; ///< From `a=control:` The RTSP control url.
std::string direction; ///< From `a=sendonly`, `a=recvonly` and `a=sendrecv`
std::string iceUFrag; ///< From `a=ice-ufrag:<ufrag>, used with WebRTC / STUN.
std::string icePwd; ///< From `a=ice-pwd:<pwd>`, used with WebRTC / STUN
std::string setupMethod; ///< From `a=setup:<passive, active, actpass>, used with WebRTC / STUN
std::string fingerprintHash; ///< From `a=fingerprint:<hash> <value>`, e.g. sha-256, used with
///< WebRTC / STUN
std::string
fingerprintValue; ///< From `a=fingerprint:<hash> <value>`, the actual fingerprint, used
///< with WebRTC / STUN, see https://tools.ietf.org/html/rfc8122#section-5
std::string mediaID; ///< From `a=mid:<value>`. When generating an WebRTC answer this value must
///< be the same as in the offer.
std::string candidateIP; ///< Used when we generate a WebRTC answer.
uint16_t candidatePort; ///< Used when we generate a WebRTC answer.
uint32_t SSRC; ///< From `a=ssrc:<SSRC> <something>`; the first SSRC that we encountered.
double framerate; ///< From `a=framerate`.
bool supportsRTCPMux; ///< From `a=rtcp-mux`, indicates if it can mux RTP and RTCP on one
///< transport channel.
bool supportsRTCPReducedSize; ///< From `a=rtcp-rsize`, reduced size RTCP packets.
std::string
payloadTypes; ///< From `m=` line, all the payload types as string, separated by space.
std::map<uint64_t, MediaFormat>
formats; ///< Formats indexed by payload type. Payload type is the number in the <fmt>
///< field(s) from the `m=` line.
};
class Session{
public:
bool parseSDP(const std::string &sdp);
Media *getMediaForType(
const std::string &type); ///< Get a `SDP::Media*` for the given type, e.g. `video` or
///< `audio`. Returns NULL when the type was not found.
MediaFormat *getMediaFormatByEncodingName(const std::string &mediaType,
const std::string &encodingName);
bool hasReceiveOnlyMedia(); ///< Returns true when one of the media sections has a `a=recvonly`
///< attribute. This is used to determine if the other peer only
///< wants to receive or also sent data. */
public:
std::vector<SDP::Media> medias; ///< For each `m=` line we create a `SDP::Media` instance. The
///< stream specific infomration is stored in a `MediaFormat`
std::string icePwd; ///< From `a=ice-pwd`, this property can be session-wide or media specific.
///< Used with WebRTC and STUN when calculating the message-integrity.
std::string
iceUFrag; ///< From `a=ice-ufag`, this property can be session-wide or media specific. Used
///< with WebRTC and STUN when calculating the message-integrity.
};
class Answer{
public:
Answer();
bool parseOffer(const std::string &sdp);
bool hasVideo(); ///< Check if the offer has video.
bool hasAudio(); ///< Check if the offer has audio.
bool enableVideo(const std::string &codecName);
bool enableAudio(const std::string &codecName);
void setCandidate(const std::string &ip, uint16_t port);
void setFingerprint(const std::string &fingerprintSha); ///< Set the SHA265 that represents the
///< certificate that is used with DTLS.
void setDirection(const std::string &dir);
bool setupVideoDTSCTrack(DTSC::Track &result);
bool setupAudioDTSCTrack(DTSC::Track &result);
std::string toString();
private:
bool enableMedia(const std::string &type, const std::string &codecName, SDP::Media &outMedia,
SDP::MediaFormat &outFormat);
void addLine(const std::string &fmt, ...);
std::string generateSessionId();
std::string generateIceUFrag(); ///< Generates the `ice-ufrag` value.
std::string generateIcePwd(); ///< Generates the `ice-pwd` value.
std::string generateRandomString(const int len);
std::vector<std::string> splitString(const std::string &str, char delim);
public:
SDP::Session sdpOffer;
SDP::Media answerVideoMedia;
SDP::Media answerAudioMedia;
SDP::MediaFormat answerVideoFormat;
SDP::MediaFormat answerAudioFormat;
bool isAudioEnabled;
bool isVideoEnabled;
std::string candidateIP; ///< We use rtcp-mux and BUNDLE; so only one candidate necessary.
uint16_t candidatePort; ///< We use rtcp-mux and BUNDLE; so only one candidate necessary.
std::string fingerprint;
std::string direction; ///< The direction used when generating the answer SDP string.
std::vector<std::string> output; ///< The lines that are used when adding lines (see `addLine()`
///< for the answer sdp.).
uint8_t videoLossPrevention; ///< See the SDP_LOSS_PREVENTION_* values at the top of this header.
};
}

422
lib/srtp.cpp Normal file
View file

@ -0,0 +1,422 @@
#include <algorithm>
#include "defines.h"
#include "srtp.h"
/* --------------------------------------- */
static std::string srtp_status_to_string(uint32_t status);
/* --------------------------------------- */
SRTPReader::SRTPReader() {
memset((void*)&session, 0x00, sizeof(session));
memset((void*)&policy, 0x00, sizeof(policy));
}
/*
Before initializing the srtp library we shut it down first
because initializing the library twice results in an error.
*/
int SRTPReader::init(const std::string& cipher, const std::string& key, const std::string& salt) {
int r = 0;
srtp_err_status_t status = srtp_err_status_ok;
srtp_profile_t profile;
memset((void*) &profile, 0x00, sizeof(profile));
/* validate input */
if (cipher.empty()) {
FAIL_MSG("Given `cipher` is empty.");
r = -1;
goto error;
}
if (key.empty()) {
FAIL_MSG("Given `key` is empty.");
r = -2;
goto error;
}
if (salt.empty()) {
FAIL_MSG("Given `salt` is empty.");
r = -3;
goto error;
}
/* re-initialize the srtp library. */
status = srtp_shutdown();
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to shutdown the srtp lib %s", srtp_status_to_string(status).c_str());
r = -1;
goto error;
}
status = srtp_init();
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to initialize the SRTP library. %s", srtp_status_to_string(status).c_str());
r = -2;
goto error;
}
/* select the right profile from exchanged cipher */
if ("SRTP_AES128_CM_SHA1_80" == cipher) {
profile = srtp_profile_aes128_cm_sha1_80;
}
else if ("SRTP_AES128_CM_SHA1_32" == cipher) {
profile = srtp_profile_aes128_cm_sha1_32;
}
else {
ERROR_MSG("Unsupported SRTP cipher used: %s.", cipher.c_str());
r = -2;
goto error;
}
/* set the crypto policy using the profile. */
status = srtp_crypto_policy_set_from_profile_for_rtp(&policy.rtp, profile);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to set the crypto policy for RTP for cipher %s.", cipher.c_str());
r = -3;
goto error;
}
status = srtp_crypto_policy_set_from_profile_for_rtcp(&policy.rtcp, profile);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to set the crypto policy for RTCP for cipher %s.", cipher.c_str());
r = -4;
goto error;
}
/* set the keying material. */
std::copy(key.begin(), key.end(), std::back_inserter(key_salt));
std::copy(salt.begin(), salt.end(), std::back_inserter(key_salt));
policy.key = (unsigned char*)&key_salt[0];
/* only unprotecting data for now, so using inbound; and some other settings. */
policy.ssrc.type = ssrc_any_inbound;
policy.window_size = 1024;
policy.allow_repeat_tx = 1;
/* create the srtp session. */
status = srtp_create(&session, &policy);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to initialize our SRTP session. Status: %s. ", srtp_status_to_string(status).c_str());
r = -3;
goto error;
}
error:
if (r < 0) {
shutdown();
}
return r;
}
int SRTPReader::shutdown() {
int r = 0;
srtp_err_status_t status = srtp_dealloc(session);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to cleanly shutdown the SRTP session. Status: %s", srtp_status_to_string(status).c_str());
r -= 5;
}
memset((void*)&policy, 0x00, sizeof(policy));
memset((char*)&session, 0x00, sizeof(session));
return r;
}
/* --------------------------------------- */
int SRTPReader::unprotectRtp(uint8_t* data, int* nbytes) {
if (NULL == data) {
ERROR_MSG("Cannot unprotect the given SRTP, because data is NULL.");
return -1;
}
if (NULL == nbytes) {
ERROR_MSG("Cannot unprotect the given SRTP, becuase nbytes is NULL.");
return -2;
}
if (0 == (*nbytes)) {
ERROR_MSG("Cannot unprotect the given SRTP, because nbytes is 0.");
return -3;
}
if (NULL == policy.key) {
ERROR_MSG("Cannot unprotect the SRTP packet, it seems we're not initialized.");
return -4;
}
srtp_err_status_t status = srtp_unprotect(session, data, nbytes);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to unprotect the given SRTP. %s.", srtp_status_to_string(status).c_str());
return -5;
}
DONTEVEN_MSG("Unprotected SRTP into %d bytes.", *nbytes);
return 0;
}
int SRTPReader::unprotectRtcp(uint8_t* data, int* nbytes) {
if (NULL == data) {
ERROR_MSG("Cannot unprotect the given SRTCP, because data is NULL.");
return -1;
}
if (NULL == nbytes) {
ERROR_MSG("Cannot unprotect the given SRTCP, becuase nbytes is NULL.");
return -2;
}
if (0 == (*nbytes)) {
ERROR_MSG("Cannot unprotect the given SRTCP, because nbytes is 0.");
return -3;
}
if (NULL == policy.key) {
ERROR_MSG("Cannot unprotect the SRTCP packet, it seems we're not initialized.");
return -4;
}
srtp_err_status_t status = srtp_unprotect_rtcp(session, data, nbytes);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to unprotect the given SRTCP. %s.", srtp_status_to_string(status).c_str());
return -5;
}
return 0;
}
/* --------------------------------------- */
SRTPWriter::SRTPWriter() {
memset((void*)&session, 0x00, sizeof(session));
memset((void*)&policy, 0x00, sizeof(policy));
}
/*
Before initializing the srtp library we shut it down first
because initializing the library twice results in an error.
*/
int SRTPWriter::init(const std::string& cipher, const std::string& key, const std::string& salt) {
int r = 0;
srtp_err_status_t status = srtp_err_status_ok;
srtp_profile_t profile;
memset((void*)&profile, 0x00, sizeof(profile));
/* validate input */
if (cipher.empty()) {
FAIL_MSG("Given `cipher` is empty.");
r = -1;
goto error;
}
if (key.empty()) {
FAIL_MSG("Given `key` is empty.");
r = -2;
goto error;
}
if (salt.empty()) {
FAIL_MSG("Given `salt` is empty.");
r = -3;
goto error;
}
/* re-initialize the srtp library. */
status = srtp_shutdown();
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to shutdown the srtp lib %s", srtp_status_to_string(status).c_str());
r = -1;
goto error;
}
status = srtp_init();
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to initialize the SRTP library. %s", srtp_status_to_string(status).c_str());
r = -2;
goto error;
}
/* select the exchanged cipher */
if ("SRTP_AES128_CM_SHA1_80" == cipher) {
profile = srtp_profile_aes128_cm_sha1_80;
}
else if ("SRTP_AES128_CM_SHA1_32" == cipher) {
profile = srtp_profile_aes128_cm_sha1_32;
}
else {
ERROR_MSG("Unsupported SRTP cipher used: %s.", cipher.c_str());
r = -2;
goto error;
}
/* set the crypto policy using the profile. */
status = srtp_crypto_policy_set_from_profile_for_rtp(&policy.rtp, profile);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to set the crypto policy for RTP for cipher %s.", cipher.c_str());
r = -3;
goto error;
}
status = srtp_crypto_policy_set_from_profile_for_rtcp(&policy.rtcp, profile);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to set the crypto policy for RTCP for cipher %s.", cipher.c_str());
r = -4;
goto error;
}
/* set the keying material. */
std::copy(key.begin(), key.end(), std::back_inserter(key_salt));
std::copy(salt.begin(), salt.end(), std::back_inserter(key_salt));
policy.key = (unsigned char*)&key_salt[0];
/* only unprotecting data for now, so using inbound; and some other settings. */
policy.ssrc.type = ssrc_any_outbound;
policy.window_size = 128;
policy.allow_repeat_tx = 0;
/* create the srtp session. */
status = srtp_create(&session, &policy);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to initialize our SRTP session. Status: %s. ", srtp_status_to_string(status).c_str());
r = -3;
goto error;
}
error:
if (r < 0) {
shutdown();
}
return r;
}
int SRTPWriter::shutdown() {
int r = 0;
srtp_err_status_t status = srtp_dealloc(session);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to cleanly shutdown the SRTP session. Status: %s", srtp_status_to_string(status).c_str());
r -= 5;
}
memset((char*)&policy, 0x00, sizeof(policy));
memset((char*)&session, 0x00, sizeof(session));
return r;
}
/* --------------------------------------- */
int SRTPWriter::protectRtp(uint8_t* data, int* nbytes) {
if (NULL == data) {
ERROR_MSG("Cannot protect the RTP packet because given data is NULL.");
return -1;
}
if (NULL == nbytes) {
ERROR_MSG("Cannot protect the RTP packet because the given nbytes is NULL.");
return -2;
}
if ((*nbytes) <= 0) {
ERROR_MSG("Cannot protect the RTP packet because the given nbytes has a value <= 0.");
return -3;
}
if (NULL == policy.key) {
ERROR_MSG("Cannot protect the RTP packet because we're not initialized.");
return -4;
}
srtp_err_status_t status = srtp_protect(session, (void*)data, nbytes);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to protect the RTP packet. %s.", srtp_status_to_string(status).c_str());
return -5;
}
return 0;
}
/*
Make sure that `data` has `SRTP_MAX_TRAILER_LEN + 4` number
of bytes at the into which libsrtp can write the
authentication tag
*/
int SRTPWriter::protectRtcp(uint8_t* data, int* nbytes) {
if (NULL == data) {
ERROR_MSG("Cannot protect the RTCP packet because given data is NULL.");
return -1;
}
if (NULL == nbytes) {
ERROR_MSG("Cannot protect the RTCP packet because nbytes is NULL.");
return -2;
}
if ((*nbytes) <= 0) {
ERROR_MSG("Cannot protect the RTCP packet because *nbytes is <= 0.");
return -3;
}
if (NULL == policy.key) {
ERROR_MSG("Not initialized cannot protect the RTCP packet.");
return -4;
}
srtp_err_status_t status = srtp_protect_rtcp(session, (void*)data, nbytes);
if (srtp_err_status_ok != status) {
ERROR_MSG("Failed to protect the RTCP packet. %s.", srtp_status_to_string(status).c_str());
return -3;
}
return 0;
}
/* --------------------------------------- */
static std::string srtp_status_to_string(uint32_t status) {
switch (status) {
case srtp_err_status_ok: { return "srtp_err_status_ok"; }
case srtp_err_status_fail: { return "srtp_err_status_fail"; }
case srtp_err_status_bad_param: { return "srtp_err_status_bad_param"; }
case srtp_err_status_alloc_fail: { return "srtp_err_status_alloc_fail"; }
case srtp_err_status_dealloc_fail: { return "srtp_err_status_dealloc_fail"; }
case srtp_err_status_init_fail: { return "srtp_err_status_init_fail"; }
case srtp_err_status_terminus: { return "srtp_err_status_terminus"; }
case srtp_err_status_auth_fail: { return "srtp_err_status_auth_fail"; }
case srtp_err_status_cipher_fail: { return "srtp_err_status_cipher_fail"; }
case srtp_err_status_replay_fail: { return "srtp_err_status_replay_fail"; }
case srtp_err_status_replay_old: { return "srtp_err_status_replay_old"; }
case srtp_err_status_algo_fail: { return "srtp_err_status_algo_fail"; }
case srtp_err_status_no_such_op: { return "srtp_err_status_no_such_op"; }
case srtp_err_status_no_ctx: { return "srtp_err_status_no_ctx"; }
case srtp_err_status_cant_check: { return "srtp_err_status_cant_check"; }
case srtp_err_status_key_expired: { return "srtp_err_status_key_expired"; }
case srtp_err_status_socket_err: { return "srtp_err_status_socket_err"; }
case srtp_err_status_signal_err: { return "srtp_err_status_signal_err"; }
case srtp_err_status_nonce_bad: { return "srtp_err_status_nonce_bad"; }
case srtp_err_status_read_fail: { return "srtp_err_status_read_fail"; }
case srtp_err_status_write_fail: { return "srtp_err_status_write_fail"; }
case srtp_err_status_parse_err: { return "srtp_err_status_parse_err"; }
case srtp_err_status_encode_err: { return "srtp_err_status_encode_err"; }
case srtp_err_status_semaphore_err: { return "srtp_err_status_semaphore_err"; }
case srtp_err_status_pfkey_err: { return "srtp_err_status_pfkey_err"; }
case srtp_err_status_bad_mki: { return "srtp_err_status_bad_mki"; }
case srtp_err_status_pkt_idx_old: { return "srtp_err_status_pkt_idx_old"; }
case srtp_err_status_pkt_idx_adv: { return "srtp_err_status_pkt_idx_adv"; }
default: { return "UNKNOWN"; }
}
}
/* --------------------------------------- */

43
lib/srtp.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include <stdint.h>
#include <string>
#include <srtp2/srtp.h>
#define SRTP_PARSER_MASTER_KEY_LEN 16
#define SRTP_PARSER_MASTER_SALT_LEN 14
#define SRTP_PARSER_MASTER_LEN (SRTP_PARSER_MASTER_KEY_LEN + SRTP_PARSER_MASTER_SALT_LEN)
/* --------------------------------------- */
class SRTPReader {
public:
SRTPReader();
int init(const std::string& cipher, const std::string& key, const std::string& salt);
int shutdown();
int unprotectRtp(uint8_t* data, int* nbytes); /* `nbytes` should contain the number of bytes in `data`. On success `nbytes` will hold the number of bytes of the decoded RTP packet. */
int unprotectRtcp(uint8_t* data, int* nbytes); /* `nbytes` should contains the number of bytes in `data`. On success `nbytes` will hold the number of bytes the decoded RTCP packet. */
private:
srtp_t session;
srtp_policy_t policy;
std::vector<uint8_t> key_salt; /* Combination of key + salt which is used to unprotect the SRTP/SRTCP data. */
};
/* --------------------------------------- */
class SRTPWriter {
public:
SRTPWriter();
int init(const std::string& cipher, const std::string& key, const std::string& salt);
int shutdown();
int protectRtp(uint8_t* data, int* nbytes);
int protectRtcp(uint8_t* data, int* nbytes);
private:
srtp_t session;
srtp_policy_t policy;
std::vector<uint8_t> key_salt; /* Combination of key + salt which is used to protect the SRTP/SRTCP data. */
};
/* --------------------------------------- */

1051
lib/stun.cpp Normal file

File diff suppressed because it is too large Load diff

250
lib/stun.h Normal file
View file

@ -0,0 +1,250 @@
#pragma once
#include <stdint.h>
#include <netinet/in.h>
#include <vector>
#include <string>
/* --------------------------------------- */
#define STUN_IP4 0x01
#define STUN_IP6 0x02
#define STUN_MSG_TYPE_NONE 0x0000
#define STUN_MSG_TYPE_BINDING_REQUEST 0x0001
#define STUN_MSG_TYPE_BINDING_RESPONSE_SUCCESS 0x0101
#define STUN_MSG_TYPE_BINDING_RESPONSE_ERROR 0x0111
#define STUN_MSG_TYPE_BINDING_INDICATION 0x0011
#define STUN_ATTR_TYPE_NONE 0x0000
#define STUN_ATTR_TYPE_MAPPED_ADDR 0x0001
#define STUN_ATTR_TYPE_CHANGE_REQ 0x0003
#define STUN_ATTR_TYPE_USERNAME 0x0006
#define STUN_ATTR_TYPE_MESSAGE_INTEGRITY 0x0008
#define STUN_ATTR_TYPE_ERR_CODE 0x0009
#define STUN_ATTR_TYPE_UNKNOWN_ATTRIBUTES 0x000a
#define STUN_ATTR_TYPE_CHANNEL_NUMBER 0x000c
#define STUN_ATTR_TYPE_LIFETIME 0x000d
#define STUN_ATTR_TYPE_XOR_PEER_ADDR 0x0012
#define STUN_ATTR_TYPE_DATA 0x0013
#define STUN_ATTR_TYPE_REALM 0x0014
#define STUN_ATTR_TYPE_NONCE 0x0015
#define STUN_ATTR_TYPE_XOR_RELAY_ADDRESS 0x0016
#define STUN_ATTR_TYPE_REQ_ADDRESS_FAMILY 0x0017
#define STUN_ATTR_TYPE_EVEN_PORT 0x0018
#define STUN_ATTR_TYPE_REQUESTED_TRANSPORT 0x0019
#define STUN_ATTR_TYPE_DONT_FRAGMENT 0x001a
#define STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS 0x0020
#define STUN_ATTR_TYPE_RESERVATION_TOKEN 0x0022
#define STUN_ATTR_TYPE_PRIORITY 0x0024
#define STUN_ATTR_TYPE_USE_CANDIDATE 0x0025
#define STUN_ATTR_TYPE_PADDING 0x0026
#define STUN_ATTR_TYPE_RESPONSE_PORT 0x0027
#define STUN_ATTR_TYPE_SOFTWARE 0x8022
#define STUN_ATTR_TYPE_ALTERNATE_SERVER 0x8023
#define STUN_ATTR_TYPE_FINGERPRINT 0x8028
#define STUN_ATTR_TYPE_ICE_CONTROLLED 0x8029
#define STUN_ATTR_TYPE_ICE_CONTROLLING 0x802a
#define STUN_ATTR_TYPE_RESPONSE_ORIGIN 0x802b
#define STUN_ATTR_TYPE_OTHER_ADDRESS 0x802c
/* --------------------------------------- */
std::string stun_message_type_to_string(uint16_t type);
std::string stun_attribute_type_to_string(uint16_t type);
std::string stun_family_type_to_string(uint8_t type);
/*
Compute the hmac-sha1 over message.
uint8_t* message: the data over which we compute the hmac sha
uint32_t nbytes: the number of bytse in message
std::string key: key to use for hmac
uint8_t* output: we write the sha1 into this buffer.
*/
int stun_compute_hmac_sha1(uint8_t* message, uint32_t nbytes, std::string key, uint8_t* output);
/*
Compute the Message-Integrity of a stun message.
This will not change the given buffer.
std::vector<uint8_t>& buffer: the buffer that contains a valid stun message
std::string key: key to use for hmac
uint8_t* output: will be filled with the correct hmac-sha1 of that represents the integrity message value.
*/
int stun_compute_message_integrity(std::vector<uint8_t>& buffer, std::string key, uint8_t* output);
/*
Compute the fingerprint value for the stun message.
This will not change the given buffer.
std::vector<uint8_t>& buffer: the buffer that contains a valid stun message.
uint32_t& result: will be set to the calculated crc value.
*/
int stun_compute_fingerprint(std::vector<uint8_t>& buffer, uint32_t& result);
/* --------------------------------------- */
/* https://tools.ietf.org/html/rfc5389#section-15.10 */
class StunAttribSoftware {
public:
char* value;
};
class StunAttribFingerprint {
public:
uint32_t value;
};
/* https://tools.ietf.org/html/rfc5389#section-15.4 */
class StunAttribMessageIntegrity {
public:
uint8_t* sha1;
};
/* https://tools.ietf.org/html/rfc5245#section-19.1 */
class StunAttribPriority {
public:
uint32_t value;
};
/* https://tools.ietf.org/html/rfc5245#section-19.1 */
class StunAttribIceControllling {
public:
uint64_t tie_breaker;
};
/* https://tools.ietf.org/html/rfc3489#section-11.2.6 */
class StunAttribUsername {
public:
char* value; /* Must use `length` member of attribute that indicates the number of valid bytes in the username. */
};
/* https://tools.ietf.org/html/rfc5389#section-15.2 */
class StunAttribXorMappedAddress {
public:
uint8_t family;
uint16_t port;
uint8_t ip[16];
};
/* --------------------------------------- */
class StunAttribute {
public:
StunAttribute();
void print();
public:
uint16_t type;
uint16_t length;
union {
StunAttribXorMappedAddress xor_address;
StunAttribUsername username;
StunAttribIceControllling ice_controlling;
StunAttribPriority priority;
StunAttribSoftware software;
StunAttribMessageIntegrity message_integrity;
StunAttribFingerprint fingerprint;
};
};
/* --------------------------------------- */
class StunMessage {
public:
StunMessage();
void setType(uint16_t type);
void setTransactionId(uint32_t a, uint32_t b, uint32_t c);
void print();
void addAttribute(StunAttribute& attr);
void removeAttributes();
StunAttribute* getAttributeByType(uint16_t type);
public:
uint16_t type;
uint16_t length;
uint32_t cookie;
uint32_t transaction_id[3];
std::vector<StunAttribute> attributes;
};
/* --------------------------------------- */
class StunReader {
public:
StunReader();
int parse(uint8_t* data, size_t nbytes, size_t& nparsed, StunMessage& msg); /* `nparsed` and `msg` are filled. */
private:
int parseXorMappedAddress(StunAttribute& attr);
int parseUsername(StunAttribute& attr);
int parseIceControlling(StunAttribute& attr);
int parsePriority(StunAttribute& attr);
int parseSoftware(StunAttribute& attr);
int parseMessageIntegrity(StunAttribute& attr);
int parseFingerprint(StunAttribute& attr);
uint8_t readU8();
uint16_t readU16();
uint32_t readU32();
uint64_t readU64();
private:
uint8_t* buffer_data;
size_t buffer_size;
size_t read_dx;
};
/* --------------------------------------- */
class StunWriter {
public:
StunWriter();
/* write header and finalize. call for each stun message */
int begin(StunMessage& msg, uint8_t paddingByte = 0x00); /* I've added the padding byte here so that we can use the examples that can be found here https://tools.ietf.org/html/rfc5769#section-2.2 as they use 0x20 or 0x00 as the padding byte which is correct as you are free to use w/e padding byte you want. */
int end();
/* write attributes */
int writeXorMappedAddress(sockaddr_in addr);
int writeXorMappedAddress(uint8_t family, uint16_t port, uint32_t ip);
int writeXorMappedAddress(uint8_t family, uint16_t port, const std::string& ip);
int writeUsername(const std::string& username);
int writeSoftware(const std::string& software);
int writeMessageIntegrity(const std::string& password); /* When using WebRtc this is the ice-upwd of the other agent. */
int writeFingerprint(); /* Must be the last attribute in the message. When adding a fingerprint, make sure that it is added after the message-integrity (when you also use a message-integrity). */
/* get buffer */
uint8_t* getBufferPtr();
size_t getBufferSize();
private:
void writeU8(uint8_t v);
void writeU16(uint16_t v);
void writeU32(uint32_t v);
void writeU64(uint64_t v);
void rewriteU16(size_t dx, uint16_t v);
void rewriteU32(size_t dx, uint32_t v);
void writeString(const std::string& str);
void writePadding();
int convertIp4StringToInt(const std::string& ip, uint32_t& result);
private:
std::vector<uint8_t> buffer;
uint8_t padding_byte;
};
/* --------------------------------------- */
inline uint8_t* StunWriter::getBufferPtr() {
if (0 == buffer.size()) {
return NULL;
}
return &buffer[0];
}
inline size_t StunWriter::getBufferSize() {
return buffer.size();
}
/* --------------------------------------- */

52
scripts/webrtc_compile.sh Executable file
View file

@ -0,0 +1,52 @@
#!/bin/sh
pd=${PWD}
d=${PWD}/../
config="Release"
if [ ! -d ${d}/external ] ; then
mkdir ${d}/external
fi
if [ ! -d ${d}/external/mbedtls ] ; then
#prepare mbedtls for build
cd ${d}/external/
git clone https://github.com/diederickh/mbedtls
cd ${d}/external/mbedtls
git checkout -b dtls_srtp_support
git merge 15179bfbaa794506c06f923f85d7c71f0dfd89e9
git am < ${pd}/webrtc_mbedtls_keying_material_fix.diff
if [ $? -ne 0 ] ; then
echo "Failed to apply patch"
exit
fi
fi
if [ ! -d ${d}/build ] ; then
mkdir ${d}/build
fi
if [ ! -d ${d}/installed ] ; then
mkdir ${d}/installed
#Build mbedtls
mkdir -p ${d}/external/mbedtls/build
cd ${d}/external/mbedtls/build
cmake -DCMAKE_INSTALL_PREFIX=${d}/installed -DENABLE_PROGRAMS=Off ..
cmake --build . --config ${config} --target install -- -j 8
fi
cd ${d}
export PATH="${PATH}:${d}/installed/include"
cmake -DCMAKE_CXX_FLAGS="-I${d}/installed/include/ -L${d}/installed/lib/" \
-DCMAKE_PREFIX_PATH=${d}/installed/include \
-DCMAKE_MODULE_PATH=${d}/installed/ \
-DPERPETUAL=1 \
-DDEBUG=3 \
-GNinja \
.
ninja

View file

@ -0,0 +1,34 @@
From ba52913047a6821dac15f8320c8857cef589bb6f Mon Sep 17 00:00:00 2001
From: roxlu <diederick@roxlu.com>
Date: Mon, 2 Jul 2018 22:26:21 +0200
Subject: [PATCH] Fixes to get DTLS SRTP to work with WebRTC
---
library/ssl_tls.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/library/ssl_tls.c b/library/ssl_tls.c
index fe27c6a8..25b86da8 100644
--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -6436,7 +6436,6 @@ mbedtls_ssl_srtp_profile mbedtls_ssl_get_dtls_srtp_protection_profile( const mbe
}
int mbedtls_ssl_get_dtls_srtp_key_material( const mbedtls_ssl_context *ssl, unsigned char *key, size_t *key_len ) {
- *key_len = 0;
/* check output buffer size */
if ( *key_len < ssl->dtls_srtp_info.dtls_srtp_keys_len) {
@@ -7706,8 +7705,7 @@ void mbedtls_ssl_free( mbedtls_ssl_context *ssl )
#endif
#if defined (MBEDTLS_SSL_DTLS_SRTP)
- mbedtls_zeroize( ssl->dtls_srtp_info.dtls_srtp_keys, ssl->dtls_srtp_info.dtls_srtp_keys_len );
- // mbedtls_free( ssl->dtls_srtp_keys );
+ mbedtls_platform_zeroize( ssl->dtls_srtp_info.dtls_srtp_keys, ssl->dtls_srtp_info.dtls_srtp_keys_len );
#endif /* MBEDTLS_SSL_DTLS_SRTP */
MBEDTLS_SSL_DEBUG_MSG( 2, ( "<= free" ) );
--
2.17.1

30
scripts/webrtc_run.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
cd ${PWD}/../build
set -x
#export MIST_CONTROL=1
make MistOutWebRTC
if [ $? -ne 0 ] ; then
echo "Failed to compile."
exit
fi
#-fsanitize=address
#export MALLOC_CHECK_=2
# valgrind --trace-children=yes
# ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=$(shell which llvm-symbolizer)
if [ 0 -ne 0 ] ; then
reset && valgrind --trace-children=yes ./MistOutHTTPS \
--port 4433 \
--cert ~/.ssh/certs/arch680.rox.lu.crt \
--key ~/.ssh/certs/arch680.rox.lu.key \
--debug 10
else
reset && ./MistOutHTTPS \
--port 4433 \
--cert ~/.ssh/certs/arch680.rox.lu.crt \
--key ~/.ssh/certs/arch680.rox.lu.key \
--debug 10
fi

View file

@ -0,0 +1,112 @@
cmake_minimum_required(VERSION 3.8)
project(srtp2)
set(bd ${CMAKE_CURRENT_LIST_DIR})
set(sd ${bd})
list(APPEND lib_sources
${sd}/srtp/srtp.c
${sd}/srtp/ekt.c
${sd}/crypto/kernel/alloc.c
${sd}/crypto/kernel/err.c
${sd}/crypto/kernel/crypto_kernel.c
${sd}/crypto/kernel/key.c
${sd}/crypto/math/datatypes.c
${sd}/crypto/math/stat.c
${sd}/crypto/replay/rdbx.c
${sd}/crypto/replay/rdb.c
${sd}/crypto/replay/ut_sim.c
${sd}/crypto/cipher/cipher.c
${sd}/crypto/cipher/null_cipher.c
${sd}/crypto/cipher/aes.c
${sd}/crypto/hash/auth.c
${sd}/crypto/hash/null_auth.c
${sd}/crypto/cipher/aes_icm.c
${sd}/crypto/hash/sha1.c
${sd}/crypto/hash/hmac.c
)
# -- start of checks
include(CheckIncludeFiles)
include(CheckFunctionExists)
include(CheckLibraryExists)
include(CheckTypeSize)
include(TestBigEndian)
set(AC_APPLE_UNIVERSAL_BUILD 0)
set(CPU_CISC 1)
set(CPU_RISC 0)
set(ENABLE_DEBUG_LOGGING 0)
set(ERR_REPORTING_FILE "libsrtp_error.log")
set(ERR_REPORTING_STDOUT 0)
set(VERSION "2.3")
check_include_files(arpa/inet.h HAVE_ARPA_INET_H)
check_include_files(byteswap.h HAVE_BYTESWAP_H)
check_function_exists(inet_aton HAVE_INET_ATON)
check_type_size(int16_t HAVE_INT16_T)
check_type_size(int32_t HAVE_INT32_T)
check_type_size(int8_t HAVE_INT8_T)
check_include_files(inttypes.h HAVE_INTTYPES_H)
check_library_exists(dl dlopen "" HAVE_LIBDL)
check_library_exists(socket socket "" HAVE_LIBSOCKET)
check_library_exists(z zlibVersion "" HAVE_LIBZ)
check_include_files(machine/types.h HAVE_MACHINE_TYPES_H)
check_include_files(memory.h HAVE_MEMORY_H)
check_include_files(netinet/in.h HAVE_NETINET_IN_H)
# @todo check winpcap
check_function_exists(sigaction HAVE_SIGACTION)
check_function_exists(socket HAVE_SOCKET)
check_include_files(stdint.h HAVE_STDINT_H)
check_include_files(stdlib.h HAVE_STDLIB_H)
check_include_files(strings.h HAVE_STRINGS_H)
check_include_files(string.h HAVE_STRING_H)
check_include_files(sys/int_types.h HAVE_SYS_INT_TYPES_H)
check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
check_include_files(sys/stat.h HAVE_SYS_STAT_H)
check_include_files(sys/types.h HAVE_SYS_TYPES_H)
check_include_files(sys/uio.h HAVE_SYS_UIO_H)
check_type_size(uint16_t HAVE_UINT16_T)
check_type_size(uint32_t HAVE_UINT32_T)
check_type_size(uint64_t HAVE_UINT64_T)
check_type_size(uint8_t HAVE_UINT8_T)
check_include_files(unistd.h HAVE_UNISTD_H)
check_function_exists(usleep HAVE_USLEEP)
check_include_files(windows.h HAVE_WINDOWS_H)
check_include_files(winsock2.h HAVE_WINSOCK2_H)
# @todo HAVE_X86
# @todo OPENSSL
# @todo OPENSSL_CLEANSE_BROKEN
# @todo OPENSSL_KDF
# @todo PACKAGE_BUGREPORT
set(PACKAGE_BUGREPORT "testers@ddvdtech.com")
set(PACKAGE_NAME "libsrtp")
set(PACKAGE_VERSION "${VERSION}")
set(PACKAGE_STRING "${PACKAGE_NAME}_${VERSION}")
set(PACKAGE_TARNAME "${PACKAGE_STRING}.tar")
set(PACKAGE_URL "http://www.mistserver.org")
check_type_size("unsigned long" SIZEOF_UNSIGNED_LONG)
check_type_size("unsigned long long" SIZEOF_UNSIGNED_LONG_LONG)
check_include_files("stdlib.h;stdarg.h;string.h;float.h" STDC_HEADERS)
configure_file(${bd}/config.cmake ${bd}/crypto/include/config.h)
#--------------------------------------------------------
include_directories(
${bd}/include/
${bd}/crypto/
${bd}/crypto/include
)
add_library(srtp2 STATIC ${lib_sources})
target_compile_definitions(srtp2 PUBLIC HAVE_CONFIG_H)
list(APPEND include_files
${bd}/include/srtp.h
${bd}/crypto/include/cipher.h
${bd}/crypto/include/auth.h
${bd}/crypto/include/crypto_types.h
)
install(FILES ${include_files} DESTINATION include)
install(TARGETS srtp2 ARCHIVE DESTINATION lib)

View file

@ -0,0 +1,181 @@
/* config_in.h. Generated from configure.ac by autoheader. */
/* Define if building universal (internal helper macro) */
#cmakedefine AC_APPLE_UNIVERSAL_BUILD 1
/* Define if building for a CISC machine (e.g. Intel). */
#cmakedefine CPU_CISC 1
/* Define if building for a RISC machine (assume slow byte access). */
#cmakedefine CPU_RISC 1
/* Define to enabled debug logging for all mudules. */
#cmakedefine ENABLE_DEBUG_LOGGING 1
/* Logging statments will be writen to this file. */
#cmakedefine ERR_REPORTING_FILE "@ERR_REPORTING_FILE@"
/* Define to redirect logging to stdout. */
#cmakedefine ERR_REPORTING_STDOUT 1
/* Define to 1 if you have the <arpa/inet.h> header file. */
#cmakedefine HAVE_ARPA_INET_H 1
/* Define to 1 if you have the <byteswap.h> header file. */
#cmakedefine HAVE_BYTESWAP_H 1
/* Define to 1 if you have the `inet_aton' function. */
#cmakedefine HAVE_INET_ATON 1
/* Define to 1 if the system has the type `int16_t'. */
#cmakedefine HAVE_INT16_T 1
/* Define to 1 if the system has the type `int32_t'. */
#cmakedefine HAVE_INT32_T 1
/* Define to 1 if the system has the type `int8_t'. */
#cmakedefine HAVE_INT8_T 1
/* Define to 1 if you have the <inttypes.h> header file. */
#cmakedefine HAVE_INTTYPES_H 1
/* Define to 1 if you have the `dl' library (-ldl). */
#cmakedefine HAVE_LIBDL 1
/* Define to 1 if you have the `socket' library (-lsocket). */
#cmakedefine HAVE_LIBSOCKET 1
/* Define to 1 if you have the `z' library (-lz). */
#cmakedefine HAVE_LIBZ 1
/* Define to 1 if you have the <machine/types.h> header file. */
#cmakedefine HAVE_MACHINE_TYPES_H 1
/* Define to 1 if you have the <memory.h> header file. */
#cmakedefine HAVE_MEMORY_H 1
/* Define to 1 if you have the <netinet/in.h> header file. */
#cmakedefine HAVE_NETINET_IN_H 1
/* Define to 1 if you have the `winpcap' library (-lwpcap) */
#cmakedefine HAVE_PCAP 1
/* Define to 1 if you have the `sigaction' function. */
#cmakedefine HAVE_SIGACTION 1
/* Define to 1 if you have the `socket' function. */
#cmakedefine HAVE_SOCKET 1
/* Define to 1 if you have the <stdint.h> header file. */
#cmakedefine HAVE_STDINT_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#cmakedefine HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#cmakedefine HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#cmakedefine HAVE_STRING_H 1
/* Define to 1 if you have the <sys/int_types.h> header file. */
#cmakedefine HAVE_SYS_INT_TYPES_H 1
/* Define to 1 if you have the <sys/socket.h> header file. */
#cmakedefine HAVE_SYS_SOCKET_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#cmakedefine HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#cmakedefine HAVE_SYS_TYPES_H 1
/* Define to 1 if you have the <sys/uio.h> header file. */
#cmakedefine HAVE_SYS_UIO_H 1
/* Define to 1 if the system has the type `uint16_t'. */
#cmakedefine HAVE_UINT16_T 1
/* Define to 1 if the system has the type `uint32_t'. */
#cmakedefine HAVE_UINT32_T 1
/* Define to 1 if the system has the type `uint64_t'. */
#cmakedefine HAVE_UINT64_T 1
/* Define to 1 if the system has the type `uint8_t'. */
#cmakedefine HAVE_UINT8_T 1
/* Define to 1 if you have the <unistd.h> header file. */
#cmakedefine HAVE_UNISTD_H 1
/* Define to 1 if you have the `usleep' function. */
#cmakedefine HAVE_USLEEP 1
/* Define to 1 if you have the <windows.h> header file. */
#cmakedefine HAVE_WINDOWS_H 1
/* Define to 1 if you have the <winsock2.h> header file. */
#cmakedefine HAVE_WINSOCK2_H 1
/* Define to use X86 inlined assembly code */
#cmakedefine HAVE_X86 1
/* Define this to use OpenSSL crypto. */
#cmakedefine OPENSSL 1
/* Define this if OPENSSL_cleanse is broken. */
#cmakedefine OPENSSL_CLEANSE_BROKEN 1
/* Define this to use OpenSSL KDF for SRTP. */
#cmakedefine OPENSSL_KDF 1
/* Define to the address where bug reports for this package should be sent. */
#cmakedefine PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@"
/* Define to the full name of this package. */
#define PACKAGE_NAME "@PACKAGE_NAME@"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "@PACKAGE_STRING@"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "@PACKAGE_TARNAME@"
/* Define to the home page for this package. */
#cmakedefine PACKAGE_URL "@PACKAGE_URL@"
/* Define to the version of this package. */
#define PACKAGE_VERSION "@PACKAGE_VERSION@"
/* The size of a `unsigned long', as computed by sizeof. */
#define SIZEOF_UNSIGNED_LONG @SIZEOF_UNSIGNED_LONG@
/* The size of a `unsigned long long', as computed by sizeof. */
#define SIZEOF_UNSIGNED_LONG_LONG @SIZEOF_UNSIGNED_LONG_LONG@
/* Define to 1 if you have the ANSI C header files. */
#cmakedefine STDC_HEADERS 1
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
significant byte first (like Motorola and SPARC, unlike Intel). */
#if defined AC_APPLE_UNIVERSAL_BUILD
# if defined __BIG_ENDIAN__
# define WORDS_BIGENDIAN 1
# endif
#else
# ifndef WORDS_BIGENDIAN
# undef WORDS_BIGENDIAN
# endif
#endif
/* Define to empty if `const' does not conform to ANSI C. */
#undef const
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
#undef inline
#endif
/* Define to `unsigned int' if <sys/types.h> does not define. */
#undef size_t

1524
src/output/output_webrtc.cpp Normal file

File diff suppressed because it is too large Load diff

173
src/output/output_webrtc.h Normal file
View file

@ -0,0 +1,173 @@
/*
SOME NOTES ON MIST
- When a user wants to start pushing video into Mist we need to
check if the user is actually allowed to do this. When the user
is allowed to push we have to call the function `allowPush("")`.
SIGNALING
1. Client sends the offer:
{
type: "offer_sdp",
offer_sdp: <the-client-offer-sdp>,
}
Server responds with:
SUCCESS:
{
type: "on_answer_sdp",
result: true,
answer_sdp: <the-server-answer-sdp>,
}
ERROR:
{
type: "on_answer_sdp",
result: false,
}
2. Client request new bitrate:
{
type: "video_bitrate"
video_bitrate: 600000
}
Server responds with:
SUCCESS:
{
type: "on_video_bitrate"
result: true
}
ERROR:
{
type: "on_video_bitrate"
result: false
}
*/
#pragma once
#include "output.h"
#include "output_http.h"
#include <mist/h264.h>
#include <mist/http_parser.h>
#include <mist/rtp_fec.h>
#include <mist/sdp_media.h>
#include <mist/socket.h>
#include <mist/tinythread.h>
#include <mist/websocket.h>
#include <mist/certificate.h>
#include <mist/stun.h>
#include <mist/dtls_srtp_handshake.h>
#include <mist/srtp.h>
#if defined(WEBRTC_PCAP)
# include <mist/pcap.h>
#endif
namespace Mist {
/* ------------------------------------------------ */
class WebRTCTrack {
public:
WebRTCTrack(); ///< Initializes to some defaults.
public:
RTP::toDTSC rtpToDTSC; ///< Converts RTP packets into DTSC packets.
RTP::FECSorter sorter; ///< Takes care of sorting the received RTP packet and keeps track of some statistics. Will call a callback whenever a packet can be used. (e.g. not lost, in correct order).
RTP::Packet rtpPacketizer; ///< Used when we're sending RTP data back to the other peer.
uint64_t payloadType; ///< The payload type that was extracted from the `m=` media line in the SDP.
std::string localIcePwd;
std::string localIceUFrag;
uint32_t SSRC; ///< The SSRC of the RTP packets.
uint32_t timestampMultiplier; ///< Used for outgoing streams to convert the DTSC timestamps into RTP timestamps.
uint8_t ULPFECPayloadType; ///< When we've enabled FEC for a video stream this holds the payload type that is used to distinguish between ordinary video RTP packets and FEC packets.
uint8_t REDPayloadType; ///< When using RED and ULPFEC this holds the payload type of the RED stream.
uint8_t RTXPayloadType; ///< The retransmission payload type when we use RTX (retransmission with separate SSRC/payload type)
uint16_t prevReceivedSequenceNumber; ///< The previously received sequence number. This is used to NACK packets when we loose one.
};
/* ------------------------------------------------ */
class OutWebRTC : public HTTPOutput {
public:
OutWebRTC(Socket::Connection &myConn);
~OutWebRTC();
static void init(Util::Config *cfg);
virtual void sendHeader();
virtual void sendNext();
virtual void onWebsocketFrame();
bool doesWebsockets(){return true;}
void handleWebRTCInputOutputFromThread();
int onDTLSHandshakeWantsToWrite(const uint8_t* data, int* nbytes);
void onRTPSorterHasPacket(const uint64_t trackID, const RTP::Packet &pkt);
void onDTSCConverterHasPacket(const DTSC::Packet& pkt);
void onDTSCConverterHasInitData(const uint64_t trackID, const std::string &initData);
void onRTPPacketizerHasRTPPacket(char* data, uint32_t nbytes);
void onRTPPacketizerHasRTCPPacket(char* data, uint32_t nbytes);
private:
bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read some data, othewise false.
void handleReceivedSTUNPacket();
void handleReceivedDTLSPacket();
void handleReceivedRTPOrRTCPPacket();
void handleSignalingCommand(HTTP::Websocket& webSock, const JSON::Value &command);
bool handleSignalingCommandRemoteOffer(HTTP::Websocket &webSock, const JSON::Value &command);
bool handleSignalingCommandRemoteOfferForInput(HTTP::Websocket &webSocket, SDP::Session &sdpSession, const std::string &sdpOffer);
bool handleSignalingCommandRemoteOfferForOutput(HTTP::Websocket &webSocket, SDP::Session &sdpSession, const std::string &sdpOffer);
bool handleSignalingCommandVideoBitrate(HTTP::Websocket& webSock, const JSON::Value &command);
bool handleSignalingCommandSeek(HTTP::Websocket& webSock, const JSON::Value &command);
bool handleSignalingCommandKeyFrameInterval(HTTP::Websocket &webSock, const JSON::Value &command); ///< Handles the command that can be used to set the keyframe interval for the current connection. We will sent RTCP PLI messages every X-millis; the other agent -should- generate keyframes when it receives PLI messages (Picture Loss Indication).
void sendSignalingError(HTTP::Websocket& webSock, const std::string& commandType, const std::string& errorMessage);
bool validateSignalingCommand(HTTP::Websocket& webSock, const JSON::Value &command, JSON::Value &errorResult);
bool createWebRTCTrackFromAnswer(const SDP::Media& mediaAnswer, const SDP::MediaFormat& formatAnswer, WebRTCTrack& result);
void sendRTCPFeedbackREMB(const WebRTCTrack &rtcTrack);
void sendRTCPFeedbackPLI(const WebRTCTrack &rtcTrack); ///< Picture Los Indication: request keyframe.
void sendRTCPFeedbackRR(WebRTCTrack &rtcTrack);
void sendRTCPFeedbackNACK(const WebRTCTrack &rtcTrack, uint16_t missingSequenceNumber); ///< Notify sender that we're missing a sequence number.
void sendSPSPPS(DTSC::Track& dtscTrack, WebRTCTrack& rtcTrack);///< When we're streaming H264 to e.g. the browser we inject the PPS and SPS nals.
void extractFrameSizeFromVP8KeyFrame(const DTSC::Packet &pkt);
void updateCapabilitiesWithSDPOffer(SDP::Session &sdpSession);
bool bindUDPSocketOnLocalCandidateAddress(uint16_t port); ///< Binds our UDP socket onto the IP address that we shared via our SDP answer. We *have to* bind on a specific IP, see https://gist.github.com/roxlu/6c5ab696840256dac71b6247bab59ce9
std::string getLocalCandidateAddress();
private:
SDP::Session sdp; ///< SDP parser.
SDP::Answer sdpAnswer; ///< WIP: Replacing our `sdp` member ..
Certificate cert; ///< The TLS certificate. Used to generate a fingerprint in SDP answers.
DTLSSRTPHandshake dtlsHandshake; ///< Implements the DTLS handshake using the mbedtls library (fork).
SRTPReader srtpReader; ///< Used to unprotect incoming RTP and RTCP data. Uses the keys that were exchanged with DTLS.
SRTPWriter srtpWriter; ///< Used to protect our RTP and RTCP data when sending data to another peer. Uses the keys that were exchanged with DTLS.
Socket::UDPConnection udp; ///< Our UDP socket over which WebRTC data is received and sent.
StunReader stunReader; ///< Decodes STUN messages; during a session we keep receiving STUN messages to which we need to reply.
std::map<uint64_t, WebRTCTrack> webrtcTracks; ///< WebRTCTracks indexed by payload type for incoming data and indexed by myMeta.tracks[].trackID for outgoing data.
tthread::thread* webRTCInputOutputThread; ///< The thread in which we read WebRTC data when we're receive media from another peer.
uint16_t udpPort; ///< The port on which our webrtc socket is bound. This is where we receive RTP, STUN, DTLS, etc. */
uint32_t SSRC; ///< The SSRC for this local instance. Is used when generating RTCP reports. */
uint64_t rtcpTimeoutInMillis; ///< When current time in millis exceeds this timeout we have to send a new RTCP packet.
uint64_t rtcpKeyFrameTimeoutInMillis;
uint64_t rtcpKeyFrameDelayInMillis;
char* rtpOutBuffer; ///< Buffer into which we copy (unprotected) RTP data that we need to deliver to the other peer. This gets protected.
uint32_t videoBitrate; ///< The bitrate to use for incoming video streams. Can be configured via the signaling channel. Defaults to 6mbit.
bool didReceiveKeyFrame; /* TODO burst delay */
#if defined(WEBRTC_PCAP)
PCAPWriter pcapOut; ///< Used during development to write unprotected packets that can be inspected in e.g. wireshark.
PCAPWriter pcapIn; ///< Used during development to write unprotected packets that can be inspected in e.g. wireshark.
#endif
std::map<uint8_t, uint64_t> payloadTypeToWebRTCTrack; ///< Maps e.g. RED to the corresponding track. Used when input supports RED/ULPFEC; can also be used to map RTX in the future.
};
}
typedef Mist::OutWebRTC mistOut;