Added OpenSSL as an alternative to GnuTLS

This commit is contained in:
Paul-Louis Ageneau
2019-12-10 15:59:05 +01:00
parent 585450d13e
commit e75ae36ba8
10 changed files with 459 additions and 34 deletions

View File

@ -44,19 +44,10 @@ else()
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member -Wno-error=format-truncation)
endif()
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
find_package(GnuTLS REQUIRED)
find_package(LibNice REQUIRED)
if(NOT TARGET GnuTLS::GnuTLS)
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
set_target_properties(GnuTLS::GnuTLS PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
endif()
add_library(datachannel SHARED ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel PROPERTIES
VERSION ${PROJECT_VERSION}
@ -65,7 +56,7 @@ set_target_properties(datachannel PROPERTIES
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel usrsctp-static GnuTLS::GnuTLS LibNice::LibNice)
target_link_libraries(datachannel usrsctp-static LibNice::LibNice)
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel-static PROPERTIES
@ -75,7 +66,29 @@ set_target_properties(datachannel-static PROPERTIES
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel-static usrsctp-static GnuTLS::GnuTLS LibNice::LibNice)
target_link_libraries(datachannel-static usrsctp-static LibNice::LibNice)
if (USE_GNUTLS)
find_package(GnuTLS REQUIRED)
if(NOT TARGET GnuTLS::GnuTLS)
add_library(GnuTLS::GnuTLS UNKNOWN IMPORTED)
set_target_properties(GnuTLS::GnuTLS PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${GNUTLS_INCLUDE_DIRS}"
INTERFACE_COMPILE_DEFINITIONS "${GNUTLS_DEFINITIONS}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${GNUTLS_LIBRARIES}")
endif()
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=1)
target_link_libraries(datachannel GnuTLS::GnuTLS)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=1)
target_link_libraries(datachannel-static GnuTLS::GnuTLS)
else()
find_package(OpenSSL REQUIRED)
target_compile_definitions(datachannel PRIVATE USE_GNUTLS=0)
target_link_libraries(datachannel OpenSSL::SSL)
target_compile_definitions(datachannel-static PRIVATE USE_GNUTLS=0)
target_link_libraries(datachannel-static OpenSSL::SSL)
endif()
add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)

View File

@ -6,13 +6,15 @@ lib libdatachannel
[ glob ./src/*.cpp ]
: # requirements
<include>./include/rtc
<cxxflags>"`pkg-config --cflags gnutls glib-2.0 gobject-2.0 nice`"
<define>USE_GNUTLS=0
<cxxflags>"`pkg-config --cflags openssl glib-2.0 gobject-2.0 nice`"
<library>/libdatachannel//usrsctp
: # default build
<link>static
: # usage requirements
<include>./include
<linkflags>"`pkg-config --libs gnutls glib-2.0 gobject-2.0 nice`"
<cxxflags>-pthread
<linkflags>"`pkg-config --libs openssl glib-2.0 gobject-2.0 nice`"
;
alias usrsctp

View File

@ -7,12 +7,21 @@ RM=rm -f
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
CXXFLAGS=-std=c++17
LDFLAGS=-pthread
LIBS=gnutls glib-2.0 gobject-2.0 nice
LIBS=glib-2.0 gobject-2.0 nice
USRSCTP_DIR=usrsctp
USE_GNUTLS ?= 0
ifeq ($(USE_GNUTLS), 1)
CPPFLAGS+= -DUSE_GNUTLS=1
LIBS+= gnutls
else
CPPFLAGS+= -DUSE_GNUTLS=0
LIBS+= openssl
endif
LDLIBS= $(shell pkg-config --libs $(LIBS))
INCLUDES=-Iinclude/rtc -I$(USRSCTP_DIR)/usrsctplib $(shell pkg-config --cflags $(LIBS))
USRSCTP_DIR:=usrsctp
SRCS=$(shell printf "%s " src/*.cpp)
OBJS=$(subst .cpp,.o,$(SRCS))

View File

@ -13,7 +13,7 @@ The library aims at fully implementing SCTP DataChannels ([draft-ietf-rtcweb-dat
## Dependencies
- libnice: https://github.com/libnice/libnice
- GnuTLS: https://www.gnutls.org/
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
Submodules:
- usrsctp: https://github.com/sctplab/usrsctp
@ -24,7 +24,7 @@ Submodules:
$ git submodule update --init --recursive
$ mkdir build
$ cd build
$ cmake ..
$ cmake -DUSE_GNUTLS=1 ..
$ make
```

View File

@ -25,10 +25,13 @@
#include <sstream>
#include <unordered_map>
#include <gnutls/crypto.h>
using std::shared_ptr;
using std::string;
using std::unique_ptr;
#if USE_GNUTLS
#include <gnutls/crypto.h>
namespace {
@ -117,10 +120,10 @@ Certificate::Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey)
"Unable to set certificate and key pair in credentials");
}
string Certificate::fingerprint() const { return mFingerprint; }
gnutls_certificate_credentials_t Certificate::credentials() const { return *mCredentials; }
string Certificate::fingerprint() const { return mFingerprint; }
string make_fingerprint(gnutls_x509_crt_t crt) {
const size_t size = 32;
unsigned char buffer[size];
@ -177,3 +180,120 @@ shared_ptr<Certificate> make_certificate(const string &commonName) {
}
} // namespace rtc
#else
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
namespace rtc {
Certificate::Certificate(string crt_pem, string key_pem) {
BIO *bio;
bio = BIO_new(BIO_s_mem());
BIO_write(bio, crt_pem.data(), crt_pem.size());
mX509 = shared_ptr<X509>(PEM_read_bio_X509(bio, nullptr, 0, 0), X509_free);
BIO_free(bio);
if (!mX509)
throw std::invalid_argument("Unable to import certificate PEM");
bio = BIO_new(BIO_s_mem());
BIO_write(bio, key_pem.data(), key_pem.size());
mPKey = shared_ptr<EVP_PKEY>(PEM_read_bio_PrivateKey(bio, nullptr, 0, 0), EVP_PKEY_free);
BIO_free(bio);
if (!mPKey)
throw std::invalid_argument("Unable to import PEM key PEM");
mFingerprint = make_fingerprint(mX509.get());
}
Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey) :
mX509(std::move(x509)), mPKey(std::move(pkey))
{
mFingerprint = make_fingerprint(mX509.get());
}
string Certificate::fingerprint() const { return mFingerprint; }
std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const { return {mX509.get(), mPKey.get()}; }
string make_fingerprint(X509 *x509) {
const size_t size = 32;
unsigned char buffer[size];
unsigned int len = size;
if (!X509_digest(x509, EVP_sha256(), buffer, &len))
throw std::runtime_error("X509 fingerprint error");
std::ostringstream oss;
oss << std::hex << std::uppercase << std::setfill('0');
for (size_t i = 0; i < len; ++i) {
if (i)
oss << std::setw(1) << ':';
oss << std::setw(2) << unsigned(buffer[i]);
}
return oss.str();
}
shared_ptr<Certificate> make_certificate(const string &commonName) {
static std::unordered_map<string, shared_ptr<Certificate>> cache;
static std::mutex cacheMutex;
std::lock_guard<std::mutex> lock(cacheMutex);
if (auto it = cache.find(commonName); it != cache.end())
return it->second;
if (cache.empty()) {
// This is the first call to OpenSSL
OPENSSL_init_ssl(0, NULL);
SSL_load_error_strings();
ERR_load_crypto_strings();
}
shared_ptr<X509> x509(X509_new(), X509_free);
shared_ptr<EVP_PKEY> pkey(EVP_PKEY_new(), EVP_PKEY_free);
unique_ptr<RSA, decltype(&RSA_free)> rsa(RSA_new(), RSA_free);
unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
unique_ptr<BIGNUM, decltype(&BN_free)> serial_number(BN_new(), BN_free);
unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), X509_NAME_free);
if (!x509 || !pkey || !rsa || !exponent || !serial_number || !name)
throw std::runtime_error("Unable allocate structures for certificate generation");
const int bits = 4096;
const unsigned int e = 65537; // 2^16 + 1
if (!pkey || !rsa || !exponent || !BN_set_word(exponent.get(), e) ||
!RSA_generate_key_ex(rsa.get(), bits, exponent.get(), NULL) ||
!EVP_PKEY_assign_RSA(pkey.get(), rsa.release())) // the key will be freed when pkey is freed
throw std::runtime_error("Unable to generate key pair");
const size_t serialSize = 16;
const auto *commonNameBytes = reinterpret_cast<const unsigned char *>(commonName.c_str());
if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 3600 * -1) ||
!X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600 * 24 * 365) ||
!X509_set_version(x509.get(), 1) || !X509_set_pubkey(x509.get(), pkey.get()) ||
!BN_pseudo_rand(serial_number.get(), serialSize, 0, 0) ||
!BN_to_ASN1_INTEGER(serial_number.get(), X509_get_serialNumber(x509.get())) ||
!X509_NAME_add_entry_by_NID(name.get(), NID_commonName, MBSTRING_UTF8, commonNameBytes, -1,
-1, 0) ||
!X509_set_subject_name(x509.get(), name.get()) ||
!X509_set_issuer_name(x509.get(), name.get()))
throw std::runtime_error("Unable to set certificate properties");
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256()))
throw std::runtime_error("Unable to auto-sign certificate");
auto certificate = std::make_shared<Certificate>(x509, pkey);
cache.emplace(std::make_pair(commonName, certificate));
return certificate;
}
} // namespace rtc
#endif

View File

@ -21,24 +21,47 @@
#include "include.hpp"
#include <tuple>
#if USE_GNUTLS
#include <gnutls/x509.h>
#else
#include <openssl/x509.h>
#endif
namespace rtc {
class Certificate {
public:
Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
Certificate(string crt_pem, string key_pem);
string fingerprint() const;
#if USE_GNUTLS
Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey);
gnutls_certificate_credentials_t credentials() const;
#else
Certificate(std::shared_ptr<X509> x509, std::shared_ptr<EVP_PKEY> pkey);
std::tuple<X509 *, EVP_PKEY *> credentials() const;
#endif
string fingerprint() const;
private:
#if USE_GNUTLS
std::shared_ptr<gnutls_certificate_credentials_t> mCredentials;
#else
std::shared_ptr<X509> mX509;
std::shared_ptr<EVP_PKEY> mPKey;
#endif
string mFingerprint;
};
#if USE_GNUTLS
string make_fingerprint(gnutls_x509_crt_t crt);
#else
string make_fingerprint(X509 *x509);
#endif
std::shared_ptr<Certificate> make_certificate(const string &commonName);
} // namespace rtc

View File

@ -24,10 +24,14 @@
#include <exception>
#include <iostream>
#include <gnutls/dtls.h>
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::weak_ptr;
#if USE_GNUTLS
#include <gnutls/dtls.h>
namespace {
@ -44,8 +48,6 @@ static bool check_gnutls(int ret, const string &message = "GnuTLS error") {
namespace rtc {
using std::shared_ptr;
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
verifier_callback verifierCallback,
state_callback stateChangeCallback)
@ -61,7 +63,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5
const char *priorities = "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-ARCFOUR-128:-COMP-ALL";
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL";
const char *err_pos = NULL;
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
"Unable to set TLS priorities");
@ -95,7 +97,7 @@ DtlsTransport::~DtlsTransport() {
DtlsTransport::State DtlsTransport::state() const { return mState; }
bool DtlsTransport::send(message_ptr message) {
if (!message)
if (!message || mState != State::Connected)
return false;
ssize_t ret;
@ -242,3 +244,238 @@ int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms)
}
} // namespace rtc
#else
#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
namespace {
const int BIO_EOF = -1;
string openssl_error_string(unsigned long err) {
const size_t bufferSize = 256;
char buffer[bufferSize];
ERR_error_string_n(err, buffer, bufferSize);
return string(buffer);
}
bool check_openssl(int success, const string &message = "OpenSSL error") {
if (success)
return true;
else
throw std::runtime_error(message + ": " + openssl_error_string(ERR_get_error()));
}
bool check_openssl_ret(SSL *ssl, int ret, const string &message = "OpenSSL error") {
if (ret == BIO_EOF)
return true;
unsigned long err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_NONE || err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
return true;
else if (err == SSL_ERROR_ZERO_RETURN)
return false;
else
throw std::runtime_error(message + ": " + openssl_error_string(err));
}
} // namespace
namespace rtc {
int DtlsTransport::TransportExIndex = -1;
std::mutex DtlsTransport::GlobalMutex;
void DtlsTransport::GlobalInit() {
std::lock_guard<std::mutex> lock(GlobalMutex);
if (TransportExIndex < 0) {
TransportExIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
}
}
DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certificate> certificate,
verifier_callback verifierCallback, state_callback stateChangeCallback)
: Transport(lower), mCertificate(certificate), mState(State::Disconnected),
mVerifierCallback(std::move(verifierCallback)),
mStateChangeCallback(std::move(stateChangeCallback)) {
GlobalInit();
if (!(mCtx = SSL_CTX_new(DTLS_method())))
throw std::runtime_error("Unable to create SSL context");
check_openssl(SSL_CTX_set_cipher_list(mCtx, "ALL:!LOW:!EXP:!RC4:!MD5:@STRENGTH"),
"Unable to set SSL priorities");
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION);
SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION);
SSL_CTX_set_read_ahead(mCtx, 1);
SSL_CTX_set_quiet_shutdown(mCtx, 1);
SSL_CTX_set_info_callback(mCtx, InfoCallback);
SSL_CTX_set_verify(mCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
CertificateCallback);
SSL_CTX_set_verify_depth(mCtx, 1);
X509 *x509;
EVP_PKEY *pkey;
std::tie(x509, pkey) = mCertificate->credentials();
SSL_CTX_use_certificate(mCtx, x509);
SSL_CTX_use_PrivateKey(mCtx, pkey);
check_openssl(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
if (!(mSsl = SSL_new(mCtx)))
throw std::runtime_error("Unable to create SSL instance");
SSL_set_ex_data(mSsl, TransportExIndex, this);
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
if (lower->role() == Description::Role::Active)
SSL_set_connect_state(mSsl);
else
SSL_set_accept_state(mSsl);
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BIO_s_mem())))
throw std::runtime_error("Unable to create BIO");
BIO_set_mem_eof_return(mInBio, BIO_EOF);
BIO_set_mem_eof_return(mOutBio, BIO_EOF);
SSL_set_bio(mSsl, mInBio, mOutBio);
auto ecdh = unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), EC_KEY_free);
SSL_set_options(mSsl, SSL_OP_SINGLE_ECDH_USE);
SSL_set_tmp_ecdh(mSsl, ecdh.get());
mRecvThread = std::thread(&DtlsTransport::runRecvLoop, this);
}
DtlsTransport::~DtlsTransport() {
onRecv(nullptr); // unset recv callback
mIncomingQueue.stop();
if (mRecvThread.joinable())
mRecvThread.join();
SSL_shutdown(mSsl);
SSL_free(mSsl);
SSL_CTX_free(mCtx);
}
DtlsTransport::State DtlsTransport::state() const { return mState; }
bool DtlsTransport::send(message_ptr message) {
const size_t bufferSize = 4096;
byte buffer[bufferSize];
if (!message || mState != State::Connected)
return false;
int ret = SSL_write(mSsl, message->data(), message->size());
if (!check_openssl_ret(mSsl, ret)) {
return false;
}
while (BIO_ctrl_pending(mOutBio) > 0) {
int ret = BIO_read(mOutBio, buffer, bufferSize);
if (check_openssl_ret(mSsl, ret) && ret > 0)
outgoing(make_message(buffer, buffer + ret));
}
return true;
}
void DtlsTransport::incoming(message_ptr message) { mIncomingQueue.push(message); }
void DtlsTransport::changeState(State state) {
if (mState.exchange(state) != state)
mStateChangeCallback(state);
}
void DtlsTransport::runRecvLoop() {
const size_t bufferSize = 4096;
byte buffer[bufferSize];
try {
changeState(State::Connecting);
SSL_do_handshake(mSsl);
while (BIO_ctrl_pending(mOutBio) > 0) {
int ret = BIO_read(mOutBio, buffer, bufferSize);
if (check_openssl_ret(mSsl, ret) && ret > 0)
outgoing(make_message(buffer, buffer + ret));
}
while (auto next = mIncomingQueue.pop()) {
auto message = *next;
BIO_write(mInBio, message->data(), message->size());
int ret = SSL_read(mSsl, buffer, bufferSize);
if (!check_openssl_ret(mSsl, ret))
break;
auto decrypted = ret > 0 ? make_message(buffer, buffer + ret) : nullptr;
if (mState == State::Connecting) {
if (unsigned long err = ERR_get_error())
throw std::runtime_error("handshake failed: " + openssl_error_string(err));
while (BIO_ctrl_pending(mOutBio) > 0) {
ret = BIO_read(mOutBio, buffer, bufferSize);
if (check_openssl_ret(mSsl, ret) && ret > 0)
outgoing(make_message(buffer, buffer + ret));
}
if (SSL_is_init_finished(mSsl))
changeState(State::Connected);
}
if (decrypted)
recv(decrypted);
}
} catch (const std::exception &e) {
std::cerr << "DTLS recv: " << e.what() << std::endl;
}
if (mState == State::Connected) {
changeState(State::Disconnected);
recv(nullptr);
} else {
changeState(State::Failed);
}
}
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
SSL *ssl =
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
DtlsTransport *t =
static_cast<DtlsTransport *>(SSL_get_ex_data(ssl, DtlsTransport::TransportExIndex));
X509 *crt = X509_STORE_CTX_get_current_cert(ctx);
std::string fingerprint = make_fingerprint(crt);
return t->mVerifierCallback(fingerprint) ? 1 : 0;
}
void DtlsTransport::InfoCallback(const SSL *ssl, int where, int ret) {
DtlsTransport *t =
static_cast<DtlsTransport *>(SSL_get_ex_data(ssl, DtlsTransport::TransportExIndex));
if (where & SSL_CB_ALERT) {
if (ret != 256) // Close Notify
std::cerr << "DTLS alert: " << SSL_alert_desc_string_long(ret) << std::endl;
t->mIncomingQueue.stop(); // Close the connection
}
}
} // namespace rtc
#endif

View File

@ -28,9 +28,14 @@
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#if USE_GNUTLS
#include <gnutls/gnutls.h>
#else
#include <openssl/ssl.h>
#endif
namespace rtc {
@ -58,7 +63,6 @@ private:
const std::shared_ptr<Certificate> mCertificate;
gnutls_session_t mSession;
Queue<message_ptr> mIncomingQueue;
std::atomic<State> mState;
std::thread mRecvThread;
@ -66,10 +70,25 @@ private:
verifier_callback mVerifierCallback;
state_callback mStateChangeCallback;
#if USE_GNUTLS
gnutls_session_t mSession;
static int CertificateCallback(gnutls_session_t session);
static ssize_t WriteCallback(gnutls_transport_ptr_t ptr, const void *data, size_t len);
static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen);
static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms);
#else
SSL_CTX *mCtx;
SSL *mSsl;
BIO *mInBio, *mOutBio;
static int TransportExIndex;
static std::mutex GlobalMutex;
static void GlobalInit();
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
static void InfoCallback(const SSL *ssl, int where, int ret);
#endif
};
} // namespace rtc

View File

@ -132,7 +132,8 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
IceTransport::~IceTransport() {
g_main_loop_quit(mMainLoop.get());
mMainLoopThread.join();
if (mMainLoopThread.joinable())
mMainLoopThread.join();
}
Description::Role IceTransport::role() const { return mRole; }

View File

@ -130,7 +130,8 @@ SctpTransport::~SctpTransport() {
usrsctp_close(mSock);
}
mSendThread.join();
if (mSendThread.joinable())
mSendThread.join();
usrsctp_deregister_address(this);
GlobalCleanup();