hyperion.ng/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp
LordGrey e9936e131b
mDNS Support (#1452)
* Allow build, if no grabbers are enabled

* Align available functions to right Qt version

* Update to next development version

* Align available functions to right Qt version

* fix workflows (apt/nightly)

* Disable QNetworkConfigurationManager deprecation warnings

* Initial go on Smart Pointers

* Add Deallocation

* Correct QT_WARNING_DISABLE_DEPRECATED (available since 5.9)

* Cluster Build Variables

* Hyperion Light

* Address build warnings

* Hyperion Light - UI

* Update Protobuf to latest master

* Removed compiler warnings

* Added restart ability to systray

* Correct Protobuf

* Ignore 'no-return' warning on protobuf build

* hyperion-remote: Fix auto discovery of hyperion server

* Fix Qt version override

* Update changelog

* Remove Grabber Components, if no Grabber exists

* Standalone Grabber - Fix fps default

* Remote Control - Have Source Selction accrosswhole screen

* Enable Blackborder detection only, if relevant input sources available

* Enable Blackborder detection only, if relevant input sources available

* Remote UI - rearrange containers

* Checkout

* Fix compilation on windows

* Re-added qmdnsengine template cmake

* chrono added for linux

* Removed existing AVAHI/Bonjour, allow to enable/disable mDNS

* hyperiond macos typo fix

* Fix macOS Bundle build

* Fix macOS bundle info details

* Correct CMake files

* Removed existing AVAHI/Bonjour (2)

* Share hyperion's services via mDNS

* Add mDNS Browser and mDNS for LED-Devices

* Support mDNS discovery for standalone grabbers

* Remove ZLib Dependency & Cleanup

* mDNS - hanle 2.local2 an ".local." domains equally

* Hue - Link discovery to bridge class, workaround port 443 for mDNS discovery

* Fix save button state when switching between devices

* Removed sessions (of other hyperions)

* mDNS Publisher - Simplify service naming

* mDNS refactoring & Forwarder discovery

* mDNS Updates to use device service name

* Consistency of standalone grabbers with mDNS Service Registry

* Merge branch 'hyperion-project:master' into mDNS

* Start JSON and WebServers only after Instance 0 is available

* Remove bespoke qDebug Output again

* MDNS updates and refactor Forwarder

* Minor updates

* Upgrade to CMake 3.1

* typo

* macOS fix

* Correct merge

* - Remove dynamic linker flag from standalone dispmanX Grabber
- Added ability to use system qmdns libs

* Cec handler library will load at runtime

* typo fix

* protobuf changes

* mDNS changes for Windows/macOS

* test window build qmdnsengine

* absolute path to protobuf cmake dir

* Rework Hue Wizard supporting mDNS

* LED-Devices - Retry support + Refactoring (excl. Hue)

* LED-Devices - Refactoring/Retry support Hue + additional alignments

* Address LGTM findings

* Fix CI-Build, revert test changes

* Build Windows in Release mode to avoid python problem

* Correct that WebServerObject is available earlier

* Ensure that instance name in logs for one instance are presented

* Update content LEDs

* Rework mDNS Address lookup

* Fix LED UI

* Fix for non mDNS Services (ignore default port)

* Disbale device when now input is available

* Revert back some updates, ensure last color is updated when switched on

* Handle reopening case and changed IP, port for API-calls

* Add UPD-DDP Device

* WLED support for DDP

* Fix printout

* LEDDevice - Allow more retries, udapte defaults

* LED-Net Devices - Select Custom device, if configured

Co-authored-by: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com>
Co-authored-by: Paulchen Panther <Paulchen-Panter@protonmail.com>
2022-05-01 19:42:47 +02:00

387 lines
8.9 KiB
C++

// STL includes
#include <cstdio>
#include <exception>
#include <algorithm>
// Linux includes
#include <fcntl.h>
#ifndef _WIN32
#include <sys/ioctl.h>
#endif
// Local Hyperion includes
#include "ProviderUdpSSL.h"
#include <utils/NetUtils.h>
#include "mbedtls/version.h"
// Constants
namespace {
const int DEFAULT_SSLPORT = 2100;
const char DEFAULT_TRANSPORT_TYPE[] = "DTLS";
const char DEFAULT_SEED_CUSTOM[] = "dtls_client";
const int DEFAULT_HANDSHAKE_ATTEMPTS = 5;
const int DEFAULT_HANDSHAKE_TIMEOUT_MIN = 300;
const int DEFAULT_HANDSHAKE_TIMEOUT_MAX = 1000;
}
ProviderUdpSSL::ProviderUdpSSL(const QJsonObject &deviceConfig)
: LedDevice(deviceConfig)
, _port(-1)
, client_fd()
, entropy()
, ssl()
, conf()
, cacert()
, ctr_drbg()
, timer()
, _transport_type(DEFAULT_TRANSPORT_TYPE)
, _custom(DEFAULT_SEED_CUSTOM)
, _ssl_port(1)
, _server_name()
, _psk()
, _psk_identity()
, _handshake_attempts(DEFAULT_HANDSHAKE_ATTEMPTS)
, _handshake_timeout_min(DEFAULT_HANDSHAKE_TIMEOUT_MIN)
, _handshake_timeout_max(DEFAULT_HANDSHAKE_TIMEOUT_MAX)
, _streamReady(false)
, _streamPaused(false)
{
bool error = false;
try
{
mbedtls_ctr_drbg_init(&ctr_drbg);
error = !seedingRNG();
}
catch (...)
{
error = true;
}
if (error)
{
Error(_log, "Failed to initialize mbedtls seed");
}
}
ProviderUdpSSL::~ProviderUdpSSL()
{
stopConnection();
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
}
bool ProviderUdpSSL::init(const QJsonObject &deviceConfig)
{
bool isInitOK = false;
// Initialise sub-class
if ( LedDevice::init(deviceConfig) )
{
//PSK Pre Shared Key
_psk = deviceConfig["psk"].toString();
_psk_identity = deviceConfig["psk_identity"].toString();
_ssl_port = deviceConfig["sslport"].toInt(DEFAULT_SSLPORT);
_server_name = deviceConfig["servername"].toString();
if( deviceConfig.contains("transport_type") ) { _transport_type = deviceConfig["transport_type"].toString(DEFAULT_TRANSPORT_TYPE); }
if( deviceConfig.contains("seed_custom") ) { _custom = deviceConfig["seed_custom"].toString(DEFAULT_SEED_CUSTOM); }
if( deviceConfig.contains("hs_attempts") ) { _handshake_attempts = deviceConfig["hs_attempts"].toInt(DEFAULT_HANDSHAKE_ATTEMPTS); }
if (deviceConfig.contains("hs_timeout_min")) { _handshake_timeout_min = static_cast<uint32_t>(deviceConfig["hs_timeout_min"].toInt(DEFAULT_HANDSHAKE_TIMEOUT_MIN)); }
if (deviceConfig.contains("hs_timeout_max")) { _handshake_timeout_max = static_cast<uint32_t>(deviceConfig["hs_timeout_max"].toInt(DEFAULT_HANDSHAKE_TIMEOUT_MAX)); }
if (!NetUtils::isValidPort(_log,_ssl_port,_server_name))
{
QString errortext = QString ("Invalid SSL port [%1]!").arg(_ssl_port);
this->setInError( errortext );
isInitOK = false;
}
else
{
isInitOK = true;
}
}
return isInitOK;
}
int ProviderUdpSSL::open()
{
int retval = -1;
_isDeviceReady = false;
Info(_log, "Open UDP SSL streaming to %s port: %d", QSTRING_CSTR(_address.toString()), _ssl_port);
if ( !initNetwork() )
{
this->setInError( "UDP SSL Network error!" );
}
else
{
// Everything is OK -> enable device
Info(_log, "Stream UDP SSL data to %s port: %d", QSTRING_CSTR(_address.toString()), _ssl_port);
_isDeviceReady = true;
retval = 0;
}
return retval;
}
int ProviderUdpSSL::close()
{
int retval = 0;
_isDeviceReady = false;
Debug(_log, "Close SSL UDP-device: %s", QSTRING_CSTR(this->getActiveDeviceType()));
stopConnection();
// Everything is OK -> device is closed
return retval;
}
const int *ProviderUdpSSL::getCiphersuites() const
{
return mbedtls_ssl_list_ciphersuites();
}
bool ProviderUdpSSL::initNetwork()
{
if ((!_isDeviceReady || _streamPaused) && _streamReady)
{
stopConnection();
}
return initConnection();
}
bool ProviderUdpSSL::initConnection()
{
if (_streamReady)
{
return true;
}
mbedtls_net_init(&client_fd);
mbedtls_ssl_init(&ssl);
mbedtls_ssl_config_init(&conf);
mbedtls_x509_crt_init(&cacert);
if (setupStructure())
{
_streamReady = true;
_streamPaused = false;
return true;
}
return false;
}
bool ProviderUdpSSL::seedingRNG()
{
mbedtls_entropy_init(&entropy);
QByteArray customDataArray = _custom.toLocal8Bit();
const char* customData = customDataArray.constData();
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
&entropy, reinterpret_cast<const unsigned char*>(customData),
std::min(strlen(customData), static_cast<size_t>(MBEDTLS_CTR_DRBG_MAX_SEED_INPUT)));
if (ret != 0)
{
Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ctr_drbg_seed FAILED %1").arg(errorMsg(ret))));
return false;
}
return true;
}
bool ProviderUdpSSL::setupStructure()
{
int transport = ( _transport_type == "DTLS" ) ? MBEDTLS_SSL_TRANSPORT_DATAGRAM : MBEDTLS_SSL_TRANSPORT_STREAM;
int ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, transport, MBEDTLS_SSL_PRESET_DEFAULT);
if (ret != 0)
{
Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_config_defaults FAILED %1").arg(errorMsg(ret))));
return false;
}
const int * ciphersuites = getCiphersuites();
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_ca_chain(&conf, &cacert, nullptr);
mbedtls_ssl_conf_handshake_timeout(&conf, _handshake_timeout_min, _handshake_timeout_max);
mbedtls_ssl_conf_ciphersuites(&conf, ciphersuites);
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0)
{
Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_setup FAILED %1").arg(errorMsg(ret))));
return false;
}
return setupPSK();
}
bool ProviderUdpSSL::startConnection()
{
mbedtls_ssl_session_reset(&ssl);
int ret = mbedtls_net_connect(&client_fd, _address.toString().toUtf8(), std::to_string(_ssl_port).c_str(), MBEDTLS_NET_PROTO_UDP);
if (ret != 0)
{
Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_net_connect FAILED %1").arg(errorMsg(ret))));
return false;
}
mbedtls_ssl_set_bio(&ssl, &client_fd, mbedtls_net_send, mbedtls_net_recv, mbedtls_net_recv_timeout);
mbedtls_ssl_set_timer_cb(&ssl, &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
return startSSLHandshake();
}
bool ProviderUdpSSL::setupPSK()
{
QByteArray pskRawArray = QByteArray::fromHex(_psk.toUtf8());
QByteArray pskIdRawArray = _psk_identity.toUtf8();
int ret = mbedtls_ssl_conf_psk( &conf,
reinterpret_cast<const unsigned char*> (pskRawArray.constData()),
pskRawArray.length(),
reinterpret_cast<const unsigned char*> (pskIdRawArray.constData()),
pskIdRawArray.length());
if (ret != 0)
{
Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_conf_psk FAILED %1").arg(errorMsg(ret))));
return false;
}
return true;
}
bool ProviderUdpSSL::startSSLHandshake()
{
int ret = 0;
for (int attempt = 1; attempt <= _handshake_attempts; ++attempt)
{
do
{
ret = mbedtls_ssl_handshake(&ssl);
} while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE);
if (ret == 0)
{
break;
}
Warning(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_handshake attempt %1/%2 FAILED. Reason: %3").arg(attempt).arg(_handshake_attempts).arg(errorMsg(ret))));
QThread::msleep(200);
}
if (ret != 0)
{
Error(_log, "%s", QSTRING_CSTR(QString("mbedtls_ssl_handshake FAILED %1").arg(errorMsg(ret))));
return false;
}
if (mbedtls_ssl_get_verify_result(&ssl) != 0)
{
Error(_log, "SSL certificate verification failed!");
return false;
}
return true;
}
void ProviderUdpSSL::stopConnection()
{
if (_streamReady)
{
closeSSLNotify();
freeSSLConnection();
_streamReady = false;
}
}
void ProviderUdpSSL::freeSSLConnection()
{
try
{
Debug(_log, "Release mbedtls");
mbedtls_ssl_session_reset(&ssl);
mbedtls_net_free(&client_fd);
mbedtls_ssl_free(&ssl);
mbedtls_ssl_config_free(&conf);
mbedtls_x509_crt_free(&cacert);
}
catch (std::exception &e)
{
Error(_log, "%s", QSTRING_CSTR(QString("SSL Connection clean-up Error: %s").arg(e.what())));
}
catch (...)
{
Error(_log, "SSL Connection clean-up Error: <unknown>");
}
}
void ProviderUdpSSL::writeBytes(unsigned int size, const uint8_t* data, bool flush)
{
if (!_streamReady || _streamPaused)
{
return;
}
if (!_streamReady || _streamPaused)
{
return;
}
_streamPaused = flush;
int ret = 0;
do
{
ret = mbedtls_ssl_write(&ssl, data, size);
} while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE);
if (ret <= 0)
{
Error(_log, "Error while writing UDP SSL stream updates. mbedtls_ssl_write returned: %s", QSTRING_CSTR(errorMsg(ret)));
if (_streamReady)
{
stopConnection();
disable();
startEnableAttemptsTimer();
}
}
}
QString ProviderUdpSSL::errorMsg(int ret)
{
char error_buf[1024];
mbedtls_strerror(ret, error_buf, 1024);
return QString("Last error was: code = %1, description = %2").arg(ret).arg(error_buf);
}
void ProviderUdpSSL::closeSSLNotify()
{
/* No error checking, the connection might be closed already */
while (mbedtls_ssl_close_notify(&ssl) == MBEDTLS_ERR_SSL_WANT_WRITE)
{
}
}