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:
parent
56193f89b1
commit
3987cfec3f
16 changed files with 1303 additions and 811 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------- */
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
645
lib/socket.cpp
645
lib/socket.cpp
|
@ -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(){
|
||||||
|
|
51
lib/socket.h
51
lib/socket.h
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
@ -218,7 +218,7 @@ int main(int argc, char **argv){
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claim a spot in shared memory for this session on the global statistics page
|
// Claim a spot in shared memory for this session on the global statistics page
|
||||||
sessions.reload();
|
sessions.reload();
|
||||||
if (!sessions){
|
if (!sessions){
|
||||||
|
|
5
subprojects/usrsctp.wrap
Normal file
5
subprojects/usrsctp.wrap
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[wrap-git]
|
||||||
|
url = https://github.com/sctplab/usrsctp.git
|
||||||
|
revision = 0.9.5.0
|
||||||
|
depth = 1
|
||||||
|
|
Loading…
Add table
Reference in a new issue