Support for WebRTC data tracks (output only, for now), rewrite of dTLS integration (now part of socket lib), support for multi-path WebRTC connections

This commit is contained in:
Thulinma 2023-12-29 00:58:03 +01:00
parent 56193f89b1
commit 3987cfec3f
16 changed files with 1303 additions and 811 deletions

View file

@ -1,395 +0,0 @@
#include "defines.h"
#include "dtls_srtp_handshake.h"
#include <algorithm>
#include <string.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. */
/* ----------------------------------------- */
DTLSSRTPHandshake::DTLSSRTPHandshake() : cert(NULL), key(NULL), write_callback(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, (int)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
}
/* ---------------------------------------- */

View file

@ -1,62 +0,0 @@
#pragma once
#include <deque>
#include <mbedtls/certs.h>
#include <mbedtls/config.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/ssl.h>
#include <mbedtls/ssl_cookie.h>
#include <mbedtls/timing.h>
#include <mbedtls/x509.h>
#include <stdint.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());
}
/* ----------------------------------------- */

View file

@ -12,7 +12,6 @@ headers = [
'comms.h', 'comms.h',
'config.h', 'config.h',
'defines.h', 'defines.h',
'dtls_srtp_handshake.h',
'dtsc.h', 'dtsc.h',
'encryption.h', 'encryption.h',
'flv_tag.h', 'flv_tag.h',
@ -69,7 +68,7 @@ install_headers(headers, subdir: 'mist')
extra_code = [] extra_code = []
if usessl if usessl
extra_code += ['dtls_srtp_handshake.cpp', 'stun.cpp', 'certificate.cpp', 'encryption.cpp',] extra_code += ['stun.cpp', 'certificate.cpp', 'encryption.cpp',]
endif endif
libmist = library('mist', libmist = library('mist',

View file

@ -35,6 +35,8 @@ namespace SDP{
return "AAC"; return "AAC";
}else if (codec == "OPUS"){ }else if (codec == "OPUS"){
return "opus"; return "opus";
}else if (codec == "WEBRTC-DATACHANNEL"){
return "JSON";
}else if (codec == "ULPFEC"){ }else if (codec == "ULPFEC"){
return ""; return "";
}else if (codec == "RED"){ }else if (codec == "RED"){
@ -67,6 +69,10 @@ namespace SDP{
return "MPA"; return "MPA";
}else if (codec == "AAC"){ }else if (codec == "AAC"){
return "MPEG4-GENERIC"; return "MPEG4-GENERIC";
}else if (codec == "JSON"){
return "WEBRTC-DATACHANNEL";
}else if (codec == "subtitle"){
return "WEBRTC-DATACHANNEL";
}else if (codec == "opus"){ }else if (codec == "opus"){
return "OPUS"; return "OPUS";
}else if (codec == "ULPFEC"){ }else if (codec == "ULPFEC"){
@ -277,6 +283,8 @@ namespace SDP{
type = "audio"; type = "audio";
}else if (words[0] == "m=video"){ }else if (words[0] == "m=video"){
type = "video"; type = "video";
}else if (words[0] == "m=application"){
type = "meta";
}else{ }else{
ERROR_MSG("Unhandled media type: `%s`.", words[0].c_str()); ERROR_MSG("Unhandled media type: `%s`.", words[0].c_str());
return false; return false;
@ -289,6 +297,7 @@ namespace SDP{
for (size_t i = 3; i < words.size(); ++i){ for (size_t i = 3; i < words.size(); ++i){
SDP::MediaFormat format; SDP::MediaFormat format;
format.payloadType = JSON::Value(words[i]).asInt(); format.payloadType = JSON::Value(words[i]).asInt();
if (words[i] == "webrtc-datachannel"){format.encodingName = "WEBRTC-DATACHANNEL";}
formats[format.payloadType] = format; formats[format.payloadType] = format;
if (!payloadTypes.empty()){payloadTypes += " ";} if (!payloadTypes.empty()){payloadTypes += " ";}
payloadTypes += words[i]; payloadTypes += words[i];
@ -711,17 +720,11 @@ namespace SDP{
static bool sdp_get_name_value_from_varval(const std::string &str, std::string &var, std::string &value){ static bool sdp_get_name_value_from_varval(const std::string &str, std::string &var, std::string &value){
if (str.empty()){ if (str.empty()){
ERROR_MSG("Cannot get `name` and `value` from string because the given string is empty. "
"String is: `%s`",
str.c_str());
return false; return false;
} }
size_t pos = str.find("="); size_t pos = str.find("=");
if (pos == std::string::npos){ if (pos == std::string::npos){
WARN_MSG("Cannot get `name` and `value` from string becuase it doesn't contain a `=` sign. "
"String is: `%s`. Returning the string as is.",
str.c_str());
value = str; value = str;
return true; return true;
} }
@ -776,7 +779,7 @@ namespace SDP{
} }
Answer::Answer() Answer::Answer()
: isAudioEnabled(false), isVideoEnabled(false), candidatePort(0), : isAudioEnabled(false), isVideoEnabled(false), isMetaEnabled(false), candidatePort(0),
videoLossPrevention(SDP_LOSS_PREVENTION_NONE){} videoLossPrevention(SDP_LOSS_PREVENTION_NONE){}
bool Answer::parseOffer(const std::string &sdp){ bool Answer::parseOffer(const std::string &sdp){
@ -817,6 +820,15 @@ namespace SDP{
return true; return true;
} }
bool Answer::enableMeta(const std::string &codecName){
if (!enableMedia("meta", codecName, answerMetaMedia, answerMetaFormat)){
DONTEVEN_MSG("Not enabling meta.");
return false;
}
isMetaEnabled = true;
return true;
}
void Answer::setCandidate(const std::string &ip, uint16_t port){ void Answer::setCandidate(const std::string &ip, uint16_t port){
if (ip.empty()){WARN_MSG("Given candidate IP is empty. It's fine if you want to unset it.");} if (ip.empty()){WARN_MSG("Given candidate IP is empty. It's fine if you want to unset it.");}
candidateIP = ip; candidateIP = ip;
@ -934,7 +946,7 @@ namespace SDP{
bool isEnabled = false; bool isEnabled = false;
std::vector<uint8_t> supportedPayloadTypes; std::vector<uint8_t> supportedPayloadTypes;
if (type != "audio" && type != "video"){continue;} if (type != "audio" && type != "video" && type != "meta"){continue;}
// port = 9 (default), port = 0 (disable this media) // port = 9 (default), port = 0 (disable this media)
if (type == "audio"){ if (type == "audio"){
@ -947,6 +959,10 @@ namespace SDP{
fmtMedia = &answerVideoFormat; fmtMedia = &answerVideoFormat;
fmtRED = media->getFormatForEncodingName("RED"); fmtRED = media->getFormatForEncodingName("RED");
fmtULPFEC = media->getFormatForEncodingName("ULPFEC"); fmtULPFEC = media->getFormatForEncodingName("ULPFEC");
}else if (type == "meta"){
isEnabled = isMetaEnabled;
media = &answerMetaMedia;
fmtMedia = &answerMetaFormat;
} }
if (!media){ if (!media){
@ -975,10 +991,17 @@ namespace SDP{
} }
std::string payloadTypes = ss.str(); std::string payloadTypes = ss.str();
std::string protocol = "UDP/TLS/RTP/SAVPF";
if (type == "meta"){
protocol = "UDP/DTLS/SCTP";
payloadTypes = "webrtc-datachannel";
type = "application";
}
if (isEnabled){ if (isEnabled){
addLine("m=%s 9 UDP/TLS/RTP/SAVPF %s", type.c_str(), payloadTypes.c_str()); addLine("m=%s 9 %s %s", type.c_str(), protocol.c_str(), payloadTypes.c_str());
}else{ }else{
addLine("m=%s %u UDP/TLS/RTP/SAVPF %s", type.c_str(), 0, mediaOffer.payloadTypes.c_str()); addLine("m=%s %u %s %s", type.c_str(), 0, protocol.c_str(), mediaOffer.payloadTypes.c_str());
} }
addLine("c=IN IP4 0.0.0.0"); addLine("c=IN IP4 0.0.0.0");
@ -996,9 +1019,14 @@ namespace SDP{
addLine("a=fingerprint:sha-256 %s", fingerprint.c_str()); addLine("a=fingerprint:sha-256 %s", fingerprint.c_str());
addLine("a=ice-ufrag:%s", fmtMedia->iceUFrag.c_str()); addLine("a=ice-ufrag:%s", fmtMedia->iceUFrag.c_str());
addLine("a=ice-pwd:%s", fmtMedia->icePwd.c_str()); addLine("a=ice-pwd:%s", fmtMedia->icePwd.c_str());
addLine("a=rtcp-mux"); if (type == "application"){
addLine("a=rtcp-rsize"); addLine("a=sctp-port:5000");
addLine("a=%s", fmtMedia->rtpmap.c_str()); addLine("a=max-message-size:262144");
}else{
addLine("a=rtcp-mux");
addLine("a=rtcp-rsize");
addLine("a=%s", fmtMedia->rtpmap.c_str());
}
// BEGIN FEC/RTX: testing with just FEC or RTX // BEGIN FEC/RTX: testing with just FEC or RTX
if ((videoLossPrevention & SDP_LOSS_PREVENTION_ULPFEC) && fmtRED && fmtULPFEC){ if ((videoLossPrevention & SDP_LOSS_PREVENTION_ULPFEC) && fmtRED && fmtULPFEC){
@ -1136,14 +1164,11 @@ namespace SDP{
return false; return false;
} }
INFO_MSG("Enabling media for codec: %s", format->encodingName.c_str());
outMedia = *media; outMedia = *media;
outFormat = *format; outFormat = *format;
outFormat.rtcpFormats.clear(); outFormat.rtcpFormats.clear();
outFormat.icePwd = generateIcePwd(); outFormat.icePwd = generateIcePwd();
outFormat.iceUFrag = generateIceUFrag(); outFormat.iceUFrag = generateIceUFrag();
return true; return true;
} }

View file

@ -167,6 +167,7 @@ namespace SDP{
bool hasAudio(); ///< Check if the offer has audio. bool hasAudio(); ///< Check if the offer has audio.
bool enableVideo(const std::string &codecName); bool enableVideo(const std::string &codecName);
bool enableAudio(const std::string &codecName); bool enableAudio(const std::string &codecName);
bool enableMeta(const std::string &codecName);
void setCandidate(const std::string &ip, uint16_t port); void setCandidate(const std::string &ip, uint16_t port);
void setFingerprint(const std::string &fingerprintSha); ///< Set the SHA265 that represents the void setFingerprint(const std::string &fingerprintSha); ///< Set the SHA265 that represents the
///< certificate that is used with DTLS. ///< certificate that is used with DTLS.
@ -189,10 +190,13 @@ namespace SDP{
SDP::Session sdpOffer; SDP::Session sdpOffer;
SDP::Media answerVideoMedia; SDP::Media answerVideoMedia;
SDP::Media answerAudioMedia; SDP::Media answerAudioMedia;
SDP::Media answerMetaMedia;
SDP::MediaFormat answerVideoFormat; SDP::MediaFormat answerVideoFormat;
SDP::MediaFormat answerAudioFormat; SDP::MediaFormat answerAudioFormat;
SDP::MediaFormat answerMetaFormat;
bool isAudioEnabled; bool isAudioEnabled;
bool isVideoEnabled; bool isVideoEnabled;
bool isMetaEnabled;
std::string candidateIP; ///< We use rtcp-mux and BUNDLE; so only one candidate necessary. 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. uint16_t candidatePort; ///< We use rtcp-mux and BUNDLE; so only one candidate necessary.
std::string fingerprint; std::string fingerprint;

View file

@ -14,6 +14,7 @@
#include <sstream> #include <sstream>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fstream>
#define BUFFER_BLOCKSIZE 4096 // set buffer blocksize to 4KiB #define BUFFER_BLOCKSIZE 4096 // set buffer blocksize to 4KiB
@ -1603,35 +1604,197 @@ int Socket::Server::getSocket(){
return sock; return sock;
} }
static int dTLS_recv(void *s, unsigned char *buf, size_t len){
return ((Socket::UDPConnection*)s)->dTLSRead(buf, len);
}
static int dTLS_send(void *s, const unsigned char *buf, size_t len){
return ((Socket::UDPConnection*)s)->dTLSWrite(buf, len);
}
/// Create a new UDP Socket. /// Create a new UDP Socket.
/// Will attempt to create an IPv6 UDP socket, on fail try a IPV4 UDP socket. /// Will attempt to create an IPv6 UDP socket, on fail try a IPV4 UDP socket.
/// If both fail, prints an DLVL_FAIL debug message. /// If both fail, prints an DLVL_FAIL debug message.
/// \param nonblock Whether the socket should be nonblocking. /// \param nonblock Whether the socket should be nonblocking.
Socket::UDPConnection::UDPConnection(bool nonblock){ Socket::UDPConnection::UDPConnection(bool nonblock){
init(nonblock);
}// Socket::UDPConnection UDP Contructor
void Socket::UDPConnection::init(bool _nonblock, int _family){
lastPace = 0; lastPace = 0;
boundPort = 0; boundPort = 0;
family = AF_INET6; family = _family;
sock = socket(AF_INET6, SOCK_DGRAM, 0); hasDTLS = false;
if (sock == -1){ isConnected = false;
wasEncrypted = false;
pretendReceive = false;
sock = socket(family, SOCK_DGRAM, 0);
if (sock == -1 && family == AF_INET6){
sock = socket(AF_INET, SOCK_DGRAM, 0); sock = socket(AF_INET, SOCK_DGRAM, 0);
family = AF_INET; family = AF_INET;
} }
if (sock == -1){ if (sock == -1){
FAIL_MSG("Could not create UDP socket: %s", strerror(errno)); FAIL_MSG("Could not create UDP socket: %s", strerror(errno));
}else{ }else{
if (nonblock){setBlocking(!nonblock);} isBlocking = !_nonblock;
if (_nonblock){setBlocking(!_nonblock);}
checkRecvBuf(); checkRecvBuf();
} }
{
// Allow address re-use
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
}
up = 0; up = 0;
down = 0; down = 0;
destAddr = 0; destAddr = 0;
destAddr_size = 0; destAddr_size = 0;
recvAddr = 0;
recvAddr_size = 0;
hasReceiveData = false;
#ifdef __CYGWIN__ #ifdef __CYGWIN__
data.allocate(SOCKETSIZE); data.allocate(SOCKETSIZE);
#else #else
data.allocate(2048); data.allocate(2048);
#endif #endif
}// Socket::UDPConnection UDP Contructor }
void Socket::UDPConnection::initDTLS(mbedtls_x509_crt *cert, mbedtls_pk_context *key){
hasDTLS = true;
nextDTLSRead = 0;
nextDTLSReadLen = 0;
int r = 0;
char mbedtls_msg[1024];
// Null out the contexts before use
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));
// Initialize 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 (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
FAIL_MSG("dTLS could not init drbg seed: %s", mbedtls_msg);
return;
}
/* 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 (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
FAIL_MSG("dTLS could not set defaults: %s", mbedtls_msg);
return;
}
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_ca_chain(&ssl_conf, cert, NULL);
mbedtls_ssl_conf_cert_profile(&ssl_conf, &mbedtls_x509_crt_profile_default);
//mbedtls_ssl_conf_dbg(&ssl_conf, print_mbedtls_debug_message, stdout);
//mbedtls_debug_set_threshold(10);
// enable SRTP support (non-fatal on error)
mbedtls_ssl_srtp_profile srtpPro[] ={MBEDTLS_SRTP_AES128_CM_HMAC_SHA1_80, MBEDTLS_SRTP_AES128_CM_HMAC_SHA1_32};
r = mbedtls_ssl_conf_dtls_srtp_protection_profiles(&ssl_conf, srtpPro, sizeof(srtpPro) / sizeof(srtpPro[0]));
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
WARN_MSG("dTLS could not set SRTP profiles: %s", mbedtls_msg);
}
/* cert certificate chain + key, so we can verify the client-hello signed data */
r = mbedtls_ssl_conf_own_cert(&ssl_conf, cert, key);
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
FAIL_MSG("dTLS could not set certificate: %s", mbedtls_msg);
return;
}
// cookie setup (to prevent ddos, server-only)
r = mbedtls_ssl_cookie_setup(&cookie_ctx, mbedtls_ctr_drbg_random, &rand_ctx);
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
FAIL_MSG("dTLS could not set SSL cookie: %s", mbedtls_msg);
return;
}
mbedtls_ssl_conf_dtls_cookies(&ssl_conf, mbedtls_ssl_cookie_write, mbedtls_ssl_cookie_check, &cookie_ctx);
// setup the ssl context
r = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf);
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
FAIL_MSG("dTLS could not setup: %s", mbedtls_msg);
return;
}
// set input/output callbacks
mbedtls_ssl_set_bio(&ssl_ctx, (void *)this, dTLS_send, dTLS_recv, NULL);
mbedtls_ssl_set_timer_cb(&ssl_ctx, &timer_ctx, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
// set transport ID (non-fatal on error)
r = mbedtls_ssl_set_client_transport_id(&ssl_ctx, (const unsigned char *)"mist", 4);
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
WARN_MSG("dTLS could not set transport ID: %s", mbedtls_msg);
}
}
void Socket::UDPConnection::deinitDTLS(){
if (hasDTLS){
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);
hasDTLS = true;
}
}
int Socket::UDPConnection::dTLSRead(unsigned char *buf, size_t _len){
if (!nextDTLSReadLen){return MBEDTLS_ERR_SSL_WANT_READ;}
size_t len = _len;
if (len > nextDTLSReadLen){len = nextDTLSReadLen;}
memcpy(buf, nextDTLSRead, len);
nextDTLSReadLen = 0;
return len;
}
int Socket::UDPConnection::dTLSWrite(const unsigned char *buf, size_t len){
sendPaced((const char *)buf, len, false);
return len;
}
void Socket::UDPConnection::dTLSReset(){
char mbedtls_msg[1024];
int r = mbedtls_ssl_session_reset(&ssl_ctx);
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
FAIL_MSG("dTLS could not reset session: %s", mbedtls_msg);
return;
}
// set transport ID (non-fatal on error)
r = mbedtls_ssl_set_client_transport_id(&ssl_ctx, (const unsigned char *)"mist", 4);
if (r){
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
WARN_MSG("dTLS could not set transport ID: %s", mbedtls_msg);
}
}
///Checks if the UDP receive buffer is at least 1 mbyte, attempts to increase and warns user through log message on failure. ///Checks if the UDP receive buffer is at least 1 mbyte, attempts to increase and warns user through log message on failure.
void Socket::UDPConnection::checkRecvBuf(){ void Socket::UDPConnection::checkRecvBuf(){
@ -1681,27 +1844,23 @@ void Socket::UDPConnection::checkRecvBuf(){
/// Copies a UDP socket, re-allocating local copies of any needed structures. /// Copies a UDP socket, re-allocating local copies of any needed structures.
/// The data/data_size/data_len variables are *not* copied over. /// The data/data_size/data_len variables are *not* copied over.
Socket::UDPConnection::UDPConnection(const UDPConnection &o){ Socket::UDPConnection::UDPConnection(const UDPConnection &o){
lastPace = 0; init(!o.isBlocking, o.family);
boundPort = 0; INFO_MSG("Copied socket of type %s", addrFam(o.family));
family = AF_INET6;
sock = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock == -1){
sock = socket(AF_INET, SOCK_DGRAM, 0);
family = AF_INET;
}
if (sock == -1){FAIL_MSG("Could not create UDP socket: %s", strerror(errno));}
checkRecvBuf();
up = 0;
down = 0;
if (o.destAddr && o.destAddr_size){ if (o.destAddr && o.destAddr_size){
destAddr = malloc(o.destAddr_size); destAddr = malloc(o.destAddr_size);
destAddr_size = o.destAddr_size; destAddr_size = o.destAddr_size;
if (destAddr){memcpy(destAddr, o.destAddr, o.destAddr_size);} if (destAddr){memcpy(destAddr, o.destAddr, o.destAddr_size);}
}else{
destAddr = 0;
destAddr_size = 0;
} }
data.allocate(2048); if (o.recvAddr && o.recvAddr_size){
recvAddr = malloc(o.recvAddr_size);
recvAddr_size = o.recvAddr_size;
if (recvAddr){memcpy(recvAddr, o.recvAddr, o.recvAddr_size);}
}
if (o.data.size()){
data.assign(o.data, o.data.size());
pretendReceive = true;
}
hasReceiveData = o.hasReceiveData;
} }
/// Close the UDP socket /// Close the UDP socket
@ -1720,8 +1879,35 @@ Socket::UDPConnection::~UDPConnection(){
free(destAddr); free(destAddr);
destAddr = 0; destAddr = 0;
} }
if (recvAddr){
free(recvAddr);
recvAddr = 0;
}
deinitDTLS();
} }
bool Socket::UDPConnection::operator==(const Socket::UDPConnection& b) const{
// UDP sockets are equal if they refer to the same underlying socket or are both closed
if (sock == b.sock){return true;}
// If either is closed (and the other is not), not equal.
if (sock == -1 || b.sock == -1){return false;}
size_t recvSize = recvAddr_size;
if (b.recvAddr_size < recvSize){recvSize = b.recvAddr_size;}
size_t destSize = destAddr_size;
if (b.destAddr_size < destSize){destSize = b.destAddr_size;}
// They are equal if they hold the same local and remote address.
if (recvSize && destSize && destAddr && b.destAddr && recvAddr && b.recvAddr){
if (!memcmp(recvAddr, b.recvAddr, recvSize) && !memcmp(destAddr, b.destAddr, destSize)){
return true;
}
}
// All other cases, not equal
return false;
}
Socket::UDPConnection::operator bool() const{return sock != -1;}
// Sets socket family type (to IPV4 or IPV6) (AF_INET=2, AF_INET6=10) // Sets socket family type (to IPV4 or IPV6) (AF_INET=2, AF_INET6=10)
void Socket::UDPConnection::setSocketFamily(int AF_TYPE){\ void Socket::UDPConnection::setSocketFamily(int AF_TYPE){\
INFO_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(AF_TYPE)); INFO_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(AF_TYPE));
@ -1742,6 +1928,22 @@ void Socket::UDPConnection::allocateDestination(){
((struct sockaddr_in *)destAddr)->sin_family = AF_UNSPEC; ((struct sockaddr_in *)destAddr)->sin_family = AF_UNSPEC;
} }
} }
if (recvAddr && recvAddr_size < sizeof(sockaddr_in6)){
free(recvAddr);
recvAddr = 0;
}
if (!recvAddr){
recvAddr = malloc(sizeof(sockaddr_in6));
if (recvAddr){
recvAddr_size = sizeof(sockaddr_in6);
memset(recvAddr, 0, sizeof(sockaddr_in6));
((struct sockaddr_in *)recvAddr)->sin_family = AF_UNSPEC;
}
const int opt = 1;
if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))){
WARN_MSG("Could not set PKTINFO to 1!");
}
}
} }
/// Stores the properties of the receiving end of this UDP socket. /// Stores the properties of the receiving end of this UDP socket.
@ -1788,6 +1990,11 @@ void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port){
close(); close();
family = rp->ai_family; family = rp->ai_family;
sock = socket(family, SOCK_DGRAM, 0); sock = socket(family, SOCK_DGRAM, 0);
{
// Allow address re-use
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
}
checkRecvBuf(); checkRecvBuf();
if (boundPort){ if (boundPort){
INFO_MSG("Rebinding to %s:%d %s", boundAddr.c_str(), boundPort, boundMulti.c_str()); INFO_MSG("Rebinding to %s:%d %s", boundAddr.c_str(), boundPort, boundMulti.c_str());
@ -1839,6 +2046,35 @@ void Socket::UDPConnection::GetDestination(std::string &destIp, uint32_t &port){
FAIL_MSG("Could not get destination for UDP socket"); FAIL_MSG("Could not get destination for UDP socket");
}// Socket::UDPConnection GetDestination }// Socket::UDPConnection GetDestination
/// Gets the properties of the receiving end of the local UDP socket.
/// This will be the sending end for all SendNow calls.
void Socket::UDPConnection::GetLocalDestination(std::string &destIp, uint32_t &port){
if (!recvAddr || !recvAddr_size){
destIp = "";
port = 0;
return;
}
char addr_str[INET6_ADDRSTRLEN + 1];
addr_str[INET6_ADDRSTRLEN] = 0; // set last byte to zero, to prevent walking out of the array
if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET6){
if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)recvAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in6 *)recvAddr)->sin6_port);
return;
}
}
if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET){
if (inet_ntop(AF_INET, &(((struct sockaddr_in *)recvAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in *)recvAddr)->sin_port);
return;
}
}
destIp = "";
port = 0;
FAIL_MSG("Could not get destination for UDP socket");
}// Socket::UDPConnection GetDestination
/// Gets the properties of the receiving end of this UDP socket. /// Gets the properties of the receiving end of this UDP socket.
/// This will be the receiving end for all SendNow calls. /// This will be the receiving end for all SendNow calls.
std::string Socket::UDPConnection::getBinDestination(){ std::string Socket::UDPConnection::getBinDestination(){
@ -1864,7 +2100,10 @@ uint32_t Socket::UDPConnection::getDestPort() const{
/// Sets the socket to be blocking if the parameters is true. /// Sets the socket to be blocking if the parameters is true.
/// Sets the socket to be non-blocking otherwise. /// Sets the socket to be non-blocking otherwise.
void Socket::UDPConnection::setBlocking(bool blocking){ void Socket::UDPConnection::setBlocking(bool blocking){
if (sock >= 0){setFDBlocking(sock, blocking);} if (sock >= 0){
setFDBlocking(sock, blocking);
isBlocking = blocking;
}
} }
/// Sends a UDP datagram using the buffer sdata. /// Sends a UDP datagram using the buffer sdata.
@ -1885,64 +2124,146 @@ void Socket::UDPConnection::SendNow(const char *sdata){
/// Does not do anything if len < 1. /// Does not do anything if len < 1.
/// Prints an DLVL_FAIL level debug message if sending failed. /// Prints an DLVL_FAIL level debug message if sending failed.
void Socket::UDPConnection::SendNow(const char *sdata, size_t len){ void Socket::UDPConnection::SendNow(const char *sdata, size_t len){
if (len < 1){return;} SendNow(sdata, len, (sockaddr*)destAddr, destAddr_size);
int r = sendto(sock, sdata, len, 0, (sockaddr *)destAddr, destAddr_size); }
if (r > 0){
up += r; /// Sends a UDP datagram using the buffer sdata of length len.
/// Does not do anything if len < 1.
/// Prints an DLVL_FAIL level debug message if sending failed.
void Socket::UDPConnection::SendNow(const char *sdata, size_t len, sockaddr * dAddr, size_t dAddrLen){
if (len < 1 || sock == -1){return;}
if (isConnected){
int r = send(sock, sdata, len, 0);
if (r > 0){
up += r;
}else{
if (errno == EDESTADDRREQ){
close();
return;
}
FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno));
}
return;
}
if (hasReceiveData && recvAddr){
msghdr mHdr;
char msg_control[0x100];
iovec iovec;
iovec.iov_base = (void*)sdata;
iovec.iov_len = len;
mHdr.msg_name = dAddr;
mHdr.msg_namelen = dAddrLen;
mHdr.msg_iov = &iovec;
mHdr.msg_iovlen = 1;
mHdr.msg_control = msg_control;
mHdr.msg_controllen = sizeof(msg_control);
mHdr.msg_flags = 0;
int cmsg_space = 0;
cmsghdr * cmsg = CMSG_FIRSTHDR(&mHdr);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
struct in_pktinfo in_pktinfo;
memcpy(&(in_pktinfo.ipi_spec_dst), &(((sockaddr_in*)recvAddr)->sin_family), sizeof(in_pktinfo.ipi_spec_dst));
in_pktinfo.ipi_ifindex = recvInterface;
cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
*(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo;
cmsg_space += CMSG_SPACE(sizeof(in_pktinfo));
mHdr.msg_controllen = cmsg_space;
int r = sendmsg(sock, &mHdr, 0);
if (r > 0){
up += r;
}else{
FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno));
}
return;
}else{ }else{
FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno)); int r = sendto(sock, sdata, len, 0, dAddr, dAddrLen);
if (r > 0){
up += r;
}else{
FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno));
}
} }
} }
/// Queues sdata, len for sending over this socket. /// Queues sdata, len for sending over this socket.
/// If there has been enough time since the last packet, sends immediately. /// If there has been enough time since the last packet, sends immediately.
/// Warning: never call sendPaced for the same socket from a different thread! /// Warning: never call sendPaced for the same socket from a different thread!
void Socket::UDPConnection::sendPaced(const char *sdata, size_t len){ /// Note: Only actually encrypts if initDTLS was called in the past.
if (!paceQueue.size() && (!lastPace || Util::getMicros(lastPace) > 10000)){ void Socket::UDPConnection::sendPaced(const char *sdata, size_t len, bool encrypt){
SendNow(sdata, len); if (hasDTLS && encrypt){
lastPace = Util::getMicros(); if (ssl_ctx.state != MBEDTLS_SSL_HANDSHAKE_OVER){
WARN_MSG("Attempting to write encrypted data before handshake completed! Data was thrown away.");
return;
}
int write = mbedtls_ssl_write(&ssl_ctx, (unsigned char*)sdata, len);
if (write <= 0){WARN_MSG("Could not write DTLS packet!");}
}else{ }else{
paceQueue.push_back(Util::ResizeablePointer()); if (!paceQueue.size() && (!lastPace || Util::getMicros(lastPace) > 10000)){
paceQueue.back().assign(sdata, len); SendNow(sdata, len);
// Try to send a packet, if time allows lastPace = Util::getMicros();
//sendPaced(0); }else{
paceQueue.push_back(Util::ResizeablePointer());
paceQueue.back().assign(sdata, len);
// Try to send a packet, if time allows
//sendPaced(0);
}
} }
} }
// Gets time in microseconds until next sendPaced call would send something
size_t Socket::UDPConnection::timeToNextPace(uint64_t uTime){
size_t qSize = paceQueue.size();
if (!qSize){return std::string::npos;} // No queue? No time. Return highest possible value.
if (!uTime){uTime = Util::getMicros();}
uint64_t paceWait = uTime - lastPace; // Time we've waited so far already
// Target clearing the queue in 25ms at most.
uint64_t targetTime = 25000 / qSize;
// If this slows us to below 1 packet per 5ms, go that speed instead.
if (targetTime > 5000){targetTime = 5000;}
// If the wait is over, send now.
if (paceWait >= targetTime){return 0;}
// Return remaining wait time
return targetTime - paceWait;
}
/// Spends uSendWindow microseconds either sending paced packets or sleeping, whichever is more appropriate /// Spends uSendWindow microseconds either sending paced packets or sleeping, whichever is more appropriate
/// Warning: never call sendPaced for the same socket from a different thread! /// Warning: never call sendPaced for the same socket from a different thread!
void Socket::UDPConnection::sendPaced(uint64_t uSendWindow){ void Socket::UDPConnection::sendPaced(uint64_t uSendWindow){
uint64_t currPace = Util::getMicros(); uint64_t currPace = Util::getMicros();
uint64_t uTime = currPace;
do{ do{
uint64_t uTime = Util::getMicros(); uint64_t sleepTime = uSendWindow - (uTime - currPace);
uint64_t sleepTime = uTime - currPace; uint64_t nextPace = timeToNextPace(uTime);
if (sleepTime > uSendWindow){ if (sleepTime > nextPace){sleepTime = nextPace;}
sleepTime = 0;
}else{ // Not sleeping? Send now!
sleepTime = uSendWindow - sleepTime; if (!sleepTime){
}
uint64_t paceWait = uTime - lastPace;
size_t qSize = paceQueue.size();
// If the queue is complete, wait out the remainder of the time
if (!qSize){
Util::usleep(sleepTime);
return;
}
// Otherwise, target clearing the queue in 25ms at most.
uint64_t targetTime = 25000 / qSize;
// If this slows us to below 1 packet per 5ms, go that speed instead.
if (targetTime > 5000){targetTime = 5000;}
// If the wait is over, send now.
if (paceWait >= targetTime){
SendNow(*paceQueue.begin(), paceQueue.begin()->size()); SendNow(*paceQueue.begin(), paceQueue.begin()->size());
paceQueue.pop_front(); paceQueue.pop_front();
lastPace = uTime; lastPace = uTime;
continue; continue;
} }
// Otherwise, wait for the smaller of remaining wait time or remaining send window time.
if (targetTime - paceWait < sleepTime){sleepTime = targetTime - paceWait;} {
Util::usleep(sleepTime); // Use select to wait until a packet arrives or until the next packet should be sent
}while(Util::getMicros(currPace) < uSendWindow); fd_set rfds;
struct timeval T;
T.tv_sec = sleepTime / 1000000;
T.tv_usec = sleepTime % 1000000;
// Watch configured FD's for input
FD_ZERO(&rfds);
int maxFD = getSock();
FD_SET(maxFD, &rfds);
int r = select(maxFD + 1, &rfds, NULL, NULL, &T);
// If we can read the socket, immediately return and stop waiting
if (r > 0){return;}
}
uTime = Util::getMicros();
}while(uTime - currPace < uSendWindow);
} }
std::string Socket::UDPConnection::getBoundAddress(){ std::string Socket::UDPConnection::getBoundAddress(){
@ -1995,6 +2316,11 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str
for (rp = addr_result; rp != NULL; rp = rp->ai_next){ for (rp = addr_result; rp != NULL; rp = rp->ai_next){
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock == -1){continue;} if (sock == -1){continue;}
{
// Allow address re-use
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
}
if (rp->ai_family == AF_INET6){ if (rp->ai_family == AF_INET6){
const int optval = 0; const int optval = 0;
if (setsockopt(sock, SOL_SOCKET, IPV6_V6ONLY, &optval, sizeof(optval)) < 0){ if (setsockopt(sock, SOL_SOCKET, IPV6_V6ONLY, &optval, sizeof(optval)) < 0){
@ -2046,7 +2372,7 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str
boundAddr = iface; boundAddr = iface;
boundMulti = multicastInterfaces; boundMulti = multicastInterfaces;
boundPort = portNo; boundPort = portNo;
INFO_MSG("UDP bind success on %s:%u (%s)", human_addr, portNo, addrFam(rp->ai_family)); INFO_MSG("UDP bind success %d on %s:%u (%s)", sock, human_addr, portNo, addrFam(rp->ai_family));
break; break;
} }
if (err_str.size()){err_str += ", ";} if (err_str.size()){err_str += ", ";}
@ -2144,21 +2470,135 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str
return portNo; return portNo;
} }
bool Socket::UDPConnection::connect(){
if (!recvAddr || !recvAddr_size || !destAddr || !destAddr_size){
WARN_MSG("Attempting to connect a UDP socket without local and/or remote address!");
return false;
}
{
std::string destIp;
uint32_t port = 0;
char addr_str[INET6_ADDRSTRLEN + 1];
if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET6){
if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)recvAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in6 *)recvAddr)->sin6_port);
}
}
if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET){
if (inet_ntop(AF_INET, &(((struct sockaddr_in *)recvAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in *)recvAddr)->sin_port);
}
}
int ret = ::bind(sock, (const struct sockaddr*)recvAddr, recvAddr_size);
if (!ret){
INFO_MSG("Bound socket %d to %s:%" PRIu32, sock, destIp.c_str(), port);
}else{
FAIL_MSG("Failed to bind socket %d (%s) %s:%" PRIu32 ": %s", sock, addrFam(((struct sockaddr_in *)recvAddr)->sin_family), destIp.c_str(), port, strerror(errno));
std::ofstream bleh("/tmp/socket_recv");
bleh.write((const char*)recvAddr, recvAddr_size);
bleh.write((const char*)destAddr, destAddr_size);
bleh.close();
return false;
}
}
{
std::string destIp;
uint32_t port;
char addr_str[INET6_ADDRSTRLEN + 1];
if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6){
if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port);
}
}
if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET){
if (inet_ntop(AF_INET, &(((struct sockaddr_in *)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){
destIp = addr_str;
port = ntohs(((struct sockaddr_in *)destAddr)->sin_port);
}
}
int ret = ::connect(sock, (const struct sockaddr*)destAddr, destAddr_size);
if (!ret){
INFO_MSG("Connected socket to %s:%" PRIu32, destIp.c_str(), port);
}else{
FAIL_MSG("Failed to connect socket to %s:%" PRIu32 ": %s", destIp.c_str(), port, strerror(errno));
return false;
}
}
isConnected = true;
return true;
}
/// Attempt to receive a UDP packet. /// Attempt to receive a UDP packet.
/// This will automatically allocate or resize the internal data buffer if needed. /// This will automatically allocate or resize the internal data buffer if needed.
/// If a packet is received, it will be placed in the "data" member, with it's length in "data_len". /// If a packet is received, it will be placed in the "data" member, with it's length in "data_len".
/// \return True if a packet was received, false otherwise. /// \return True if a packet was received, false otherwise.
bool Socket::UDPConnection::Receive(){ bool Socket::UDPConnection::Receive(){
if (pretendReceive){
pretendReceive = false;
return onData();
}
if (sock == -1){return false;} if (sock == -1){return false;}
data.truncate(0); data.truncate(0);
if (isConnected){
int r = recv(sock, data, data.rsize(), MSG_TRUNC | MSG_DONTWAIT);
if (r == -1){
if (errno != EAGAIN){
INFO_MSG("UDP receive: %d (%s)", errno, strerror(errno));
if (errno == ECONNREFUSED){close();}
}
return false;
}
if (r > 0){
data.append(0, r);
down += r;
if (data.rsize() < (unsigned int)r){
INFO_MSG("Doubling UDP socket buffer from %" PRIu32 " to %" PRIu32, data.rsize(), data.rsize()*2);
data.allocate(data.rsize()*2);
}
return onData();
}
return false;
}
sockaddr_in6 addr; sockaddr_in6 addr;
socklen_t destsize = sizeof(addr); socklen_t destsize = sizeof(addr);
int r = recvfrom(sock, data, data.rsize(), MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&addr, &destsize); //int r = recvfrom(sock, data, data.rsize(), MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&addr, &destsize);
msghdr mHdr;
memset(&mHdr, 0, sizeof(mHdr));
char ctrl[0x100];
iovec dBufs;
dBufs.iov_base = data;
dBufs.iov_len = data.rsize();
mHdr.msg_name = &addr;
mHdr.msg_namelen = destsize;
mHdr.msg_control = ctrl;
mHdr.msg_controllen = 0x100;
mHdr.msg_iov = &dBufs;
mHdr.msg_iovlen = 1;
int r = recvmsg(sock, &mHdr, MSG_TRUNC | MSG_DONTWAIT);
destsize = mHdr.msg_namelen;
if (r == -1){ if (r == -1){
if (errno != EAGAIN){INFO_MSG("UDP receive: %d (%s)", errno, strerror(errno));} if (errno != EAGAIN){INFO_MSG("UDP receive: %d (%s)", errno, strerror(errno));}
return false; return false;
} }
if (destAddr && destsize && destAddr_size >= destsize){memcpy(destAddr, &addr, destsize);} if (destAddr && destsize && destAddr_size >= destsize){memcpy(destAddr, &addr, destsize);}
if (recvAddr){
for ( struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mHdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&mHdr, cmsg)){
if (cmsg->cmsg_level != IPPROTO_IP || cmsg->cmsg_type != IP_PKTINFO){continue;}
struct in_pktinfo* pi = (in_pktinfo*)CMSG_DATA(cmsg);
struct sockaddr_in * recvCast = (sockaddr_in*)recvAddr;
recvCast->sin_family = family;
recvCast->sin_port = htons(boundPort);
memcpy(&(recvCast->sin_addr), &(pi->ipi_spec_dst), sizeof(pi->ipi_spec_dst));
recvInterface = pi->ipi_ifindex;
hasReceiveData = true;
}
}
data.append(0, r); data.append(0, r);
down += r; down += r;
//Handle UDP packets that are too large //Handle UDP packets that are too large
@ -2166,7 +2606,88 @@ bool Socket::UDPConnection::Receive(){
INFO_MSG("Doubling UDP socket buffer from %" PRIu32 " to %" PRIu32, data.rsize(), data.rsize()*2); INFO_MSG("Doubling UDP socket buffer from %" PRIu32 " to %" PRIu32, data.rsize(), data.rsize()*2);
data.allocate(data.rsize()*2); data.allocate(data.rsize()*2);
} }
return (r > 0); return onData();
}
bool Socket::UDPConnection::onData(){
wasEncrypted = false;
if (!data.size()){return false;}
uint8_t fb = 0;
int r = data.size();
if (r){fb = (uint8_t)data[0];}
if (r && hasDTLS && fb > 19 && fb < 64){
if (nextDTLSReadLen){
INFO_MSG("Overwriting %zu bytes of unread dTLS data!", nextDTLSReadLen);
}
nextDTLSRead = data;
nextDTLSReadLen = data.size();
// Complete dTLS handshake if needed
if (ssl_ctx.state != MBEDTLS_SSL_HANDSHAKE_OVER){
do{
r = mbedtls_ssl_handshake(&ssl_ctx);
switch (r){
case 0:{ // Handshake complete
INFO_MSG("dTLS handshake complete!");
int extrRes = 0;
uint8_t keying_material[MBEDTLS_DTLS_SRTP_MAX_KEY_MATERIAL_LENGTH];
size_t keying_material_len = sizeof(keying_material);
extrRes = mbedtls_ssl_get_dtls_srtp_key_material(&ssl_ctx, keying_material, &keying_material_len);
if (extrRes){
char mbedtls_msg[1024];
mbedtls_strerror(extrRes, mbedtls_msg, sizeof(mbedtls_msg));
WARN_MSG("dTLS could not extract keying material: %s", mbedtls_msg);
return Receive();
}
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:{
WARN_MSG("Unhandled SRTP profile, cannot extract keying material.");
return Receive();
}
}
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);
return Receive(); // No application-level data to read
}
case MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED:{
dTLSReset();
return Receive(); // No application-level data to read
}
case MBEDTLS_ERR_SSL_WANT_READ:{
return Receive(); // No application-level data to read
}
default:{
char mbedtls_msg[1024];
mbedtls_strerror(r, mbedtls_msg, sizeof(mbedtls_msg));
WARN_MSG("dTLS could not handshake: %s", mbedtls_msg);
return Receive(); // No application-level data to read
}
}
}while (r == MBEDTLS_ERR_SSL_WANT_WRITE);
}else{
int read = mbedtls_ssl_read(&ssl_ctx, (unsigned char *)(char*)data, data.size());
if (read <= 0){
// Non-encrypted read (encrypted read fail)
return true;
}
// Encrypted read success
wasEncrypted = true;
data.truncate(read);
return true;
}
}
return r > 0;
} }
int Socket::UDPConnection::getSock(){ int Socket::UDPConnection::getSock(){

View file

@ -18,12 +18,14 @@
#include "util.h" #include "util.h"
#ifdef SSL #ifdef SSL
#include "mbedtls/ctr_drbg.h" #include <mbedtls/ctr_drbg.h>
#include "mbedtls/debug.h" #include <mbedtls/debug.h>
#include "mbedtls/entropy.h" #include <mbedtls/entropy.h>
#include "mbedtls/error.h" #include <mbedtls/error.h>
#include "mbedtls/net.h" #include <mbedtls/net.h>
#include "mbedtls/ssl.h" #include <mbedtls/ssl.h>
#include <mbedtls/ssl_cookie.h>
#include <mbedtls/timing.h>
#endif #endif
#include "util.h" #include "util.h"
@ -196,10 +198,13 @@ namespace Socket{
class UDPConnection{ class UDPConnection{
private: private:
void init(bool nonblock, int family = AF_INET6);
int sock; ///< Internally saved socket number. int sock; ///< Internally saved socket number.
std::string remotehost; ///< Stores remote host address std::string remotehost; ///< Stores remote host address
void *destAddr; ///< Destination address pointer. void *destAddr; ///< Destination address pointer.
unsigned int destAddr_size; ///< Size of the destination address pointer. unsigned int destAddr_size; ///< Size of the destination address pointer.
void *recvAddr; ///< Destination address pointer.
unsigned int recvAddr_size; ///< Size of the destination address pointer.
unsigned int up; ///< Amount of bytes transferred up. unsigned int up; ///< Amount of bytes transferred up.
unsigned int down; ///< Amount of bytes transferred down. unsigned int down; ///< Amount of bytes transferred down.
int family; ///< Current socket address family int family; ///< Current socket address family
@ -208,19 +213,46 @@ namespace Socket{
void checkRecvBuf(); void checkRecvBuf();
std::deque<Util::ResizeablePointer> paceQueue; std::deque<Util::ResizeablePointer> paceQueue;
uint64_t lastPace; uint64_t lastPace;
int recvInterface;
bool hasReceiveData;
bool isBlocking;
bool isConnected;
bool pretendReceive; ///< If true, will pretend to have just received the current data buffer on new Receive() call
bool onData();
// dTLS-related members
bool hasDTLS; ///< True if dTLS is enabled
void * nextDTLSRead;
size_t nextDTLSReadLen;
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: public:
Util::ResizeablePointer data; Util::ResizeablePointer data;
UDPConnection(const UDPConnection &o); UDPConnection(const UDPConnection &o);
UDPConnection(bool nonblock = false); UDPConnection(bool nonblock = false);
~UDPConnection(); ~UDPConnection();
bool operator==(const UDPConnection& b) const;
operator bool() const;
void initDTLS(mbedtls_x509_crt *cert, mbedtls_pk_context *key);
void deinitDTLS();
int dTLSRead(unsigned char *buf, size_t len);
int dTLSWrite(const unsigned char *buf, size_t len);
void dTLSReset();
bool wasEncrypted;
void close(); void close();
int getSock(); int getSock();
uint16_t bind(int port, std::string iface = "", const std::string &multicastAddress = ""); uint16_t bind(int port, std::string iface = "", const std::string &multicastAddress = "");
bool connect();
void setBlocking(bool blocking); void setBlocking(bool blocking);
void allocateDestination(); void allocateDestination();
void SetDestination(std::string hostname, uint32_t port); void SetDestination(std::string hostname, uint32_t port);
void GetDestination(std::string &hostname, uint32_t &port); void GetDestination(std::string &hostname, uint32_t &port);
void GetLocalDestination(std::string &hostname, uint32_t &port);
std::string getBinDestination(); std::string getBinDestination();
const void * getDestAddr(){return destAddr;} const void * getDestAddr(){return destAddr;}
size_t getDestAddrLen(){return destAddr_size;} size_t getDestAddrLen(){return destAddr_size;}
@ -230,8 +262,13 @@ namespace Socket{
void SendNow(const std::string &data); void SendNow(const std::string &data);
void SendNow(const char *data); void SendNow(const char *data);
void SendNow(const char *data, size_t len); void SendNow(const char *data, size_t len);
void sendPaced(const char * data, size_t len); void SendNow(const char *sdata, size_t len, sockaddr * dAddr, size_t dAddrLen);
void sendPaced(const char * data, size_t len, bool encrypt = true);
void sendPaced(uint64_t uSendWindow); void sendPaced(uint64_t uSendWindow);
size_t timeToNextPace(uint64_t uTime = 0);
void setSocketFamily(int AF_TYPE); void setSocketFamily(int AF_TYPE);
// dTLS-related public members
std::string cipher, remote_key, local_key, remote_salt, local_salt;
}; };
}// namespace Socket }// namespace Socket

View file

@ -129,6 +129,8 @@ if usessl
mist_deps += [mbedtls, mbedx509, mbedcrypto] mist_deps += [mbedtls, mbedx509, mbedcrypto]
mist_deps += dependency('libsrtp2', default_options: ['tests=disabled'], fallback: ['libsrtp2', 'libsrtp2_dep']) mist_deps += dependency('libsrtp2', default_options: ['tests=disabled'], fallback: ['libsrtp2', 'libsrtp2_dep'])
usrsctp_dep = dependency('usrsctp', fallback: ['usrsctp', 'usrsctp_dep'])
endif endif
libsrt = false libsrt = false

View file

@ -91,6 +91,7 @@ foreach output : outputs
endif endif
if extra.contains('srtp') if extra.contains('srtp')
sources += files('output_webrtc_srtp.cpp', 'output_webrtc_srtp.h') sources += files('output_webrtc_srtp.cpp', 'output_webrtc_srtp.h')
deps += usrsctp_dep
endif endif
if extra.contains('embed') if extra.contains('embed')
sources += embed_tgts sources += embed_tgts

View file

@ -735,18 +735,22 @@ namespace Mist{
std::set<size_t> validTracks = M.getValidTracks(); std::set<size_t> validTracks = M.getValidTracks();
if (!validTracks.size()){return 0;} if (!validTracks.size()){return 0;}
uint64_t start = 0xFFFFFFFFFFFFFFFFull; uint64_t start = 0xFFFFFFFFFFFFFFFFull;
uint64_t nonMetaStart = 0xFFFFFFFFFFFFFFFFull;
if (userSelect.size()){ if (userSelect.size()){
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (M.trackValid(it->first) && start > M.getFirstms(it->first)){ if (M.trackValid(it->first) && start > M.getFirstms(it->first)){
start = M.getFirstms(it->first); start = M.getFirstms(it->first);
} }
if (M.trackValid(it->first) && M.getType(it->first) != "meta" && nonMetaStart > M.getFirstms(it->first)){
nonMetaStart = M.getFirstms(it->first);
}
} }
}else{ }else{
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){ for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (start > M.getFirstms(*it)){start = M.getFirstms(*it);} if (start > M.getFirstms(*it)){start = M.getFirstms(*it);}
} }
} }
return start; return nonMetaStart != 0xFFFFFFFFFFFFFFFFull ? nonMetaStart: start;
} }
/// Return the end time of the selected tracks, or 0 if unknown or live. /// Return the end time of the selected tracks, or 0 if unknown or live.

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,6 @@
#include "output.h" #include "output.h"
#include "output_http.h" #include "output_http.h"
#include <mist/certificate.h> #include <mist/certificate.h>
#include <mist/dtls_srtp_handshake.h>
#include <mist/h264.h> #include <mist/h264.h>
#include <mist/http_parser.h> #include <mist/http_parser.h>
#include <mist/rtp_fec.h> #include <mist/rtp_fec.h>
@ -14,6 +13,7 @@
#include <mist/websocket.h> #include <mist/websocket.h>
#include <fstream> #include <fstream>
#include "output_webrtc_srtp.h" #include "output_webrtc_srtp.h"
#include <usrsctp.h>
#define NACK_BUFFER_SIZE 1024 #define NACK_BUFFER_SIZE 1024
@ -67,7 +67,19 @@ namespace Mist{
double jitter; double jitter;
}; };
/* ------------------------------------------------ */ class WebRTCSocket{
public:
WebRTCSocket();
Socket::UDPConnection* udpSock;
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.
std::map<uint32_t, nackBuffer> outBuffers;
size_t sendRTCP(const char * data, size_t len);
size_t ackNACK(uint32_t pSSRC, uint16_t seq);
Util::ResizeablePointer dataBuffer;
};
class OutWebRTC : public HTTPOutput{ class OutWebRTC : public HTTPOutput{
public: public:
@ -82,10 +94,13 @@ namespace Mist{
virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason); virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason);
void handleWebsocketIdle(); void handleWebsocketIdle();
virtual void onFail(const std::string &msg, bool critical = false); virtual void onFail(const std::string &msg, bool critical = false);
bool onFinish();
bool doesWebsockets(){return true;} bool doesWebsockets(){return true;}
void handleWebRTCInputOutputFromThread(); void handleWebRTCInputOutputFromThread();
int onDTLSHandshakeWantsToWrite(const uint8_t *data, int *nbytes); bool handleUDPSocket(Socket::UDPConnection & sock);
bool handleUDPSocket(WebRTCSocket & wSock);
void sendSCTPPacket(const char * data, size_t len);
void sendPaced(uint64_t uSendWindow);
void onSCTP(const char * data, size_t len, uint16_t stream, uint32_t ppid);
void onRTPSorterHasPacket(size_t tid, const RTP::Packet &pkt); void onRTPSorterHasPacket(size_t tid, const RTP::Packet &pkt);
void onDTSCConverterHasPacket(const DTSC::Packet &pkt); void onDTSCConverterHasPacket(const DTSC::Packet &pkt);
void onDTSCConverterHasInitData(const size_t trackID, const std::string &initData); void onDTSCConverterHasInitData(const size_t trackID, const std::string &initData);
@ -95,7 +110,7 @@ namespace Mist{
inline virtual bool keepGoing(){return config->is_active && (noSignalling || myConn);} inline virtual bool keepGoing(){return config->is_active && (noSignalling || myConn);}
virtual void requestHandler(); virtual void requestHandler();
protected: protected:
virtual void idleTime(uint64_t ms){udp.sendPaced(ms*1000);} virtual void idleTime(uint64_t ms){sendPaced(ms*1000);}
private: private:
bool noSignalling; bool noSignalling;
uint64_t lastRecv; uint64_t lastRecv;
@ -109,9 +124,8 @@ namespace Mist{
void ackNACK(uint32_t SSRC, uint16_t seq); void ackNACK(uint32_t SSRC, uint16_t seq);
bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read
///< some data, othewise false. ///< some data, othewise false.
void handleReceivedSTUNPacket(); void handleReceivedSTUNPacket(WebRTCSocket &wSock);
void handleReceivedDTLSPacket(); void handleReceivedRTPOrRTCPPacket(WebRTCSocket &wSock);
void handleReceivedRTPOrRTCPPacket();
bool handleSignalingCommandRemoteOfferForInput(SDP::Session &sdpSession); bool handleSignalingCommandRemoteOfferForInput(SDP::Session &sdpSession);
bool handleSignalingCommandRemoteOfferForOutput(SDP::Session &sdpSession); bool handleSignalingCommandRemoteOfferForOutput(SDP::Session &sdpSession);
void sendSignalingError(const std::string &commandType, const std::string &errorMessage); void sendSignalingError(const std::string &commandType, const std::string &errorMessage);
@ -136,20 +150,19 @@ namespace Mist{
SDP::Session sdp; ///< SDP parser. SDP::Session sdp; ///< SDP parser.
SDP::Answer sdpAnswer; ///< WIP: Replacing our `sdp` member .. SDP::Answer sdpAnswer; ///< WIP: Replacing our `sdp` member ..
Certificate cert; ///< The TLS certificate. Used to generate a fingerprint in SDP answers. Certificate cert; ///< The TLS certificate. Used to generate a fingerprint in SDP answers.
DTLSSRTPHandshake dtlsHandshake; ///< Implements the DTLS handshake using the mbedtls library (fork). Socket::UDPConnection mainSocket; //< Main socket created during the initial handshake
SRTPReader srtpReader; ///< Used to unprotect incoming RTP and RTCP data. Uses the keys that std::map<int, WebRTCSocket> sockets; ///< UDP sockets over which WebRTC data is received and sent.
///< were exchanged with DTLS. std::set<int> rtpSockets; ///< UDP sockets over which (S)RTP data is transmitted/received
SRTPWriter srtpWriter; ///< Used to protect our RTP and RTCP data when sending data to another std::set<int> sctpSockets; ///< UDP sockets over which (S)RTP data is transmitted/received
///< peer. Uses the keys that were exchanged with DTLS. uint16_t lastMediaSocket; //< Last socket number we received video/audio on
Socket::UDPConnection udp; ///< Our UDP socket over which WebRTC data is received and sent. uint16_t lastMetaSocket; //< Last socket number we received non-media data on
uint16_t udpPort; ///< Port where we receive RTP, STUN, DTLS, etc.
StunReader stunReader; ///< Decodes STUN messages; during a session we keep receiving STUN StunReader stunReader; ///< Decodes STUN messages; during a session we keep receiving STUN
///< messages to which we need to reply. ///< messages to which we need to reply.
std::map<uint64_t, WebRTCTrack> webrtcTracks; ///< WebRTCTracks indexed by payload type for incoming data and indexed by std::map<uint64_t, WebRTCTrack> webrtcTracks; ///< WebRTCTracks indexed by payload type for incoming data and indexed by
///< myMeta.tracks[].trackID for outgoing data. ///< myMeta.tracks[].trackID for outgoing data.
tthread::thread *webRTCInputOutputThread; ///< The thread in which we read WebRTC data when tthread::thread *webRTCInputOutputThread; ///< The thread in which we read WebRTC data when
///< we're receive media from another peer. ///< 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. */ 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 uint64_t rtcpTimeoutInMillis; ///< When current time in millis exceeds this timeout we have to
///< send a new RTCP packet. ///< send a new RTCP packet.
@ -161,18 +174,15 @@ namespace Mist{
///< the signaling channel. Defaults to 6mbit. ///< the signaling channel. Defaults to 6mbit.
uint32_t videoConstraint; uint32_t videoConstraint;
size_t audTrack, vidTrack, prevVidTrack; size_t audTrack, vidTrack, prevVidTrack, metaTrack;
double target_rate; ///< Target playback speed rate (1.0 = normal, 0 = auto) double target_rate; ///< Target playback speed rate (1.0 = normal, 0 = auto)
bool didReceiveKeyFrame; /* TODO burst delay */ bool didReceiveKeyFrame;
bool setPacketOffset; bool setPacketOffset;
int64_t packetOffset; ///< For timestamp rewrite with BMO int64_t packetOffset; ///< For timestamp rewrite with BMO
uint64_t lastTimeSync; uint64_t lastTimeSync;
bool firstKey; bool firstKey;
bool repeatInit; bool repeatInit;
bool stayLive;
bool doDTLS;
bool volkswagenMode;
double stats_jitter; double stats_jitter;
uint64_t stats_nacknum; uint64_t stats_nacknum;
@ -191,13 +201,17 @@ namespace Mist{
std::map<uint8_t, uint64_t> payloadTypeToWebRTCTrack; ///< Maps e.g. RED to the corresponding track. Used when input 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 ///< supports RED/ULPFEC; can also be used to map RTX in the
///< future. ///< future.
std::map<uint32_t, nackBuffer> outBuffers;
uint64_t lastSR; uint64_t lastSR;
std::set<size_t> mustSendSR; std::set<size_t> mustSendSR;
int64_t ntpClockDifference; int64_t ntpClockDifference;
bool syncedNTPClock; bool syncedNTPClock;
bool sctpInited;
bool sctpConnected;
struct socket * sctp_sock;
std::map<std::string, uint16_t> dataChannels;
std::deque<std::string> queuedJSON;
}; };
}// namespace Mist }// namespace Mist

View file

@ -14,6 +14,10 @@ SRTPReader::SRTPReader(){
memset((void *)&policy, 0x00, sizeof(policy)); memset((void *)&policy, 0x00, sizeof(policy));
} }
SRTPReader::~SRTPReader(){
if (shutdown() != 0){FAIL_MSG("Failed to cleanly shutdown the srtp reader.");}
}
/* /*
Before initializing the srtp library we shut it down first Before initializing the srtp library we shut it down first
because initializing the library twice results in an error. because initializing the library twice results in an error.
@ -203,6 +207,11 @@ SRTPWriter::SRTPWriter(){
memset((void *)&policy, 0x00, sizeof(policy)); memset((void *)&policy, 0x00, sizeof(policy));
} }
SRTPWriter::~SRTPWriter(){
if (shutdown() != 0){FAIL_MSG("Failed to cleanly shutdown the srtp writer.");}
}
/* /*
Before initializing the srtp library we shut it down first Before initializing the srtp library we shut it down first
because initializing the library twice results in an error. because initializing the library twice results in an error.

View file

@ -14,6 +14,7 @@
class SRTPReader{ class SRTPReader{
public: public:
SRTPReader(); SRTPReader();
~SRTPReader();
int init(const std::string &cipher, const std::string &key, const std::string &salt); int init(const std::string &cipher, const std::string &key, const std::string &salt);
int shutdown(); int shutdown();
int unprotectRtp(uint8_t *data, int *nbytes); /* `nbytes` should contain the number of bytes in `data`. On success `nbytes` int unprotectRtp(uint8_t *data, int *nbytes); /* `nbytes` should contain the number of bytes in `data`. On success `nbytes`
@ -32,6 +33,7 @@ private:
class SRTPWriter{ class SRTPWriter{
public: public:
SRTPWriter(); SRTPWriter();
~SRTPWriter();
int init(const std::string &cipher, const std::string &key, const std::string &salt); int init(const std::string &cipher, const std::string &key, const std::string &salt);
int shutdown(); int shutdown();
int protectRtp(uint8_t *data, int *nbytes); int protectRtp(uint8_t *data, int *nbytes);

View file

@ -67,23 +67,23 @@ void userOnActive(Comms::Connections &connections, size_t idx){
} }
// Sanity checks // Sanity checks
if (connections.getDown(idx) < connDown[idx]){ if (connections.getDown(idx) < connDown[idx]){
WARN_MSG("Connection downloaded bytes should be a counter, but has decreased in value"); MEDIUM_MSG("Connection downloaded bytes should be a counter, but has decreased in value");
connDown[idx] = connections.getDown(idx); connDown[idx] = connections.getDown(idx);
} }
if (connections.getUp(idx) < connUp[idx]){ if (connections.getUp(idx) < connUp[idx]){
WARN_MSG("Connection uploaded bytes should be a counter, but has decreased in value"); MEDIUM_MSG("Connection uploaded bytes should be a counter, but has decreased in value");
connUp[idx] = connections.getUp(idx); connUp[idx] = connections.getUp(idx);
} }
if (connections.getPacketCount(idx) < connPktcount[idx]){ if (connections.getPacketCount(idx) < connPktcount[idx]){
WARN_MSG("Connection packet count should be a counter, but has decreased in value"); MEDIUM_MSG("Connection packet count should be a counter, but has decreased in value");
connPktcount[idx] = connections.getPacketCount(idx); connPktcount[idx] = connections.getPacketCount(idx);
} }
if (connections.getPacketLostCount(idx) < connPktloss[idx]){ if (connections.getPacketLostCount(idx) < connPktloss[idx]){
WARN_MSG("Connection packet loss count should be a counter, but has decreased in value"); MEDIUM_MSG("Connection packet loss count should be a counter, but has decreased in value");
connPktloss[idx] = connections.getPacketLostCount(idx); connPktloss[idx] = connections.getPacketLostCount(idx);
} }
if (connections.getPacketRetransmitCount(idx) < connPktretrans[idx]){ if (connections.getPacketRetransmitCount(idx) < connPktretrans[idx]){
WARN_MSG("Connection packets retransmitted should be a counter, but has decreased in value"); MEDIUM_MSG("Connection packets retransmitted should be a counter, but has decreased in value");
connPktretrans[idx] = connections.getPacketRetransmitCount(idx); connPktretrans[idx] = connections.getPacketRetransmitCount(idx);
} }
// Add increase in stats to global stats // Add increase in stats to global stats

5
subprojects/usrsctp.wrap Normal file
View file

@ -0,0 +1,5 @@
[wrap-git]
url = https://github.com/sctplab/usrsctp.git
revision = 0.9.5.0
depth = 1