IPv6 support (#1369)

* hyperion-remote - Support IPv6

* LEDDevices - Remove IPv6 limitations

* Separate JsonEditorHostValidation

* Standalone grabbers & JSON/Flatbuffer forwarder: IPv6 support

* remote: Fix setting multiple colors via Hex, add standard logging

* IPv6 Updates -Add db migration activities

* Addressing non-Windows compile issues

* Code cleanup, address clang feedback

* Update address (hostname, IPv4/IPv6) help text

* Apply migration steps to "old" configurations imported

* Show user the UI-Url, if hyperion is already running, address clang findings

* Windows Cmake OpenSLL output

* Minor Text update
This commit is contained in:
LordGrey
2021-11-17 20:30:43 +00:00
committed by GitHub
parent b33466d392
commit ad293b2fb6
61 changed files with 1714 additions and 926 deletions

View File

@@ -19,7 +19,6 @@ const bool verbose3 = false;
// Configuration settings
const char CONFIG_ADDRESS[] = "host";
//const char CONFIG_PORT[] = "port";
const char CONFIG_AUTH_TOKEN[] = "token";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness";
@@ -182,8 +181,6 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
_startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0);
// TODO: Allow to handle port dynamically
//Set hostname as per configuration and_defaultHost default port
_hostName = deviceConfig[CONFIG_ADDRESS].toString();
_apiPort = API_DEFAULT_PORT;
@@ -442,21 +439,7 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
QString authToken = params["token"].toString("");
QString filter = params["filter"].toString("");
// Resolve hostname and port (or use default API port)
QStringList addressparts = QStringUtils::split(hostName, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort;
if (addressparts.size() > 1)
{
apiPort = addressparts[1].toInt();
}
else
{
apiPort = API_DEFAULT_PORT;
}
initRestAPI(apiHost, apiPort, authToken);
initRestAPI(hostName, API_DEFAULT_PORT, authToken);
_restApi->setPath(filter);
// Perform request
@@ -482,21 +465,7 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
{
QString authToken = params["token"].toString("");
// Resolve hostname and port (or use default API port)
QStringList addressparts = QStringUtils::split(hostName, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort;
if (addressparts.size() > 1)
{
apiPort = addressparts[1].toInt();
}
else
{
apiPort = API_DEFAULT_PORT;
}
initRestAPI(apiHost, apiPort, authToken);
initRestAPI(hostName, API_DEFAULT_PORT, authToken);
_restApi->setPath("identify");
// Perform request

View File

@@ -61,7 +61,7 @@ public:
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP [:port]",
/// "host" : "hostname or IP",
/// "token" : "authentication token",
/// "filter": "resource to query", root "/" is used, if empty
/// }
@@ -78,7 +78,7 @@ public:
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP [:port]",
/// "host" : "hostname or IP",
/// "token" : "authentication token",
/// }
///@endcode

View File

@@ -12,8 +12,8 @@ namespace {
bool verbose = false;
// Configuration settings
const char CONFIG_ADDRESS[] = "host";
//const char CONFIG_PORT[] = "port";
const char CONFIG_HOST[] = "host";
const char CONFIG_PORT[] = "port";
const char CONFIG_USERNAME[] = "username";
const char CONFIG_CLIENTKEY[] = "clientkey";
const char CONFIG_BRIGHTNESSFACTOR[] = "brightnessFactor";
@@ -282,31 +282,30 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig)
log( "LatchTime", "%d", this->getLatchTime() );
//Set hostname as per configuration and_defaultHost default port
QString address = deviceConfig[ CONFIG_ADDRESS ].toString();
_hostname = deviceConfig[CONFIG_HOST].toString();
//If host not configured the init failed
if ( address.isEmpty() )
if (_hostname.isEmpty())
{
this->setInError("No target hostname nor IP defined");
this->setInError("No target hostname defined");
isInitOK = false;
}
else
{
QStringList addressparts = QStringUtils::split(address,":", QStringUtils::SplitBehavior::SkipEmptyParts);
_hostname = addressparts[0];
log( "Hostname/IP", "%s", QSTRING_CSTR( _hostname ) );
log("Hostname", "%s", QSTRING_CSTR(_hostname));
if ( addressparts.size() > 1 )
int _apiPort = deviceConfig[CONFIG_PORT].toInt();
if (_apiPort == 0)
{
_apiPort = addressparts[1].toInt();
log( "Port", "%u", _apiPort );
_apiPort = API_DEFAULT_PORT;
}
log("Port", "%d", _apiPort);
_username = deviceConfig[ CONFIG_USERNAME ].toString();
_username = deviceConfig[CONFIG_USERNAME].toString();
if ( initRestAPI( _hostname, _apiPort, _username ) )
if (initRestAPI(_hostname, _apiPort, _username))
{
if ( initMaps() )
if (initMaps())
{
isInitOK = ProviderUdpSSL::init(_devConfig);
}
@@ -1602,7 +1601,7 @@ QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/)
// Discover Devices
SSDPDiscover discover;
discover.skipDuplicateKeys(false);
discover.skipDuplicateKeys(true);
discover.setSearchFilter(SSDP_FILTER, SSDP_FILTER_HEADER);
QString searchTarget = SSDP_ID;
@@ -1619,37 +1618,39 @@ QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/)
QJsonObject LedDevicePhilipsHue::getProperties(const QJsonObject& params)
{
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
// Get Phillips-Bridge device properties
QString host = params["host"].toString("");
if ( !host.isEmpty() )
QString hostName = params[CONFIG_HOST].toString("");
if (!hostName.isEmpty())
{
QString username = params["user"].toString("");
QString filter = params["filter"].toString("");
// Resolve hostname and port (or use default API port)
QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort;
if ( addressparts.size() > 1 )
if (params[CONFIG_PORT].isString())
{
apiPort = addressparts[1].toInt();
apiPort = params[CONFIG_PORT].toString().toInt();
}
else
{
apiPort = params[CONFIG_PORT].toInt();
}
if (apiPort == 0)
{
apiPort = API_DEFAULT_PORT;
}
initRestAPI(apiHost, apiPort, username);
initRestAPI(hostName, apiPort, username);
_restApi->setPath(filter);
// Perform request
httpResponse response = _restApi->get();
if ( response.error() )
if (response.error())
{
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
// Perform request
@@ -1660,45 +1661,46 @@ QJsonObject LedDevicePhilipsHue::getProperties(const QJsonObject& params)
void LedDevicePhilipsHue::identify(const QJsonObject& params)
{
Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
// Identify Phillips-Bridge device
QString host = params["host"].toString("");
if ( !host.isEmpty() )
QString hostName = params[CONFIG_HOST].toString("");
if (!hostName.isEmpty())
{
QString username = params["user"].toString("");
int lightId = params["lightId"].toInt(0);
// Resolve hostname and port (or use default API port)
QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort;
if ( addressparts.size() > 1 )
if (params[CONFIG_PORT].isString())
{
apiPort = addressparts[1].toInt();
apiPort = params[CONFIG_PORT].toString().toInt();
}
else
{
apiPort = API_DEFAULT_PORT;
apiPort = params[CONFIG_PORT].toInt();
}
initRestAPI(apiHost, apiPort, username);
if (apiPort == 0)
{
apiPort = API_DEFAULT_PORT;
}
QString resource = QString("%1/%2/%3").arg( API_LIGHTS ).arg( lightId ).arg( API_STATE);
initRestAPI(hostName, apiPort, username);
QString resource = QString("%1/%2/%3").arg(API_LIGHTS).arg(lightId).arg(API_STATE);
_restApi->setPath(resource);
QString stateCmd;
stateCmd += QString("\"%1\":%2,").arg( API_STATE_ON, API_STATE_VALUE_TRUE );
stateCmd += QString("\"%1\":\"%2\"").arg( "alert", "select" );
stateCmd += QString("\"%1\":%2,").arg(API_STATE_ON, API_STATE_VALUE_TRUE);
stateCmd += QString("\"%1\":\"%2\"").arg("alert", "select");
stateCmd = "{" + stateCmd + "}";
// Perform request
httpResponse response = _restApi->put(stateCmd);
if ( response.error() )
if (response.error())
{
Warning (_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
}
}

View File

@@ -376,7 +376,8 @@ public:
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP [:port]",
/// "host" : "hostname or IP
/// "port" : port
/// "user" : "username",
/// "filter": "resource to query", root "/" is used, if empty
/// }
@@ -390,7 +391,15 @@ public:
///
/// @brief Send an update to the device to identify it.
///
/// Used in context of a set of devices of the same type.
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP
/// "port" : port
/// "user" : "username",
/// "filter": "resource to query", root "/" is used, if empty
/// }
///@endcode
///
/// @param[in] params Parameters to address device
///

View File

@@ -47,7 +47,7 @@ public:
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP [:port]",
/// "host" : "hostname or IP",
/// "filter": "resource to query", root "/" is used, if empty
/// }
///@endcode
@@ -63,7 +63,7 @@ public:
/// Following parameters are required
/// @code
/// {
/// "host" : "hostname or IP [:port]",
/// "host" : "hostname or IP",
/// }
///@endcode
///

View File

@@ -1112,11 +1112,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
QString hostName = configuredYeelightLights[j].toObject().value("host").toString();
int port = configuredYeelightLights[j].toObject().value("port").toInt(API_DEFAULT_PORT);
QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString apiHost = addressparts[0];
int apiPort = port;
_lightsAddressList.append( {apiHost, apiPort} );
_lightsAddressList.append( { hostName, port} );
}
if ( updateLights(_lightsAddressList) )
@@ -1150,19 +1146,19 @@ bool LedDeviceYeelight::startMusicModeServer()
}
else
{
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
// use the first non-localhost IPv4 address
for (int i = 0; i < ipAddressesList.size(); ++i) {
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
(ipAddressesList.at(i).toIPv4Address() != 0U))
// use the first non-localhost IPv4 address, IPv6 are not supported by Yeelight currently
for (const auto& address : QNetworkInterface::allAddresses())
{
// is valid when, no loopback, IPv4
if (!address.isLoopback() && (address.protocol() == QAbstractSocket::IPv4Protocol))
{
_musicModeServerAddress = ipAddressesList.at(i);
_musicModeServerAddress = address;
break;
}
}
if ( _musicModeServerAddress.isNull() )
if (_musicModeServerAddress.isNull())
{
Error( _log, "Failed to resolve IP for music mode server");
Error(_log, "Failed to resolve IP for music mode server");
}
}
}

View File

@@ -70,7 +70,7 @@ bool ProviderUdp::init(const QJsonObject& deviceConfig)
else
{
_port = static_cast<quint16>(config_port);
Debug(_log, "UDP socket will write to %s:%u", QSTRING_CSTR(_address.toString()), _port);
Debug(_log, "UDP socket will write to %s port: %u", QSTRING_CSTR(_address.toString()), _port);
_udpSocket = new QUdpSocket(this);

View File

@@ -74,9 +74,6 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig)
if( deviceConfig.contains("hs_attempts") ) _handshake_attempts = deviceConfig["hs_attempts"].toInt(5);
QString host = deviceConfig["host"].toString(_defaultHost);
//Split hostname from API-port in case given
QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString udpHost = addressparts[0];
QStringList debugLevels = QStringList() << "No Debug" << "Error" << "State Change" << "Informational" << "Verbose";
@@ -96,25 +93,24 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig)
configLog( "SSL Handshake Timeout max", "%d", _handshake_timeout_max );
configLog( "SSL Handshake attempts", "%d", _handshake_attempts );
if ( _address.setAddress(udpHost) )
if (_address.setAddress(host))
{
Debug( _log, "Successfully parsed %s as an ip address.", QSTRING_CSTR(udpHost) );
Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString()));
}
else
{
Debug( _log, "Failed to parse [%s] as an ip address.", QSTRING_CSTR(udpHost) );
QHostInfo info = QHostInfo::fromName(udpHost);
if ( info.addresses().isEmpty() )
QHostInfo hostInfo = QHostInfo::fromName(host);
if (hostInfo.error() == QHostInfo::NoError)
{
Debug( _log, "Failed to parse [%s] as a hostname.", QSTRING_CSTR(udpHost) );
QString errortext = QString("Invalid target address [%1]!").arg(host);
this->setInError( errortext );
isInitOK = false;
_address = hostInfo.addresses().first();
Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host));
}
else
{
Debug( _log, "Successfully parsed %s as a hostname.", QSTRING_CSTR(udpHost) );
_address = info.addresses().first();
QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString());
this->setInError(errortext);
isInitOK = false;
return isInitOK;
}
}
@@ -129,7 +125,7 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig)
else
{
_ssl_port = config_port;
Debug( _log, "UDP SSL using %s:%u", QSTRING_CSTR( _address.toString() ), _ssl_port );
Debug(_log, "UDP SSL will write to %s port: %u", QSTRING_CSTR(_address.toString()), _ssl_port);
isInitOK = true;
}
}
@@ -250,23 +246,26 @@ bool ProviderUdpSSL::initConnection()
bool ProviderUdpSSL::seedingRNG()
{
int ret = 0;
sslLog( "Seeding the random number generator..." );
sslLog("Seeding the random number generator...");
mbedtls_entropy_init(&entropy);
sslLog( "Set mbedtls_ctr_drbg_seed..." );
sslLog("Set mbedtls_ctr_drbg_seed...");
const char* custom = QSTRING_CSTR( _custom );
QByteArray customDataArray = _custom.toLocal8Bit();
const char* customData = customDataArray.constData();
if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, reinterpret_cast<const unsigned char *>(custom), strlen(custom))) != 0)
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
&entropy, reinterpret_cast<const unsigned char*>(customData),
std::min(strlen(customData), (size_t)MBEDTLS_CTR_DRBG_MAX_SEED_INPUT));
if (ret != 0)
{
sslLog( QString("mbedtls_ctr_drbg_seed FAILED %1").arg( errorMsg( ret ) ), "error" );
sslLog(QString("mbedtls_ctr_drbg_seed FAILED %1").arg(errorMsg(ret)), "error");
return false;
}
sslLog( "Seeding the random number generator...ok" );
sslLog("Seeding the random number generator...ok");
return true;
}